diff --git a/internal/test/parameters/shared_anyof/config.yaml b/internal/test/parameters/shared_anyof/config.yaml new file mode 100644 index 000000000..3b67ae177 --- /dev/null +++ b/internal/test/parameters/shared_anyof/config.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +# From issue-2090 +package: sharedanyof +generate: + echo-server: true + client: true + models: true +output: shared_anyof.gen.go diff --git a/internal/test/parameters/shared_anyof/doc.go b/internal/test/parameters/shared_anyof/doc.go new file mode 100644 index 000000000..b460b9fb8 --- /dev/null +++ b/internal/test/parameters/shared_anyof/doc.go @@ -0,0 +1,8 @@ +// Package sharedanyof is a regression test for issue-2090: a path-level anyOf +// parameter shared by multiple methods on a path (and on webhooks/callbacks) +// must have its union member types declared once for the path item, not once +// per method. The committed generated file compiling as part of the test +// module is the guard. +package sharedanyof + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/parameters/shared_anyof/shared_anyof.gen.go b/internal/test/parameters/shared_anyof/shared_anyof.gen.go new file mode 100644 index 000000000..75ed74d7a --- /dev/null +++ b/internal/test/parameters/shared_anyof/shared_anyof.gen.go @@ -0,0 +1,1639 @@ +// Package sharedanyof provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package sharedanyof + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +// WidgetId defines model for WidgetId. +type WidgetId struct { + union json.RawMessage +} + +// WidgetId0 defines model for WidgetId.0. +type WidgetId0 = openapi_types.UUID + +// WidgetId1 defines model for WidgetId.1. +type WidgetId1 = string + +// Id0 defines parameters for DeleteResource. +type Id0 = openapi_types.UUID + +// Id1 defines parameters for DeleteResource. +type Id1 = string + +// OnWebhookGetParams defines parameters for OnWebhookGet. +type OnWebhookGetParams struct { + Whid struct { + union json.RawMessage + } `form:"whid" json:"whid"` +} + +// Whid0 defines parameters for OnWebhookGet. +type Whid0 = openapi_types.UUID + +// Whid1 defines parameters for OnWebhookGet. +type Whid1 = string + +// OnWebhookPostParams defines parameters for OnWebhookPost. +type OnWebhookPostParams struct { + Whid struct { + union json.RawMessage + } `form:"whid" json:"whid"` +} + +// OnResourceGetParams defines parameters for OnResourceGet. +type OnResourceGetParams struct { + Cbid struct { + union json.RawMessage + } `form:"cbid" json:"cbid"` +} + +// Cbid0 defines parameters for OnResourceGet. +type Cbid0 = openapi_types.UUID + +// Cbid1 defines parameters for OnResourceGet. +type Cbid1 = string + +// OnResourcePostParams defines parameters for OnResourcePost. +type OnResourcePostParams struct { + Cbid struct { + union json.RawMessage + } `form:"cbid" json:"cbid"` +} + +// AsWidgetId0 returns the union data inside the WidgetId as a WidgetId0 +func (t WidgetId) AsWidgetId0() (WidgetId0, error) { + var body WidgetId0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromWidgetId0 overwrites any union data inside the WidgetId as the provided WidgetId0 +func (t *WidgetId) FromWidgetId0(v WidgetId0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeWidgetId0 performs a merge with any union data inside the WidgetId, using the provided WidgetId0 +func (t *WidgetId) MergeWidgetId0(v WidgetId0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsWidgetId1 returns the union data inside the WidgetId as a WidgetId1 +func (t WidgetId) AsWidgetId1() (WidgetId1, error) { + var body WidgetId1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromWidgetId1 overwrites any union data inside the WidgetId as the provided WidgetId1 +func (t *WidgetId) FromWidgetId1(v WidgetId1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeWidgetId1 performs a merge with any union data inside the WidgetId, using the provided WidgetId1 +func (t *WidgetId) MergeWidgetId1(v WidgetId1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t WidgetId) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *WidgetId) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + + // GetGadget performs a GET /gadgets/{wid} (the `GetGadget` operationId) request. + GetGadget(ctx context.Context, wid WidgetId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // Subscribe performs a POST /subscribe (the `Subscribe` operationId) request. + Subscribe(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteResource performs a DELETE /v1/resource/{id} (the `DeleteResource` operationId) request. + DeleteResource(ctx context.Context, id struct { + union json.RawMessage + }, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetResource performs a GET /v1/resource/{id} (the `GetResource` operationId) request. + GetResource(ctx context.Context, id struct { + union json.RawMessage + }, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PostResource performs a POST /v1/resource/{id} (the `PostResource` operationId) request. + PostResource(ctx context.Context, id struct { + union json.RawMessage + }, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetWidget performs a GET /widgets/{wid} (the `GetWidget` operationId) request. + GetWidget(ctx context.Context, wid WidgetId, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +// GetGadget performs a GET /gadgets/{wid} (the `GetGadget` operationId) request. +func (c *Client) GetGadget(ctx context.Context, wid WidgetId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetGadgetRequest(c.Server, wid) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// Subscribe performs a POST /subscribe (the `Subscribe` operationId) request. +func (c *Client) Subscribe(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSubscribeRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// DeleteResource performs a DELETE /v1/resource/{id} (the `DeleteResource` operationId) request. +func (c *Client) DeleteResource(ctx context.Context, id struct { + union json.RawMessage +}, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteResourceRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// GetResource performs a GET /v1/resource/{id} (the `GetResource` operationId) request. +func (c *Client) GetResource(ctx context.Context, id struct { + union json.RawMessage +}, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetResourceRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// PostResource performs a POST /v1/resource/{id} (the `PostResource` operationId) request. +func (c *Client) PostResource(ctx context.Context, id struct { + union json.RawMessage +}, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostResourceRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// GetWidget performs a GET /widgets/{wid} (the `GetWidget` operationId) request. +func (c *Client) GetWidget(ctx context.Context, wid WidgetId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetWidgetRequest(c.Server, wid) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetGadgetRequest constructs an http.Request for the GetGadget method +func NewGetGadgetRequest(server string, wid WidgetId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "wid", wid, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/gadgets/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewSubscribeRequest constructs an http.Request for the Subscribe method +func NewSubscribeRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/subscribe") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewDeleteResourceRequest constructs an http.Request for the DeleteResource method +func NewDeleteResourceRequest(server string, id struct { + union json.RawMessage +}) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/resource/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetResourceRequest constructs an http.Request for the GetResource method +func NewGetResourceRequest(server string, id struct { + union json.RawMessage +}) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/resource/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPostResourceRequest constructs an http.Request for the PostResource method +func NewPostResourceRequest(server string, id struct { + union json.RawMessage +}) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/resource/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetWidgetRequest constructs an http.Request for the GetWidget method +func NewGetWidgetRequest(server string, wid WidgetId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "wid", wid, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/widgets/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + + // GetGadgetWithResponse performs a GET /gadgets/{wid} (the `GetGadget` operationId) request. + // + // Returns a wrapper object for the known response body format(s). + GetGadgetWithResponse(ctx context.Context, wid WidgetId, reqEditors ...RequestEditorFn) (*GetGadgetResponse, error) + + // SubscribeWithResponse performs a POST /subscribe (the `Subscribe` operationId) request. + // + // Returns a wrapper object for the known response body format(s). + SubscribeWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*SubscribeResponse, error) + + // DeleteResourceWithResponse performs a DELETE /v1/resource/{id} (the `DeleteResource` operationId) request. + // + // Returns a wrapper object for the known response body format(s). + DeleteResourceWithResponse(ctx context.Context, id struct { + union json.RawMessage + }, reqEditors ...RequestEditorFn) (*DeleteResourceResponse, error) + + // GetResourceWithResponse performs a GET /v1/resource/{id} (the `GetResource` operationId) request. + // + // Returns a wrapper object for the known response body format(s). + GetResourceWithResponse(ctx context.Context, id struct { + union json.RawMessage + }, reqEditors ...RequestEditorFn) (*GetResourceResponse, error) + + // PostResourceWithResponse performs a POST /v1/resource/{id} (the `PostResource` operationId) request. + // + // Returns a wrapper object for the known response body format(s). + PostResourceWithResponse(ctx context.Context, id struct { + union json.RawMessage + }, reqEditors ...RequestEditorFn) (*PostResourceResponse, error) + + // GetWidgetWithResponse performs a GET /widgets/{wid} (the `GetWidget` operationId) request. + // + // Returns a wrapper object for the known response body format(s). + GetWidgetWithResponse(ctx context.Context, wid WidgetId, reqEditors ...RequestEditorFn) (*GetWidgetResponse, error) +} + +type GetGadgetResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// GetBody returns the raw response body bytes +func (r GetGadgetResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r GetGadgetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetGadgetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetGadgetResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type SubscribeResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// GetBody returns the raw response body bytes +func (r SubscribeResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r SubscribeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r SubscribeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r SubscribeResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type DeleteResourceResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// GetBody returns the raw response body bytes +func (r DeleteResourceResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r DeleteResourceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteResourceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r DeleteResourceResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetResourceResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// GetBody returns the raw response body bytes +func (r GetResourceResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r GetResourceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetResourceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetResourceResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type PostResourceResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// GetBody returns the raw response body bytes +func (r PostResourceResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r PostResourceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostResourceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r PostResourceResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetWidgetResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// GetBody returns the raw response body bytes +func (r GetWidgetResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r GetWidgetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetWidgetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetWidgetResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +// GetGadgetWithResponse performs a GET /gadgets/{wid} (the `GetGadget` operationId) request. +// +// Returns a wrapper object for the known response body format(s). +func (c *ClientWithResponses) GetGadgetWithResponse(ctx context.Context, wid WidgetId, reqEditors ...RequestEditorFn) (*GetGadgetResponse, error) { + rsp, err := c.GetGadget(ctx, wid, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetGadgetResponse(rsp) +} + +// SubscribeWithResponse performs a POST /subscribe (the `Subscribe` operationId) request. +// +// Returns a wrapper object for the known response body format(s). +func (c *ClientWithResponses) SubscribeWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*SubscribeResponse, error) { + rsp, err := c.Subscribe(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseSubscribeResponse(rsp) +} + +// DeleteResourceWithResponse performs a DELETE /v1/resource/{id} (the `DeleteResource` operationId) request. +// +// Returns a wrapper object for the known response body format(s). +func (c *ClientWithResponses) DeleteResourceWithResponse(ctx context.Context, id struct { + union json.RawMessage +}, reqEditors ...RequestEditorFn) (*DeleteResourceResponse, error) { + rsp, err := c.DeleteResource(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteResourceResponse(rsp) +} + +// GetResourceWithResponse performs a GET /v1/resource/{id} (the `GetResource` operationId) request. +// +// Returns a wrapper object for the known response body format(s). +func (c *ClientWithResponses) GetResourceWithResponse(ctx context.Context, id struct { + union json.RawMessage +}, reqEditors ...RequestEditorFn) (*GetResourceResponse, error) { + rsp, err := c.GetResource(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetResourceResponse(rsp) +} + +// PostResourceWithResponse performs a POST /v1/resource/{id} (the `PostResource` operationId) request. +// +// Returns a wrapper object for the known response body format(s). +func (c *ClientWithResponses) PostResourceWithResponse(ctx context.Context, id struct { + union json.RawMessage +}, reqEditors ...RequestEditorFn) (*PostResourceResponse, error) { + rsp, err := c.PostResource(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostResourceResponse(rsp) +} + +// GetWidgetWithResponse performs a GET /widgets/{wid} (the `GetWidget` operationId) request. +// +// Returns a wrapper object for the known response body format(s). +func (c *ClientWithResponses) GetWidgetWithResponse(ctx context.Context, wid WidgetId, reqEditors ...RequestEditorFn) (*GetWidgetResponse, error) { + rsp, err := c.GetWidget(ctx, wid, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetWidgetResponse(rsp) +} + +// ParseGetGadgetResponse parses an HTTP response from a GetGadgetWithResponse call +func ParseGetGadgetResponse(rsp *http.Response) (*GetGadgetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetGadgetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseSubscribeResponse parses an HTTP response from a SubscribeWithResponse call +func ParseSubscribeResponse(rsp *http.Response) (*SubscribeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &SubscribeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseDeleteResourceResponse parses an HTTP response from a DeleteResourceWithResponse call +func ParseDeleteResourceResponse(rsp *http.Response) (*DeleteResourceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteResourceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetResourceResponse parses an HTTP response from a GetResourceWithResponse call +func ParseGetResourceResponse(rsp *http.Response) (*GetResourceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetResourceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParsePostResourceResponse parses an HTTP response from a PostResourceWithResponse call +func ParsePostResourceResponse(rsp *http.Response) (*PostResourceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostResourceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetWidgetResponse parses an HTTP response from a GetWidgetWithResponse call +func ParseGetWidgetResponse(rsp *http.Response) (*GetWidgetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetWidgetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// WebhookInitiator sends OpenAPI 3.1 webhook requests to target URLs. +// Modeled on the generated Client, but with no stored Server -- the full +// target URL is provided per-call by the caller (typically discovered +// from a subscription registration). +type WebhookInitiator struct { + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// WebhookInitiatorOption allows setting custom parameters during construction. +type WebhookInitiatorOption func(*WebhookInitiator) error + +// NewWebhookInitiator creates a new WebhookInitiator with reasonable defaults. +func NewWebhookInitiator(opts ...WebhookInitiatorOption) (*WebhookInitiator, error) { + initiator := WebhookInitiator{} + for _, o := range opts { + if err := o(&initiator); err != nil { + return nil, err + } + } + if initiator.Client == nil { + initiator.Client = &http.Client{} + } + return &initiator, nil +} + +// WithWebhookHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithWebhookHTTPClient(doer HttpRequestDoer) WebhookInitiatorOption { + return func(p *WebhookInitiator) error { + p.Client = doer + return nil + } +} + +// WithWebhookRequestEditorFn allows setting up a callback function, which +// will be called right before sending the webhook request. This can be +// used to mutate the request, e.g. to add signature headers. +func WithWebhookRequestEditorFn(fn RequestEditorFn) WebhookInitiatorOption { + return func(p *WebhookInitiator) error { + p.RequestEditors = append(p.RequestEditors, fn) + return nil + } +} + +func (p *WebhookInitiator) applyWebhookEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range p.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// WebhookInitiatorInterface is the interface specification for the webhook initiator. +type WebhookInitiatorInterface interface { + // OnWebhookGet fires the resourceWebhook webhook + OnWebhookGet(ctx context.Context, targetURL string, params *OnWebhookGetParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // OnWebhookPost fires the resourceWebhook webhook + OnWebhookPost(ctx context.Context, targetURL string, params *OnWebhookPostParams, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (p *WebhookInitiator) OnWebhookGet(ctx context.Context, targetURL string, params *OnWebhookGetParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewOnWebhookGetWebhookRequest(targetURL, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := p.applyWebhookEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return p.Client.Do(req) +} + +func (p *WebhookInitiator) OnWebhookPost(ctx context.Context, targetURL string, params *OnWebhookPostParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewOnWebhookPostWebhookRequest(targetURL, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := p.applyWebhookEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return p.Client.Do(req) +} + +// NewOnWebhookGetWebhookRequest builds a GET request for the resourceWebhook webhook +func NewOnWebhookGetWebhookRequest(targetURL string, params *OnWebhookGetParams) (*http.Request, error) { + var err error + _ = err + + reqURL, err := url.Parse(targetURL) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := reqURL.Query() + var rawQueryFragments []string + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "whid", params.Whid, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + if encoded := queryValues.Encode(); encoded != "" { + rawQueryFragments = append(rawQueryFragments, encoded) + } + reqURL.RawQuery = strings.Join(rawQueryFragments, "&") + } + + req, err := http.NewRequest(http.MethodGet, reqURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewOnWebhookPostWebhookRequest builds a POST request for the resourceWebhook webhook +func NewOnWebhookPostWebhookRequest(targetURL string, params *OnWebhookPostParams) (*http.Request, error) { + var err error + _ = err + + reqURL, err := url.Parse(targetURL) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := reqURL.Query() + var rawQueryFragments []string + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "whid", params.Whid, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + if encoded := queryValues.Encode(); encoded != "" { + rawQueryFragments = append(rawQueryFragments, encoded) + } + reqURL.RawQuery = strings.Join(rawQueryFragments, "&") + } + + req, err := http.NewRequest(http.MethodPost, reqURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// CallbackInitiator sends OpenAPI callback requests to target URLs. +// Modeled on the generated Client, but with no stored Server -- the full +// target URL is provided per-call by the caller (typically discovered +// from a callback expression on the parent operation's request body, +// e.g. `{$request.body#/callbackUrl}`). +type CallbackInitiator struct { + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// CallbackInitiatorOption allows setting custom parameters during construction. +type CallbackInitiatorOption func(*CallbackInitiator) error + +// NewCallbackInitiator creates a new CallbackInitiator with reasonable defaults. +func NewCallbackInitiator(opts ...CallbackInitiatorOption) (*CallbackInitiator, error) { + initiator := CallbackInitiator{} + for _, o := range opts { + if err := o(&initiator); err != nil { + return nil, err + } + } + if initiator.Client == nil { + initiator.Client = &http.Client{} + } + return &initiator, nil +} + +// WithCallbackHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithCallbackHTTPClient(doer HttpRequestDoer) CallbackInitiatorOption { + return func(p *CallbackInitiator) error { + p.Client = doer + return nil + } +} + +// WithCallbackRequestEditorFn allows setting up a callback function, which +// will be called right before sending the callback request. This can be +// used to mutate the request, e.g. to add signature headers. +func WithCallbackRequestEditorFn(fn RequestEditorFn) CallbackInitiatorOption { + return func(p *CallbackInitiator) error { + p.RequestEditors = append(p.RequestEditors, fn) + return nil + } +} + +func (p *CallbackInitiator) applyCallbackEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range p.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// CallbackInitiatorInterface is the interface specification for the callback initiator. +type CallbackInitiatorInterface interface { + // OnResourceGet fires the resourceEvent callback + OnResourceGet(ctx context.Context, targetURL string, params *OnResourceGetParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // OnResourcePost fires the resourceEvent callback + OnResourcePost(ctx context.Context, targetURL string, params *OnResourcePostParams, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (p *CallbackInitiator) OnResourceGet(ctx context.Context, targetURL string, params *OnResourceGetParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewOnResourceGetCallbackRequest(targetURL, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := p.applyCallbackEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return p.Client.Do(req) +} + +func (p *CallbackInitiator) OnResourcePost(ctx context.Context, targetURL string, params *OnResourcePostParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewOnResourcePostCallbackRequest(targetURL, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := p.applyCallbackEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return p.Client.Do(req) +} + +// NewOnResourceGetCallbackRequest builds a GET request for the resourceEvent callback +func NewOnResourceGetCallbackRequest(targetURL string, params *OnResourceGetParams) (*http.Request, error) { + var err error + _ = err + + reqURL, err := url.Parse(targetURL) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := reqURL.Query() + var rawQueryFragments []string + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "cbid", params.Cbid, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + if encoded := queryValues.Encode(); encoded != "" { + rawQueryFragments = append(rawQueryFragments, encoded) + } + reqURL.RawQuery = strings.Join(rawQueryFragments, "&") + } + + req, err := http.NewRequest(http.MethodGet, reqURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewOnResourcePostCallbackRequest builds a POST request for the resourceEvent callback +func NewOnResourcePostCallbackRequest(targetURL string, params *OnResourcePostParams) (*http.Request, error) { + var err error + _ = err + + reqURL, err := url.Parse(targetURL) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := reqURL.Query() + var rawQueryFragments []string + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "cbid", params.Cbid, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + if encoded := queryValues.Encode(); encoded != "" { + rawQueryFragments = append(rawQueryFragments, encoded) + } + reqURL.RawQuery = strings.Join(rawQueryFragments, "&") + } + + req, err := http.NewRequest(http.MethodPost, reqURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (GET /gadgets/{wid}) + GetGadget(ctx echo.Context, wid WidgetId) error + + // (POST /subscribe) + Subscribe(ctx echo.Context) error + + // (DELETE /v1/resource/{id}) + DeleteResource(ctx echo.Context, id struct { + union json.RawMessage + }) error + + // (GET /v1/resource/{id}) + GetResource(ctx echo.Context, id struct { + union json.RawMessage + }) error + + // (POST /v1/resource/{id}) + PostResource(ctx echo.Context, id struct { + union json.RawMessage + }) error + + // (GET /widgets/{wid}) + GetWidget(ctx echo.Context, wid WidgetId) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// GetGadget converts echo context to params. +func (w *ServerInterfaceWrapper) GetGadget(ctx echo.Context) error { + var err error + // ------------- Path parameter "wid" ------------- + var wid WidgetId + + err = runtime.BindStyledParameterWithOptions("simple", "wid", ctx.Param("wid"), &wid, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "", Format: "", ValueIsUnescaped: ctx.Request().URL.RawPath == ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter wid: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetGadget(ctx, wid) + return err +} + +// Subscribe converts echo context to params. +func (w *ServerInterfaceWrapper) Subscribe(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.Subscribe(ctx) + return err +} + +// DeleteResource converts echo context to params. +func (w *ServerInterfaceWrapper) DeleteResource(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id struct { + union json.RawMessage + } + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "", Format: "", ValueIsUnescaped: ctx.Request().URL.RawPath == ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.DeleteResource(ctx, id) + return err +} + +// GetResource converts echo context to params. +func (w *ServerInterfaceWrapper) GetResource(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id struct { + union json.RawMessage + } + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "", Format: "", ValueIsUnescaped: ctx.Request().URL.RawPath == ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetResource(ctx, id) + return err +} + +// PostResource converts echo context to params. +func (w *ServerInterfaceWrapper) PostResource(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id struct { + union json.RawMessage + } + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "", Format: "", ValueIsUnescaped: ctx.Request().URL.RawPath == ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PostResource(ctx, id) + return err +} + +// GetWidget converts echo context to params. +func (w *ServerInterfaceWrapper) GetWidget(ctx echo.Context) error { + var err error + // ------------- Path parameter "wid" ------------- + var wid WidgetId + + err = runtime.BindStyledParameterWithOptions("simple", "wid", ctx.Param("wid"), &wid, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "", Format: "", ValueIsUnescaped: ctx.Request().URL.RawPath == ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter wid: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetWidget(ctx, wid) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlersOptions configures RegisterHandlersWithOptions. +type RegisterHandlersOptions struct { + // BaseURL is prepended to every registered path so the API can be served + // under a prefix. + BaseURL string + // OperationMiddlewares lets the caller attach per-operation middleware at + // registration time. The map key is the OpenAPI `operationId` value as it + // appears in the spec (the raw, un-normalized form). Operations that have + // no entry are registered with no extra middleware. A nil map disables + // per-operation middleware entirely. + OperationMiddlewares map[string][]echo.MiddlewareFunc +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithOptions(router, si, RegisterHandlersOptions{}) +} + +// RegisterHandlersWithBaseURL registers handlers and prepends BaseURL to the +// paths so the API can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + RegisterHandlersWithOptions(router, si, RegisterHandlersOptions{BaseURL: baseURL}) +} + +// RegisterHandlersWithOptions registers handlers using the supplied options, +// including any per-operation middleware. +func RegisterHandlersWithOptions(router EchoRouter, si ServerInterface, options RegisterHandlersOptions) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.DELETE(options.BaseURL+"/v1/resource/:id", wrapper.DeleteResource, options.OperationMiddlewares["deleteResource"]...) + router.GET(options.BaseURL+"/v1/resource/:id", wrapper.GetResource, options.OperationMiddlewares["getResource"]...) + router.POST(options.BaseURL+"/v1/resource/:id", wrapper.PostResource, options.OperationMiddlewares["postResource"]...) + router.POST(options.BaseURL+"/subscribe", wrapper.Subscribe, options.OperationMiddlewares["subscribe"]...) + router.GET(options.BaseURL+"/widgets/:wid", wrapper.GetWidget, options.OperationMiddlewares["getWidget"]...) + router.GET(options.BaseURL+"/gadgets/:wid", wrapper.GetGadget, options.OperationMiddlewares["getGadget"]...) + +} + +// WebhookReceiverInterface represents handlers for receiving inbound +// webhook requests. Each webhook becomes a Handle*Webhook +// method that the implementation fills in. The caller mounts the per- +// webhook echo.HandlerFunc returned by {Op}WebhookHandler at +// whatever URL path they advertise to senders. +type WebhookReceiverInterface interface { + + // HandleOnWebhookGetWebhook handles the GET webhook for resourceWebhook. + HandleOnWebhookGetWebhook(ctx echo.Context, params OnWebhookGetParams) error + + // HandleOnWebhookPostWebhook handles the POST webhook for resourceWebhook. + HandleOnWebhookPostWebhook(ctx echo.Context, params OnWebhookPostParams) error +} + +// OnWebhookGetWebhookHandler returns the echo.HandlerFunc for the resourceWebhook webhook. +// Mount this at the URL path advertised to webhook senders. Errors +// during parameter binding are returned via echo.NewHTTPError so echo's +// error-handling chain reports them as 400. Middlewares are applied in +// the order provided -- the last argument becomes the outermost wrapper. +func OnWebhookGetWebhookHandler(si WebhookReceiverInterface, middlewares ...echo.MiddlewareFunc) echo.HandlerFunc { + h := echo.HandlerFunc(func(ctx echo.Context) error { + var err error + _ = err + + // Parameter object where we will unmarshal all parameters from the context. + var params OnWebhookGetParams + + // ------------- Required query parameter "whid" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, true, "whid", ctx.QueryParams(), ¶ms.Whid, runtime.BindQueryParameterOptions{Type: "", Format: ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter whid: %s", err)) + } + + return si.HandleOnWebhookGetWebhook(ctx, params) + }) + for _, mw := range middlewares { + h = mw(h) + } + return h +} + +// OnWebhookPostWebhookHandler returns the echo.HandlerFunc for the resourceWebhook webhook. +// Mount this at the URL path advertised to webhook senders. Errors +// during parameter binding are returned via echo.NewHTTPError so echo's +// error-handling chain reports them as 400. Middlewares are applied in +// the order provided -- the last argument becomes the outermost wrapper. +func OnWebhookPostWebhookHandler(si WebhookReceiverInterface, middlewares ...echo.MiddlewareFunc) echo.HandlerFunc { + h := echo.HandlerFunc(func(ctx echo.Context) error { + var err error + _ = err + + // Parameter object where we will unmarshal all parameters from the context. + var params OnWebhookPostParams + + // ------------- Required query parameter "whid" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, true, "whid", ctx.QueryParams(), ¶ms.Whid, runtime.BindQueryParameterOptions{Type: "", Format: ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter whid: %s", err)) + } + + return si.HandleOnWebhookPostWebhook(ctx, params) + }) + for _, mw := range middlewares { + h = mw(h) + } + return h +} + +// CallbackReceiverInterface represents handlers for receiving inbound +// callback requests. Each callback becomes a Handle*Callback +// method that the implementation fills in. The caller mounts the per- +// callback echo.HandlerFunc returned by {Op}CallbackHandler at +// whatever URL path they advertise to senders. +type CallbackReceiverInterface interface { + + // HandleOnResourceGetCallback handles the GET callback for resourceEvent. + HandleOnResourceGetCallback(ctx echo.Context, params OnResourceGetParams) error + + // HandleOnResourcePostCallback handles the POST callback for resourceEvent. + HandleOnResourcePostCallback(ctx echo.Context, params OnResourcePostParams) error +} + +// OnResourceGetCallbackHandler returns the echo.HandlerFunc for the resourceEvent callback. +// Mount this at the URL path advertised to callback senders. Errors +// during parameter binding are returned via echo.NewHTTPError so echo's +// error-handling chain reports them as 400. Middlewares are applied in +// the order provided -- the last argument becomes the outermost wrapper. +func OnResourceGetCallbackHandler(si CallbackReceiverInterface, middlewares ...echo.MiddlewareFunc) echo.HandlerFunc { + h := echo.HandlerFunc(func(ctx echo.Context) error { + var err error + _ = err + + // Parameter object where we will unmarshal all parameters from the context. + var params OnResourceGetParams + + // ------------- Required query parameter "cbid" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, true, "cbid", ctx.QueryParams(), ¶ms.Cbid, runtime.BindQueryParameterOptions{Type: "", Format: ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter cbid: %s", err)) + } + + return si.HandleOnResourceGetCallback(ctx, params) + }) + for _, mw := range middlewares { + h = mw(h) + } + return h +} + +// OnResourcePostCallbackHandler returns the echo.HandlerFunc for the resourceEvent callback. +// Mount this at the URL path advertised to callback senders. Errors +// during parameter binding are returned via echo.NewHTTPError so echo's +// error-handling chain reports them as 400. Middlewares are applied in +// the order provided -- the last argument becomes the outermost wrapper. +func OnResourcePostCallbackHandler(si CallbackReceiverInterface, middlewares ...echo.MiddlewareFunc) echo.HandlerFunc { + h := echo.HandlerFunc(func(ctx echo.Context) error { + var err error + _ = err + + // Parameter object where we will unmarshal all parameters from the context. + var params OnResourcePostParams + + // ------------- Required query parameter "cbid" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, true, "cbid", ctx.QueryParams(), ¶ms.Cbid, runtime.BindQueryParameterOptions{Type: "", Format: ""}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter cbid: %s", err)) + } + + return si.HandleOnResourcePostCallback(ctx, params) + }) + for _, mw := range middlewares { + h = mw(h) + } + return h +} diff --git a/internal/test/parameters/shared_anyof/shared_anyof_test.go b/internal/test/parameters/shared_anyof/shared_anyof_test.go new file mode 100644 index 000000000..b11be615b --- /dev/null +++ b/internal/test/parameters/shared_anyof/shared_anyof_test.go @@ -0,0 +1,34 @@ +package sharedanyof + +import ( + "testing" + + openapi_types "github.com/oapi-codegen/runtime/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSharedAnyOfParamTypesDeclaredOnce is the issue-2090 regression guard: a +// path-level anyOf parameter shared by several methods, and a component +// parameter referenced from several paths, each have their union member types +// declared exactly once. The blank declarations below only build because each +// type exists (and is not redeclared); the generated package failing to build +// as part of the test module would also catch a regression. +func TestSharedAnyOfParamTypesDeclaredOnce(t *testing.T) { + var ( + _ Id0 // path parameter, shared by get/post/delete + _ Id1 // + _ Whid0 // webhook query parameter + _ Cbid0 // callback query parameter + _ WidgetId0 // component parameter, $ref'd from two paths + _ WidgetId1 // + ) + + // The component parameter's union type carries its accessors once. + u := openapi_types.UUID{} + var w WidgetId + require.NoError(t, w.FromWidgetId0(u)) + got, err := w.AsWidgetId0() + require.NoError(t, err) + assert.Equal(t, u, got) +} diff --git a/internal/test/parameters/shared_anyof/spec.yaml b/internal/test/parameters/shared_anyof/spec.yaml new file mode 100644 index 000000000..62ef86497 --- /dev/null +++ b/internal/test/parameters/shared_anyof/spec.yaml @@ -0,0 +1,63 @@ +openapi: 3.1.0 +info: + title: shared-anyof-param + version: "1.0.0" +# Path-level anyOf parameters shared across multiple methods. The union member +# types (Id0/Id1, ...) are declared once for the path item rather than once per +# method, so this compiles (issue #2090). Distinct parameter names across the +# regular path, webhook, and callback avoid a cross-path collision. +paths: + /v1/resource/{id}: + parameters: + - name: id + in: path + required: true + schema: + anyOf: + - {type: string, format: uuid} + - {type: string, pattern: '^[a-z0-9_-]+$'} + get: {operationId: getResource, responses: {'200': {description: ok}}} + post: {operationId: postResource, responses: {'200': {description: ok}}} + delete: {operationId: deleteResource, responses: {'200': {description: ok}}} + /subscribe: + post: + operationId: subscribe + responses: {'200': {description: ok}} + callbacks: + resourceEvent: + '{$request.body#/callbackUrl}': + parameters: + - name: cbid + in: query + required: true + schema: + anyOf: [{type: string, format: uuid}, {type: string, pattern: '^[a-z0-9_-]+$'}] + post: {operationId: onResourcePost, responses: {'200': {description: ok}}} + get: {operationId: onResourceGet, responses: {'200': {description: ok}}} + # A component parameter with an anyOf schema, referenced from two different + # paths: its union member types are declared once with the component, so the + # two references do not collide (issue #2090). + /widgets/{wid}: + parameters: [{$ref: '#/components/parameters/WidgetId'}] + get: {operationId: getWidget, responses: {'200': {description: ok}}} + /gadgets/{wid}: + parameters: [{$ref: '#/components/parameters/WidgetId'}] + get: {operationId: getGadget, responses: {'200': {description: ok}}} +components: + parameters: + WidgetId: + name: wid + in: path + required: true + schema: + anyOf: [{type: string, format: uuid}, {type: string, pattern: '^[a-z0-9_-]+$'}] +webhooks: + resourceWebhook: + parameters: + - name: whid + in: query + required: true + schema: + anyOf: [{type: string, format: uuid}, {type: string, pattern: '^[a-z0-9_-]+$'}] + post: {operationId: onWebhookPost, responses: {'200': {description: ok}}} + get: {operationId: onWebhookGet, responses: {'200': {description: ok}}} diff --git a/internal/test/spec_validation/spec_validation_test.go b/internal/test/spec_validation/spec_validation_test.go index 3f5441639..f355d3192 100644 --- a/internal/test/spec_validation/spec_validation_test.go +++ b/internal/test/spec_validation/spec_validation_test.go @@ -31,6 +31,8 @@ func TestValidateSpecRejects(t *testing.T) { "x_go_type_semicolon.yaml": "x-go-type", "ref_sibling_x_go_type.yaml": "x-go-type", "security_scope_quote.yaml": "scope", + + "shared_path_param_type_collision.yaml": "path-level parameter", } for file, want := range cases { diff --git a/internal/test/spec_validation/testdata/shared_path_param_type_collision.yaml b/internal/test/spec_validation/testdata/shared_path_param_type_collision.yaml new file mode 100644 index 000000000..8392c683f --- /dev/null +++ b/internal/test/spec_validation/testdata/shared_path_param_type_collision.yaml @@ -0,0 +1,27 @@ +openapi: "3.0.0" +info: {title: t, version: "1.0.0"} +# Two different path items each declare a path-level anyOf parameter named +# "id". Each path declares the union member types (Id0, Id1) once, so across +# the two paths they are declared twice and collide (issue #2090). ValidateSpec +# should reject this before code generation. +paths: + /a/{id}: + parameters: + - name: id + in: path + required: true + schema: + anyOf: [{type: string, format: uuid}, {type: string, pattern: '^[a-z]+$'}] + get: + operationId: getA + responses: {'200': {description: ok}} + /b/{id}: + parameters: + - name: id + in: path + required: true + schema: + anyOf: [{type: string, format: uuid}, {type: string, pattern: '^[a-z]+$'}] + get: + operationId: getB + responses: {'200': {description: ok}} diff --git a/internal/test/spec_validation/testdata/valid.yaml b/internal/test/spec_validation/testdata/valid.yaml index 8724b4a0d..442a57fcb 100644 --- a/internal/test/spec_validation/testdata/valid.yaml +++ b/internal/test/spec_validation/testdata/valid.yaml @@ -10,6 +10,25 @@ paths: schema: {$ref: '#/components/schemas/Thing'} responses: '200': {description: ok} + # A path-level anyOf parameter shared by several methods on ONE path is + # declared once for the path item, so it is accepted (no collision). + /resource/{id}: + parameters: + - name: id + in: path + required: true + schema: + anyOf: + - {type: string, format: uuid} + - {type: string, pattern: '^[a-z]+$'} + get: + operationId: getResource + responses: + '200': {description: ok} + delete: + operationId: deleteResource + responses: + '200': {description: ok} components: schemas: Thing: diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 90ced55a3..54dee5e99 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -1144,11 +1144,6 @@ func GenerateTypesForParameters(t *template.Template, params map[string]*openapi for _, paramName := range SortedMapKeys(params) { paramOrRef := params[paramName] - goType, err := paramToGoType(paramOrRef.Value, nil) - if err != nil { - return nil, fmt.Errorf("error generating Go type for schema in parameter %s: %w", paramName, err) - } - goTypeName, err := renameParameter(paramName, paramOrRef) if err != nil { return nil, fmt.Errorf("error making name for components/parameters/%s: %w", paramName, err) @@ -1158,6 +1153,16 @@ func GenerateTypesForParameters(t *template.Template, params map[string]*openapi goTypeName = resolved } + // Root the schema path at the parameter's Go type name so any helper + // types the schema needs (e.g. anyOf union members) are named after the + // parameter — GoTypeName0, GoTypeName1 — matching the union accessors + // generated on the parameter type, rather than an unrelated + // numeric-derived name (issue #2090). + goType, err := paramToGoType(paramOrRef.Value, []string{goTypeName}) + if err != nil { + return nil, fmt.Errorf("error generating Go type for schema in parameter %s: %w", paramName, err) + } + typeDef := TypeDefinition{ JsonName: paramName, Schema: goType, @@ -1174,6 +1179,10 @@ func GenerateTypesForParameters(t *template.Template, params map[string]*openapi } types = append(types, typeDef) + // Declare the parameter's helper types (union members, ...) once here, + // with the component, so operations that reference the parameter do not + // each redeclare them (issue #2090). + types = append(types, goType.AdditionalTypes...) } return types, nil } diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 937012ef5..6d4f65d3d 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -36,6 +36,12 @@ type ParameterDefinition struct { Required bool // Is this a required parameter? Spec *openapi3.Parameter Schema Schema + + // Shared is true for a parameter declared at the path-item level, which + // is inherited by every method on the path. Its helper types are + // generated once for the path item rather than once per operation, so + // operation type collection skips them (issue #2090). + Shared bool } // TypeDef is here as an adapter after a large refactoring so that I don't @@ -284,7 +290,14 @@ func DescribeParameters(params openapi3.Parameters, path []string) ([]ParameterD return nil, fmt.Errorf("error dereferencing (%s) for param (%s): %s", paramOrRef.Ref, param.Name, err) } + // The parameter references a component parameter, whose type and + // any helper types (e.g. anyOf union members) are declared once + // with the component. Point at it and drop the inline schema this + // operation would otherwise redeclare per operation (issue #2090). pd.Schema.GoType = goType + pd.Schema.RefType = goType + pd.Schema.AdditionalTypes = nil + pd.Schema.UnionElements = nil } outParams = append(outParams, pd) } @@ -351,11 +364,11 @@ type OperationDefinition struct { // appear in the spec rather than sorted (issue #1887). Zero when the // source location is unavailable (e.g. a programmatically-built spec), // in which case route registration falls back to the default order. - SpecOrder int - Spec *openapi3.Operation - IsAlias bool // True when this path is a $ref alias of another path item - AliasTarget string // When IsAlias is true, this is the OperationId of the canonical operation (for route registration to reference the correct wrapper) - PathItemRef string // The path item's $ref (if any); used to qualify externally-loaded schemas referenced from this operation's responses + SpecOrder int + Spec *openapi3.Operation + IsAlias bool // True when this path is a $ref alias of another path item + AliasTarget string // When IsAlias is true, this is the OperationId of the canonical operation (for route registration to reference the correct wrapper) + PathItemRef string // The path item's $ref (if any); used to qualify externally-loaded schemas referenced from this operation's responses // IsWebhook is true when this OperationDefinition was sourced from // spec.Webhooks (OpenAPI 3.1+). Webhook operations have no path @@ -1105,12 +1118,17 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { // spec declaration order (issue #1887). Zero when unavailable. pathSpecOrder := pathItemSourceLine(pathItem) // These are parameters defined for all methods on a given path. They - // are shared by all methods. + // are shared by all methods, so their helper types are declared once + // for the path item (on its first operation) rather than once per + // operation (issue #2090). globalParams, err := DescribeParameters(pathItem.Parameters, nil) if err != nil { return nil, fmt.Errorf("error describing global parameters for %s: %s", requestPath, err) } + markShared(globalParams) + sharedParamTypeDefs := sharedParameterTypeDefs(globalParams) + sharedTypeDefsEmitted := false // Each path can have a number of operations, POST, GET, OPTIONS, etc. pathOps := pathItem.Operations() @@ -1257,6 +1275,13 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { // Generate all the type definitions needed for this operation opDef.TypeDefinitions = append(opDef.TypeDefinitions, GenerateTypeDefsForOperation(opDef)...) + // Declare the shared (path-item-level) parameter helper types once, + // on the first operation of the path item (issue #2090). + if !sharedTypeDefsEmitted { + opDef.TypeDefinitions = append(opDef.TypeDefinitions, sharedParamTypeDefs...) + sharedTypeDefsEmitted = true + } + operations = append(operations, opDef) } } @@ -1289,11 +1314,15 @@ func WebhookOperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, er } // Path-item-level parameters apply to every method on the - // webhook (rare for webhooks, but honored defensively). + // webhook (rare for webhooks, but honored defensively). Their helper + // types are declared once for the path item (issue #2090). globalParams, err := DescribeParameters(pathItem.Parameters, nil) if err != nil { return nil, fmt.Errorf("error describing webhook %q parameters: %w", webhookName, err) } + markShared(globalParams) + sharedParamTypeDefs := sharedParameterTypeDefs(globalParams) + sharedTypeDefsEmitted := false pathOps := pathItem.Operations() for _, opName := range SortedMapKeys(pathOps) { @@ -1355,6 +1384,10 @@ func WebhookOperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, er } opDef.TypeDefinitions = append(opDef.TypeDefinitions, GenerateTypeDefsForOperation(opDef)...) + if !sharedTypeDefsEmitted { + opDef.TypeDefinitions = append(opDef.TypeDefinitions, sharedParamTypeDefs...) + sharedTypeDefsEmitted = true + } operations = append(operations, opDef) } } @@ -1429,6 +1462,12 @@ func CallbackOperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, e return nil, fmt.Errorf("error describing callback %q parameters: %w", callbackName, err) } + // Shared callback path-item parameters: declare helper + // types once for the path item (issue #2090). + markShared(globalParams) + sharedParamTypeDefs := sharedParameterTypeDefs(globalParams) + sharedTypeDefsEmitted := false + cbOps := cbPathItem.Operations() for _, opName := range SortedMapKeys(cbOps) { op := cbOps[opName] @@ -1486,6 +1525,10 @@ func CallbackOperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, e } opDef.TypeDefinitions = append(opDef.TypeDefinitions, GenerateTypeDefsForOperation(opDef)...) + if !sharedTypeDefsEmitted { + opDef.TypeDefinitions = append(opDef.TypeDefinitions, sharedParamTypeDefs...) + sharedTypeDefsEmitted = true + } operations = append(operations, opDef) } } @@ -1809,6 +1852,26 @@ func GenerateResponseDefinitions(operationID string, responses map[string]*opena return responseDefinitions, nil } +// markShared flags every parameter as shared at the path-item level, so its +// helper types are declared once for the path item rather than once per +// operation (issue #2090). +func markShared(params []ParameterDefinition) { + for i := range params { + params[i].Shared = true + } +} + +// sharedParameterTypeDefs returns the helper TypeDefinitions produced by +// path-item-level parameters. These are emitted once for the path item +// (attributed to its first operation) instead of once per operation. +func sharedParameterTypeDefs(sharedParams []ParameterDefinition) []TypeDefinition { + var typeDefs []TypeDefinition + for _, param := range sharedParams { + typeDefs = append(typeDefs, param.Schema.AdditionalTypes...) + } + return typeDefs +} + func GenerateTypeDefsForOperation(op OperationDefinition) []TypeDefinition { var typeDefs []TypeDefinition // Start with the params object itself @@ -1816,8 +1879,14 @@ func GenerateTypeDefsForOperation(op OperationDefinition) []TypeDefinition { typeDefs = append(typeDefs, GenerateParamsTypes(op)...) } - // Now, go through all the additional types we need to declare. + // Now, go through all the additional types we need to declare. Skip + // parameters shared at the path-item level: their helper types are + // declared once for the path item (see sharedParameterTypeDefs), not once + // per operation, which would redeclare them (issue #2090). for _, param := range op.AllParams() { + if param.Shared { + continue + } typeDefs = append(typeDefs, param.Schema.AdditionalTypes...) } diff --git a/pkg/codegen/validate_spec.go b/pkg/codegen/validate_spec.go index d7f31e46c..ff9e6bf44 100644 --- a/pkg/codegen/validate_spec.go +++ b/pkg/codegen/validate_spec.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "go/token" + "slices" + "strings" "unicode" "github.com/getkin/kin-openapi/openapi3" @@ -30,13 +32,101 @@ func ValidateSpec(spec *openapi3.T) error { if spec == nil { return nil } - v := &specValidator{} + v := &specValidator{ + pathParamTypeDecls: map[string]int{}, + pathParamTypeWhere: map[string][]string{}, + } v.walkDocument(spec) + v.checkSharedPathParamTypeCollisions() return errors.Join(v.errs...) } type specValidator struct { errs []error + + // A path-item-level parameter whose inline schema needs a helper Go type + // (an anyOf/oneOf union, an enum, or an inline object) has that type + // declared once for the path item under a name derived only from the + // parameter. When two different path items declare a parameter of the + // same name, both emit the same type name and Go rejects the duplicate + // (issue #2090). These maps count, per generated type name, how many path + // items declare it and where, so checkSharedPathParamTypeCollisions can + // turn that into a clear pre-generation error. + pathParamTypeDecls map[string]int + pathParamTypeWhere map[string][]string +} + +// recordPathLevelParams accounts for the helper types generated by a path +// item's shared (path-level) parameters. Each type is declared once for the +// path item, so a name declared by more than one path item collides. +func (v *specValidator) recordPathLevelParams(item *openapi3.PathItem, where string) { + if item == nil || len(item.Operations()) == 0 { + return + } + for _, p := range item.Parameters { + name, ok := generatedParamTypeName(p) + if !ok { + continue + } + v.pathParamTypeDecls[name]++ + v.pathParamTypeWhere[name] = append(v.pathParamTypeWhere[name], where) + } +} + +// checkSharedPathParamTypeCollisions reports a generated helper type name that +// is produced by a path-level parameter on more than one path item, which +// would declare the same type twice. +func (v *specValidator) checkSharedPathParamTypeCollisions() { + for _, name := range SortedMapKeys(v.pathParamTypeDecls) { + if v.pathParamTypeDecls[name] <= 1 { + continue + } + wheres := dedupeStrings(v.pathParamTypeWhere[name]) + v.addf("the Go type %q is generated for a path-level parameter declared on more than one path (%s), so it would be declared more than once and the output will not compile; define the parameter once under #/components/parameters and $ref it from each path, give it a distinct name with the x-go-type-name extension or a spec overlay, or move it into each operation", + name, strings.Join(wheres, ", ")) + } +} + +// generatedParamTypeName returns the base Go type name a parameter's inline +// schema would generate, and whether it generates a named helper type that can +// collide across path items. An inline anyOf/oneOf produces union member types +// named from the parameter alone (Id0, Id1, ...); plain scalars, enums and +// objects used in a parameter generate no such shared named type, and a $ref +// schema references an already-declared one. +func generatedParamTypeName(ref *openapi3.ParameterRef) (string, bool) { + if ref == nil || ref.Ref != "" || ref.Value == nil { + return "", false + } + sr := ref.Value.Schema + if sr == nil || sr.Ref != "" || sr.Value == nil { + return "", false + } + s := sr.Value + if len(s.AnyOf) == 0 && len(s.OneOf) == 0 { + return "", false + } + // An x-go-type-name (on the schema) overrides the generated name, so + // collisions are keyed by that instead of the parameter name. + if raw, ok := s.Extensions[extGoTypeName]; ok { + if n, err := extTypeName(raw); err == nil { + return n, true + } + } + return SchemaNameToTypeName(ref.Value.Name), true +} + +func dedupeStrings(items []string) []string { + seen := map[string]struct{}{} + var uniq []string + for _, it := range items { + if _, ok := seen[it]; ok { + continue + } + seen[it] = struct{}{} + uniq = append(uniq, it) + } + slices.Sort(uniq) + return uniq } func (v *specValidator) addf(format string, args ...any) { @@ -230,6 +320,7 @@ func (v *specValidator) walkPathItem(item *openapi3.PathItem, where string) { if item == nil { return } + v.recordPathLevelParams(item, where) for _, p := range item.Parameters { v.walkParameterRef(p, where) }