From 8ed9b4ea7c5b33d9a465cb350fd14ae637aea7aa Mon Sep 17 00:00:00 2001 From: Weinan Qiu Date: Fri, 31 Jan 2020 21:54:36 -0500 Subject: [PATCH] add handlers for resource types and schemas --- cmd/api/cmd.go | 6 ++ cmd/api/handler.go | 110 +++++++++++++++++++++++++++++++++++ go.mod | 4 ++ go.sum | 9 +++ pkg/v2/service/query.go | 10 +++- pkg/v2/service/query_test.go | 6 +- pkg/v2/spec/schema.go | 7 ++- 7 files changed, 144 insertions(+), 8 deletions(-) diff --git a/cmd/api/cmd.go b/cmd/api/cmd.go index c482eca2..2149d502 100644 --- a/cmd/api/cmd.go +++ b/cmd/api/cmd.go @@ -18,9 +18,15 @@ func Command() *cli.Command { app := args.Initialize() defer app.Close() + app.ensureSchemaRegistered() + var router = httprouter.New() { router.GET("/ServiceProviderConfig", ServiceProviderConfigHandler(app.ServiceProviderConfig())) + router.GET("/Schemas", SchemasHandler()) + router.GET("/Schemas/:id", SchemaByIdHandler()) + router.GET("/ResourceTypes", ResourceTypesHandler(app.UserResourceType(), app.GroupResourceType())) + router.GET("/ResourceTypes/:id", ResourceTypeByIdHandler(app.userResourceType, app.GroupResourceType())) router.GET("/Users/:id", GetHandler(app.UserGetService(), app.Logger())) router.GET("/Users", SearchHandler(app.UserQueryService(), app.Logger())) diff --git a/cmd/api/handler.go b/cmd/api/handler.go index ab950777..e97f64f7 100644 --- a/cmd/api/handler.go +++ b/cmd/api/handler.go @@ -14,6 +14,7 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/readpref" "net/http" + "net/http/httptest" ) // CreateHandler returns a route handler function for creating SCIM resources. @@ -247,6 +248,115 @@ func ServiceProviderConfigHandler(config *spec.ServiceProviderConfig) func(rw ht } } +// ResourceTypesHandler returns a route handler function for getting all defined ResourceType. +func ResourceTypesHandler(resourceTypes ...*spec.ResourceType) func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { + result := &service.QueryResponse{ + TotalResults: len(resourceTypes), + StartIndex: 1, + ItemsPerPage: len(resourceTypes), + Resources: []json.Serializable{}, + } + for _, resourceType := range resourceTypes { + result.Resources = append(result.Resources, json.ResourceTypeToSerializable(resourceType)) + } + + // use recorder to cache render result + recorder := httptest.NewRecorder() + if err := handlerutil.WriteSearchResultToResponse(recorder, result); err != nil { + panic(err) + } + + return func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { + rw.WriteHeader(200) + rw.Header().Set("Content-Type", recorder.Header().Get("Content-Type")) + _, _ = rw.Write(recorder.Body.Bytes()) + } +} + +// ResourceTypeByIdHandler returns a route handler function get ResourceType by its id. +func ResourceTypeByIdHandler(resourceTypes ...*spec.ResourceType) func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { + cache := map[string]gojson.RawMessage{} + for _, resourceType := range resourceTypes { + raw, err := json.Serialize(json.ResourceTypeToSerializable(resourceType)) + if err != nil { + panic(err) + } + cache[resourceType.ID()] = raw + } + + return func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { + raw, ok := cache[params.ByName("id")] + if !ok { + _ = handlerutil.WriteError(rw, fmt.Errorf("%w: resource type is not found", spec.ErrNotFound)) + return + } + + rw.WriteHeader(200) + rw.Header().Set("Content-Type", "application/json+scim") + _, _ = rw.Write(raw) + } +} + +// SchemasHandler returns a route handler function for getting all defined Schema. +func SchemasHandler() func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { + result := &service.QueryResponse{StartIndex: 1, Resources: []json.Serializable{}} + if err := spec.Schemas().ForEachSchema(func(schema *spec.Schema) error { + if schema.ID() == spec.CoreSchemaId { + return nil + } + result.Resources = append(result.Resources, json.SchemaToSerializable(schema)) + return nil + }); err != nil { + panic(err) + } + result.TotalResults = len(result.Resources) + result.ItemsPerPage = len(result.Resources) + + // use recorder to cache render result + recorder := httptest.NewRecorder() + if err := handlerutil.WriteSearchResultToResponse(recorder, result); err != nil { + panic(err) + } + + return func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { + rw.WriteHeader(200) + rw.Header().Set("Content-Type", recorder.Header().Get("Content-Type")) + _, _ = rw.Write(recorder.Body.Bytes()) + } +} + +// SchemaByIdHandler returns a route handler function get Schema by its id. +func SchemaByIdHandler() func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { + cache := map[string]gojson.RawMessage{} + if err := spec.Schemas().ForEachSchema(func(schema *spec.Schema) error { + if schema.ID() == spec.CoreSchemaId { + return nil + } + + raw, err := json.Serialize(json.SchemaToSerializable(schema)) + if err != nil { + return err + } + cache[schema.ID()] = raw + + return nil + }); err != nil { + panic(err) + } + + return func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { + raw, ok := cache[params.ByName("id")] + if !ok { + _ = handlerutil.WriteError(rw, fmt.Errorf("%w: schema is not found", spec.ErrNotFound)) + return + } + + rw.WriteHeader(200) + rw.Header().Set("Content-Type", "application/json+scim") + _, _ = rw.Write(raw) + } +} + // HealthHandler returns a http handler to report service health status. func HealthHandler(mongoClient *mongo.Client, rabbitConn *amqp.Connection) func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { return func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) { diff --git a/go.mod b/go.mod index 94472ca9..453aeff7 100644 --- a/go.mod +++ b/go.mod @@ -9,14 +9,18 @@ require ( github.com/imulab/go-scim/mongo/v2 v2.0.0 github.com/imulab/go-scim/pkg/v2 v2.0.0 github.com/julienschmidt/httprouter v1.3.0 + github.com/opencontainers/runc v1.0.0-rc9 // indirect github.com/ory/dockertest v3.3.5+incompatible github.com/rs/zerolog v1.17.2 github.com/satori/go.uuid v1.2.0 github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71 + github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.4.0 github.com/urfave/cli/v2 v2.1.1 go.mongodb.org/mongo-driver v1.2.1 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e + golang.org/x/sys v0.0.0-20200121082415-34d275377bf9 // indirect + gopkg.in/yaml.v2 v2.2.7 // indirect ) replace github.com/imulab/go-scim/mongo/v2 => ./mongo/v2 diff --git a/go.sum b/go.sum index 4d9743cb..70a56eba 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v1.0.0-rc5 h1:rYjdzMDXVly2Av0RLs3nf/iVkaWh2UrDhuTdTT2KggQ= github.com/opencontainers/runc v1.0.0-rc5/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9 h1:/k06BMULKF5hidyoZymkoDCzdJzltZpz/UU4LguQVtc= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -69,7 +71,10 @@ github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71/go.mod h1:AZpEONHx3 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= @@ -103,6 +108,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200121082415-34d275377bf9 h1:N19i1HjUnR7TF7rMt8O4p3dLvqvmYyzB6ifMFmrbY50= +golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -116,3 +123,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/v2/service/query.go b/pkg/v2/service/query.go index 1878d657..e3f6a936 100644 --- a/pkg/v2/service/query.go +++ b/pkg/v2/service/query.go @@ -6,7 +6,7 @@ import ( "github.com/imulab/go-scim/pkg/v2/crud" "github.com/imulab/go-scim/pkg/v2/crud/expr" "github.com/imulab/go-scim/pkg/v2/db" - "github.com/imulab/go-scim/pkg/v2/prop" + "github.com/imulab/go-scim/pkg/v2/json" "github.com/imulab/go-scim/pkg/v2/spec" ) @@ -36,7 +36,7 @@ type ( TotalResults int StartIndex int ItemsPerPage int - Resources []*prop.Resource + Resources []json.Serializable Projection *crud.Projection // included so that caller may render properly } ) @@ -77,9 +77,13 @@ func (s *queryService) Do(ctx context.Context, req *QueryRequest) (resp *QueryRe } } - if resp.Resources, err = s.database.Query(ctx, req.Filter, req.Sort, req.Pagination, req.Projection); err != nil { + resources, err := s.database.Query(ctx, req.Filter, req.Sort, req.Pagination, req.Projection) + if err != nil { return } + for _, r := range resources { + resp.Resources = append(resp.Resources, r) + } resp.ItemsPerPage = len(resp.Resources) return diff --git a/pkg/v2/service/query_test.go b/pkg/v2/service/query_test.go index 591d68da..7b83d20e 100644 --- a/pkg/v2/service/query_test.go +++ b/pkg/v2/service/query_test.go @@ -87,7 +87,7 @@ func (s *QueryServiceTestSuite) TestDo() { assert.Nil(t, err) assert.Equal(t, 1, resp.TotalResults) assert.Len(t, resp.Resources, 1) - assert.Equal(t, "user003", resp.Resources[0].Navigator().Dot("id").Current().Raw()) + assert.Equal(t, "user003", resp.Resources[0].(*prop.Resource).Navigator().Dot("id").Current().Raw()) }, }, { @@ -119,7 +119,7 @@ func (s *QueryServiceTestSuite) TestDo() { assert.Equal(t, 5, resp.TotalResults) assert.Len(t, resp.Resources, 5) for i, expected := range []string{"user005", "user004", "user003", "user002", "user001"} { - assert.Equal(t, expected, resp.Resources[i].Navigator().Dot("id").Current().Raw()) + assert.Equal(t, expected, resp.Resources[i].(*prop.Resource).Navigator().Dot("id").Current().Raw()) } }, }, @@ -156,7 +156,7 @@ func (s *QueryServiceTestSuite) TestDo() { assert.Equal(t, 5, resp.TotalResults) assert.Len(t, resp.Resources, 2) for i, expected := range []string{"user002", "user003"} { - assert.Equal(t, expected, resp.Resources[i].Navigator().Dot("id").Current().Raw()) + assert.Equal(t, expected, resp.Resources[i].(*prop.Resource).Navigator().Dot("id").Current().Raw()) } }, }, diff --git a/pkg/v2/spec/schema.go b/pkg/v2/spec/schema.go index c1a1dee6..e49a24a0 100644 --- a/pkg/v2/spec/schema.go +++ b/pkg/v2/spec/schema.go @@ -112,10 +112,13 @@ func (r *schemaRegistry) Get(schemaId string) (schema *Schema, ok bool) { } // ForEachSchema invokes the callback function on each registered schema. -func (r *schemaRegistry) ForEachSchema(callback func(schema *Schema)) { +func (r *schemaRegistry) ForEachSchema(callback func(schema *Schema) error) error { for _, schema := range r.db { - callback(schema) + if err := callback(schema); err != nil { + return err + } } + return nil } func (r *schemaRegistry) mustGet(schemaId string) *Schema {