From 3538975202e3aa50691c9165548091ecc98b5484 Mon Sep 17 00:00:00 2001 From: Viacheslav Poturaev Date: Mon, 26 Oct 2020 09:51:37 +0100 Subject: [PATCH] Use deepObject serialization for objects in query (#1) --- _examples/advanced/_testdata/openapi.json | 21 ++++++++++++ _examples/advanced/query_object.go | 42 +++++++++++++++++++++++ _examples/advanced/router.go | 2 ++ _examples/go.mod | 4 +-- _examples/go.sum | 4 +-- go.mod | 2 +- go.sum | 4 +-- nethttp/options.go | 7 ++++ request/decoder_test.go | 26 ++++++++++++++ 9 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 _examples/advanced/query_object.go diff --git a/_examples/advanced/_testdata/openapi.json b/_examples/advanced/_testdata/openapi.json index a4c2326..b5e1228 100644 --- a/_examples/advanced/_testdata/openapi.json +++ b/_examples/advanced/_testdata/openapi.json @@ -208,6 +208,23 @@ } } }, + "/query-object":{ + "get":{ + "summary":"Request With Object As Query Parameter","description":"","operationId":"", + "parameters":[ + { + "name":"in_query","in":"query","description":"Object value in query.","style":"deepObject","explode":true, + "schema":{"type":"object","additionalProperties":{"type":"number"},"description":"Object value in query."} + } + ], + "responses":{ + "200":{ + "description":"OK", + "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedOutputQueryObject"}}} + } + } + } + }, "/req-resp-mapping":{ "post":{ "summary":"Request Response Mapping", @@ -315,6 +332,10 @@ "required":["data"],"type":"object", "properties":{"data":{"type":"object","properties":{"value":{"maxLength":7,"type":"string"}}}} }, + "AdvancedOutputQueryObject":{ + "type":"object", + "properties":{"inQuery":{"type":"object","additionalProperties":{"type":"number"},"nullable":true}} + }, "AdvancedOutputWithJSON":{ "type":"object", "properties":{ diff --git a/_examples/advanced/query_object.go b/_examples/advanced/query_object.go new file mode 100644 index 0000000..30219d2 --- /dev/null +++ b/_examples/advanced/query_object.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + + "github.com/swaggest/usecase" +) + +func queryObject() usecase.Interactor { + u := struct { + usecase.Interactor + usecase.Info + usecase.WithInput + usecase.WithOutput + }{} + + u.SetTitle("Request With Object As Query Parameter") + + type inputQueryObject struct { + Query map[int]float64 `query:"in_query" description:"Object value in query."` + } + + type outputQueryObject struct { + Query map[int]float64 `json:"inQuery"` + } + + u.Input = new(inputQueryObject) + u.Output = new(outputQueryObject) + + u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) { + var ( + in = input.(*inputQueryObject) + out = output.(*outputQueryObject) + ) + + out.Query = in.Query + + return nil + }) + + return u +} diff --git a/_examples/advanced/router.go b/_examples/advanced/router.go index 254c9db..50c37cb 100644 --- a/_examples/advanced/router.go +++ b/_examples/advanced/router.go @@ -53,6 +53,8 @@ func NewRouter() http.Handler { return nil }) + r.Method(http.MethodGet, "/query-object", nethttp.NewHandler(queryObject())) + r.Method(http.MethodPost, "/file-upload", nethttp.NewHandler(fileUploader())) r.Method(http.MethodPost, "/json-body/{in-path}", nethttp.NewHandler(jsonBody())) r.Method(http.MethodPost, "/json-body-validation/{in-path}", nethttp.NewHandler(jsonBodyValidation())) diff --git a/_examples/go.mod b/_examples/go.mod index dfae8cb..3916446 100644 --- a/_examples/go.mod +++ b/_examples/go.mod @@ -14,8 +14,8 @@ require ( github.com/stretchr/testify v1.6.1 github.com/swaggest/assertjson v1.3.0 github.com/swaggest/jsonschema-go v0.3.11 - github.com/swaggest/openapi-go v0.2.1 - github.com/swaggest/rest v0.0.0-20201021182107-c25bd05be4b6 + github.com/swaggest/openapi-go v0.2.2 + github.com/swaggest/rest v0.0.0-20201021182948-25d31f7f38d4 github.com/swaggest/swgui v1.0.9 github.com/swaggest/usecase v0.0.0-20200928062416-27f47131b0f8 github.com/valyala/fasthttp v1.16.0 diff --git a/_examples/go.sum b/_examples/go.sum index 3a19002..8c507a4 100644 --- a/_examples/go.sum +++ b/_examples/go.sum @@ -86,8 +86,8 @@ github.com/swaggest/jsonschema-go v0.3.8/go.mod h1:AQijowS82ZcUId9RStqvyGTxyhzYU github.com/swaggest/jsonschema-go v0.3.11 h1:rbYBJhAV2Uz1PB4iJyKbvzGO+o1FsdX60aLi+o67IHU= github.com/swaggest/jsonschema-go v0.3.11/go.mod h1:lJUbAc2E3OpGrrUGvayONeRBXsFJeedWTpm23jhOtKs= github.com/swaggest/openapi-go v0.1.3/go.mod h1:Zx4ZgJ7XvlFH9wCOHE7u8RAjLfiHAnCHeaD5kUDujVM= -github.com/swaggest/openapi-go v0.2.1 h1:dZaR+vzBm34r/9706lbO7oqLWzD2SFP87qrjh9SPtlE= -github.com/swaggest/openapi-go v0.2.1/go.mod h1:GyvI08XD7gnTZxFteI2/Rnk6hgmcSufqG8wVBz/yzqo= +github.com/swaggest/openapi-go v0.2.2 h1:ZV6tjpR/W97sIOknkaOAt/U2FpqoEptRZrYiksfNKkY= +github.com/swaggest/openapi-go v0.2.2/go.mod h1:GyvI08XD7gnTZxFteI2/Rnk6hgmcSufqG8wVBz/yzqo= github.com/swaggest/refl v0.1.0/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= github.com/swaggest/refl v0.1.2/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= github.com/swaggest/refl v0.1.3 h1:cnzEBcCNhYeLPG8Yy9JQixUkxMDsF0mo0GyzblLWrjE= diff --git a/go.mod b/go.mod index 1faf0db..43023c7 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/swaggest/assertjson v1.3.0 github.com/swaggest/form v3.6.4+incompatible github.com/swaggest/jsonschema-go v0.3.11 // indirect - github.com/swaggest/openapi-go v0.2.1 + github.com/swaggest/openapi-go v0.2.2 github.com/swaggest/refl v0.1.5 github.com/swaggest/usecase v0.0.0-20200928062416-27f47131b0f8 golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect diff --git a/go.sum b/go.sum index fe6f266..6a7d6cb 100644 --- a/go.sum +++ b/go.sum @@ -73,8 +73,8 @@ github.com/swaggest/jsonschema-go v0.3.8/go.mod h1:AQijowS82ZcUId9RStqvyGTxyhzYU github.com/swaggest/jsonschema-go v0.3.11 h1:rbYBJhAV2Uz1PB4iJyKbvzGO+o1FsdX60aLi+o67IHU= github.com/swaggest/jsonschema-go v0.3.11/go.mod h1:lJUbAc2E3OpGrrUGvayONeRBXsFJeedWTpm23jhOtKs= github.com/swaggest/openapi-go v0.1.3/go.mod h1:Zx4ZgJ7XvlFH9wCOHE7u8RAjLfiHAnCHeaD5kUDujVM= -github.com/swaggest/openapi-go v0.2.1 h1:dZaR+vzBm34r/9706lbO7oqLWzD2SFP87qrjh9SPtlE= -github.com/swaggest/openapi-go v0.2.1/go.mod h1:GyvI08XD7gnTZxFteI2/Rnk6hgmcSufqG8wVBz/yzqo= +github.com/swaggest/openapi-go v0.2.2 h1:ZV6tjpR/W97sIOknkaOAt/U2FpqoEptRZrYiksfNKkY= +github.com/swaggest/openapi-go v0.2.2/go.mod h1:GyvI08XD7gnTZxFteI2/Rnk6hgmcSufqG8wVBz/yzqo= github.com/swaggest/refl v0.1.0/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= github.com/swaggest/refl v0.1.2/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o= github.com/swaggest/refl v0.1.3 h1:cnzEBcCNhYeLPG8Yy9JQixUkxMDsF0mo0GyzblLWrjE= diff --git a/nethttp/options.go b/nethttp/options.go index 25fef3b..b3cad14 100644 --- a/nethttp/options.go +++ b/nethttp/options.go @@ -22,6 +22,13 @@ func SuccessfulResponseContentType(contentType string) func(h *Handler) { } } +// SuccessStatus sets status code of successful response. +func SuccessStatus(status int) func(h *Handler) { + return func(h *Handler) { + h.SuccessStatus = status + } +} + // RequestMapping creates rest.RequestMapping from struct tags. // // This can be used to decouple mapping from usecase input with additional struct. diff --git a/request/decoder_test.go b/request/decoder_test.go index bf1e349..bf6cdbc 100644 --- a/request/decoder_test.go +++ b/request/decoder_test.go @@ -138,3 +138,29 @@ func TestDecoder_Decode_json(t *testing.T) { assert.Error(t, err) assert.Equal(t, rest.ValidationErrors{"body": []string{"#/bodyTwo: minimum 2 items allowed, but found 1 items"}}, err) } + +func TestDecoder_Decode_queryObject(t *testing.T) { + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, + "/?in_query[1]=1.0&in_query[2]=2.1&in_query[3]=0", nil) + assert.NoError(t, err) + + df := request.NewDecoderFactory() + + input := new(struct { + InQuery map[int]float64 `query:"in_query"` + }) + dec := df.MakeDecoder(http.MethodGet, input, nil) + + assert.NoError(t, dec.Decode(req, input, nil)) + assert.Equal(t, map[int]float64{1: 1, 2: 2.1, 3: 0}, input.InQuery) + + req, err = http.NewRequestWithContext(context.Background(), http.MethodGet, + "/?in_query[1]=1.0&in_query[2]=2.1&in_query[c]=0", nil) + assert.NoError(t, err) + + err = dec.Decode(req, input, nil) + assert.Error(t, err) + assert.Equal(t, rest.RequestErrors{"query:in_query": []string{ + "#: Invalid Integer Value 'c' Type 'int' Namespace 'in_query'", + }}, err) +}