Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Primitive-based types are not reused in the generated OAS. #560

Open
superstas opened this issue Aug 26, 2024 · 3 comments
Open

Primitive-based types are not reused in the generated OAS. #560

superstas opened this issue Aug 26, 2024 · 3 comments
Labels
question Further information is requested

Comments

@superstas
Copy link
Contributor

Hey there!

I've got the following types

var _ huma.SchemaTransformer = CustomHeader("")

type CustomHeader string

func (h CustomHeader) TransformSchema(r huma.Registry, s *huma.Schema) *huma.Schema {
	s.Type = "string"
	s.Description = "Custom header Description. The CustomHeader schema should be reused across all endpoints that use it."
	return s
}

type CreateUser struct {
	Body struct {
		Name string `json:"name" doc:"The name."`
	}

	CustomHeader CustomHeader `header:"X-Custom-Header"`
}

type UpdateUser struct {
	Body struct {
		Name string `json:"name" doc:"The name."`
	}

	CustomHeader CustomHeader `header:"X-Custom-Header"`
}

type Output struct {
	Body struct {
		Message string `json:"message" doc:"The message."`
	}
}

With the following routes

	huma.Post(api, "/user", func(ctx context.Context, req *CreateUser) (*Output, error) {
		resp := &Output{}
		resp.Body.Message = "It works!"
		return resp, nil
	})

	huma.Patch(api, "/user/{id}", func(ctx context.Context, req *UpdateUser) (*Output, error) {
		resp := &Output{}
		resp.Body.Message = "It works!"
		return resp, nil
	}}

I expect that CustomHeader would be re-used via $ref in the generated OAS.

Instead the CustomHeader schema is embedded into all the endpoints

paths:
  /user:
    post:
      operationId: post-user
      parameters:
        - description: Custom header Description. The CustomHeader schema should be reused across all endpoints that use it.
          in: header
          name: X-Custom-Header
          schema:
            description: Custom header Description. The CustomHeader schema should be reused across all endpoints that use it.
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateUserBody"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OutputBody"
          description: OK
        default:
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorModel"
          description: Error
      summary: Post user
  /user/{id}:
    patch:
      operationId: patch-user-by-id
      parameters:
        - description: Custom header Description. The CustomHeader schema should be reused across all endpoints that use it.
          in: header
          name: X-Custom-Header
          schema:
            description: Custom header Description. The CustomHeader schema should be reused across all endpoints that use it.
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateUserBody"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OutputBody"
          description: OK
        default:
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ErrorModel"
          description: Error
      summary: Patch user by ID

Can an operation be registered to force Huma to reuse shared schemas?

Thank you.

@superstas
Copy link
Contributor Author

I also tried to reuse this schema via registry, but no luck

	router := chi.NewMux()
	cfg := huma.DefaultConfig("My API", "1.0.0")
	api := humachi.New(router, cfg)

	customHeaderSchema := cfg.Components.Schemas.Schema(reflect.TypeOf(CustomHeader("")), true, "")
	huma.Register(api, huma.Operation{
		OperationID: "UserCreate",
		Method:      http.MethodPost,
		Path:        "/users",
		Parameters: []*huma.Param{
			{
				// Omitting the header fields.
				Ref: customHeaderSchema.Ref,
			},
		},
		Tags: []string{"users"},
	}, func(ctx context.Context, req *CreateUser) (*Output, error) {
		resp := &Output{}
		resp.Body.Message = "It works!"
		return resp, nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "UserUpdate",
		Method:      http.MethodPatch,
		Path:        "/users/{id}",
		Parameters: []*huma.Param{
			{
				// Omitting the header fields.
				Ref: customHeaderSchema.Ref,
			},
		},
		Tags: []string{"users"},
	}, func(ctx context.Context, req *UpdateUser) (*Output, error) {
		resp := &Output{}
		resp.Body.Message = "It works!"
		return resp, nil
	})

@danielgtaylor danielgtaylor added the question Further information is requested label Sep 13, 2024
@danielgtaylor
Copy link
Owner

@superstas you are correct that this is currently not supported. All primitive types will be defined inline in the schema, which is done to keep from having every type get its own $ref. In the future we may want to let you opt-in to using a ref, but there's no code for that at the moment. I'm open to ideas & a PR on this if the feature is important to you.

@superstas
Copy link
Contributor Author

@danielgtaylor, thank you for the clarification.

Yes, it is important to us to simplify our specs as much as possible by using $ref.
I'll look deeper at the codebase and the possibilities of extending this logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants