diff --git a/_testdata/examples/api.github.com.json b/_testdata/examples/api.github.com.json index 300ab9ada..f6acfc943 100644 --- a/_testdata/examples/api.github.com.json +++ b/_testdata/examples/api.github.com.json @@ -166,7 +166,7 @@ "200": { "description": "Response", "content": { - "application/json": { + "application/problem+json": { "schema": { "type": "object", "properties": { @@ -96299,4 +96299,4 @@ } } } -} \ No newline at end of file +} diff --git a/_testdata/examples/k8s.json b/_testdata/examples/k8s.json index 274fafbe8..48a412cb6 100644 --- a/_testdata/examples/k8s.json +++ b/_testdata/examples/k8s.json @@ -1863,7 +1863,7 @@ ], "requestBody": { "content": { - "application/json-patch+json": { + "application/problem+json": { "schema": { "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" } diff --git a/_testdata/examples/problemjson.yml b/_testdata/examples/problemjson.yml new file mode 100644 index 000000000..2d005be12 --- /dev/null +++ b/_testdata/examples/problemjson.yml @@ -0,0 +1,60 @@ +openapi: 3.0.1 +info: + version: 1.0.0 + title: API with Problems +paths: + /v1/test: + get: + operationId: getTest + responses: + "200": + x-summary: OK + description: Process me. + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: "Hello, World!" + "400": + x-summary: Bad Request + description: | + Something was wrong + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemJsonErrorResponse' + +components: + schemas: + ProblemJsonErrorResponse: + type: object + description: >- + An errors that occurred. + required: + - type + - title + - status + properties: + type: + type: string + description: >- + Identifies the type of the problem. If the problem type has no additional semantics beyond the HTTP status code, the URI `about:blank` is used. + example: "about:blank" + title: + type: string + description: >- + Roughly describes the problem. This is a static description, not a detailed one. If `type` is `about:blank`, the reason phrase of the status should be used as title. + example: Not Found + status: + type: integer + format: int32 + description: HTTP status code. + example: 404 + detail: + type: string + description: >- + Further details about the error. + example: The email address could not be found. diff --git a/examples/ex_github/oas_response_decoders_gen.go b/examples/ex_github/oas_response_decoders_gen.go index 73808a751..b6f6be777 100644 --- a/examples/ex_github/oas_response_decoders_gen.go +++ b/examples/ex_github/oas_response_decoders_gen.go @@ -23377,7 +23377,7 @@ func decodeMetaRootResponse(resp *http.Response) (res *MetaRootOK, _ error) { return res, errors.Wrap(err, "parse media type") } switch { - case ct == "application/json": + case ct == "application/problem+json": buf, err := io.ReadAll(resp.Body) if err != nil { return res, err diff --git a/examples/ex_github/oas_response_encoders_gen.go b/examples/ex_github/oas_response_encoders_gen.go index 48bf9fa7e..19a4d894e 100644 --- a/examples/ex_github/oas_response_encoders_gen.go +++ b/examples/ex_github/oas_response_encoders_gen.go @@ -9336,7 +9336,7 @@ func encodeMetaGetZenResponse(response MetaGetZenOK, w http.ResponseWriter, span } func encodeMetaRootResponse(response *MetaRootOK, w http.ResponseWriter, span trace.Span) error { - w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("Content-Type", "application/problem+json") w.WriteHeader(200) span.SetStatus(codes.Ok, http.StatusText(200)) diff --git a/examples/ex_k8s/oas_client_gen.go b/examples/ex_k8s/oas_client_gen.go index bc79993cb..585257060 100644 --- a/examples/ex_k8s/oas_client_gen.go +++ b/examples/ex_k8s/oas_client_gen.go @@ -1255,6 +1255,12 @@ type Invoker interface { // // GET /logs/ LogFileListHandler(ctx context.Context) error + // PatchCoreV1NamespacedConfigMap invokes patchCoreV1NamespacedConfigMap operation. + // + // Partially update the specified ConfigMap. + // + // PATCH /api/v1/namespaces/{namespace}/configmaps/{name} + PatchCoreV1NamespacedConfigMap(ctx context.Context, request *IoK8sApimachineryPkgApisMetaV1Patch, params PatchCoreV1NamespacedConfigMapParams) (PatchCoreV1NamespacedConfigMapRes, error) // ReadAdmissionregistrationV1MutatingWebhookConfiguration invokes readAdmissionregistrationV1MutatingWebhookConfiguration operation. // // Read the specified MutatingWebhookConfiguration. @@ -45532,6 +45538,223 @@ func (c *Client) sendLogFileListHandler(ctx context.Context) (res *LogFileListHa return result, nil } +// PatchCoreV1NamespacedConfigMap invokes patchCoreV1NamespacedConfigMap operation. +// +// Partially update the specified ConfigMap. +// +// PATCH /api/v1/namespaces/{namespace}/configmaps/{name} +func (c *Client) PatchCoreV1NamespacedConfigMap(ctx context.Context, request *IoK8sApimachineryPkgApisMetaV1Patch, params PatchCoreV1NamespacedConfigMapParams) (PatchCoreV1NamespacedConfigMapRes, error) { + res, err := c.sendPatchCoreV1NamespacedConfigMap(ctx, request, params) + return res, err +} + +func (c *Client) sendPatchCoreV1NamespacedConfigMap(ctx context.Context, request *IoK8sApimachineryPkgApisMetaV1Patch, params PatchCoreV1NamespacedConfigMapParams) (res PatchCoreV1NamespacedConfigMapRes, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("patchCoreV1NamespacedConfigMap"), + semconv.HTTPRequestMethodKey.String("PATCH"), + semconv.HTTPRouteKey.String("/api/v1/namespaces/{namespace}/configmaps/{name}"), + } + + // 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(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, PatchCoreV1NamespacedConfigMapOperation, + 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 [4]string + pathParts[0] = "/api/v1/namespaces/" + { + // Encode "namespace" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "namespace", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.StringToString(params.Namespace)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/configmaps/" + { + // Encode "name" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "name", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.StringToString(params.Name)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[3] = encoded + } + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "dryRun" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "dryRun", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.DryRun.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "fieldManager" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "fieldManager", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.FieldManager.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "force" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "force", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.Force.Get(); ok { + return e.EncodeValue(conv.BoolToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "pretty" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "pretty", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.Pretty.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "PATCH", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodePatchCoreV1NamespacedConfigMapRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:BearerToken" + switch err := c.securityBearerToken(ctx, PatchCoreV1NamespacedConfigMapOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"BearerToken\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + + 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 := decodePatchCoreV1NamespacedConfigMapResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ReadAdmissionregistrationV1MutatingWebhookConfiguration invokes readAdmissionregistrationV1MutatingWebhookConfiguration operation. // // Read the specified MutatingWebhookConfiguration. diff --git a/examples/ex_k8s/oas_faker_gen.go b/examples/ex_k8s/oas_faker_gen.go index 5692bcab3..2adfd2ea5 100644 --- a/examples/ex_k8s/oas_faker_gen.go +++ b/examples/ex_k8s/oas_faker_gen.go @@ -15312,6 +15312,10 @@ func (s *IoK8sApimachineryPkgApisMetaV1OwnerReference) SetFake() { } } +// SetFake set fake values. +func (s *IoK8sApimachineryPkgApisMetaV1Patch) SetFake() { +} + // SetFake set fake values. func (s *IoK8sApimachineryPkgApisMetaV1ServerAddressByClientCIDR) SetFake() { { @@ -18202,6 +18206,24 @@ func (s *OptString) SetFake() { s.SetTo(elem) } +// SetFake set fake values. +func (s *PatchCoreV1NamespacedConfigMapCreated) SetFake() { + var unwrapped IoK8sAPICoreV1ConfigMap + { + unwrapped.SetFake() + } + *s = PatchCoreV1NamespacedConfigMapCreated(unwrapped) +} + +// SetFake set fake values. +func (s *PatchCoreV1NamespacedConfigMapOK) SetFake() { + var unwrapped IoK8sAPICoreV1ConfigMap + { + unwrapped.SetFake() + } + *s = PatchCoreV1NamespacedConfigMapOK(unwrapped) +} + // SetFake set fake values. func (s *ReadCoreV1NamespacedPodLogOKApplicationJSON) SetFake() { var unwrapped string diff --git a/examples/ex_k8s/oas_handlers_gen.go b/examples/ex_k8s/oas_handlers_gen.go index 6081b3386..2848ae4d1 100644 --- a/examples/ex_k8s/oas_handlers_gen.go +++ b/examples/ex_k8s/oas_handlers_gen.go @@ -40622,6 +40622,223 @@ func (s *Server) handleLogFileListHandlerRequest(args [0]string, argsEscaped boo } } +// handlePatchCoreV1NamespacedConfigMapRequest handles patchCoreV1NamespacedConfigMap operation. +// +// Partially update the specified ConfigMap. +// +// PATCH /api/v1/namespaces/{namespace}/configmaps/{name} +func (s *Server) handlePatchCoreV1NamespacedConfigMapRequest(args [2]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("patchCoreV1NamespacedConfigMap"), + semconv.HTTPRequestMethodKey.String("PATCH"), + semconv.HTTPRouteKey.String("/api/v1/namespaces/{namespace}/configmaps/{name}"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), PatchCoreV1NamespacedConfigMapOperation, + 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) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // 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(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: PatchCoreV1NamespacedConfigMapOperation, + ID: "patchCoreV1NamespacedConfigMap", + } + ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securityBearerToken(ctx, PatchCoreV1NamespacedConfigMapOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "BearerToken", + Err: err, + } + defer recordError("Security:BearerToken", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } + params, err := decodePatchCoreV1NamespacedConfigMapParams(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 + } + request, close, err := s.decodePatchCoreV1NamespacedConfigMapRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response PatchCoreV1NamespacedConfigMapRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: PatchCoreV1NamespacedConfigMapOperation, + OperationSummary: "", + OperationID: "patchCoreV1NamespacedConfigMap", + Body: request, + Params: middleware.Parameters{ + { + Name: "dryRun", + In: "query", + }: params.DryRun, + { + Name: "fieldManager", + In: "query", + }: params.FieldManager, + { + Name: "force", + In: "query", + }: params.Force, + { + Name: "name", + In: "path", + }: params.Name, + { + Name: "namespace", + In: "path", + }: params.Namespace, + { + Name: "pretty", + In: "query", + }: params.Pretty, + }, + Raw: r, + } + + type ( + Request = *IoK8sApimachineryPkgApisMetaV1Patch + Params = PatchCoreV1NamespacedConfigMapParams + Response = PatchCoreV1NamespacedConfigMapRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackPatchCoreV1NamespacedConfigMapParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.PatchCoreV1NamespacedConfigMap(ctx, request, params) + return response, err + }, + ) + } else { + response, err = s.h.PatchCoreV1NamespacedConfigMap(ctx, request, params) + } + if err != nil { + defer recordError("Internal", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + if err := encodePatchCoreV1NamespacedConfigMapResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleReadAdmissionregistrationV1MutatingWebhookConfigurationRequest handles readAdmissionregistrationV1MutatingWebhookConfiguration operation. // // Read the specified MutatingWebhookConfiguration. diff --git a/examples/ex_k8s/oas_interfaces_gen.go b/examples/ex_k8s/oas_interfaces_gen.go index 388ef77e6..ad2840a20 100644 --- a/examples/ex_k8s/oas_interfaces_gen.go +++ b/examples/ex_k8s/oas_interfaces_gen.go @@ -813,6 +813,10 @@ type ListStorageV1beta1NamespacedCSIStorageCapacityRes interface { listStorageV1beta1NamespacedCSIStorageCapacityRes() } +type PatchCoreV1NamespacedConfigMapRes interface { + patchCoreV1NamespacedConfigMapRes() +} + type ReadAdmissionregistrationV1MutatingWebhookConfigurationRes interface { readAdmissionregistrationV1MutatingWebhookConfigurationRes() } diff --git a/examples/ex_k8s/oas_json_gen.go b/examples/ex_k8s/oas_json_gen.go index bc3af22dd..f1d146977 100644 --- a/examples/ex_k8s/oas_json_gen.go +++ b/examples/ex_k8s/oas_json_gen.go @@ -74940,6 +74940,50 @@ func (s *IoK8sApimachineryPkgApisMetaV1OwnerReference) UnmarshalJSON(data []byte return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *IoK8sApimachineryPkgApisMetaV1Patch) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *IoK8sApimachineryPkgApisMetaV1Patch) encodeFields(e *jx.Encoder) { +} + +var jsonFieldsNameOfIoK8sApimachineryPkgApisMetaV1Patch = [0]string{} + +// Decode decodes IoK8sApimachineryPkgApisMetaV1Patch from json. +func (s *IoK8sApimachineryPkgApisMetaV1Patch) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode IoK8sApimachineryPkgApisMetaV1Patch to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + default: + return d.Skip() + } + }); err != nil { + return errors.Wrap(err, "decode IoK8sApimachineryPkgApisMetaV1Patch") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *IoK8sApimachineryPkgApisMetaV1Patch) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *IoK8sApimachineryPkgApisMetaV1Patch) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *IoK8sApimachineryPkgApisMetaV1ServerAddressByClientCIDR) Encode(e *jx.Encoder) { e.ObjStart() @@ -86033,6 +86077,82 @@ func (s *OptString) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode encodes PatchCoreV1NamespacedConfigMapCreated as json. +func (s *PatchCoreV1NamespacedConfigMapCreated) Encode(e *jx.Encoder) { + unwrapped := (*IoK8sAPICoreV1ConfigMap)(s) + + unwrapped.Encode(e) +} + +// Decode decodes PatchCoreV1NamespacedConfigMapCreated from json. +func (s *PatchCoreV1NamespacedConfigMapCreated) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode PatchCoreV1NamespacedConfigMapCreated to nil") + } + var unwrapped IoK8sAPICoreV1ConfigMap + if err := func() error { + if err := unwrapped.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = PatchCoreV1NamespacedConfigMapCreated(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *PatchCoreV1NamespacedConfigMapCreated) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *PatchCoreV1NamespacedConfigMapCreated) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes PatchCoreV1NamespacedConfigMapOK as json. +func (s *PatchCoreV1NamespacedConfigMapOK) Encode(e *jx.Encoder) { + unwrapped := (*IoK8sAPICoreV1ConfigMap)(s) + + unwrapped.Encode(e) +} + +// Decode decodes PatchCoreV1NamespacedConfigMapOK from json. +func (s *PatchCoreV1NamespacedConfigMapOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode PatchCoreV1NamespacedConfigMapOK to nil") + } + var unwrapped IoK8sAPICoreV1ConfigMap + if err := func() error { + if err := unwrapped.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = PatchCoreV1NamespacedConfigMapOK(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *PatchCoreV1NamespacedConfigMapOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *PatchCoreV1NamespacedConfigMapOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode encodes ReadCoreV1NamespacedPodLogOKApplicationJSON as json. func (s ReadCoreV1NamespacedPodLogOKApplicationJSON) Encode(e *jx.Encoder) { unwrapped := string(s) diff --git a/examples/ex_k8s/oas_operations_gen.go b/examples/ex_k8s/oas_operations_gen.go index f4da51594..00ca7ec4d 100644 --- a/examples/ex_k8s/oas_operations_gen.go +++ b/examples/ex_k8s/oas_operations_gen.go @@ -211,6 +211,7 @@ const ( ListStorageV1beta1NamespacedCSIStorageCapacityOperation OperationName = "ListStorageV1beta1NamespacedCSIStorageCapacity" LogFileHandlerOperation OperationName = "LogFileHandler" LogFileListHandlerOperation OperationName = "LogFileListHandler" + PatchCoreV1NamespacedConfigMapOperation OperationName = "PatchCoreV1NamespacedConfigMap" ReadAdmissionregistrationV1MutatingWebhookConfigurationOperation OperationName = "ReadAdmissionregistrationV1MutatingWebhookConfiguration" ReadAdmissionregistrationV1ValidatingWebhookConfigurationOperation OperationName = "ReadAdmissionregistrationV1ValidatingWebhookConfiguration" ReadApiextensionsV1CustomResourceDefinitionOperation OperationName = "ReadApiextensionsV1CustomResourceDefinition" diff --git a/examples/ex_k8s/oas_parameters_gen.go b/examples/ex_k8s/oas_parameters_gen.go index df14a237f..7b762b7b9 100644 --- a/examples/ex_k8s/oas_parameters_gen.go +++ b/examples/ex_k8s/oas_parameters_gen.go @@ -67441,6 +67441,342 @@ func decodeLogFileHandlerParams(args [1]string, argsEscaped bool, r *http.Reques return params, nil } +// PatchCoreV1NamespacedConfigMapParams is parameters of patchCoreV1NamespacedConfigMap operation. +type PatchCoreV1NamespacedConfigMapParams struct { + // When present, indicates that modifications should not be persisted. An invalid or unrecognized + // dryRun directive will result in an error response and no further processing of the request. Valid + // values are: - All: all dry run stages will be processed. + DryRun OptString + // FieldManager is a name associated with the actor or entity that is making these changes. The value + // must be less than or 128 characters long, and only contain printable characters, as defined by + // https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests + // (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, + // StrategicMergePatch). + FieldManager OptString + // Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned + // by other people. Force flag must be unset for non-apply patch requests. + Force OptBool + // Name of the ConfigMap. + Name string + // Object name and auth scope, such as for teams and projects. + Namespace string + // If 'true', then the output is pretty printed. + Pretty OptString +} + +func unpackPatchCoreV1NamespacedConfigMapParams(packed middleware.Parameters) (params PatchCoreV1NamespacedConfigMapParams) { + { + key := middleware.ParameterKey{ + Name: "dryRun", + In: "query", + } + if v, ok := packed[key]; ok { + params.DryRun = v.(OptString) + } + } + { + key := middleware.ParameterKey{ + Name: "fieldManager", + In: "query", + } + if v, ok := packed[key]; ok { + params.FieldManager = v.(OptString) + } + } + { + key := middleware.ParameterKey{ + Name: "force", + In: "query", + } + if v, ok := packed[key]; ok { + params.Force = v.(OptBool) + } + } + { + key := middleware.ParameterKey{ + Name: "name", + In: "path", + } + params.Name = packed[key].(string) + } + { + key := middleware.ParameterKey{ + Name: "namespace", + In: "path", + } + params.Namespace = packed[key].(string) + } + { + key := middleware.ParameterKey{ + Name: "pretty", + In: "query", + } + if v, ok := packed[key]; ok { + params.Pretty = v.(OptString) + } + } + return params +} + +func decodePatchCoreV1NamespacedConfigMapParams(args [2]string, argsEscaped bool, r *http.Request) (params PatchCoreV1NamespacedConfigMapParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode query: dryRun. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "dryRun", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotDryRunVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + paramsDotDryRunVal = c + return nil + }(); err != nil { + return err + } + params.DryRun.SetTo(paramsDotDryRunVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "dryRun", + In: "query", + Err: err, + } + } + // Decode query: fieldManager. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "fieldManager", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotFieldManagerVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + paramsDotFieldManagerVal = c + return nil + }(); err != nil { + return err + } + params.FieldManager.SetTo(paramsDotFieldManagerVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "fieldManager", + In: "query", + Err: err, + } + } + // Decode query: force. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "force", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotForceVal bool + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToBool(val) + if err != nil { + return err + } + + paramsDotForceVal = c + return nil + }(); err != nil { + return err + } + params.Force.SetTo(paramsDotForceVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "force", + In: "query", + Err: err, + } + } + // Decode path: name. + if err := func() error { + param := args[1] + if argsEscaped { + unescaped, err := url.PathUnescape(args[1]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "name", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.Name = c + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "name", + In: "path", + Err: err, + } + } + // Decode path: namespace. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "namespace", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.Namespace = c + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "namespace", + In: "path", + Err: err, + } + } + // Decode query: pretty. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "pretty", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotPrettyVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + paramsDotPrettyVal = c + return nil + }(); err != nil { + return err + } + params.Pretty.SetTo(paramsDotPrettyVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "pretty", + In: "query", + Err: err, + } + } + return params, nil +} + // ReadAdmissionregistrationV1MutatingWebhookConfigurationParams is parameters of readAdmissionregistrationV1MutatingWebhookConfiguration operation. type ReadAdmissionregistrationV1MutatingWebhookConfigurationParams struct { // Name of the MutatingWebhookConfiguration. diff --git a/examples/ex_k8s/oas_request_decoders_gen.go b/examples/ex_k8s/oas_request_decoders_gen.go index ae379a2db..7124b7fec 100644 --- a/examples/ex_k8s/oas_request_decoders_gen.go +++ b/examples/ex_k8s/oas_request_decoders_gen.go @@ -1,3 +1,78 @@ // Code generated by ogen, DO NOT EDIT. package api + +import ( + "io" + "mime" + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/validate" +) + +func (s *Server) decodePatchCoreV1NamespacedConfigMapRequest(r *http.Request) ( + req *IoK8sApimachineryPkgApisMetaV1Patch, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/problem+json": + if r.ContentLength == 0 { + return req, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + if err != nil { + return req, close, err + } + + if len(buf) == 0 { + return req, close, validate.ErrBodyRequired + } + + d := jx.DecodeBytes(buf) + + var request IoK8sApimachineryPkgApisMetaV1Patch + if err := func() error { + if err := request.Decode(d); 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 req, close, err + } + return &request, close, nil + default: + return req, close, validate.InvalidContentType(ct) + } +} diff --git a/examples/ex_k8s/oas_request_encoders_gen.go b/examples/ex_k8s/oas_request_encoders_gen.go index ae379a2db..b9bc2039e 100644 --- a/examples/ex_k8s/oas_request_encoders_gen.go +++ b/examples/ex_k8s/oas_request_encoders_gen.go @@ -1,3 +1,26 @@ // Code generated by ogen, DO NOT EDIT. package api + +import ( + "bytes" + "net/http" + + "github.com/go-faster/jx" + + ht "github.com/ogen-go/ogen/http" +) + +func encodePatchCoreV1NamespacedConfigMapRequest( + req *IoK8sApimachineryPkgApisMetaV1Patch, + r *http.Request, +) error { + const contentType = "application/problem+json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} diff --git a/examples/ex_k8s/oas_response_decoders_gen.go b/examples/ex_k8s/oas_response_decoders_gen.go index 4b569f782..78b104dbe 100644 --- a/examples/ex_k8s/oas_response_decoders_gen.go +++ b/examples/ex_k8s/oas_response_decoders_gen.go @@ -11232,6 +11232,103 @@ func decodeLogFileListHandlerResponse(resp *http.Response) (res *LogFileListHand return res, validate.UnexpectedStatusCode(resp.StatusCode) } +func decodePatchCoreV1NamespacedConfigMapResponse(resp *http.Response) (res PatchCoreV1NamespacedConfigMapRes, _ 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 PatchCoreV1NamespacedConfigMapOK + if err := func() error { + if err := response.Decode(d); 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 + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + case 201: + // Code 201. + 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 PatchCoreV1NamespacedConfigMapCreated + if err := func() error { + if err := response.Decode(d); 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 + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + case 401: + // Code 401. + return &PatchCoreV1NamespacedConfigMapUnauthorized{}, nil + } + return res, validate.UnexpectedStatusCode(resp.StatusCode) +} + func decodeReadAdmissionregistrationV1MutatingWebhookConfigurationResponse(resp *http.Response) (res ReadAdmissionregistrationV1MutatingWebhookConfigurationRes, _ error) { switch resp.StatusCode { case 200: diff --git a/examples/ex_k8s/oas_response_encoders_gen.go b/examples/ex_k8s/oas_response_encoders_gen.go index 188c55f2f..780a0bf4f 100644 --- a/examples/ex_k8s/oas_response_encoders_gen.go +++ b/examples/ex_k8s/oas_response_encoders_gen.go @@ -6125,6 +6125,45 @@ func encodeLogFileListHandlerResponse(response *LogFileListHandlerUnauthorized, return nil } +func encodePatchCoreV1NamespacedConfigMapResponse(response PatchCoreV1NamespacedConfigMapRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *PatchCoreV1NamespacedConfigMapOK: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *PatchCoreV1NamespacedConfigMapCreated: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(201) + span.SetStatus(codes.Ok, http.StatusText(201)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *PatchCoreV1NamespacedConfigMapUnauthorized: + w.WriteHeader(401) + span.SetStatus(codes.Error, http.StatusText(401)) + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + func encodeReadAdmissionregistrationV1MutatingWebhookConfigurationResponse(response ReadAdmissionregistrationV1MutatingWebhookConfigurationRes, w http.ResponseWriter, span trace.Span) error { switch response := response.(type) { case *IoK8sAPIAdmissionregistrationV1MutatingWebhookConfiguration: diff --git a/examples/ex_k8s/oas_router_gen.go b/examples/ex_k8s/oas_router_gen.go index 1a79d10a9..0da24c6f2 100644 --- a/examples/ex_k8s/oas_router_gen.go +++ b/examples/ex_k8s/oas_router_gen.go @@ -410,8 +410,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { args[0], args[1], }, elemIsEscaped, w, r) + case "PATCH": + s.handlePatchCoreV1NamespacedConfigMapRequest([2]string{ + args[0], + args[1], + }, elemIsEscaped, w, r) default: - s.notAllowed(w, r, "GET") + s.notAllowed(w, r, "GET,PATCH") } return @@ -13408,6 +13413,14 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { r.args = args r.count = 2 return r, true + case "PATCH": + r.name = PatchCoreV1NamespacedConfigMapOperation + r.summary = "" + r.operationID = "patchCoreV1NamespacedConfigMap" + r.pathPattern = "/api/v1/namespaces/{namespace}/configmaps/{name}" + r.args = args + r.count = 2 + return r, true default: return } diff --git a/examples/ex_k8s/oas_schemas_gen.go b/examples/ex_k8s/oas_schemas_gen.go index d00c3abcd..cfb9b0747 100644 --- a/examples/ex_k8s/oas_schemas_gen.go +++ b/examples/ex_k8s/oas_schemas_gen.go @@ -33943,6 +33943,10 @@ func (s *IoK8sApimachineryPkgApisMetaV1OwnerReference) SetUID(val string) { s.UID = val } +// Patch is provided to give a concrete name and type to the Kubernetes PATCH request body. +// Ref: #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch +type IoK8sApimachineryPkgApisMetaV1Patch struct{} + // ServerAddressByClientCIDR helps the client to determine the server address that they should use, // depending on the clientCIDR that they match. // Ref: #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR @@ -48759,6 +48763,19 @@ func (o OptString) Or(d string) string { return d } +type PatchCoreV1NamespacedConfigMapCreated IoK8sAPICoreV1ConfigMap + +func (*PatchCoreV1NamespacedConfigMapCreated) patchCoreV1NamespacedConfigMapRes() {} + +type PatchCoreV1NamespacedConfigMapOK IoK8sAPICoreV1ConfigMap + +func (*PatchCoreV1NamespacedConfigMapOK) patchCoreV1NamespacedConfigMapRes() {} + +// PatchCoreV1NamespacedConfigMapUnauthorized is response for PatchCoreV1NamespacedConfigMap operation. +type PatchCoreV1NamespacedConfigMapUnauthorized struct{} + +func (*PatchCoreV1NamespacedConfigMapUnauthorized) patchCoreV1NamespacedConfigMapRes() {} + // ReadAdmissionregistrationV1MutatingWebhookConfigurationUnauthorized is response for ReadAdmissionregistrationV1MutatingWebhookConfiguration operation. type ReadAdmissionregistrationV1MutatingWebhookConfigurationUnauthorized struct{} diff --git a/examples/ex_k8s/oas_server_gen.go b/examples/ex_k8s/oas_server_gen.go index dbf6b4e6a..e4f1c8ba4 100644 --- a/examples/ex_k8s/oas_server_gen.go +++ b/examples/ex_k8s/oas_server_gen.go @@ -1234,6 +1234,12 @@ type Handler interface { // // GET /logs/ LogFileListHandler(ctx context.Context) error + // PatchCoreV1NamespacedConfigMap implements patchCoreV1NamespacedConfigMap operation. + // + // Partially update the specified ConfigMap. + // + // PATCH /api/v1/namespaces/{namespace}/configmaps/{name} + PatchCoreV1NamespacedConfigMap(ctx context.Context, req *IoK8sApimachineryPkgApisMetaV1Patch, params PatchCoreV1NamespacedConfigMapParams) (PatchCoreV1NamespacedConfigMapRes, error) // ReadAdmissionregistrationV1MutatingWebhookConfiguration implements readAdmissionregistrationV1MutatingWebhookConfiguration operation. // // Read the specified MutatingWebhookConfiguration. diff --git a/examples/ex_k8s/oas_test_examples_gen_test.go b/examples/ex_k8s/oas_test_examples_gen_test.go index 64fa413b8..324afbf9d 100644 --- a/examples/ex_k8s/oas_test_examples_gen_test.go +++ b/examples/ex_k8s/oas_test_examples_gen_test.go @@ -6655,6 +6655,18 @@ func TestIoK8sApimachineryPkgApisMetaV1OwnerReference_EncodeDecode(t *testing.T) var typ2 IoK8sApimachineryPkgApisMetaV1OwnerReference require.NoError(t, typ2.Decode(jx.DecodeBytes(data))) } +func TestIoK8sApimachineryPkgApisMetaV1Patch_EncodeDecode(t *testing.T) { + var typ IoK8sApimachineryPkgApisMetaV1Patch + typ.SetFake() + + e := jx.Encoder{} + typ.Encode(&e) + data := e.Bytes() + require.True(t, std.Valid(data), "Encoded: %s", data) + + var typ2 IoK8sApimachineryPkgApisMetaV1Patch + require.NoError(t, typ2.Decode(jx.DecodeBytes(data))) +} func TestIoK8sApimachineryPkgApisMetaV1ServerAddressByClientCIDR_EncodeDecode(t *testing.T) { var typ IoK8sApimachineryPkgApisMetaV1ServerAddressByClientCIDR typ.SetFake() @@ -6799,6 +6811,30 @@ func TestIoK8sKubeAggregatorPkgApisApiregistrationV1ServiceReference_EncodeDecod var typ2 IoK8sKubeAggregatorPkgApisApiregistrationV1ServiceReference require.NoError(t, typ2.Decode(jx.DecodeBytes(data))) } +func TestPatchCoreV1NamespacedConfigMapCreated_EncodeDecode(t *testing.T) { + var typ PatchCoreV1NamespacedConfigMapCreated + typ.SetFake() + + e := jx.Encoder{} + typ.Encode(&e) + data := e.Bytes() + require.True(t, std.Valid(data), "Encoded: %s", data) + + var typ2 PatchCoreV1NamespacedConfigMapCreated + require.NoError(t, typ2.Decode(jx.DecodeBytes(data))) +} +func TestPatchCoreV1NamespacedConfigMapOK_EncodeDecode(t *testing.T) { + var typ PatchCoreV1NamespacedConfigMapOK + typ.SetFake() + + e := jx.Encoder{} + typ.Encode(&e) + data := e.Bytes() + require.True(t, std.Valid(data), "Encoded: %s", data) + + var typ2 PatchCoreV1NamespacedConfigMapOK + require.NoError(t, typ2.Decode(jx.DecodeBytes(data))) +} func TestReadCoreV1NamespacedPodLogOKApplicationJSON_EncodeDecode(t *testing.T) { var typ ReadCoreV1NamespacedPodLogOKApplicationJSON typ.SetFake() diff --git a/examples/ex_k8s/oas_unimplemented_gen.go b/examples/ex_k8s/oas_unimplemented_gen.go index 7a95af4e2..8c9880352 100644 --- a/examples/ex_k8s/oas_unimplemented_gen.go +++ b/examples/ex_k8s/oas_unimplemented_gen.go @@ -1854,6 +1854,15 @@ func (UnimplementedHandler) LogFileListHandler(ctx context.Context) error { return ht.ErrNotImplemented } +// PatchCoreV1NamespacedConfigMap implements patchCoreV1NamespacedConfigMap operation. +// +// Partially update the specified ConfigMap. +// +// PATCH /api/v1/namespaces/{namespace}/configmaps/{name} +func (UnimplementedHandler) PatchCoreV1NamespacedConfigMap(ctx context.Context, req *IoK8sApimachineryPkgApisMetaV1Patch, params PatchCoreV1NamespacedConfigMapParams) (r PatchCoreV1NamespacedConfigMapRes, _ error) { + return r, ht.ErrNotImplemented +} + // ReadAdmissionregistrationV1MutatingWebhookConfiguration implements readAdmissionregistrationV1MutatingWebhookConfiguration operation. // // Read the specified MutatingWebhookConfiguration. diff --git a/examples/ex_k8s/oas_validators_gen.go b/examples/ex_k8s/oas_validators_gen.go index a4fc0e3e4..27aefce55 100644 --- a/examples/ex_k8s/oas_validators_gen.go +++ b/examples/ex_k8s/oas_validators_gen.go @@ -5406,3 +5406,19 @@ func (s *IoK8sKubeAggregatorPkgApisApiregistrationV1APIServiceSpec) Validate() e } return nil } + +func (s *PatchCoreV1NamespacedConfigMapCreated) Validate() error { + alias := (*IoK8sAPICoreV1ConfigMap)(s) + if err := alias.Validate(); err != nil { + return err + } + return nil +} + +func (s *PatchCoreV1NamespacedConfigMapOK) Validate() error { + alias := (*IoK8sAPICoreV1ConfigMap)(s) + if err := alias.Validate(); err != nil { + return err + } + return nil +} diff --git a/gen/_template/request_decode.tmpl b/gen/_template/request_decode.tmpl index b0cb33a60..5c87a8d8b 100644 --- a/gen/_template/request_decode.tmpl +++ b/gen/_template/request_decode.tmpl @@ -69,7 +69,7 @@ func (s *{{ if $op.WebhookInfo }}Webhook{{ end }}Server) decode{{ $op.Name }}Req reader := r.Body {{- end }} request := {{ $t.Go }}{Data: reader} - {{- else if $e.JSON }} + {{- else if or $e.JSON $e.ProblemJSON}} if r.ContentLength == 0 { {{- if not $op.Request.Spec.Required }} return req, close, nil diff --git a/gen/_template/request_encode.tmpl b/gen/_template/request_encode.tmpl index 358783a8c..54c82321f 100644 --- a/gen/_template/request_encode.tmpl +++ b/gen/_template/request_encode.tmpl @@ -105,7 +105,7 @@ func encode{{ $op.Name }}Request( ht.SetBody(r, body, contentType) {{- end }} return nil -{{- else if $encoding.JSON }} +{{- else if or $encoding.JSON $encoding.ProblemJSON }} {{- if $.JSONStreaming }} body := ht.CreateBodyWriter(func(w io.Writer) (rerr error) { e := jx.NewStreamingEncoder(w, -1) diff --git a/gen/_template/response_decode.tmpl b/gen/_template/response_decode.tmpl index 54f0fb2e5..015824d3d 100644 --- a/gen/_template/response_decode.tmpl +++ b/gen/_template/response_decode.tmpl @@ -107,7 +107,7 @@ func decode{{ $op.Name }}Response(resp *http.Response) (res {{ $op.Responses.GoT {{- $type = ($type.MustField "Response").Type }} {{- end }} - {{- if $encoding.JSON }} + {{- if or $encoding.JSON $encoding.ProblemJSON }} {{- if $media.JSONStreaming }} d := jx.Decode(resp.Body, -1) {{- else }} diff --git a/gen/_template/response_encode.tmpl b/gen/_template/response_encode.tmpl index dbe8224be..edaec70e5 100644 --- a/gen/_template/response_encode.tmpl +++ b/gen/_template/response_encode.tmpl @@ -160,7 +160,7 @@ func encode{{ $op.Name }}Response(response {{ $op.Responses.GoType }}, w http.Re {{- $var = "response.Response" }} {{- end }} - {{- if $.Encoding.JSON }} + {{- if or $.Encoding.JSON $.Encoding.ProblemJSON }} {{- if $.JSONStreaming }} e := jx.NewStreamingEncoder(w, -1) {{- template "json/enc" elem $type $var }} diff --git a/gen/gen_contents.go b/gen/gen_contents.go index a75e2f846..951866fd4 100644 --- a/gen/gen_contents.go +++ b/gen/gen_contents.go @@ -153,7 +153,7 @@ func (g *Generator) generateFormContent( switch ct := getEncoding(f); ct { case "", ir.EncodingFormURLEncoded: f.Type.AddFeature("uri") - case ir.EncodingJSON: + case ir.EncodingJSON, ir.EncodingProblemJSON: f.Type.AddFeature("json") default: return errors.Wrapf( @@ -218,7 +218,7 @@ func (g *Generator) generateFormContent( if err := isParamAllowed(f.Type, true, map[*ir.Type]struct{}{}); err != nil { return err } - case ir.EncodingJSON: + case ir.EncodingJSON, ir.EncodingProblemJSON: spec.Content = &openapi.ParameterContent{ Name: ct.String(), } @@ -300,7 +300,7 @@ func (g *Generator) generateContents( encoding = r } - if encoding != ir.EncodingJSON && media.XOgenJSONStreaming { + if encoding != ir.EncodingJSON && encoding != ir.EncodingProblemJSON && media.XOgenJSONStreaming { g.log.Warn(`Extension "x-ogen-json-streaming" will be ignored for non-JSON encoding`, zapPosition(media), zap.String("contentType", contentType), @@ -322,6 +322,34 @@ func (g *Generator) generateContents( } return nil + case ir.EncodingProblemJSON: + // In rfc9457, the only MUST defined for generators is to keep the status field + // synced with the HTTP status code. + if media.Schema.Type == jsonschema.Object { + for _, prop := range media.Schema.Properties { + if prop.Name == "status" { + g.log.Warn(`Ensuring "status" matching HTTP status code will not be enforced yet!`, + zapPosition(media), + zap.String("contentType", contentType), + ) + break + } + } + } + t, err := g.generateSchema(ctx, typeName, media.Schema, optional, nil) + if err != nil { + return errors.Wrap(err, "generate schema") + } + + t.AddFeature("json") + result[ir.ContentType(parsedContentType)] = ir.Media{ + Encoding: encoding, + Type: t, + JSONStreaming: media.XOgenJSONStreaming, + } + + return nil + case ir.EncodingFormURLEncoded: t, err := g.generateFormContent(ctx, typeName, media, optional, encoding) if err != nil { diff --git a/gen/gen_responses.go b/gen/gen_responses.go index e6bc9a1ab..80547595f 100644 --- a/gen/gen_responses.go +++ b/gen/gen_responses.go @@ -187,7 +187,7 @@ func (g *Generator) responseToIR( var unsupported []string for ct, content := range contents { t, e := content.Type, content.Encoding - if e.JSON() || t.IsStream() || isBinary(t.Schema) { + if e.JSON() || e.ProblemJSON() || t.IsStream() || isBinary(t.Schema) { continue } delete(contents, ct) diff --git a/gen/ir/media.go b/gen/ir/media.go index 802c38fca..ad744afe1 100644 --- a/gen/ir/media.go +++ b/gen/ir/media.go @@ -14,7 +14,8 @@ type Encoding string const ( // EncodingJSON is Encoding for json. - EncodingJSON Encoding = "application/json" + EncodingJSON Encoding = "application/json" + EncodingProblemJSON Encoding = "application/problem+json" // EncodingFormURLEncoded is Encoding for URL-encoded form. EncodingFormURLEncoded Encoding = "application/x-www-form-urlencoded" // EncodingMultipart is Encoding for multipart form. @@ -29,6 +30,8 @@ func (t Encoding) String() string { return string(t) } func (t Encoding) JSON() bool { return t == EncodingJSON } +func (t Encoding) ProblemJSON() bool { return t == EncodingProblemJSON } + func (t Encoding) FormURLEncoded() bool { return t == EncodingFormURLEncoded } func (t Encoding) MultipartForm() bool { return t == EncodingMultipart } diff --git a/gen_test.go b/gen_test.go index f0f901561..f50b7be40 100644 --- a/gen_test.go +++ b/gen_test.go @@ -155,6 +155,9 @@ func TestGenerate(t *testing.T) { "application/merge-patch+json": ir.EncodingJSON, "application/strategic-merge-patch+json": ir.EncodingJSON, }, + "problemjson.yml": { + "application/problem+json": ir.EncodingProblemJSON, + }, }, map[string][]string{ "autorest/additionalProperties.json": {}, @@ -176,6 +179,7 @@ func TestGenerate(t *testing.T) { "unsupported content types", }, "petstore-expanded.yml": {}, + "problemjson.yml": {}, "redoc/discriminator.json": { "unsupported content types", },