From bb8841eda271220d2c712536942f810d94842515 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Fri, 17 May 2024 06:33:25 +0300 Subject: [PATCH 1/3] fix(gen): properly encode optional array parameter --- gen/_template/json/encode.tmpl | 2 +- gen/_template/uri/encode.tmpl | 29 +++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/gen/_template/json/encode.tmpl b/gen/_template/json/encode.tmpl index 1fdfc6ae4..1320ee8c5 100644 --- a/gen/_template/json/encode.tmpl +++ b/gen/_template/json/encode.tmpl @@ -63,7 +63,7 @@ if {{ $.Var }}.Set { {{- template "json/enc_array_elems" $ -}} } {{- else -}} -{{ errorf "unexpected nil semantic %s" $t.NilSemantic }} + {{ errorf "unexpected nil semantic %s" $t.NilSemantic }} {{- end -}} {{- end }} diff --git a/gen/_template/uri/encode.tmpl b/gen/_template/uri/encode.tmpl index 42e862da2..538eec1db 100644 --- a/gen/_template/uri/encode.tmpl +++ b/gen/_template/uri/encode.tmpl @@ -7,16 +7,16 @@ {{- else if $t.IsEnum }} return e.EncodeValue(conv.{{ $t.ToString }}({{ $t.Primitive.String }}({{ $var }}))) {{- else if $t.IsArray }} - return e.EncodeArray(func(e uri.Encoder) error { - for i, item := range {{ $var }} { - if err := func() error { - {{- template "uri/encode" elem $t.Item "item" }} - }(); err != nil { - return errors.Wrapf(err, "[%d]", i) - } + {{- if $t.NilSemantic.Invalid -}} + {{- template "uri/encode_array_elems" $ }} + {{- else if $t.NilSemantic.Optional -}} + if {{ $.Var }} != nil { + {{- template "uri/encode_array_elems" $ }} } return nil - }) + {{- else -}} + {{ errorf "unexpected nil semantic %s" $t.NilSemantic }} + {{- end -}} {{- else if $t.IsAlias }} if unwrapped := {{ $t.AliasTo.Go }}({{ $var }}); true { {{- template "uri/encode" elem $t.AliasTo "unwrapped" }} @@ -38,3 +38,16 @@ {{ errorf "unexpected kind %s" $t.Kind }} {{- end }} {{- end }} + +{{- define "uri/encode_array_elems" }} +return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range {{ $.Var }} { + if err := func() error { + {{- template "uri/encode" elem $.Type.Item "item" }} + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } + } + return nil +}) +{{- end }} From e1b6e06919513e514f3793aa1ffba339add8b442 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Fri, 17 May 2024 06:43:32 +0300 Subject: [PATCH 2/3] test(integration): ensure that optional array parameters are not sent --- _testdata/positive/parameters.json | 41 +++++++++++++++++++++++++ internal/integration/parameters_test.go | 35 +++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/_testdata/positive/parameters.json b/_testdata/positive/parameters.json index 2c7f5c9ad..ea461017b 100644 --- a/_testdata/positive/parameters.json +++ b/_testdata/positive/parameters.json @@ -5,6 +5,47 @@ "version": "0.1.0" }, "paths": { + "/optionalArrayParameter": { + "get": { + "operationId": "optionalArrayParameter", + "parameters": [ + { + "name": "query", + "in": "query", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "header", + "in": "header", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/objectQueryParameter": { "get": { "operationId": "objectQueryParameter", diff --git a/internal/integration/parameters_test.go b/internal/integration/parameters_test.go index 185df52cf..5b2b6524b 100644 --- a/internal/integration/parameters_test.go +++ b/internal/integration/parameters_test.go @@ -3,6 +3,8 @@ package integration import ( "context" "fmt" + "io" + "net/http" "net/http/httptest" "testing" @@ -15,6 +17,12 @@ import ( type testParameters struct{} +var _ api.Handler = (*testParameters)(nil) + +func (s *testParameters) OptionalArrayParameter(ctx context.Context, params api.OptionalArrayParameterParams) (string, error) { + return "", nil +} + func (s *testParameters) ObjectQueryParameter(ctx context.Context, params api.ObjectQueryParameterParams) (*api.ObjectQueryParameterOK, error) { if param, ok := params.FormObject.Get(); ok { return &api.ObjectQueryParameterOK{ @@ -175,3 +183,30 @@ func TestParameters(t *testing.T) { assert.Equal(t, user, resp.Cookie) }) } + +func TestOptionalArrayParameter(t *testing.T) { + ctx := context.Background() + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Ensure that client does not send these query parameters and headers. + if q := r.URL.Query(); q.Has("query") { + http.Error(w, "must have not query", http.StatusBadRequest) + return + } + if h := r.Header; len(h.Values("header")) > 0 { + http.Error(w, "must have not header", http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + _, _ = io.WriteString(w, `"ok"`) + })) + defer s.Close() + + client, err := api.NewClient(s.URL, api.WithClient(s.Client())) + require.NoError(t, err) + + resp, err := client.OptionalArrayParameter(ctx, api.OptionalArrayParameterParams{}) + require.NoError(t, err) + require.Equal(t, "ok", resp) +} From dfdcdd7ee0ddc9e773e7d6b8cc51c14ca630011a Mon Sep 17 00:00:00 2001 From: tdakkota Date: Fri, 17 May 2024 06:34:06 +0300 Subject: [PATCH 3/3] chore: commit generated files --- examples/ex_2ch/oas_request_encoders_gen.go | 21 +-- examples/ex_github/oas_client_gen.go | 63 ++++---- examples/ex_oauth2/oas_client_gen.go | 21 +-- .../ex_petstore_expanded/oas_client_gen.go | 21 +-- .../test_form/oas_request_encoders_gen.go | 42 ++--- .../test_parameters/oas_client_gen.go | 130 +++++++++++++++ .../test_parameters/oas_handlers_gen.go | 114 ++++++++++++++ .../test_parameters/oas_parameters_gen.go | 118 ++++++++++++++ .../oas_response_decoders_gen.go | 43 +++++ .../oas_response_encoders_gen.go | 14 ++ .../test_parameters/oas_router_gen.go | 148 +++++++++++++----- .../test_parameters/oas_server_gen.go | 4 + .../test_parameters/oas_unimplemented_gen.go | 7 + 13 files changed, 638 insertions(+), 108 deletions(-) diff --git a/examples/ex_2ch/oas_request_encoders_gen.go b/examples/ex_2ch/oas_request_encoders_gen.go index 4d8cc2d26..ea7cf2cfe 100644 --- a/examples/ex_2ch/oas_request_encoders_gen.go +++ b/examples/ex_2ch/oas_request_encoders_gen.go @@ -281,16 +281,19 @@ func encodeUserReportPostRequest( Explode: true, } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeArray(func(e uri.Encoder) error { - for i, item := range request.Post { - if err := func() error { - return e.EncodeValue(conv.IntToString(item)) - }(); err != nil { - return errors.Wrapf(err, "[%d]", i) + if request.Post != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range request.Post { + if err := func() error { + return e.EncodeValue(conv.IntToString(item)) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } } - } - return nil - }) + return nil + }) + } + return nil }); err != nil { return errors.Wrap(err, "encode query") } diff --git a/examples/ex_github/oas_client_gen.go b/examples/ex_github/oas_client_gen.go index 7bcf2899e..7e122c2c3 100644 --- a/examples/ex_github/oas_client_gen.go +++ b/examples/ex_github/oas_client_gen.go @@ -45423,16 +45423,19 @@ func (c *Client) sendMigrationsGetStatusForAuthenticatedUser(ctx context.Context } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeArray(func(e uri.Encoder) error { - for i, item := range params.Exclude { - if err := func() error { - return e.EncodeValue(conv.StringToString(item)) - }(); err != nil { - return errors.Wrapf(err, "[%d]", i) + if params.Exclude != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range params.Exclude { + if err := func() error { + return e.EncodeValue(conv.StringToString(item)) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } } - } - return nil - }) + return nil + }) + } + return nil }); err != nil { return res, errors.Wrap(err, "encode query") } @@ -45564,16 +45567,19 @@ func (c *Client) sendMigrationsGetStatusForOrg(ctx context.Context, params Migra } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeArray(func(e uri.Encoder) error { - for i, item := range params.Exclude { - if err := func() error { - return e.EncodeValue(conv.StringToString(string(item))) - }(); err != nil { - return errors.Wrapf(err, "[%d]", i) + if params.Exclude != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range params.Exclude { + if err := func() error { + return e.EncodeValue(conv.StringToString(string(item))) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } } - } - return nil - }) + return nil + }) + } + return nil }); err != nil { return res, errors.Wrap(err, "encode query") } @@ -45826,16 +45832,19 @@ func (c *Client) sendMigrationsListForOrg(ctx context.Context, params Migrations } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeArray(func(e uri.Encoder) error { - for i, item := range params.Exclude { - if err := func() error { - return e.EncodeValue(conv.StringToString(string(item))) - }(); err != nil { - return errors.Wrapf(err, "[%d]", i) + if params.Exclude != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range params.Exclude { + if err := func() error { + return e.EncodeValue(conv.StringToString(string(item))) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } } - } - return nil - }) + return nil + }) + } + return nil }); err != nil { return res, errors.Wrap(err, "encode query") } diff --git a/examples/ex_oauth2/oas_client_gen.go b/examples/ex_oauth2/oas_client_gen.go index f004bbe14..0d8f2d224 100644 --- a/examples/ex_oauth2/oas_client_gen.go +++ b/examples/ex_oauth2/oas_client_gen.go @@ -553,16 +553,19 @@ func (c *Client) sendFindPets(ctx context.Context, params FindPetsParams) (res [ } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeArray(func(e uri.Encoder) error { - for i, item := range params.Tags { - if err := func() error { - return e.EncodeValue(conv.StringToString(item)) - }(); err != nil { - return errors.Wrapf(err, "[%d]", i) + if params.Tags != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range params.Tags { + if err := func() error { + return e.EncodeValue(conv.StringToString(item)) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } } - } - return nil - }) + return nil + }) + } + return nil }); err != nil { return res, errors.Wrap(err, "encode query") } diff --git a/examples/ex_petstore_expanded/oas_client_gen.go b/examples/ex_petstore_expanded/oas_client_gen.go index 48c3598d4..27f82066e 100644 --- a/examples/ex_petstore_expanded/oas_client_gen.go +++ b/examples/ex_petstore_expanded/oas_client_gen.go @@ -451,16 +451,19 @@ func (c *Client) sendFindPets(ctx context.Context, params FindPetsParams) (res [ } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeArray(func(e uri.Encoder) error { - for i, item := range params.Tags { - if err := func() error { - return e.EncodeValue(conv.StringToString(item)) - }(); err != nil { - return errors.Wrapf(err, "[%d]", i) + if params.Tags != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range params.Tags { + if err := func() error { + return e.EncodeValue(conv.StringToString(item)) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } } - } - return nil - }) + return nil + }) + } + return nil }); err != nil { return res, errors.Wrap(err, "encode query") } diff --git a/internal/integration/test_form/oas_request_encoders_gen.go b/internal/integration/test_form/oas_request_encoders_gen.go index e95144e25..f67d8a1bd 100644 --- a/internal/integration/test_form/oas_request_encoders_gen.go +++ b/internal/integration/test_form/oas_request_encoders_gen.go @@ -156,16 +156,19 @@ func encodeTestFormURLEncodedRequest( Explode: true, } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeArray(func(e uri.Encoder) error { - for i, item := range request.Array { - if err := func() error { - return e.EncodeValue(conv.StringToString(item)) - }(); err != nil { - return errors.Wrapf(err, "[%d]", i) + if request.Array != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range request.Array { + if err := func() error { + return e.EncodeValue(conv.StringToString(item)) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } } - } - return nil - }) + return nil + }) + } + return nil }); err != nil { return errors.Wrap(err, "encode query") } @@ -270,16 +273,19 @@ func encodeTestMultipartRequest( Explode: true, } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeArray(func(e uri.Encoder) error { - for i, item := range request.Array { - if err := func() error { - return e.EncodeValue(conv.StringToString(item)) - }(); err != nil { - return errors.Wrapf(err, "[%d]", i) + if request.Array != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range request.Array { + if err := func() error { + return e.EncodeValue(conv.StringToString(item)) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } } - } - return nil - }) + return nil + }) + } + return nil }); err != nil { return errors.Wrap(err, "encode query") } diff --git a/internal/integration/test_parameters/oas_client_gen.go b/internal/integration/test_parameters/oas_client_gen.go index 812ded3ed..234785129 100644 --- a/internal/integration/test_parameters/oas_client_gen.go +++ b/internal/integration/test_parameters/oas_client_gen.go @@ -52,6 +52,10 @@ type Invoker interface { // // GET /objectQueryParameter ObjectQueryParameter(ctx context.Context, params ObjectQueryParameterParams) (*ObjectQueryParameterOK, error) + // OptionalArrayParameter invokes optionalArrayParameter operation. + // + // GET /optionalArrayParameter + OptionalArrayParameter(ctx context.Context, params OptionalArrayParameterParams) (string, error) // PathParameter invokes pathParameter operation. // // Test for path param. @@ -783,6 +787,132 @@ func (c *Client) sendObjectQueryParameter(ctx context.Context, params ObjectQuer return result, nil } +// OptionalArrayParameter invokes optionalArrayParameter operation. +// +// GET /optionalArrayParameter +func (c *Client) OptionalArrayParameter(ctx context.Context, params OptionalArrayParameterParams) (string, error) { + res, err := c.sendOptionalArrayParameter(ctx, params) + return res, err +} + +func (c *Client) sendOptionalArrayParameter(ctx context.Context, params OptionalArrayParameterParams) (res string, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("optionalArrayParameter"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/optionalArrayParameter"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(float64(elapsedDuration)/float64(time.Millisecond)), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, "OptionalArrayParameter", + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/optionalArrayParameter" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "query" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "query", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if params.Query != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range params.Query { + if err := func() error { + return e.EncodeValue(conv.StringToString(item)) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } + } + return nil + }) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "EncodeHeaderParams" + h := uri.NewHeaderEncoder(r.Header) + { + cfg := uri.HeaderParameterEncodingConfig{ + Name: "header", + Explode: false, + } + if err := h.EncodeParam(cfg, func(e uri.Encoder) error { + if params.Header != nil { + return e.EncodeArray(func(e uri.Encoder) error { + for i, item := range params.Header { + if err := func() error { + return e.EncodeValue(conv.StringToString(item)) + }(); err != nil { + return errors.Wrapf(err, "[%d]", i) + } + } + return nil + }) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode header") + } + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeOptionalArrayParameterResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // PathParameter invokes pathParameter operation. // // Test for path param. diff --git a/internal/integration/test_parameters/oas_handlers_gen.go b/internal/integration/test_parameters/oas_handlers_gen.go index 8b827ba91..d3c39b1bc 100644 --- a/internal/integration/test_parameters/oas_handlers_gen.go +++ b/internal/integration/test_parameters/oas_handlers_gen.go @@ -715,6 +715,120 @@ func (s *Server) handleObjectQueryParameterRequest(args [0]string, argsEscaped b } } +// handleOptionalArrayParameterRequest handles optionalArrayParameter operation. +// +// GET /optionalArrayParameter +func (s *Server) handleOptionalArrayParameterRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("optionalArrayParameter"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/optionalArrayParameter"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), "OptionalArrayParameter", + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + attrOpt := metric.WithAttributeSet(labeler.AttributeSet()) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(float64(elapsedDuration)/float64(time.Millisecond)), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + s.errors.Add(ctx, 1, metric.WithAttributeSet(labeler.AttributeSet())) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: "OptionalArrayParameter", + ID: "optionalArrayParameter", + } + ) + params, err := decodeOptionalArrayParameterParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response string + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: "OptionalArrayParameter", + OperationSummary: "", + OperationID: "optionalArrayParameter", + Body: nil, + Params: middleware.Parameters{ + { + Name: "query", + In: "query", + }: params.Query, + { + Name: "header", + In: "header", + }: params.Header, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = OptionalArrayParameterParams + Response = string + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackOptionalArrayParameterParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.OptionalArrayParameter(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.OptionalArrayParameter(ctx, params) + } + if err != nil { + defer recordError("Internal", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + if err := encodeOptionalArrayParameterResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handlePathParameterRequest handles pathParameter operation. // // Test for path param. diff --git a/internal/integration/test_parameters/oas_parameters_gen.go b/internal/integration/test_parameters/oas_parameters_gen.go index 01e700239..3eb10a80d 100644 --- a/internal/integration/test_parameters/oas_parameters_gen.go +++ b/internal/integration/test_parameters/oas_parameters_gen.go @@ -730,6 +730,124 @@ func decodeObjectQueryParameterParams(args [0]string, argsEscaped bool, r *http. return params, nil } +// OptionalArrayParameterParams is parameters of optionalArrayParameter operation. +type OptionalArrayParameterParams struct { + Query []string + Header []string +} + +func unpackOptionalArrayParameterParams(packed middleware.Parameters) (params OptionalArrayParameterParams) { + { + key := middleware.ParameterKey{ + Name: "query", + In: "query", + } + if v, ok := packed[key]; ok { + params.Query = v.([]string) + } + } + { + key := middleware.ParameterKey{ + Name: "header", + In: "header", + } + if v, ok := packed[key]; ok { + params.Header = v.([]string) + } + } + return params +} + +func decodeOptionalArrayParameterParams(args [0]string, argsEscaped bool, r *http.Request) (params OptionalArrayParameterParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + h := uri.NewHeaderDecoder(r.Header) + // Decode query: query. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "query", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + return d.DecodeArray(func(d uri.Decoder) error { + var paramsDotQueryVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + paramsDotQueryVal = c + return nil + }(); err != nil { + return err + } + params.Query = append(params.Query, paramsDotQueryVal) + return nil + }) + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "query", + In: "query", + Err: err, + } + } + // Decode header: header. + if err := func() error { + cfg := uri.HeaderParameterDecodingConfig{ + Name: "header", + Explode: false, + } + if err := h.HasParam(cfg); err == nil { + if err := h.DecodeParam(cfg, func(d uri.Decoder) error { + return d.DecodeArray(func(d uri.Decoder) error { + var paramsDotHeaderVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + paramsDotHeaderVal = c + return nil + }(); err != nil { + return err + } + params.Header = append(params.Header, paramsDotHeaderVal) + return nil + }) + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "header", + In: "header", + Err: err, + } + } + return params, nil +} + // PathParameterParams is parameters of pathParameter operation. type PathParameterParams struct { Value string diff --git a/internal/integration/test_parameters/oas_response_decoders_gen.go b/internal/integration/test_parameters/oas_response_decoders_gen.go index d7aa033b8..9e7d5cb25 100644 --- a/internal/integration/test_parameters/oas_response_decoders_gen.go +++ b/internal/integration/test_parameters/oas_response_decoders_gen.go @@ -237,6 +237,49 @@ func decodeObjectQueryParameterResponse(resp *http.Response) (res *ObjectQueryPa return res, validate.UnexpectedStatusCode(resp.StatusCode) } +func decodeOptionalArrayParameterResponse(resp *http.Response) (res string, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response string + if err := func() error { + v, err := d.Str() + response = string(v) + if err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + return res, validate.UnexpectedStatusCode(resp.StatusCode) +} + func decodePathParameterResponse(resp *http.Response) (res *Value, _ error) { switch resp.StatusCode { case 200: diff --git a/internal/integration/test_parameters/oas_response_encoders_gen.go b/internal/integration/test_parameters/oas_response_encoders_gen.go index 9a72afb36..bce761da8 100644 --- a/internal/integration/test_parameters/oas_response_encoders_gen.go +++ b/internal/integration/test_parameters/oas_response_encoders_gen.go @@ -88,6 +88,20 @@ func encodeObjectQueryParameterResponse(response *ObjectQueryParameterOK, w http return nil } +func encodeOptionalArrayParameterResponse(response string, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + e.Str(response) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodePathParameterResponse(response *Value, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) diff --git a/internal/integration/test_parameters/oas_router_gen.go b/internal/integration/test_parameters/oas_router_gen.go index 9c2880d8b..4c3f92a03 100644 --- a/internal/integration/test_parameters/oas_router_gen.go +++ b/internal/integration/test_parameters/oas_router_gen.go @@ -167,9 +167,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } elem = origElem - case 'o': // Prefix: "object" + case 'o': // Prefix: "o" origElem := elem - if l := len("object"); len(elem) >= l && elem[0:l] == "object" { + if l := len("o"); len(elem) >= l && elem[0:l] == "o" { elem = elem[l:] } else { break @@ -179,30 +179,66 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { - case 'C': // Prefix: "CookieParameter" + case 'b': // Prefix: "bject" origElem := elem - if l := len("CookieParameter"); len(elem) >= l && elem[0:l] == "CookieParameter" { + if l := len("bject"); len(elem) >= l && elem[0:l] == "bject" { elem = elem[l:] } else { break } if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "GET": - s.handleObjectCookieParameterRequest([0]string{}, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "GET") + break + } + switch elem[0] { + case 'C': // Prefix: "CookieParameter" + origElem := elem + if l := len("CookieParameter"); len(elem) >= l && elem[0:l] == "CookieParameter" { + elem = elem[l:] + } else { + break } - return + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleObjectCookieParameterRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + + elem = origElem + case 'Q': // Prefix: "QueryParameter" + origElem := elem + if l := len("QueryParameter"); len(elem) >= l && elem[0:l] == "QueryParameter" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleObjectQueryParameterRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + + elem = origElem } elem = origElem - case 'Q': // Prefix: "QueryParameter" + case 'p': // Prefix: "ptionalArrayParameter" origElem := elem - if l := len("QueryParameter"); len(elem) >= l && elem[0:l] == "QueryParameter" { + if l := len("ptionalArrayParameter"); len(elem) >= l && elem[0:l] == "ptionalArrayParameter" { elem = elem[l:] } else { break @@ -212,7 +248,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Leaf node. switch r.Method { case "GET": - s.handleObjectQueryParameterRequest([0]string{}, elemIsEscaped, w, r) + s.handleOptionalArrayParameterRequest([0]string{}, elemIsEscaped, w, r) default: s.notAllowed(w, r, "GET") } @@ -531,9 +567,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } elem = origElem - case 'o': // Prefix: "object" + case 'o': // Prefix: "o" origElem := elem - if l := len("object"); len(elem) >= l && elem[0:l] == "object" { + if l := len("o"); len(elem) >= l && elem[0:l] == "o" { elem = elem[l:] } else { break @@ -543,34 +579,74 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case 'C': // Prefix: "CookieParameter" + case 'b': // Prefix: "bject" origElem := elem - if l := len("CookieParameter"); len(elem) >= l && elem[0:l] == "CookieParameter" { + if l := len("bject"); len(elem) >= l && elem[0:l] == "bject" { elem = elem[l:] } else { break } if len(elem) == 0 { - switch method { - case "GET": - // Leaf: ObjectCookieParameter - r.name = "ObjectCookieParameter" - r.summary = "" - r.operationID = "objectCookieParameter" - r.pathPattern = "/objectCookieParameter" - r.args = args - r.count = 0 - return r, true - default: - return + break + } + switch elem[0] { + case 'C': // Prefix: "CookieParameter" + origElem := elem + if l := len("CookieParameter"); len(elem) >= l && elem[0:l] == "CookieParameter" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch method { + case "GET": + // Leaf: ObjectCookieParameter + r.name = "ObjectCookieParameter" + r.summary = "" + r.operationID = "objectCookieParameter" + r.pathPattern = "/objectCookieParameter" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + elem = origElem + case 'Q': // Prefix: "QueryParameter" + origElem := elem + if l := len("QueryParameter"); len(elem) >= l && elem[0:l] == "QueryParameter" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch method { + case "GET": + // Leaf: ObjectQueryParameter + r.name = "ObjectQueryParameter" + r.summary = "" + r.operationID = "objectQueryParameter" + r.pathPattern = "/objectQueryParameter" + r.args = args + r.count = 0 + return r, true + default: + return + } } + + elem = origElem } elem = origElem - case 'Q': // Prefix: "QueryParameter" + case 'p': // Prefix: "ptionalArrayParameter" origElem := elem - if l := len("QueryParameter"); len(elem) >= l && elem[0:l] == "QueryParameter" { + if l := len("ptionalArrayParameter"); len(elem) >= l && elem[0:l] == "ptionalArrayParameter" { elem = elem[l:] } else { break @@ -579,11 +655,11 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { if len(elem) == 0 { switch method { case "GET": - // Leaf: ObjectQueryParameter - r.name = "ObjectQueryParameter" + // Leaf: OptionalArrayParameter + r.name = "OptionalArrayParameter" r.summary = "" - r.operationID = "objectQueryParameter" - r.pathPattern = "/objectQueryParameter" + r.operationID = "optionalArrayParameter" + r.pathPattern = "/optionalArrayParameter" r.args = args r.count = 0 return r, true diff --git a/internal/integration/test_parameters/oas_server_gen.go b/internal/integration/test_parameters/oas_server_gen.go index a77c0cad7..16b2414b8 100644 --- a/internal/integration/test_parameters/oas_server_gen.go +++ b/internal/integration/test_parameters/oas_server_gen.go @@ -36,6 +36,10 @@ type Handler interface { // // GET /objectQueryParameter ObjectQueryParameter(ctx context.Context, params ObjectQueryParameterParams) (*ObjectQueryParameterOK, error) + // OptionalArrayParameter implements optionalArrayParameter operation. + // + // GET /optionalArrayParameter + OptionalArrayParameter(ctx context.Context, params OptionalArrayParameterParams) (string, error) // PathParameter implements pathParameter operation. // // Test for path param. diff --git a/internal/integration/test_parameters/oas_unimplemented_gen.go b/internal/integration/test_parameters/oas_unimplemented_gen.go index 3ae718bc6..7e5625746 100644 --- a/internal/integration/test_parameters/oas_unimplemented_gen.go +++ b/internal/integration/test_parameters/oas_unimplemented_gen.go @@ -59,6 +59,13 @@ func (UnimplementedHandler) ObjectQueryParameter(ctx context.Context, params Obj return r, ht.ErrNotImplemented } +// OptionalArrayParameter implements optionalArrayParameter operation. +// +// GET /optionalArrayParameter +func (UnimplementedHandler) OptionalArrayParameter(ctx context.Context, params OptionalArrayParameterParams) (r string, _ error) { + return r, ht.ErrNotImplemented +} + // PathParameter implements pathParameter operation. // // Test for path param.