From ffdb97696f929e64d5c55bd08a209124a0c29ad5 Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Mon, 21 Feb 2022 12:23:57 -0400 Subject: [PATCH 01/16] feature: WEOS-1308 Generate and ensure BDD Test is passing -Updated spec file to have the correct db config --- features/add-context-data-oas.feature | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/features/add-context-data-oas.feature b/features/add-context-data-oas.feature index f1e7a78e..f7abd380 100644 --- a/features/add-context-data-oas.feature +++ b/features/add-context-data-oas.feature @@ -30,8 +30,12 @@ Feature: Add data to request context via the spec report-caller: true formatter: json database: - driver: sqlite3 - database: e2e.db + database: "%s" + driver: "%s" + host: "%s" + password: "%s" + username: "%s" + port: %d event-source: - title: default driver: service @@ -125,8 +129,12 @@ Feature: Add data to request context via the spec report-caller: true formatter: json database: - driver: sqlite3 - database: e2e.db + database: "%s" + driver: "%s" + host: "%s" + password: "%s" + username: "%s" + port: %d event-source: - title: default driver: service From a39f3eb7e02d5a4ebdc8e589d6aecfc827f78c24 Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Mon, 21 Feb 2022 13:07:06 -0400 Subject: [PATCH 02/16] feature: WEOS-1308 Generate and ensure BDD Test is passing -Generated bdd test --- end2end_test.go | 23 +++++++++++++++++++++-- features/add-context-data-oas.feature | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/end2end_test.go b/end2end_test.go index ccaf48b2..a88aceaf 100644 --- a/end2end_test.go +++ b/end2end_test.go @@ -1426,6 +1426,22 @@ func theTotalNoEventsAndProcessedAndFailuresShouldBeReturned() error { return nil } +func thereShouldBeAKeyInTheRequestContextWithObject(key string) error { + ctx := resp.Request.Context() + if ctx.Value(key) == nil { + return fmt.Errorf("expected key %s to be found got nil", key) + } + return nil +} + +func thereShouldBeAKeyInTheRequestContextWithValue(key, value string) error { + ctx := resp.Request.Context() + if ctx.Value(key).(string) == value { + return fmt.Errorf("expected key %s value to be %s got %s", key, value, ctx.Value(key).(string)) + } + return nil +} + func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Before(reset) ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { @@ -1504,6 +1520,9 @@ func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^Sojourner" deletes the "([^"]*)" table$`, sojournerDeletesTheTable) ctx.Step(`^the "([^"]*)" table should be populated with$`, theTableShouldBePopulatedWith) ctx.Step(`^the total no\. events and processed and failures should be returned$`, theTotalNoEventsAndProcessedAndFailuresShouldBeReturned) + ctx.Step(`^there should be a key "([^"]*)" in the request context with object$`, thereShouldBeAKeyInTheRequestContextWithObject) + ctx.Step(`^there should be a key "([^"]*)" in the request context with value "([^"]*)"$`, thereShouldBeAKeyInTheRequestContextWithValue) + } func TestBDD(t *testing.T) { @@ -1513,8 +1532,8 @@ func TestBDD(t *testing.T) { TestSuiteInitializer: InitializeSuite, Options: &godog.Options{ Format: "pretty", - Tags: "~long && ~skipped", - //Tags: "focus1", + //Tags: "~long && ~skipped", + Tags: "WEOS-1308", //Tags: "WEOS-1110 && ~skipped", }, }.Run() diff --git a/features/add-context-data-oas.feature b/features/add-context-data-oas.feature index f7abd380..8428b202 100644 --- a/features/add-context-data-oas.feature +++ b/features/add-context-data-oas.feature @@ -1,4 +1,4 @@ -@WEOS-1308 @skipped +@WEOS-1308 Feature: Add data to request context via the spec A developer can hardcode data that should be in the request context of an api From bf9415a96463cdfaff0ef80f4f5e458862e9b3a9 Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Mon, 21 Feb 2022 16:04:43 -0400 Subject: [PATCH 03/16] feature: WEOS-1308 Update the context middleware to take parameters from x-context and add to the context -Added test for getting the params from x-context and adding it to the context -Added functionality for getting the params from x-context and putting it into the context -Added openapi extension x-context --- controllers/rest/fixtures/blog.yaml | 6 +- controllers/rest/middleware_context.go | 107 ++++++++++++++++++++ controllers/rest/middleware_context_test.go | 52 ++++++++++ controllers/rest/openapi_extensions.go | 3 + 4 files changed, 166 insertions(+), 2 deletions(-) diff --git a/controllers/rest/fixtures/blog.yaml b/controllers/rest/fixtures/blog.yaml index 3d8f3677..2dc50c65 100644 --- a/controllers/rest/fixtures/blog.yaml +++ b/controllers/rest/fixtures/blog.yaml @@ -207,10 +207,10 @@ paths: required: false description: query string x-context: - filters: + _filters: - field: status operator: eq - values: + value: - Active - field: lastUpdated operator: between @@ -288,6 +288,8 @@ paths: schema: type: number format: double + x-context: + id: 2 summary: Get Blog by id operationId: Get Blog responses: diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index d1836b50..93a339c4 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -34,6 +34,14 @@ func Context(api *RESTAPI, projection projections.Projection, commandDispatcher for _, parameter := range path.Parameters { cc, err = parseParams(c, cc, parameter, entityFactory) } + //use x-context to get parameter + if tcontextParams, ok := operation.ExtensionProps.Extensions[ContextExtension]; ok { + var contextParams map[string]interface{} + err = json.Unmarshal(tcontextParams.(json.RawMessage), &contextParams) + if err == nil { + cc, err = parseContextExtension(c, cc, contextParams, entityFactory) + } + } //use the operation information to get the parameter values and add them to the context for _, parameter := range operation.Parameters { cc, err = parseParams(c, cc, parameter, entityFactory) @@ -378,3 +386,102 @@ func convertProperties(properties map[string]interface{}, schema *openapi3.Schem } return properties, nil } + +//parseContextExtension takes the parameters from x-context andadds it to the context +func parseContextExtension(c echo.Context, cc context.Context, params map[string]interface{}, entityFactory model.EntityFactory) (context.Context, error) { + schema := entityFactory.Schema() + if params == nil { + return cc, nil + } + for key, value := range params { + var contextValue interface{} + switch key { + case "sequence_no": //default type is integer + contextValue = int(value.(float64)) + case "page", "limit": + contextValue = int(value.(float64)) + case "use_entity_id": //default type is boolean + v, err := strconv.ParseBool(value.(string)) + if err == nil { + contextValue = v + } + case "_filters": + if value == nil { + return cc, fmt.Errorf("unexpected error no filters specified") + } + var tfilters map[string]*FilterProperties + tfilters = map[string]*FilterProperties{} + var filters map[string]interface{} + filters = map[string]interface{}{} + for _, filterProp := range value.([]interface{}) { + if filterProp.(map[string]interface{})["operator"] == nil || filterProp.(map[string]interface{})["field"] == nil || (filterProp.(map[string]interface{})["value"] == nil && filterProp.(map[string]interface{})["values"] == nil) { + return cc, fmt.Errorf("unexpected error all filter fields are not filled out") + } + if filterProp.(map[string]interface{})["values"] != nil { + tfilters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ + Field: filterProp.(map[string]interface{})["field"].(string), + Operator: filterProp.(map[string]interface{})["operator"].(string), + Values: filterProp.(map[string]interface{})["values"].([]interface{}), + } + } else { + tfilters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ + Field: filterProp.(map[string]interface{})["field"].(string), + Operator: filterProp.(map[string]interface{})["operator"].(string), + Value: filterProp.(map[string]interface{})["value"], + } + } + + } + marshalledFilter, err := json.Marshal(tfilters) + if err != nil { + return cc, err + } + err = json.Unmarshal(marshalledFilter, &filters) + if err != nil { + return cc, err + } + filters, err = convertProperties(filters, entityFactory.Schema()) + if err != nil { + return cc, err + } + contextValue = filters + + case "If-Match", "If-None-Match": //default type is string + contextValue = value.(string) + case "sorts": + sortOptions := map[string]string{} + for _, sortOption := range value.([]interface{}) { + if sortOption.(map[string]interface{})["field"] == nil || sortOption.(map[string]interface{})["order"] == nil { + return cc, fmt.Errorf("unexpected error all sort fields are not filled out") + } + sortOptions[sortOption.(map[string]interface{})["field"].(string)] = sortOption.(map[string]interface{})["field"].(string) + } + contextValue = sortOptions + default: + if schema.Properties[key] != nil { + pType := schema.Properties[key].Value.Type + switch strings.ToLower(pType) { + case "integer": + contextValue = int(value.(float64)) + case "boolean": + v, err := strconv.ParseBool(value.(string)) + if err == nil { + contextValue = v + } + case "number": + format := schema.Properties[key].Value.Format + if format == "float" || format == "double" { + contextValue = value.(float64) + } else { + contextValue = int(value.(float64)) + } + + } + } else if schema.Properties[key] == nil && key == "id" { + contextValue = int(value.(float64)) + } + } + cc = context.WithValue(cc, key, contextValue) + } + return cc, nil +} diff --git a/controllers/rest/middleware_context_test.go b/controllers/rest/middleware_context_test.go index dc5669c8..3814ac8a 100644 --- a/controllers/rest/middleware_context_test.go +++ b/controllers/rest/middleware_context_test.go @@ -489,4 +489,56 @@ func TestContext(t *testing.T) { e.POST("/blogs", handler) e.ServeHTTP(resp, req) }) + t.Run("x-content extension should be used to add data to the request context", func(t *testing.T) { + path := swagger.Paths.Find("/blogs") + mw := rest.Context(restApi, nil, nil, nil, entityFactory, path, path.Get) + handler := mw(func(ctxt echo.Context) error { + //check that certain parameters are in the context + cc := ctxt.Request().Context() + if cc.Value("page") == nil { + t.Fatalf("expected a page in context") + } + if cc.Value("limit") == nil { + t.Fatalf("expected a page in context") + } + if cc.Value("sorts") == nil { + t.Fatalf("expected a page in context") + } + if cc.Value("_filters") == nil { + t.Fatalf("expected a page in context") + } + return nil + }) + e := echo.New() + resp := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/blogs", nil) + e.GET("/blogs", handler) + e.ServeHTTP(resp, req) + }) + t.Run("the request parameter value should take preference over x-context parameters values", func(t *testing.T) { + path := swagger.Paths.Find("/blogs/:id") + mw := rest.Context(restApi, nil, nil, nil, entityFactory, path, path.Get) + handler := mw(func(ctxt echo.Context) error { + //check that certain parameters are in the context + cc := ctxt.Request().Context() + if cc.Value("id").(string) != "123" { + t.Fatalf("expected an id in context to be %s got %s", "123", cc.Value("id").(string)) + } + return nil + }) + e := echo.New() + resp := httptest.NewRecorder() + payload := &struct { + Title string + }{ + Title: "Lorem Ipsum", + } + data, err := json.Marshal(payload) + if err != nil { + t.Fatalf("unexpected error marshaling payload '%s'", err) + } + req := httptest.NewRequest(http.MethodGet, "/blogs/123", bytes.NewBuffer(data)) + e.GET("/blogs/:id", handler) + e.ServeHTTP(resp, req) + }) } diff --git a/controllers/rest/openapi_extensions.go b/controllers/rest/openapi_extensions.go index 38e8981a..40fd0302 100644 --- a/controllers/rest/openapi_extensions.go +++ b/controllers/rest/openapi_extensions.go @@ -35,3 +35,6 @@ const CommandDispatcherExtension = "x-command-dispatcher" //EventStoreExtension set custom event store const EventStoreExtension = "x-event-store" + +//ContextExtension set parameters directly in the context +const ContextExtension = "x-context" From 9ead050f785979fe965a9c236a5b4417634978f8 Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Tue, 22 Feb 2022 12:41:18 -0400 Subject: [PATCH 04/16] feature: WEOS-1308 Generate and ensure BDD Test is passing -Updated the spec file -Debug the BDD test --- controllers/rest/middleware_context.go | 19 ++++----------- end2end_test.go | 33 +++++++++++++++++++------- features/add-context-data-oas.feature | 9 +++++-- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index 93a339c4..954613e7 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -409,22 +409,19 @@ func parseContextExtension(c echo.Context, cc context.Context, params map[string if value == nil { return cc, fmt.Errorf("unexpected error no filters specified") } - var tfilters map[string]*FilterProperties - tfilters = map[string]*FilterProperties{} - var filters map[string]interface{} - filters = map[string]interface{}{} + filters := map[string]interface{}{} for _, filterProp := range value.([]interface{}) { if filterProp.(map[string]interface{})["operator"] == nil || filterProp.(map[string]interface{})["field"] == nil || (filterProp.(map[string]interface{})["value"] == nil && filterProp.(map[string]interface{})["values"] == nil) { return cc, fmt.Errorf("unexpected error all filter fields are not filled out") } if filterProp.(map[string]interface{})["values"] != nil { - tfilters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ + filters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ Field: filterProp.(map[string]interface{})["field"].(string), Operator: filterProp.(map[string]interface{})["operator"].(string), Values: filterProp.(map[string]interface{})["values"].([]interface{}), } } else { - tfilters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ + filters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ Field: filterProp.(map[string]interface{})["field"].(string), Operator: filterProp.(map[string]interface{})["operator"].(string), Value: filterProp.(map[string]interface{})["value"], @@ -432,15 +429,7 @@ func parseContextExtension(c echo.Context, cc context.Context, params map[string } } - marshalledFilter, err := json.Marshal(tfilters) - if err != nil { - return cc, err - } - err = json.Unmarshal(marshalledFilter, &filters) - if err != nil { - return cc, err - } - filters, err = convertProperties(filters, entityFactory.Schema()) + filters, err := convertProperties(filters, entityFactory.Schema()) if err != nil { return cc, err } diff --git a/end2end_test.go b/end2end_test.go index a88aceaf..a02f903c 100644 --- a/end2end_test.go +++ b/end2end_test.go @@ -67,6 +67,7 @@ var success int var failed int var errArray []error var filters string +var contextWithValues context.Context type FilterProperties struct { Operator string @@ -99,7 +100,7 @@ func InitializeSuite(ctx *godog.TestSuiteContext) { contentTypeID = map[string]bool{} Developer = &User{} filters = "" - page = 1 + page = 0 limit = 0 result = api.ListApiResponse{} blogfixtures = []interface{}{} @@ -153,7 +154,7 @@ func reset(ctx context.Context, sc *godog.Scenario) (context.Context, error) { contentTypeID = map[string]bool{} Developer = &User{} filters = "" - page = 1 + page = 0 limit = 0 result = api.ListApiResponse{} errs = nil @@ -712,6 +713,15 @@ func theServiceIsRunning() error { tapi.DB = db tapi.EchoInstance().Logger.SetOutput(&buf) API = *tapi + API.RegisterMiddleware("Handler", func(api *api.RESTAPI, projection projections.Projection, commandDispatcher model.CommandDispatcher, eventSource model.EventRepository, entityFactory model.EntityFactory, path *openapi3.PathItem, operation *openapi3.Operation) echo.MiddlewareFunc { + return func(handlerFunc echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + contextWithValues = c.Request().Context() + + return nil + } + } + }) err = API.Initialize(scenarioContext) if err != nil { return err @@ -1427,18 +1437,25 @@ func theTotalNoEventsAndProcessedAndFailuresShouldBeReturned() error { } func thereShouldBeAKeyInTheRequestContextWithObject(key string) error { - ctx := resp.Request.Context() - if ctx.Value(key) == nil { + if contextWithValues.Value(key) == nil { return fmt.Errorf("expected key %s to be found got nil", key) } return nil } func thereShouldBeAKeyInTheRequestContextWithValue(key, value string) error { - ctx := resp.Request.Context() - if ctx.Value(key).(string) == value { - return fmt.Errorf("expected key %s value to be %s got %s", key, value, ctx.Value(key).(string)) + val, _ := strconv.Atoi(value) + switch contextWithValues.Value(key).(type) { + case int: + if contextWithValues.Value(key).(int) != val { + return fmt.Errorf("expected key %s value to be %d got %d", key, val, contextWithValues.Value(key).(int)) + } + case string: + if contextWithValues.Value(key).(string) != value { + return fmt.Errorf("expected key %s value to be %s got %s", key, value, contextWithValues.Value(key).(string)) + } } + return nil } @@ -1533,7 +1550,7 @@ func TestBDD(t *testing.T) { Options: &godog.Options{ Format: "pretty", //Tags: "~long && ~skipped", - Tags: "WEOS-1308", + //Tags: "focus", //Tags: "WEOS-1110 && ~skipped", }, }.Run() diff --git a/features/add-context-data-oas.feature b/features/add-context-data-oas.feature index 8428b202..7629f894 100644 --- a/features/add-context-data-oas.feature +++ b/features/add-context-data-oas.feature @@ -62,6 +62,8 @@ Feature: Add data to request context via the spec description: blog title description: type: string + status: + type: string required: - title paths: @@ -75,6 +77,8 @@ Feature: Add data to request context via the spec get: operationId: Get Blogs summary: Get List of Blogs + x-middleware: + - Handler x-context: page: 1 limit: 10 @@ -177,6 +181,8 @@ Feature: Add data to request context via the spec name: sequence_no schema: type: string + x-middleware: + - Handler x-content: id: 2 summary: Get Blog by id @@ -190,6 +196,5 @@ Feature: Add data to request context via the spec $ref: "#/components/schemas/Blog" """ And the service is running - And "Sojourner" is on the "Blog" edit screen with id "1234" - When the "Blog" is submitted + When the "GET" endpoint "/blogs/1234" is hit Then there should be a key "id" in the request context with value "1234" \ No newline at end of file From 65ce97c572e7cd0d94cbb797e209fb5b3930a986 Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Tue, 22 Feb 2022 12:43:43 -0400 Subject: [PATCH 05/16] feature: WEOS-1308 Generate and ensure BDD Test is passing -Updated the spec file -Debug the BDD test --- end2end_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/end2end_test.go b/end2end_test.go index a02f903c..57d507c1 100644 --- a/end2end_test.go +++ b/end2end_test.go @@ -1549,7 +1549,7 @@ func TestBDD(t *testing.T) { TestSuiteInitializer: InitializeSuite, Options: &godog.Options{ Format: "pretty", - //Tags: "~long && ~skipped", + Tags: "~long && ~skipped", //Tags: "focus", //Tags: "WEOS-1110 && ~skipped", }, From e0c92e15da59252fedc9c8faf7676a732205f47e Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Tue, 22 Feb 2022 12:56:24 -0400 Subject: [PATCH 06/16] feature: WEOS-1308 Generate and ensure BDD Test is passing -Added a warning if the parameters in the x-context is not one of the default parameters --- controllers/rest/middleware_context.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index 954613e7..0564f4b6 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -39,7 +39,7 @@ func Context(api *RESTAPI, projection projections.Projection, commandDispatcher var contextParams map[string]interface{} err = json.Unmarshal(tcontextParams.(json.RawMessage), &contextParams) if err == nil { - cc, err = parseContextExtension(c, cc, contextParams, entityFactory) + cc, err = parseContextExtension(c, cc, contextParams, entityFactory, api) } } //use the operation information to get the parameter values and add them to the context @@ -388,7 +388,7 @@ func convertProperties(properties map[string]interface{}, schema *openapi3.Schem } //parseContextExtension takes the parameters from x-context andadds it to the context -func parseContextExtension(c echo.Context, cc context.Context, params map[string]interface{}, entityFactory model.EntityFactory) (context.Context, error) { +func parseContextExtension(c echo.Context, cc context.Context, params map[string]interface{}, entityFactory model.EntityFactory, api *RESTAPI) (context.Context, error) { schema := entityFactory.Schema() if params == nil { return cc, nil @@ -468,6 +468,9 @@ func parseContextExtension(c echo.Context, cc context.Context, params map[string } } else if schema.Properties[key] == nil && key == "id" { contextValue = int(value.(float64)) + } else { + api.e.Logger.Warnf("parameter is not apart of default parameters: %s", key) + contextValue = value } } cc = context.WithValue(cc, key, contextValue) From 97ee2555f80abbc9094bc3d5bcc03e444d1f3934 Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Wed, 23 Feb 2022 18:38:34 -0400 Subject: [PATCH 07/16] feature: WEOS-1308 Update the context middleware to take parameters from x-context and add to the context -Updated parse params to put the parameters into a map -Added a function to put the map of parameter values in the context --- controllers/rest/middleware_context.go | 398 +++++++++++++------------ 1 file changed, 205 insertions(+), 193 deletions(-) diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index 93a339c4..78c5e231 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -30,22 +30,22 @@ func Context(api *RESTAPI, projection projections.Projection, commandDispatcher if accountID != "" { cc = context.WithValue(cc, weosContext.ACCOUNT_ID, accountID) } - //use the path information to get the parameter values and add them to the context - for _, parameter := range path.Parameters { - cc, err = parseParams(c, cc, parameter, entityFactory) - } + //use the path information to get the parameter values + contextValues, err := parseParams(c, path.Parameters, nil, entityFactory) + //add parameter values to the context + cc = AddToContext(cc, contextValues) + //use x-context to get parameter if tcontextParams, ok := operation.ExtensionProps.Extensions[ContextExtension]; ok { var contextParams map[string]interface{} err = json.Unmarshal(tcontextParams.(json.RawMessage), &contextParams) if err == nil { - cc, err = parseContextExtension(c, cc, contextParams, entityFactory) + //use the operation information to get the parameter values + contextValues, err = parseParams(c, operation.Parameters, contextParams, entityFactory) + //add parameter values to the context + cc = AddToContext(cc, contextValues) } } - //use the operation information to get the parameter values and add them to the context - for _, parameter := range operation.Parameters { - cc, err = parseParams(c, cc, parameter, entityFactory) - } //parse request body based on content type var payload []byte @@ -86,119 +86,230 @@ func Context(api *RESTAPI, projection projections.Projection, commandDispatcher } //parseParams uses the parameter type to determine where to pull the value from -func parseParams(c echo.Context, cc context.Context, parameter *openapi3.ParameterRef, entityFactory model.EntityFactory) (context.Context, error) { +func parseParams(c echo.Context, parameters openapi3.Parameters, params map[string]interface{}, entityFactory model.EntityFactory) (map[string]interface{}, error) { if entityFactory == nil { c.Logger().Error("no entity factory found") - return cc, fmt.Errorf("no entity factory found ") + return nil, fmt.Errorf("no entity factory found ") } - if parameter.Value != nil { - contextName := parameter.Value.Name - paramType := parameter.Value.Schema - //if there is a context name specified use that instead. The value is a json.RawMessage (not a string) - if tcontextName, ok := parameter.Value.ExtensionProps.Extensions[ContextNameExtension]; ok { - err := json.Unmarshal(tcontextName.(json.RawMessage), &contextName) - if err != nil { - return nil, err + schema := entityFactory.Schema() + var errors error + contextValues := map[string]interface{}{} + //getting the parameters from x-context + for key, value := range params { + var contextValue interface{} + switch key { + case "sequence_no": //default type is integer + contextValue = int(value.(float64)) + case "page", "limit": + contextValue = int(value.(float64)) + case "use_entity_id": //default type is boolean + v, err := strconv.ParseBool(value.(string)) + if err == nil { + contextValue = v } - } - //if there is an alias name specified use that instead. The value is a json.RawMessage (not a string) - if tcontextName, ok := parameter.Value.ExtensionProps.Extensions[AliasExtension]; ok { - err := json.Unmarshal(tcontextName.(json.RawMessage), &contextName) + case "_filters": + if value == nil { + errors = fmt.Errorf("unexpected error no filters specified") + continue + } + filters := map[string]interface{}{} + for _, filterProp := range value.([]interface{}) { + if filterProp.(map[string]interface{})["operator"] == nil || filterProp.(map[string]interface{})["field"] == nil || (filterProp.(map[string]interface{})["value"] == nil && filterProp.(map[string]interface{})["values"] == nil) { + errors = fmt.Errorf("unexpected error all filter fields are not filled out") + break + } + if filterProp.(map[string]interface{})["values"] != nil { + filters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ + Field: filterProp.(map[string]interface{})["field"].(string), + Operator: filterProp.(map[string]interface{})["operator"].(string), + Values: filterProp.(map[string]interface{})["values"].([]interface{}), + } + } else { + filters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ + Field: filterProp.(map[string]interface{})["field"].(string), + Operator: filterProp.(map[string]interface{})["operator"].(string), + Value: filterProp.(map[string]interface{})["value"], + } + } + } + if len(value.([]interface{})) != len(filters) { + continue + } + filters, err := convertProperties(filters, entityFactory.Schema()) if err != nil { - return nil, err + errors = err + continue } - } - var val interface{} - switch strings.ToLower(parameter.Value.In) { - //parameter values stored as strings - case "header": - //have to normalize the key name to be able to retrieve from header because of how echo setup up the headers map - headerName := textproto.CanonicalMIMEHeaderKey(parameter.Value.Name) - if value, ok := c.Request().Header[headerName]; ok { - val = value[0] + contextValue = filters + + case "If-Match", "If-None-Match": //default type is string + contextValue = value.(string) + case "sorts": + sortOptions := map[string]string{} + for _, sortOption := range value.([]interface{}) { + if sortOption.(map[string]interface{})["field"] == nil || sortOption.(map[string]interface{})["order"] == nil { + errors = fmt.Errorf("unexpected error all sort fields are not filled out") + continue + } + sortOptions[sortOption.(map[string]interface{})["field"].(string)] = sortOption.(map[string]interface{})["field"].(string) } - case "query": - val = c.QueryParam(parameter.Value.Name) - case "path": - val = c.Param(parameter.Value.Name) - } + contextValue = sortOptions + default: + if schema.Properties[key] != nil { + pType := schema.Properties[key].Value.Type + switch strings.ToLower(pType) { + case "integer": + contextValue = int(value.(float64)) + case "boolean": + v, err := strconv.ParseBool(value.(string)) + if err == nil { + contextValue = v + } + case "number": + format := schema.Properties[key].Value.Format + if format == "float" || format == "double" { + contextValue = value.(float64) + } else { + contextValue = int(value.(float64)) + } - if _, ok := val.(string); ok { - switch parameter.Value.Name { - case "sequence_no": //default type is integer - v, err := strconv.Atoi(val.(string)) - if err == nil { - val = v } - case "use_entity_id": //default type is boolean - v, err := strconv.ParseBool(val.(string)) - if err == nil { - val = v + } else if schema.Properties[key] == nil && key == "id" { + contextValue = int(value.(float64)) + } else { + c.Logger().Warnf("parameter is not apart of default parameters: %s", key) + contextValue = value + } + } + contextValues[key] = contextValue + } + //get the parameters from the requests + for _, parameter := range parameters { + if parameter.Value != nil { + contextName := parameter.Value.Name + paramType := parameter.Value.Schema + //if there is a context name specified use that instead. The value is a json.RawMessage (not a string) + if tcontextName, ok := parameter.Value.ExtensionProps.Extensions[ContextNameExtension]; ok { + err := json.Unmarshal(tcontextName.(json.RawMessage), &contextName) + if err != nil { + errors = err + continue } - case "If-Match", "If-None-Match": //default type is string - default: - var filters map[string]interface{} - filters = map[string]interface{}{} - if parameter.Value.Name == "_filters" { - decodedQuery, err := url.PathUnescape(c.Request().URL.RawQuery) - if err != nil { - return cc, fmt.Errorf("Error decoding the string %v", err) + } + //if there is an alias name specified use that instead. The value is a json.RawMessage (not a string) + if tcontextName, ok := parameter.Value.ExtensionProps.Extensions[AliasExtension]; ok { + err := json.Unmarshal(tcontextName.(json.RawMessage), &contextName) + if err != nil { + errors = err + continue + } + } + var val interface{} + switch strings.ToLower(parameter.Value.In) { + //parameter values stored as strings + case "header": + //have to normalize the key name to be able to retrieve from header because of how echo setup up the headers map + headerName := textproto.CanonicalMIMEHeaderKey(parameter.Value.Name) + if value, ok := c.Request().Header[headerName]; ok { + val = value[0] + } + case "query": + val = c.QueryParam(parameter.Value.Name) + case "path": + val = c.Param(parameter.Value.Name) + } + + if _, ok := val.(string); ok { + switch parameter.Value.Name { + case "sequence_no": //default type is integer + v, err := strconv.Atoi(val.(string)) + if err == nil { + val = v + } + case "use_entity_id": //default type is boolean + v, err := strconv.ParseBool(val.(string)) + if err == nil { + val = v } - filtersArray := SplitFilters(decodedQuery) - if filtersArray != nil && len(filtersArray) > 0 { - for _, value := range filtersArray { - if strings.Contains(value, "_filters") { - prop := SplitFilter(value) - if prop == nil { - return cc, fmt.Errorf("unexpected error filter format is incorrect: %s", value) + case "If-Match", "If-None-Match": //default type is string + default: + var filters map[string]interface{} + filters = map[string]interface{}{} + if parameter.Value.Name == "_filters" { + decodedQuery, err := url.PathUnescape(c.Request().URL.RawQuery) + if err != nil { + errors = fmt.Errorf("Error decoding the string %v", err) + continue + } + filtersArray := SplitFilters(decodedQuery) + if filtersArray != nil && len(filtersArray) > 0 { + for _, value := range filtersArray { + if strings.Contains(value, "_filters") { + prop := SplitFilter(value) + if prop == nil { + errors = fmt.Errorf("unexpected error filter format is incorrect: %s", value) + break + } + filters[prop.Field] = prop } - filters[prop.Field] = prop } + filters, err = convertProperties(filters, entityFactory.Schema()) + if err != nil { + errors = err + continue + } + contextValues[contextName] = filters + continue } - filters, err = convertProperties(filters, entityFactory.Schema()) - if err != nil { - return cc, err - } - val = filters - break - } - } - if paramType != nil && paramType.Value != nil { - pType := paramType.Value.Type - switch strings.ToLower(pType) { - case "integer": - v, err := strconv.Atoi(val.(string)) - if err == nil { - val = v - } - case "boolean": - v, err := strconv.ParseBool(val.(string)) - if err == nil { - val = v - } - case "number": - format := paramType.Value.Format - if format == "float" || format == "double" { - v, err := strconv.ParseFloat(val.(string), 64) + } + if paramType != nil && paramType.Value != nil { + pType := paramType.Value.Type + switch strings.ToLower(pType) { + case "integer": + v, err := strconv.Atoi(val.(string)) if err == nil { val = v } - } else { - v, err := strconv.Atoi(val.(string)) + case "boolean": + v, err := strconv.ParseBool(val.(string)) if err == nil { val = v } - } + case "number": + format := paramType.Value.Format + if format == "float" || format == "double" { + v, err := strconv.ParseFloat(val.(string), 64) + if err == nil { + val = v + } + } else { + v, err := strconv.Atoi(val.(string)) + if err == nil { + val = v + } + } + } } } } + contextValues[contextName] = val } - cc = context.WithValue(cc, contextName, val) } + //TODO account for $ref tag reference - return cc, nil + return contextValues, errors +} + +func AddToContext(cc context.Context, contextValues map[string]interface{}) context.Context { + if contextValues == nil { + return cc + } + for key, value := range contextValues { + cc = context.WithValue(cc, key, value) + } + return cc } //convertProperties is used to convert the filter value to the correct data type based on the schema @@ -386,102 +497,3 @@ func convertProperties(properties map[string]interface{}, schema *openapi3.Schem } return properties, nil } - -//parseContextExtension takes the parameters from x-context andadds it to the context -func parseContextExtension(c echo.Context, cc context.Context, params map[string]interface{}, entityFactory model.EntityFactory) (context.Context, error) { - schema := entityFactory.Schema() - if params == nil { - return cc, nil - } - for key, value := range params { - var contextValue interface{} - switch key { - case "sequence_no": //default type is integer - contextValue = int(value.(float64)) - case "page", "limit": - contextValue = int(value.(float64)) - case "use_entity_id": //default type is boolean - v, err := strconv.ParseBool(value.(string)) - if err == nil { - contextValue = v - } - case "_filters": - if value == nil { - return cc, fmt.Errorf("unexpected error no filters specified") - } - var tfilters map[string]*FilterProperties - tfilters = map[string]*FilterProperties{} - var filters map[string]interface{} - filters = map[string]interface{}{} - for _, filterProp := range value.([]interface{}) { - if filterProp.(map[string]interface{})["operator"] == nil || filterProp.(map[string]interface{})["field"] == nil || (filterProp.(map[string]interface{})["value"] == nil && filterProp.(map[string]interface{})["values"] == nil) { - return cc, fmt.Errorf("unexpected error all filter fields are not filled out") - } - if filterProp.(map[string]interface{})["values"] != nil { - tfilters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ - Field: filterProp.(map[string]interface{})["field"].(string), - Operator: filterProp.(map[string]interface{})["operator"].(string), - Values: filterProp.(map[string]interface{})["values"].([]interface{}), - } - } else { - tfilters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ - Field: filterProp.(map[string]interface{})["field"].(string), - Operator: filterProp.(map[string]interface{})["operator"].(string), - Value: filterProp.(map[string]interface{})["value"], - } - } - - } - marshalledFilter, err := json.Marshal(tfilters) - if err != nil { - return cc, err - } - err = json.Unmarshal(marshalledFilter, &filters) - if err != nil { - return cc, err - } - filters, err = convertProperties(filters, entityFactory.Schema()) - if err != nil { - return cc, err - } - contextValue = filters - - case "If-Match", "If-None-Match": //default type is string - contextValue = value.(string) - case "sorts": - sortOptions := map[string]string{} - for _, sortOption := range value.([]interface{}) { - if sortOption.(map[string]interface{})["field"] == nil || sortOption.(map[string]interface{})["order"] == nil { - return cc, fmt.Errorf("unexpected error all sort fields are not filled out") - } - sortOptions[sortOption.(map[string]interface{})["field"].(string)] = sortOption.(map[string]interface{})["field"].(string) - } - contextValue = sortOptions - default: - if schema.Properties[key] != nil { - pType := schema.Properties[key].Value.Type - switch strings.ToLower(pType) { - case "integer": - contextValue = int(value.(float64)) - case "boolean": - v, err := strconv.ParseBool(value.(string)) - if err == nil { - contextValue = v - } - case "number": - format := schema.Properties[key].Value.Format - if format == "float" || format == "double" { - contextValue = value.(float64) - } else { - contextValue = int(value.(float64)) - } - - } - } else if schema.Properties[key] == nil && key == "id" { - contextValue = int(value.(float64)) - } - } - cc = context.WithValue(cc, key, contextValue) - } - return cc, nil -} From 3e1cb876387674866112e6995c4ed4dcb569b56b Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Thu, 24 Feb 2022 11:15:48 -0400 Subject: [PATCH 08/16] feature: WEOS-1308 Update the context middleware to take parameters from x-context and add to the context -Updated parse params to put the parameters into a map -Added a function to put the map of parameter values in the context and convert properties to the correct data types --- controllers/rest/fixtures/blog.yaml | 2 +- controllers/rest/middleware_context.go | 125 +++++++++----------- controllers/rest/middleware_context_test.go | 8 +- 3 files changed, 63 insertions(+), 72 deletions(-) diff --git a/controllers/rest/fixtures/blog.yaml b/controllers/rest/fixtures/blog.yaml index 7c6beaf8..98c09b8d 100644 --- a/controllers/rest/fixtures/blog.yaml +++ b/controllers/rest/fixtures/blog.yaml @@ -233,7 +233,7 @@ paths: values: - Technology - Javascript - sorts: + _sorts: - field: title order: asc page: 1 diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index c8d15a1f..8be58e02 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -34,7 +34,7 @@ func Context(api *RESTAPI, projection projections.Projection, commandDispatcher //use the path information to get the parameter values contextValues, err := parseParams(c, path.Parameters, nil, entityFactory) //add parameter values to the context - cc = AddToContext(cc, contextValues) + cc = AddToContext(c, cc, contextValues, entityFactory) //use x-context to get parameter if tcontextParams, ok := operation.ExtensionProps.Extensions[ContextExtension]; ok { @@ -44,7 +44,7 @@ func Context(api *RESTAPI, projection projections.Projection, commandDispatcher //use the operation information to get the parameter values contextValues, err = parseParams(c, operation.Parameters, contextParams, entityFactory) //add parameter values to the context - cc = AddToContext(cc, contextValues) + cc = AddToContext(c, cc, contextValues, entityFactory) } } @@ -104,11 +104,7 @@ func parseResponses(c echo.Context, cc context.Context, operation *openapi3.Oper //parseParams uses the parameter type to determine where to pull the value from func parseParams(c echo.Context, parameters openapi3.Parameters, params map[string]interface{}, entityFactory model.EntityFactory) (map[string]interface{}, error) { - if entityFactory == nil { - c.Logger().Error("no entity factory found") - return nil, fmt.Errorf("no entity factory found ") - } - schema := entityFactory.Schema() + var errors error contextValues := map[string]interface{}{} //getting the parameters from x-context @@ -161,7 +157,7 @@ func parseParams(c echo.Context, parameters openapi3.Parameters, params map[stri case "If-Match", "If-None-Match": //default type is string contextValue = value.(string) - case "sorts": + case "_sorts": sortOptions := map[string]string{} for _, sortOption := range value.([]interface{}) { if sortOption.(map[string]interface{})["field"] == nil || sortOption.(map[string]interface{})["order"] == nil { @@ -172,31 +168,7 @@ func parseParams(c echo.Context, parameters openapi3.Parameters, params map[stri } contextValue = sortOptions default: - if schema.Properties[key] != nil { - pType := schema.Properties[key].Value.Type - switch strings.ToLower(pType) { - case "integer": - contextValue = int(value.(float64)) - case "boolean": - v, err := strconv.ParseBool(value.(string)) - if err == nil { - contextValue = v - } - case "number": - format := schema.Properties[key].Value.Format - if format == "float" || format == "double" { - contextValue = value.(float64) - } else { - contextValue = int(value.(float64)) - } - - } - } else if schema.Properties[key] == nil && key == "id" { - contextValue = int(value.(float64)) - } else { - c.Logger().Warnf("parameter is not apart of default parameters: %s", key) - contextValue = value - } + contextValue = value } contextValues[key] = contextValue } @@ -204,7 +176,6 @@ func parseParams(c echo.Context, parameters openapi3.Parameters, params map[stri for _, parameter := range parameters { if parameter.Value != nil { contextName := parameter.Value.Name - paramType := parameter.Value.Schema //if there is a context name specified use that instead. The value is a json.RawMessage (not a string) if tcontextName, ok := parameter.Value.ExtensionProps.Extensions[ContextNameExtension]; ok { err := json.Unmarshal(tcontextName.(json.RawMessage), &contextName) @@ -237,8 +208,8 @@ func parseParams(c echo.Context, parameters openapi3.Parameters, params map[stri } if _, ok := val.(string); ok { - switch parameter.Value.Name { - case "sequence_no": //default type is integer + switch contextName { + case "sequence_no", "page", "limit": //default type is integer v, err := strconv.Atoi(val.(string)) if err == nil { val = v @@ -278,36 +249,6 @@ func parseParams(c echo.Context, parameters openapi3.Parameters, params map[stri contextValues[contextName] = filters continue } - - } - if paramType != nil && paramType.Value != nil { - pType := paramType.Value.Type - switch strings.ToLower(pType) { - case "integer": - v, err := strconv.Atoi(val.(string)) - if err == nil { - val = v - } - case "boolean": - v, err := strconv.ParseBool(val.(string)) - if err == nil { - val = v - } - case "number": - format := paramType.Value.Format - if format == "float" || format == "double" { - v, err := strconv.ParseFloat(val.(string), 64) - if err == nil { - val = v - } - } else { - v, err := strconv.Atoi(val.(string)) - if err == nil { - val = v - } - } - - } } } } @@ -319,10 +260,60 @@ func parseParams(c echo.Context, parameters openapi3.Parameters, params map[stri return contextValues, errors } -func AddToContext(cc context.Context, contextValues map[string]interface{}) context.Context { +func AddToContext(c echo.Context, cc context.Context, contextValues map[string]interface{}, entityFactory model.EntityFactory) context.Context { if contextValues == nil { return cc } + if entityFactory == nil { + c.Logger().Error("no entity factory found") + return cc + } + schema := entityFactory.Schema() + for key, value := range contextValues { + if schema.Properties[key] != nil { + switch schema.Properties[key].Value.Type { + case "integer": + switch value.(type) { + case float64: + value = int(value.(float64)) + case string: + v, err := strconv.Atoi(value.(string)) + if err == nil { + value = v + } + } + + case "boolean": + v, err := strconv.ParseBool(value.(string)) + if err == nil { + value = v + } + case "number": + format := schema.Properties[key].Value.Format + if format == "float" || format == "double" { + switch value.(type) { + case string: + v, err := strconv.ParseFloat(value.(string), 64) + if err == nil { + value = v + } + } + + } else { + switch value.(type) { + case float64: + value = int(value.(float64)) + case string: + v, err := strconv.Atoi(value.(string)) + if err == nil { + value = v + } + } + } + + } + } + } for key, value := range contextValues { cc = context.WithValue(cc, key, value) } diff --git a/controllers/rest/middleware_context_test.go b/controllers/rest/middleware_context_test.go index a9881199..616afc7b 100644 --- a/controllers/rest/middleware_context_test.go +++ b/controllers/rest/middleware_context_test.go @@ -519,13 +519,13 @@ func TestContext(t *testing.T) { t.Fatalf("expected a page in context") } if cc.Value("limit") == nil { - t.Fatalf("expected a page in context") + t.Fatalf("expected a limit in context") } - if cc.Value("sorts") == nil { - t.Fatalf("expected a page in context") + if cc.Value("_sorts") == nil { + t.Fatalf("expected a sort in context") } if cc.Value("_filters") == nil { - t.Fatalf("expected a page in context") + t.Fatalf("expected a filter in context") } return nil }) From cbcdb9972baf7846c8357e3cccb00a47d9822feb Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Thu, 24 Feb 2022 11:28:02 -0400 Subject: [PATCH 09/16] feature: WEOS-1308 Update the context middleware to take parameters from x-context and add to the context -Fixing any test that fails --- controllers/rest/controller_standard_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/controllers/rest/controller_standard_test.go b/controllers/rest/controller_standard_test.go index 997df94a..1303b266 100644 --- a/controllers/rest/controller_standard_test.go +++ b/controllers/rest/controller_standard_test.go @@ -298,6 +298,9 @@ func TestStandardControllers_CreateBatch(t *testing.T) { NameFunc: func() string { return "Blog" }, + SchemaFunc: func() *openapi3.Schema { + return swagger.Components.Schemas["Blog"].Value + }, } t.Run("basic batch create based on simple content type", func(t *testing.T) { From 39ade57799f4ce3932a55d8d22d9b9ea50b479db Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Thu, 24 Feb 2022 11:56:38 -0400 Subject: [PATCH 10/16] feature: WEOS-1308 Update the context middleware to take parameters from x-context and add to the context -Fixing any test that fails --- controllers/rest/controller_standard_test.go | 9 +++++++++ controllers/rest/middleware_context.go | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/controllers/rest/controller_standard_test.go b/controllers/rest/controller_standard_test.go index 1303b266..4bac8d67 100644 --- a/controllers/rest/controller_standard_test.go +++ b/controllers/rest/controller_standard_test.go @@ -1395,6 +1395,9 @@ func TestStandardControllers_FormUrlEncoded_Create(t *testing.T) { NameFunc: func() string { return "Blog" }, + SchemaFunc: func() *openapi3.Schema { + return swagger.Components.Schemas["Blog"].Value + }, } eventRepository := &EventRepositoryMock{} @@ -1554,6 +1557,9 @@ func TestStandardControllers_FormData_Create(t *testing.T) { NameFunc: func() string { return "Blog" }, + SchemaFunc: func() *openapi3.Schema { + return swagger.Components.Schemas["Blog"].Value + }, } t.Run("basic create based on multipart/form-data content type", func(t *testing.T) { @@ -1720,6 +1726,9 @@ func TestStandardControllers_DeleteEtag(t *testing.T) { NameFunc: func() string { return "Blog" }, + SchemaFunc: func() *openapi3.Schema { + return swagger.Components.Schemas["Blog"].Value + }, } resp := httptest.NewRecorder() req := httptest.NewRequest(http.MethodDelete, "/blogs/"+weosId, nil) diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index 8be58e02..2caccebd 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -46,6 +46,11 @@ func Context(api *RESTAPI, projection projections.Projection, commandDispatcher //add parameter values to the context cc = AddToContext(c, cc, contextValues, entityFactory) } + } else { + //use the operation information to get the parameter values + contextValues, err = parseParams(c, operation.Parameters, nil, entityFactory) + //add parameter values to the context + cc = AddToContext(c, cc, contextValues, entityFactory) } //use the operation information to get the parameter values and add them to the context From ae70c295e8f13af364a079c0548a5026596d28d8 Mon Sep 17 00:00:00 2001 From: shaniah868 Date: Mon, 28 Feb 2022 15:45:54 -0400 Subject: [PATCH 11/16] feature: WEOS-1308 As a developer I should be able to add data to the context via the api specification -Update parse params to only parse the parameters -update addtocontext to convert the values and add to the context -Ensure tests are passing --- controllers/rest/middleware_context.go | 283 ++++++++++++------------- 1 file changed, 131 insertions(+), 152 deletions(-) diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index 2caccebd..3f47ef93 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -32,9 +32,9 @@ func Context(api *RESTAPI, projection projections.Projection, commandDispatcher cc = context.WithValue(cc, weosContext.ACCOUNT_ID, accountID) } //use the path information to get the parameter values - contextValues, err := parseParams(c, path.Parameters, nil, entityFactory) + contextValues, err := parseParams(c, path.Parameters, entityFactory) //add parameter values to the context - cc = AddToContext(c, cc, contextValues, entityFactory) + cc, err = AddToContext(c, cc, contextValues, entityFactory) //use x-context to get parameter if tcontextParams, ok := operation.ExtensionProps.Extensions[ContextExtension]; ok { @@ -42,16 +42,13 @@ func Context(api *RESTAPI, projection projections.Projection, commandDispatcher err = json.Unmarshal(tcontextParams.(json.RawMessage), &contextParams) if err == nil { //use the operation information to get the parameter values - contextValues, err = parseParams(c, operation.Parameters, contextParams, entityFactory) - //add parameter values to the context - cc = AddToContext(c, cc, contextValues, entityFactory) + cc, err = AddToContext(c, cc, contextParams, entityFactory) } - } else { - //use the operation information to get the parameter values - contextValues, err = parseParams(c, operation.Parameters, nil, entityFactory) - //add parameter values to the context - cc = AddToContext(c, cc, contextValues, entityFactory) } + //use the operation information to get the parameter values + contextValues, err = parseParams(c, operation.Parameters, entityFactory) + //add parameter values to the context + cc, err = AddToContext(c, cc, contextValues, entityFactory) //use the operation information to get the parameter values and add them to the context @@ -108,79 +105,14 @@ func parseResponses(c echo.Context, cc context.Context, operation *openapi3.Oper } //parseParams uses the parameter type to determine where to pull the value from -func parseParams(c echo.Context, parameters openapi3.Parameters, params map[string]interface{}, entityFactory model.EntityFactory) (map[string]interface{}, error) { - +func parseParams(c echo.Context, parameters openapi3.Parameters, entityFactory model.EntityFactory) (map[string]interface{}, error) { var errors error contextValues := map[string]interface{}{} - //getting the parameters from x-context - for key, value := range params { - var contextValue interface{} - switch key { - case "sequence_no": //default type is integer - contextValue = int(value.(float64)) - case "page", "limit": - contextValue = int(value.(float64)) - case "use_entity_id": //default type is boolean - v, err := strconv.ParseBool(value.(string)) - if err == nil { - contextValue = v - } - case "_filters": - if value == nil { - errors = fmt.Errorf("unexpected error no filters specified") - continue - } - filters := map[string]interface{}{} - for _, filterProp := range value.([]interface{}) { - if filterProp.(map[string]interface{})["operator"] == nil || filterProp.(map[string]interface{})["field"] == nil || (filterProp.(map[string]interface{})["value"] == nil && filterProp.(map[string]interface{})["values"] == nil) { - errors = fmt.Errorf("unexpected error all filter fields are not filled out") - break - } - if filterProp.(map[string]interface{})["values"] != nil { - filters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ - Field: filterProp.(map[string]interface{})["field"].(string), - Operator: filterProp.(map[string]interface{})["operator"].(string), - Values: filterProp.(map[string]interface{})["values"].([]interface{}), - } - } else { - filters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ - Field: filterProp.(map[string]interface{})["field"].(string), - Operator: filterProp.(map[string]interface{})["operator"].(string), - Value: filterProp.(map[string]interface{})["value"], - } - } - } - if len(value.([]interface{})) != len(filters) { - continue - } - filters, err := convertProperties(filters, entityFactory.Schema()) - if err != nil { - errors = err - continue - } - contextValue = filters - - case "If-Match", "If-None-Match": //default type is string - contextValue = value.(string) - case "_sorts": - sortOptions := map[string]string{} - for _, sortOption := range value.([]interface{}) { - if sortOption.(map[string]interface{})["field"] == nil || sortOption.(map[string]interface{})["order"] == nil { - errors = fmt.Errorf("unexpected error all sort fields are not filled out") - continue - } - sortOptions[sortOption.(map[string]interface{})["field"].(string)] = sortOption.(map[string]interface{})["field"].(string) - } - contextValue = sortOptions - default: - contextValue = value - } - contextValues[key] = contextValue - } //get the parameters from the requests for _, parameter := range parameters { if parameter.Value != nil { contextName := parameter.Value.Name + paramType := parameter.Value.Schema //if there is a context name specified use that instead. The value is a json.RawMessage (not a string) if tcontextName, ok := parameter.Value.ExtensionProps.Extensions[ContextNameExtension]; ok { err := json.Unmarshal(tcontextName.(json.RawMessage), &contextName) @@ -214,45 +146,39 @@ func parseParams(c echo.Context, parameters openapi3.Parameters, params map[stri if _, ok := val.(string); ok { switch contextName { - case "sequence_no", "page", "limit": //default type is integer - v, err := strconv.Atoi(val.(string)) - if err == nil { - val = v - } - case "use_entity_id": //default type is boolean - v, err := strconv.ParseBool(val.(string)) - if err == nil { - val = v + case "_filters": + val = c.Request().URL.RawQuery + if val.(string) == "" { + delete(contextValues, contextName) } - case "If-Match", "If-None-Match": //default type is string default: - var filters map[string]interface{} - filters = map[string]interface{}{} - if parameter.Value.Name == "_filters" { - decodedQuery, err := url.PathUnescape(c.Request().URL.RawQuery) - if err != nil { - errors = fmt.Errorf("Error decoding the string %v", err) - continue - } - filtersArray := SplitFilters(decodedQuery) - if filtersArray != nil && len(filtersArray) > 0 { - for _, value := range filtersArray { - if strings.Contains(value, "_filters") { - prop := SplitFilter(value) - if prop == nil { - errors = fmt.Errorf("unexpected error filter format is incorrect: %s", value) - break - } - filters[prop.Field] = prop - } + if paramType != nil && paramType.Value != nil { + pType := paramType.Value.Type + switch strings.ToLower(pType) { + case "integer": + v, err := strconv.Atoi(val.(string)) + if err == nil { + val = v } - filters, err = convertProperties(filters, entityFactory.Schema()) - if err != nil { - errors = err - continue + case "boolean": + v, err := strconv.ParseBool(val.(string)) + if err == nil { + val = v } - contextValues[contextName] = filters - continue + case "number": + format := paramType.Value.Format + if format == "float" || format == "double" { + v, err := strconv.ParseFloat(val.(string), 64) + if err == nil { + val = v + } + } else { + v, err := strconv.Atoi(val.(string)) + if err == nil { + val = v + } + } + } } } @@ -265,64 +191,117 @@ func parseParams(c echo.Context, parameters openapi3.Parameters, params map[stri return contextValues, errors } -func AddToContext(c echo.Context, cc context.Context, contextValues map[string]interface{}, entityFactory model.EntityFactory) context.Context { +func AddToContext(c echo.Context, cc context.Context, contextValues map[string]interface{}, entityFactory model.EntityFactory) (context.Context, error) { if contextValues == nil { - return cc + return cc, nil } - if entityFactory == nil { - c.Logger().Error("no entity factory found") - return cc - } - schema := entityFactory.Schema() + var errors error for key, value := range contextValues { - if schema.Properties[key] != nil { - switch schema.Properties[key].Value.Type { - case "integer": - switch value.(type) { - case float64: - value = int(value.(float64)) - case string: - v, err := strconv.Atoi(value.(string)) - if err == nil { - value = v - } + switch key { + case "sequence_no", "page", "limit": //default type is integer + switch value.(type) { + case float64: + contextValues[key] = int(value.(float64)) + case string: + v, err := strconv.Atoi(value.(string)) + if err == nil { + contextValues[key] = v } - - case "boolean": - v, err := strconv.ParseBool(value.(string)) + } + case "use_entity_id": + if val, ok := value.(string); ok { + //default type is boolean + v, err := strconv.ParseBool(val) if err == nil { - value = v + contextValues[key] = v } - case "number": - format := schema.Properties[key].Value.Format - if format == "float" || format == "double" { - switch value.(type) { - case string: - v, err := strconv.ParseFloat(value.(string), 64) - if err == nil { - value = v + } + case "_filters": + if value == nil { + errors = fmt.Errorf("unexpected error no filters specified") + continue + } + if val, ok := value.(string); ok { + //if the filter comes from x-context do this conversion + filters := map[string]interface{}{} + decodedQuery, err := url.PathUnescape(val) + if err != nil { + errors = fmt.Errorf("Error decoding the string %v", err) + continue + } + filtersArray := SplitFilters(decodedQuery) + if filtersArray != nil && len(filtersArray) > 0 { + for _, value := range filtersArray { + if strings.Contains(value, "_filters") { + prop := SplitFilter(value) + if prop == nil { + errors = fmt.Errorf("unexpected error filter format is incorrect: %s", value) + break + } + filters[prop.Field] = prop } } + filters, err = convertProperties(filters, entityFactory.Schema()) + if err != nil { + errors = err + continue + } + contextValues[key] = filters + continue + } - } else { - switch value.(type) { - case float64: - value = int(value.(float64)) - case string: - v, err := strconv.Atoi(value.(string)) - if err == nil { - value = v + } else { + //if the filter comes from request do this conversion + filters := map[string]interface{}{} + for _, filterProp := range value.([]interface{}) { + if filterProp.(map[string]interface{})["operator"] == nil || filterProp.(map[string]interface{})["field"] == nil || (filterProp.(map[string]interface{})["value"] == nil && filterProp.(map[string]interface{})["values"] == nil) { + errors = fmt.Errorf("unexpected error all filter fields are not filled out") + break + } + if filterProp.(map[string]interface{})["values"] != nil { + filters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ + Field: filterProp.(map[string]interface{})["field"].(string), + Operator: filterProp.(map[string]interface{})["operator"].(string), + Values: filterProp.(map[string]interface{})["values"].([]interface{}), + } + } else { + filters[filterProp.(map[string]interface{})["field"].(string)] = &FilterProperties{ + Field: filterProp.(map[string]interface{})["field"].(string), + Operator: filterProp.(map[string]interface{})["operator"].(string), + Value: filterProp.(map[string]interface{})["value"], } } } - + if len(value.([]interface{})) != len(filters) { + continue + } + filters, err := convertProperties(filters, entityFactory.Schema()) + if err != nil { + errors = err + continue + } + contextValues[key] = filters + } + case "If-Match", "If-None-Match": //default type is string + if value != nil { + contextValues[key] = value.(string) + } + case "_sorts": + sortOptions := map[string]string{} + for _, sortOption := range value.([]interface{}) { + if sortOption.(map[string]interface{})["field"] == nil || sortOption.(map[string]interface{})["order"] == nil { + errors = fmt.Errorf("unexpected error all sort fields are not filled out") + continue + } + sortOptions[sortOption.(map[string]interface{})["field"].(string)] = sortOption.(map[string]interface{})["field"].(string) } + contextValues[key] = sortOptions } } for key, value := range contextValues { cc = context.WithValue(cc, key, value) } - return cc + return cc, errors } //convertProperties is used to convert the filter value to the correct data type based on the schema From be0d6e63a875d660103a91e9463e4fa2a58d19b9 Mon Sep 17 00:00:00 2001 From: shaniah868 <65876481+shaniah868@users.noreply.github.com> Date: Fri, 4 Mar 2022 11:27:35 -0400 Subject: [PATCH 12/16] feature: WEOS-1365 As a developer I should be able set an example to be returned if there is no controller set -Updated controller --- controllers/rest/controller_standard.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/controllers/rest/controller_standard.go b/controllers/rest/controller_standard.go index b50dd88f..81c6b58a 100644 --- a/controllers/rest/controller_standard.go +++ b/controllers/rest/controller_standard.go @@ -755,11 +755,7 @@ func DefaultResponseMiddleware(api *RESTAPI, projection projections.Projection, func DefaultResponseController(api *RESTAPI, projection projections.Projection, commandDispatcher model.CommandDispatcher, eventSource model.EventRepository, entityFactory model.EntityFactory) echo.HandlerFunc { return func(context echo.Context) error { newContext := context.Request().Context() - value := newContext.Value("resp") - if value == nil { - return NewControllerError("unexpected error all responses were parsed, nothing was found", nil, http.StatusBadRequest) - } - return value.(error) + return newContext.Value("resp").(error) } } From 0c4a0aeb10faa7c39a8b1b5e81742f964a88856c Mon Sep 17 00:00:00 2001 From: shaniah868 <65876481+shaniah868@users.noreply.github.com> Date: Fri, 4 Mar 2022 11:33:20 -0400 Subject: [PATCH 13/16] feature: WEOS-1365 As a developer I should be able set an example to be returned if there is no controller set -Updated controller --- controllers/rest/controller_standard.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/controllers/rest/controller_standard.go b/controllers/rest/controller_standard.go index 81c6b58a..587c61e3 100644 --- a/controllers/rest/controller_standard.go +++ b/controllers/rest/controller_standard.go @@ -755,7 +755,11 @@ func DefaultResponseMiddleware(api *RESTAPI, projection projections.Projection, func DefaultResponseController(api *RESTAPI, projection projections.Projection, commandDispatcher model.CommandDispatcher, eventSource model.EventRepository, entityFactory model.EntityFactory) echo.HandlerFunc { return func(context echo.Context) error { newContext := context.Request().Context() - return newContext.Value("resp").(error) + value := newContext.Value("resp") + if value == nil { + return nil + } + return value.(error) } } From f4434c4ea5e98e0a913311af69b6529f276dd083 Mon Sep 17 00:00:00 2001 From: shaniah868 <65876481+shaniah868@users.noreply.github.com> Date: Mon, 7 Mar 2022 13:05:18 -0400 Subject: [PATCH 14/16] feature: WEOS-1308 As a developer I should be able to add data to the context via the api specification -Added checks so if nothing is passed in the request it should be removed from the map --- controllers/rest/middleware_context.go | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index 3f47ef93..5a4c06d0 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -156,16 +156,28 @@ func parseParams(c echo.Context, parameters openapi3.Parameters, entityFactory m pType := paramType.Value.Type switch strings.ToLower(pType) { case "integer": + if val.(string) == "" { + delete(contextValues, contextName) + break + } v, err := strconv.Atoi(val.(string)) if err == nil { val = v } case "boolean": + if val.(string) == "" { + delete(contextValues, contextName) + break + } v, err := strconv.ParseBool(val.(string)) if err == nil { val = v } case "number": + if val.(string) == "" { + delete(contextValues, contextName) + break + } format := paramType.Value.Format if format == "float" || format == "double" { v, err := strconv.ParseFloat(val.(string), 64) @@ -203,6 +215,10 @@ func AddToContext(c echo.Context, cc context.Context, contextValues map[string]i case float64: contextValues[key] = int(value.(float64)) case string: + if value.(string) == "" { + delete(contextValues, key) + break + } v, err := strconv.Atoi(value.(string)) if err == nil { contextValues[key] = v @@ -211,6 +227,10 @@ func AddToContext(c echo.Context, cc context.Context, contextValues map[string]i case "use_entity_id": if val, ok := value.(string); ok { //default type is boolean + if value.(string) == "" { + delete(contextValues, key) + break + } v, err := strconv.ParseBool(val) if err == nil { contextValues[key] = v @@ -222,6 +242,11 @@ func AddToContext(c echo.Context, cc context.Context, contextValues map[string]i continue } if val, ok := value.(string); ok { + if value.(string) == "" { + delete(contextValues, key) + break + + } //if the filter comes from x-context do this conversion filters := map[string]interface{}{} decodedQuery, err := url.PathUnescape(val) @@ -252,6 +277,10 @@ func AddToContext(c echo.Context, cc context.Context, contextValues map[string]i } else { //if the filter comes from request do this conversion + if value.([]interface{}) == nil { + delete(contextValues, key) + break + } filters := map[string]interface{}{} for _, filterProp := range value.([]interface{}) { if filterProp.(map[string]interface{})["operator"] == nil || filterProp.(map[string]interface{})["field"] == nil || (filterProp.(map[string]interface{})["value"] == nil && filterProp.(map[string]interface{})["values"] == nil) { @@ -284,9 +313,17 @@ func AddToContext(c echo.Context, cc context.Context, contextValues map[string]i } case "If-Match", "If-None-Match": //default type is string if value != nil { + if value.(string) == "" { + delete(contextValues, key) + break + } contextValues[key] = value.(string) } case "_sorts": + if value.([]interface{}) == nil { + delete(contextValues, key) + break + } sortOptions := map[string]string{} for _, sortOption := range value.([]interface{}) { if sortOption.(map[string]interface{})["field"] == nil || sortOption.(map[string]interface{})["order"] == nil { From b847bbb47c8cd82467cb5adcbf3d1ef251986092 Mon Sep 17 00:00:00 2001 From: shaniah868 <65876481+shaniah868@users.noreply.github.com> Date: Mon, 7 Mar 2022 13:19:01 -0400 Subject: [PATCH 15/16] feature: WEOS-1308 As a developer I should be able to add data to the context via the api specification -Added skipped tag to 1365 since it is being worked on, on another ticket --- features/default-response.feature | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/default-response.feature b/features/default-response.feature index cc3a40f4..fc608ad4 100644 --- a/features/default-response.feature +++ b/features/default-response.feature @@ -1,4 +1,5 @@ -Feature: Hardcode the response for an endpoint +@skipped +Feature: Hardcode the response for an endpoint There are times when we want to be able to return a fixed response on a specific endpoint. To do this you can create an example response and if there is not controller specified for that endpoint From 93850c4f40eb50a9c7aee4fa81bb416ea8838b99 Mon Sep 17 00:00:00 2001 From: shaniah868 <65876481+shaniah868@users.noreply.github.com> Date: Mon, 7 Mar 2022 15:17:26 -0400 Subject: [PATCH 16/16] feature: WEOS-1308 As a developer I should be able to add data to the context via the api sepcification -Debug an issue --- api.yaml | 20 +++----------------- controllers/rest/middleware_context.go | 5 ----- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/api.yaml b/api.yaml index 0d06ac8f..c31a437c 100644 --- a/api.yaml +++ b/api.yaml @@ -258,24 +258,10 @@ paths: required: false description: query string x-context: - filters: - - field: status + _filters: + - field: active operator: eq - values: - - Active - - field: lastUpdated - operator: between - values: - - 2021-12-17 15:46:00 - - 2021-12-18 15:46:00 - - field: categories - operator: in - values: - - Technology - - Javascript - sorts: - - field: title - order: asc + value: true page: 1 limit: 10 responses: diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index 5a4c06d0..42ff1e82 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -304,11 +304,6 @@ func AddToContext(c echo.Context, cc context.Context, contextValues map[string]i if len(value.([]interface{})) != len(filters) { continue } - filters, err := convertProperties(filters, entityFactory.Schema()) - if err != nil { - errors = err - continue - } contextValues[key] = filters } case "If-Match", "If-None-Match": //default type is string