huma
huma copied to clipboard
#560: Add reusing of primitive-based types in the generated OAS.
Hey @danielgtaylor
This PR proposes a solution for the issue #560.
We tested this change internally, and it decreased the size of our generated OAS by ~22%.
Conversely, this change may look backward-incompatible ( inlined types are being replaced by $ref ), so I implemented it as the registry option.
Could you please look and share your thoughts on what could be improved or what I overlooked?
Thank you.
Codecov Report
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 93.02%. Comparing base (
6f2a42b) to head (233a02e).
Additional details and impacted files
@@ Coverage Diff @@
## main #758 +/- ##
==========================================
+ Coverage 93.00% 93.02% +0.01%
==========================================
Files 23 23
Lines 5250 5265 +15
==========================================
+ Hits 4883 4898 +15
Misses 315 315
Partials 52 52
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
🚀 New features to boost your workflow:
- ❄ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
@superstas thanks this is a nice approach! I also think using a field tag could work to determine when to use a ref vs. when not to (important for refactoring without breaking clients potentially) instead of having it be all-or-nothing at the registry level.
One scenario that seems problematic though is this:
type MyCustomType int
type ResponseBody struct {
Field1 MyCustomType `json:"field1" enum="1,2,3"`
Field2 MyCustomType `json:"field2" minimum:"5" multipleOf="5"`
}
How would this get represented in the JSON schema if you are creating refs?
@danielgtaylor, an approach with a field tag looks good; I'll try implementing it in a separate commit.
As for your question:
How would this get represented in the JSON schema if you are creating refs?
Well, according to the spec (ref), there can only be the summary and description fields. A situation like this is a conflict that should be shown to the user in some way.
Possible solutions:
- Use
$refonly for the very 1st field and keep the rest untouched - Keep all the conflict cases untouched
WDYT?
@superstas I'm not sure how to solve this honestly. Maybe if there is additional validation on the struct field via tags then you just cannot use a $ref and must generate it inline. So maybe a $ref is only generated when:
- You use a named type
- There is no additional validation (
docshould be okay, maybeexampleis okay?)
So if you want to use validation your custom primitive type needs to implement huma.SchemaProvider or huma.SchemaTransformer and your struct fields shouldn't add any validation.
type MyEnum string
var _ huma.SchemaTransformer = (*MyEnum)(nil)
func (e MyEnum) TransformSchema(r huma.Registry, s *huma.Schema) *huma.Schema {
s.Enum = []any{"one", "two", "three"}
s.Description = "This is a description of the enum"
return s
}
// Then later use it:
type MyInput struct {
Body struct {
Value MyEnum `json:"value"`
}
}
Would generate something like:
components:
schemas:
MyEnum:
type: string
enum: ["one", "two", "three"]
description: "This is a description of the enum"
MyInputBody:
type: object
properties:
value:
$ref: "#/components/schemas/MyEnum