-
-
Notifications
You must be signed in to change notification settings - Fork 248
Description
First off, I love this project! Thank you for all of the incredible work you do.
I really enjoy just being able to define request inputs and outputs via struct -- but it seems that currently the only way to use oneOf, anyOf, allOf, or not are by passing structs to huma.Operation.RequestBody or huma.Operation.Response. I think it would be useful to have struct tags to more conveniently define this.
For example, here's how I'm currently building oneOf tagged union requests (minus the oneOf struct tag). This works well enough, but would love to have a way to do achieve this via struct tags for the input and output structs. I'd love to hear other ideas and feedback, or if there's already an existing way to do this I'm just not aware of.
// Request body DTO
type Example struct {
Type string `json:"type" enum:"foo,bar,baz"`
RawOpts json.RawMessage `json:"opts"`
Opts struct {
Foo Foo
Bar Bar
Baz Baz
} `json:"-" oneOf:"opts"` // oneOf tag value refers to json tag value above
// could be used to generate what would otherwise be defined manually in huma.Schema
}
// It might be more appropriate to do this with huma.Resolve? but this works too
// I chose this approach because the same could potentially be done for
// response bodies via MarshalJSON
func (x *Example) UnmarshalJSON(b []byte) error {
type alias Example
tmp := &struct{ *alias }{alias: (*alias)(x)}
if err := json.Unmarshal(b, &tmp); err != nil {
return err
}
*x = Example(*tmp.alias)
var err error
switch x.Type {
case "foo":
err = json.Unmarshal(x.RawOpts, &x.Opts.Foo)
case "bar":
err = json.Unmarshal(x.RawOpts, &x.Opts.Bar)
case "baz":
err = json.Unmarshal(x.RawOpts, &x.Opts.Baz)
default:
err = errors.New("invalid example type")
}
return err
}
// From what I can tell, all of the code below can be derived from the
// input struct body via tags and reflection
func (h *ApiHandler) RegisterExample(api huma.API) {
registry := api.OpenAPI().Components.Schemas
optsSchema := &huma.Schema{
OneOf: []*huma.Schema{
registry.Schema(reflect.TypeFor[Foo](), true, ""),
registry.Schema(reflect.TypeFor[Bar](), true, ""),
registry.Schema(reflect.TypeFor[Baz](), true, ""),
},
Nullable: false,
}
exampleSchema := registry.SchemaFromRef(registry.Schema(reflect.TypeFor[Example](), true, "").Ref)
exampleSchema.Properties["opts"] = optsSchema
exampleSchema.PrecomputeMessages()
exampleRequestBody := &huma.RequestBody{
Required: true,
Content: map[string]*huma.MediaType{"application/json": {
Schema: exampleSchema,
}},
}
// huma.Register ...
}It would be great to hear your thoughts on this. Thanks again!