Recursive components are not pruned correctly
Les mainteneurs répondent en général sous 1 jour
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 3/5
- Temps estimé
- 1-2 jours
- Accessibilité débutants
- 50/100
Piste de recherche
The issue is in the pruning logic for unused schemas. Look at the code that traverses and prunes components, likely in the generator's schema resolution or code generation phase. The test case is a recursive type Bar that references itself. Start by understanding how the generator tracks used schemas and why it fails to prune a recursive type that is not referenced by any included operation. Run the provided spec.yaml through the generator to see the problem, then examine the pruning algorithm.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Hello !
Summary
When generating a client for a huge API I did not own, I noticed that there were a lot of unused types in my generated clients.
Looking into the spec that caused this issues, it seems that the pruning does not detect whether recursive types are used or not.
And I cannot exclude them explicitly since the generator think they are used.
How to reproduce
I want to generate a client for the following file, but without the operation bar_get.
I run oapi-codegen -generate "types,client" -include-operation-ids=foo_get -package test -o ./test.gen.go ./spec.yaml on the following file :
# spec.yaml
openapi: 3.0.0
paths:
/api/foo:
get:
operationId: foo_get
responses:
"200": {}
/api/bar:
get:
operationId: bar_get
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Bar"
components:
schemas:
Bar:
properties:
recursion:
$ref: "#/components/schemas/Bar"
The generator correctly don't generate functions for the operation bar_get, but still generates types for the component `bar :
// Bar defines model for Bar.
type Bar struct {
Recursion *Bar `json:"recursion,omitempty"`
}
// The rest of the client is generated as expected...
Full generated file
// Package test provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
package test
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
// Bar defines model for Bar.
type Bar struct {
Recursion *Bar `json:"recursion,omitempty"`
}
// 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 {
// FooGet request
FooGet(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
}
func (c *Client) FooGet(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewFooGetRequest(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)
}
// NewFooGetRequest generates requests for FooGet
func NewFooGetRequest(server string) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/api/foo")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", 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 {
// FooGetWithResponse request
FooGetWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*FooGetResponse, error)
}
type FooGetResponse struct {
Body []byte
HTTPResponse *http.Response
}
// Status returns HTTPResponse.Status
func (r FooGetResponse) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r FooGetResponse) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
// FooGetWithResponse request returning *FooGetResponse
func (c *ClientWithResponses) FooGetWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*FooGetResponse, error) {
rsp, err := c.FooGet(ctx, reqEditors...)
if err != nil {
return nil, err
}
return ParseFooGetResponse(rsp)
}
// ParseFooGetResponse parses an HTTP response from a FooGetWithResponse call
func ParseFooGetResponse(rsp *http.Response) (*FooGetResponse, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &FooGetResponse{
Body: bodyBytes,
HTTPResponse: rsp,
}
return response, nil
}
If I break the recursion by replacing the ref with type: object the generator correctly detect it should prune bar.
- Langage dominant
- Go
- Étoiles
- 8.6k
- Forks
- 1.1k
- Merge moyen
- 14 h 45 min
- PR mergées (30 j)
- 29
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Aucun modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de oapi-codegen/oapi-codegen
-
v2.8.0: identical schemas with the same x-go-type-name now fail with "duplicate typename"Peut-être pris @eyoshioka l’a pris il y a 2 jours. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 84/100
oapi-codegen/oapi-codegen#2599 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
Render `x-stability-level` in GodocPeut-être pris @Anandb71 l’a pris il y a 4 jours. Ouverteclient
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
oapi-codegen/oapi-codegen#2483 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 65/100
oapi-codegen/oapi-codegen#2408 ·
Les mainteneurs répondent en général sous 1 jour
-
Use constants from net/http for the status codesPeut-être pris @mromaszewicz l’a pris il y a 126 jours. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
oapi-codegen/oapi-codegen#2397 · 3 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
documentation
Difficulté 2/5 1-3 heures Accessibilité débutants 65/100
oapi-codegen/oapi-codegen#2168 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de oapi-codegen/oapi-codegen
Issues similaires
-
bug
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
LanternOps/breeze#8353 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
prime-radiant-inc/evener#4223 ·
Les mainteneurs répondent en général sous 1 jour
-
bug
Difficulté 2/5 1-3 heures Accessibilité débutants 74/100
Les mainteneurs répondent en général sous 1 jour
-
input.Scanner.Scan loops forever when the input reaches EOF, hanging every ConfirmAction promptPeut-être pris @mlliarm l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 80/100
elastic/cloud-sdk-go#521 ·
-
bug
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
open-telemetry/opentelemetry-go-compile-instrumentation#1467 ·
Les mainteneurs répondent en général sous 3 jours