diff --git a/docs/client.md b/docs/client.md index 34276391..5312f8a0 100644 --- a/docs/client.md +++ b/docs/client.md @@ -254,6 +254,7 @@ bracket form most form APIs read, Stripe's among them: a bracket per level, an i | `[{price: p1}]` | `items[0][price]=p1` | | `{a: {b: [1]}}` | `filter[a][b][0]=1` | | `https://x.test`, one value | `url=https%3A%2F%2Fx.test` | +| a union, `{gte: 1}` or `1` | `created[gte]=1` or `created=1` | Brackets are shown as they read. On the wire they are `%5B` and `%5D`. diff --git a/docs/server.md b/docs/server.md index 12653b74..4a840fe5 100644 --- a/docs/server.md +++ b/docs/server.md @@ -102,13 +102,17 @@ OpenAPI defines `deepObject` for an object of scalars only. Lists, nesting and s [the bracket form](client.md#deepobject-past-an-object) of the client. The server also reads `[]` and a repeated bare name: `expand[]=a`, `expand=a&expand=b`. +A union reads keys with brackets as an object member and a bare name as one value: `created[gte]=1` +and `created=1` both work. A `deepObject` parameter that comes both ways, `created=1&created[gte]=1`, +is a 400. + ### Parameters without a field Some parameters cannot be read, so they get no field. Generation warns about each one. | Parameter | Warning | |---|---| -| a query, header or cookie parameter whose union has an object or array variant, or a list or map of such unions. No style writes it as text. | `param-unsupported` | +| a query, header or cookie parameter whose union has an object or array variant, or a list or map of such unions. No style but `deepObject` writes it. | `param-unsupported` | | a parameter or response header in a style the OpenAPI style table does not define for its location or value. See below. | `param-unsupported` | | a form-style cookie that holds a list or object with `explode: true`. OpenAPI says it writes the wrong delimiter for cookies. `style: cookie` writes it. | `param-unsupported` | | nesting the table above does not list | `param-unsupported` | diff --git a/docs/types.md b/docs/types.md index 6b52d435..e3f522c6 100644 --- a/docs/types.md +++ b/docs/types.md @@ -115,7 +115,8 @@ type Device struct { a union takes any JSON for it. - A parameter or header of such a type needs one that implements `encoding.TextMarshaler` and `encoding.TextUnmarshaler`. -- A union parameter with such a member gets no field, with a warning. +- A union parameter with such a member gets no field, with a warning, unless its style is + `deepObject`. ## Pointers @@ -489,7 +490,8 @@ A union of strings, numbers and booleans with no shared properties also gets `Ma - Text that reads as a JSON number or boolean is tried as one first, then as a string. - A union with nothing set has no text: sending it is an error. - A query, header or cookie parameter whose union has an object or array variant gets no field, - with a warning. So does a list or map of such unions. + with a warning. So does a list or map of such unions. A `deepObject` query parameter is the + exception: it is written with brackets, `created[gte]=1` or `created=1`. #### Forms diff --git a/examples/client/params/api.yaml b/examples/client/params/api.yaml index 678aa8dc..41c27bc2 100644 --- a/examples/client/params/api.yaml +++ b/examples/client/params/api.yaml @@ -30,6 +30,7 @@ paths: in: query schema: {type: object, properties: {reference: {type: string}, status: {type: array, items: {type: string}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: ids, in: query, allowReserved: true, schema: {type: string}} - {name: needed, in: query, required: true, schema: {type: string}} responses: @@ -92,6 +93,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/client/params/gen.go b/examples/client/params/gen.go index d5a7a8d9..22ffc25e 100644 --- a/examples/client/params/gen.go +++ b/examples/client/params/gen.go @@ -4,6 +4,7 @@ package params import ( "context" + "mime/multipart" "net/http" "net/url" "slices" @@ -17,6 +18,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -84,6 +90,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -166,6 +228,7 @@ type QueryStylesQuery struct { Filter *Filter `json:"filter,omitempty"` Queries *QueryStylesQueryQueries `json:"queries,omitempty"` ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` IDs *string `json:"ids,omitempty"` Needed string `json:"needed"` } @@ -176,6 +239,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -431,6 +497,7 @@ func (c *Client) QueryStylesRequest(ctx context.Context, opts *QueryStylesReques b.QueryParam(opts.Query.Filter, runtime.Param{Name: "filter", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}) b.QueryParam(opts.Query.Queries, runtime.Param{Name: "queries", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}) b.QueryParam(opts.Query.ID, runtime.Param{Name: "id", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}) + b.QueryParam(opts.Query.Created, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}) b.QueryParam(opts.Query.IDs, runtime.Param{Name: "ids", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false, IsReserved: true}) b.QueryParam(opts.Query.Needed, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}) } diff --git a/examples/client/params/gen_test.go b/examples/client/params/gen_test.go index a87a40d9..12787f69 100644 --- a/examples/client/params/gen_test.go +++ b/examples/client/params/gen_test.go @@ -84,6 +84,16 @@ func TestQueryStyles(t *testing.T) { query: &QueryStylesQuery{ID: &QueryStylesQueryID{String: new("a7")}, Needed: "yes"}, want: "id=a7&needed=yes", }, + { + name: "A deepObject union writes an object variant under brackets", + query: &QueryStylesQuery{Created: &QueryStylesQueryCreated{Range: &Range{Gte: new(1), Lt: new(2)}}, Needed: "yes"}, + want: "created%5Bgte%5D=1&created%5Blt%5D=2&needed=yes", + }, + { + name: "A deepObject union writes one value under its name", + query: &QueryStylesQuery{Created: &QueryStylesQueryCreated{Int: new(5)}, Needed: "yes"}, + want: "created=5&needed=yes", + }, { name: "A space and a comma inside an item are escaped, reserved characters kept where allowed", query: &QueryStylesQuery{Csv: []string{"a b", "c,d"}, IDs: new("List(1,2)"), Needed: "x y"}, diff --git a/examples/server/beego/params/api.yaml b/examples/server/beego/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/beego/params/api.yaml +++ b/examples/server/beego/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/beego/params/gen.go b/examples/server/beego/params/gen.go index 40e9bae1..08d05bac 100644 --- a/examples/server/beego/params/gen.go +++ b/examples/server/beego/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "strings" "time" @@ -20,6 +21,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -81,6 +87,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -153,16 +215,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -179,6 +242,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -681,6 +747,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/chi/params/api.yaml b/examples/server/chi/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/chi/params/api.yaml +++ b/examples/server/chi/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/chi/params/gen.go b/examples/server/chi/params/gen.go index 23955112..7a6c3926 100644 --- a/examples/server/chi/params/gen.go +++ b/examples/server/chi/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -18,6 +19,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -79,6 +85,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -151,16 +213,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -177,6 +240,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -679,6 +745,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/echo/params/api.yaml b/examples/server/echo/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/echo/params/api.yaml +++ b/examples/server/echo/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/echo/params/gen.go b/examples/server/echo/params/gen.go index 0f007214..e7d2eb5e 100644 --- a/examples/server/echo/params/gen.go +++ b/examples/server/echo/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -18,6 +19,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -79,6 +85,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -151,16 +213,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -177,6 +240,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -682,6 +748,10 @@ func (a *HTTPAdapter) QueryStyles(c echo.Context) error { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return nil } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return nil + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return nil diff --git a/examples/server/echov5/params/api.yaml b/examples/server/echov5/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/echov5/params/api.yaml +++ b/examples/server/echov5/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/echov5/params/gen.go b/examples/server/echov5/params/gen.go index e83a3fe8..c9dd27c9 100644 --- a/examples/server/echov5/params/gen.go +++ b/examples/server/echov5/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -18,6 +19,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -79,6 +85,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -151,16 +213,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -177,6 +240,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -682,6 +748,10 @@ func (a *HTTPAdapter) QueryStyles(c *echo.Context) error { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return nil } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return nil + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return nil diff --git a/examples/server/fasthttp/params/api.yaml b/examples/server/fasthttp/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/fasthttp/params/api.yaml +++ b/examples/server/fasthttp/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/fasthttp/params/gen.go b/examples/server/fasthttp/params/gen.go index 6465b1ce..321f3d60 100644 --- a/examples/server/fasthttp/params/gen.go +++ b/examples/server/fasthttp/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -20,6 +21,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -81,6 +87,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -153,16 +215,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -179,6 +242,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -681,6 +747,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/fiber/params/api.yaml b/examples/server/fiber/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/fiber/params/api.yaml +++ b/examples/server/fiber/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/fiber/params/gen.go b/examples/server/fiber/params/gen.go index 4d8cf01c..6a165c5d 100644 --- a/examples/server/fiber/params/gen.go +++ b/examples/server/fiber/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "strings" "time" @@ -20,6 +21,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -81,6 +87,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -153,16 +215,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -179,6 +242,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -681,6 +747,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/gin/params/api.yaml b/examples/server/gin/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/gin/params/api.yaml +++ b/examples/server/gin/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/gin/params/gen.go b/examples/server/gin/params/gen.go index 4c7b2793..bdfecf39 100644 --- a/examples/server/gin/params/gen.go +++ b/examples/server/gin/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -18,6 +19,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -79,6 +85,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -151,16 +213,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -177,6 +240,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -679,6 +745,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/goframe/params/api.yaml b/examples/server/goframe/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/goframe/params/api.yaml +++ b/examples/server/goframe/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/goframe/params/gen.go b/examples/server/goframe/params/gen.go index 8b7b271a..f21827a0 100644 --- a/examples/server/goframe/params/gen.go +++ b/examples/server/goframe/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "net/url" "time" @@ -21,6 +22,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -82,6 +88,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -154,16 +216,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -180,6 +243,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -682,6 +748,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/gorillamux/params/api.yaml b/examples/server/gorillamux/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/gorillamux/params/api.yaml +++ b/examples/server/gorillamux/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/gorillamux/params/gen.go b/examples/server/gorillamux/params/gen.go index 097bf144..35831338 100644 --- a/examples/server/gorillamux/params/gen.go +++ b/examples/server/gorillamux/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -18,6 +19,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -79,6 +85,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -151,16 +213,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -177,6 +240,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -679,6 +745,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/gozero/params/api.yaml b/examples/server/gozero/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/gozero/params/api.yaml +++ b/examples/server/gozero/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/gozero/params/gen.go b/examples/server/gozero/params/gen.go index 0b1fc001..8f0ec820 100644 --- a/examples/server/gozero/params/gen.go +++ b/examples/server/gozero/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -20,6 +21,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -81,6 +87,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -153,16 +215,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -179,6 +242,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -681,6 +747,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/hertz/params/api.yaml b/examples/server/hertz/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/hertz/params/api.yaml +++ b/examples/server/hertz/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/hertz/params/gen.go b/examples/server/hertz/params/gen.go index d09242d4..13c622db 100644 --- a/examples/server/hertz/params/gen.go +++ b/examples/server/hertz/params/gen.go @@ -7,6 +7,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -22,6 +23,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -83,6 +89,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -155,16 +217,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -181,6 +244,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -683,6 +749,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/internal/servertest/servertest.go b/examples/server/internal/servertest/servertest.go index 3c867b46..16bbb32f 100644 --- a/examples/server/internal/servertest/servertest.go +++ b/examples/server/internal/servertest/servertest.go @@ -166,6 +166,22 @@ var Params = []Request{ Path: "/query?id=a7&needed=yes", WantBody: `{"query":{"id":"a7","needed":"yes","limit":20}}`, }, + { + Name: "A deepObject union takes an object under brackets", + Path: "/query?created[gte]=1&created[lt]=2&needed=yes", + WantBody: `{"query":{"created":{"gte":1,"lt":2},"needed":"yes","limit":20}}`, + }, + { + Name: "A deepObject union takes one value under its name", + Path: "/query?created=5&needed=yes", + WantBody: `{"query":{"created":5,"needed":"yes","limit":20}}`, + }, + { + Name: "A deepObject parameter that comes as one value and with brackets", + Path: "/query?created=5&created[gte]=1&needed=yes", + WantStatus: 400, + WantBody: `{"error":"invalid query parameter \"created\": invalid parameter value: created comes both as one value and with brackets"}`, + }, { Name: "Query parameters left out stay nil or take their default", Path: "/query?needed=yes", diff --git a/examples/server/iris/params/api.yaml b/examples/server/iris/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/iris/params/api.yaml +++ b/examples/server/iris/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/iris/params/gen.go b/examples/server/iris/params/gen.go index 5276265a..95cac4d2 100644 --- a/examples/server/iris/params/gen.go +++ b/examples/server/iris/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -18,6 +19,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -79,6 +85,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -151,16 +213,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -177,6 +240,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -679,6 +745,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/examples/server/kratos/params/api.yaml b/examples/server/kratos/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/kratos/params/api.yaml +++ b/examples/server/kratos/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/kratos/params/gen.go b/examples/server/kratos/params/gen.go index b33eec2f..a2dc547e 100644 --- a/examples/server/kratos/params/gen.go +++ b/examples/server/kratos/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -18,6 +19,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -79,6 +85,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -151,16 +213,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -177,6 +240,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -682,6 +748,10 @@ func (a *HTTPAdapter) QueryStyles(c khttp.Context) error { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return nil } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return nil + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return nil diff --git a/examples/server/stdhttp/params/api.yaml b/examples/server/stdhttp/params/api.yaml index 691a7c8b..3819ea38 100644 --- a/examples/server/stdhttp/params/api.yaml +++ b/examples/server/stdhttp/params/api.yaml @@ -26,6 +26,7 @@ paths: - {name: flat, in: query, explode: false, schema: {$ref: '#/components/schemas/Point'}} - {name: json, in: query, content: {application/json: {schema: {$ref: '#/components/schemas/Point'}}}} - {name: id, in: query, schema: {oneOf: [{type: integer}, {type: string}]}} + - {name: created, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Range'}, {type: integer}]}} - {name: needed, in: query, required: true, schema: {type: string}} - {name: limit, in: query, schema: {type: integer, default: 20}} responses: @@ -77,6 +78,11 @@ paths: application/json: {schema: {$ref: '#/components/schemas/Echo'}} components: schemas: + Range: + type: object + properties: + gte: {type: integer} + lt: {type: integer} Point: type: object properties: diff --git a/examples/server/stdhttp/params/gen.go b/examples/server/stdhttp/params/gen.go index f1d404bf..807e7861 100644 --- a/examples/server/stdhttp/params/gen.go +++ b/examples/server/stdhttp/params/gen.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "mime/multipart" "net/http" "time" @@ -17,6 +18,11 @@ import ( // Fails to compile when the runtime does not match the generator that wrote this file. const _ = runtime.SupportsGeneratorV1 +type Range struct { + Gte *int `json:"gte,omitempty"` + Lt *int `json:"lt,omitempty"` +} + type Point struct { X *int `json:"x,omitempty"` Y *int `json:"y,omitempty"` @@ -78,6 +84,62 @@ func (q QueryStylesQueryID) Validate() error { return errs.Err() } +// QueryStylesQueryCreated is any of Range or Int. +type QueryStylesQueryCreated struct { + Range *Range `json:"-"` + Int *int `json:"-"` +} + +// MarshalJSON writes the variants that are set, merged. +func (q QueryStylesQueryCreated) MarshalJSON() ([]byte, error) { + var set []any + if q.Range != nil { + set = append(set, q.Range) + } + if q.Int != nil { + set = append(set, q.Int) + } + return runtime.MarshalUnion(nil, set...) +} + +// UnmarshalJSON sets every variant data matches. +func (q *QueryStylesQueryCreated) UnmarshalJSON(data []byte) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnion(data, q.union()) +} + +// UnmarshalForm sets every variant the form matches. +func (q *QueryStylesQueryCreated) UnmarshalForm(form *multipart.Form) error { + *q = QueryStylesQueryCreated{} + return runtime.UnmarshalUnionForm(form, nil, q.union()) +} + +func (q *QueryStylesQueryCreated) union() runtime.Union { + return runtime.Union{ + IsAnyOf: true, + Variants: []runtime.Variant{ + { + Name: "Range", + Kind: runtime.KindObject, + Known: []string{"gte", "lt"}, + Into: runtime.Into(&q.Range), + }, + { + Name: "Int", + Kind: runtime.KindInteger, + Into: runtime.Into(&q.Int), + }, + }, + } +} + +// Validate checks the value against the constraints of the spec. +func (q QueryStylesQueryCreated) Validate() error { + var errs validation.Errors + errs.Append("", validation.AtLeastOne(q.Range != nil, q.Int != nil)) + return errs.Err() +} + // HeaderStylesHeadersXLimit is any of Int or Bool. type HeaderStylesHeadersXLimit struct { Int *int `json:"-"` @@ -150,16 +212,17 @@ func (p PathStylesPathParams) Validate() error { } type QueryStylesQuery struct { - Form []int `json:"form,omitzero"` - Csv []string `json:"csv,omitzero"` - Space []string `json:"space,omitzero"` - Pipe []string `json:"pipe,omitzero"` - Deep *Point `json:"deep,omitempty"` - Flat *Point `json:"flat,omitempty"` - JSON *Point `json:"json,omitempty"` - ID *QueryStylesQueryID `json:"id,omitempty"` - Needed string `json:"needed"` - Limit *int `json:"limit,omitempty"` + Form []int `json:"form,omitzero"` + Csv []string `json:"csv,omitzero"` + Space []string `json:"space,omitzero"` + Pipe []string `json:"pipe,omitzero"` + Deep *Point `json:"deep,omitempty"` + Flat *Point `json:"flat,omitempty"` + JSON *Point `json:"json,omitempty"` + ID *QueryStylesQueryID `json:"id,omitempty"` + Created *QueryStylesQueryCreated `json:"created,omitempty"` + Needed string `json:"needed"` + Limit *int `json:"limit,omitempty"` } // GetLimit returns Limit, or 20 when it is nil. @@ -176,6 +239,9 @@ func (q QueryStylesQuery) Validate() error { if q.ID != nil { errs.Append("id", q.ID.Validate()) } + if q.Created != nil { + errs.Append("created", q.Created.Validate()) + } return errs.Err() } @@ -678,6 +744,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) return } + if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { + a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) + return + } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) return diff --git a/internal/gomodel/forms.go b/internal/gomodel/forms.go index a18fd5c8..fb52b347 100644 --- a/internal/gomodel/forms.go +++ b/internal/gomodel/forms.go @@ -3,16 +3,19 @@ // Licensed under the MIT License, see LICENSE in the repository root. This copyright notice and // permission notice shall be included in all copies or substantial portions of the Software. -// Which declarations a form body holds that read a form themselves. +// Which declarations a form body or a deepObject parameter holds that read a form themselves. package gomodel import "slices" -// markForms sets IsForm on what a form body of a request or a response holds, at any depth. +// markForms sets IsForm on what a form body or a deepObject parameter holds, at any depth. func markForms(ops []*Operation) { seen := map[*Decl]bool{} for _, op := range ops { + for _, g := range op.Params { + markDeep(g, seen) + } contents := slices.Clone(op.Bodies) for _, r := range op.Responses { contents = append(contents, r.Contents...) @@ -26,6 +29,15 @@ func markForms(ops []*Operation) { } } +// markDeep marks what the deepObject parameters of g hold: their bracketed keys read like a form's. +func markDeep(g ParamGroup, seen map[*Decl]bool) { + for i, f := range g.Decl.Struct.Fields { + if g.Params[i].Style == styleDeep { + markForm(f.Type, seen) + } + } +} + // markForm marks the declarations a value of type t holds. func markForm(t Type, seen map[*Decl]bool) { switch x := t.(type) { diff --git a/internal/gomodel/styles.go b/internal/gomodel/styles.go index 89c20ce4..595bea99 100644 --- a/internal/gomodel/styles.go +++ b/internal/gomodel/styles.go @@ -45,7 +45,7 @@ func (c *collector) paramIssue(p *spec.Parameter) string { if p.Schema == nil { return "" } - if p.In != spec.InPath && c.isUnionLost(p.Schema, map[*spec.Schema]bool{}) { + if p.In != spec.InPath && p.Style != styleDeep && c.isUnionLost(p.Schema, map[*spec.Schema]bool{}) { return "is or holds a union of more than scalars" } return c.shapeIssue(p) diff --git a/internal/gomodel/styles_test.go b/internal/gomodel/styles_test.go index 9787b832..b99c915d 100644 --- a/internal/gomodel/styles_test.go +++ b/internal/gomodel/styles_test.go @@ -51,6 +51,10 @@ func TestParamIssue(t *testing.T) { param: spec.Parameter{In: spec.InQuery, Style: "form", Schema: &spec.Schema{OneOf: []*spec.Schema{textSchema, pointSchema}}}, want: "is or holds a union of more than scalars", }, + { + name: "A deep object union with an object variant", + param: spec.Parameter{In: spec.InQuery, Style: "deepObject", Schema: &spec.Schema{AnyOf: []*spec.Schema{pointSchema, textSchema}}}, + }, { name: "A style the location does not allow comes before the shape", param: spec.Parameter{In: spec.InQuery, Style: "label", Schema: &spec.Schema{Types: spec.TypeArray, Items: textList}}, diff --git a/internal/gomodel/testdata/styles.golden b/internal/gomodel/testdata/styles.golden index 734ecb73..992bcb9a 100644 --- a/internal/gomodel/testdata/styles.golden +++ b/internal/gomodel/testdata/styles.golden @@ -14,6 +14,7 @@ ListItemsQuery struct models.params DeepGrid *ListItemsQueryDeepGrid json=deepGrid omitempty DeepNest *ListItemsQueryDeepNest json=deepNest omitempty Anything any json=anything omitempty + DeepUnion *ListItemsQueryDeepUnion json=deepUnion omitempty Queries *ListItemsQueryQueries json=queries omitempty DeepMap map[string]Point json=deepMap omitempty omitzero When []runtime.Date json=when omitempty omitzero @@ -27,6 +28,10 @@ ListItemsQueryDeepNest struct models.params Size *ListItemsQueryDeepNestSize json=size omitempty ListItemsQueryDeepNestSize struct models.params Grid [][]int json=grid omitempty omitzero +ListItemsQueryDeepUnion union models.unions form + | anyOf + | Point *Point kinds=object known=x,y + | Int *int kinds=integer ListItemsQueryQueries struct models.params Reference *string json=reference omitempty Status []string json=status omitempty omitzero @@ -58,17 +63,18 @@ info deepobject-convention 15:11 /paths/~1items~1{id}~1{form}/get/parameters/7: info deepobject-convention 17:11 /paths/~1items~1{id}~1{form}/get/parameters/9: query parameter "filter" has style deepObject on a value other than an object of scalars, which OpenAPI leaves undefined; it is written with brackets at every level and an index per list item, as in items[0][price]=1, and one value as name=value info deepobject-convention 26:11 /paths/~1items~1{id}~1{form}/get/parameters/10: query parameter "deepGrid" has style deepObject on a value other than an object of scalars, which OpenAPI leaves undefined; it is written with brackets at every level and an index per list item, as in items[0][price]=1, and one value as name=value info deepobject-convention 30:11 /paths/~1items~1{id}~1{form}/get/parameters/11: query parameter "deepNest" has style deepObject on a value other than an object of scalars, which OpenAPI leaves undefined; it is written with brackets at every level and an index per list item, as in items[0][price]=1, and one value as name=value -warning param-unsupported 38:11 /paths/~1items~1{id}~1{form}/get/parameters/14: query parameter "flat" holds a list in property "status", which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read -warning param-unsupported 42:11 /paths/~1items~1{id}~1{form}/get/parameters/15: query parameter "nested" holds an object in property "size", which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read -warning param-unsupported 45:11 /paths/~1items~1{id}~1{form}/get/parameters/16: query parameter "byKey" holds a list of objects in its map values, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read -info deepobject-convention 48:11 /paths/~1items~1{id}~1{form}/get/parameters/17: query parameter "deepMap" has style deepObject on a value other than an object of scalars, which OpenAPI leaves undefined; it is written with brackets at every level and an index per list item, as in items[0][price]=1, and one value as name=value -warning param-unsupported 52:11 /paths/~1items~1{id}~1{form}/get/parameters/18: query parameter "grid" is a list of lists, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read -warning param-unsupported 53:11 /paths/~1items~1{id}~1{form}/get/parameters/19: query parameter "points" is a list of objects, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read -warning param-unsupported 55:11 /paths/~1items~1{id}~1{form}/get/parameters/21: header parameter "X-Form" has style form, which OpenAPI allows only in query and cookie parameters; it gets no field and is neither sent nor read -warning param-unsupported 57:11 /paths/~1items~1{id}~1{form}/get/parameters/23: header parameter "X-Tags" holds a list in property "tags", which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read -warning param-unsupported 59:11 /paths/~1items~1{id}~1{form}/get/parameters/25: cookie parameter "colors" has style form with explode true, which OpenAPI says writes the wrong delimiter for several cookie values (use style cookie); it gets no field and is neither sent nor read -warning param-unsupported 62:11 /paths/~1items~1{id}~1{form}/get/parameters/28: cookie parameter "flags" holds a list in property "tags", which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read -warning param-unsupported 63:11 /paths/~1items~1{id}~1{form}/get/parameters/29: cookie parameter "deepCookie" has style deepObject, which OpenAPI allows only in query parameters; it gets no field and is neither sent nor read -warning param-unsupported 71:21 /paths/~1items~1{id}~1{form}/get/responses/200/headers/X-Grid: response header "X-Grid" is a list of lists, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read -warning param-unsupported 72:21 /paths/~1items~1{id}~1{form}/get/responses/200/headers/X-Form: response header "X-Form" has style form, which OpenAPI allows only in query and cookie parameters; it gets no field and is neither sent nor read -warning param-unsupported 76:21 /paths/~1items~1{id}~1{form}/get/responses/204/headers/X-Grid: response header "X-Grid" is a list of lists, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read +info deepobject-convention 35:11 /paths/~1items~1{id}~1{form}/get/parameters/13: query parameter "deepUnion" has style deepObject on a value other than an object of scalars, which OpenAPI leaves undefined; it is written with brackets at every level and an index per list item, as in items[0][price]=1, and one value as name=value +warning param-unsupported 39:11 /paths/~1items~1{id}~1{form}/get/parameters/15: query parameter "flat" holds a list in property "status", which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read +warning param-unsupported 43:11 /paths/~1items~1{id}~1{form}/get/parameters/16: query parameter "nested" holds an object in property "size", which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read +warning param-unsupported 46:11 /paths/~1items~1{id}~1{form}/get/parameters/17: query parameter "byKey" holds a list of objects in its map values, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read +info deepobject-convention 49:11 /paths/~1items~1{id}~1{form}/get/parameters/18: query parameter "deepMap" has style deepObject on a value other than an object of scalars, which OpenAPI leaves undefined; it is written with brackets at every level and an index per list item, as in items[0][price]=1, and one value as name=value +warning param-unsupported 53:11 /paths/~1items~1{id}~1{form}/get/parameters/19: query parameter "grid" is a list of lists, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read +warning param-unsupported 54:11 /paths/~1items~1{id}~1{form}/get/parameters/20: query parameter "points" is a list of objects, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read +warning param-unsupported 56:11 /paths/~1items~1{id}~1{form}/get/parameters/22: header parameter "X-Form" has style form, which OpenAPI allows only in query and cookie parameters; it gets no field and is neither sent nor read +warning param-unsupported 58:11 /paths/~1items~1{id}~1{form}/get/parameters/24: header parameter "X-Tags" holds a list in property "tags", which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read +warning param-unsupported 60:11 /paths/~1items~1{id}~1{form}/get/parameters/26: cookie parameter "colors" has style form with explode true, which OpenAPI says writes the wrong delimiter for several cookie values (use style cookie); it gets no field and is neither sent nor read +warning param-unsupported 63:11 /paths/~1items~1{id}~1{form}/get/parameters/29: cookie parameter "flags" holds a list in property "tags", which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read +warning param-unsupported 64:11 /paths/~1items~1{id}~1{form}/get/parameters/30: cookie parameter "deepCookie" has style deepObject, which OpenAPI allows only in query parameters; it gets no field and is neither sent nor read +warning param-unsupported 72:21 /paths/~1items~1{id}~1{form}/get/responses/200/headers/X-Grid: response header "X-Grid" is a list of lists, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read +warning param-unsupported 73:21 /paths/~1items~1{id}~1{form}/get/responses/200/headers/X-Form: response header "X-Form" has style form, which OpenAPI allows only in query and cookie parameters; it gets no field and is neither sent nor read +warning param-unsupported 77:21 /paths/~1items~1{id}~1{form}/get/responses/204/headers/X-Grid: response header "X-Grid" is a list of lists, which OpenAPI leaves to the implementation; it gets no field and is neither sent nor read diff --git a/internal/gomodel/testdata/styles.yaml b/internal/gomodel/testdata/styles.yaml index 3327d8d6..bd11b892 100644 --- a/internal/gomodel/testdata/styles.yaml +++ b/internal/gomodel/testdata/styles.yaml @@ -32,6 +32,7 @@ paths: style: deepObject schema: {type: object, properties: {size: {type: object, properties: {grid: {type: array, items: {type: array, items: {type: integer}}}}}}} - {name: anything, in: query, style: deepObject, schema: {}} + - {name: deepUnion, in: query, style: deepObject, schema: {anyOf: [{$ref: '#/components/schemas/Point'}, {type: integer}]}} - name: queries in: query schema: {type: object, properties: {reference: {type: string}, status: {type: array, items: {type: string}}}} diff --git a/pkg/runtime/param.go b/pkg/runtime/param.go index 59d5e2f4..446f6811 100644 --- a/pkg/runtime/param.go +++ b/pkg/runtime/param.go @@ -380,7 +380,11 @@ func decodeQuery(q Query, p Param, dst any, read func(string) (string, error)) e if fields, err = nested(q, p.Name, read); err != nil { return err } - if fields != nil { + _, isBare := q[p.Name] + switch { + case fields != nil && isBare: + return fmt.Errorf("%w: %s comes both as one value and with brackets", ErrParamValue, p.Name) + case fields != nil: return setParam(target, listsOf(fields)) } return bareValues(target, q, p, read) diff --git a/pkg/runtime/param_test.go b/pkg/runtime/param_test.go index 53f663d4..b2135185 100644 --- a/pkg/runtime/param_test.go +++ b/pkg/runtime/param_test.go @@ -426,6 +426,9 @@ func TestDecodeParamEdges(t *testing.T) { {name: "A deep object list under its bare name that does not unescape", decode: func(dst any) error { return DecodeQuery(Query{"color": {"%zz"}}, explode(StyleDeepObject, true), dst) }, dst: new([]string), wantErr: ErrParamValue}, + {name: "A deep object under its bare name and with brackets", decode: func(dst any) error { + return DecodeQuery(Query{"color": {"1"}, "color[R]": {"2"}}, explode(StyleDeepObject, true), dst) + }, dst: new(rgb), wantErr: ErrParamValue}, {name: "An exploded object value that does not unescape", decode: func(dst any) error { return DecodeQuery(Query{"R": {"%zz"}}, explode(StyleForm, true), dst) }, dst: new(rgb), wantErr: ErrParamValue},