diff --git a/registry.go b/registry.go index 87ed262d..813a3de6 100644 --- a/registry.go +++ b/registry.go @@ -4,9 +4,14 @@ import ( "encoding/json" "fmt" "reflect" + "regexp" "strings" ) +// reGenericName helps to convert `MyType[path/to.SubType]` to `MyTypeSubType` +// when using the default schema namer. +var reGenericName = regexp.MustCompile(`\[[^\]]+\]`) + // Registry creates and stores schemas and their references, and supports // marshalling to JSON/YAML for use as an OpenAPI #/components/schemas object. // Behavior is implementation-dependent, but the design allows for recursive @@ -30,6 +35,12 @@ func DefaultSchemaNamer(t reflect.Type, hint string) string { name := deref(t).Name() // Fix up generics, if used, for nicer refs & URLs. + name = reGenericName.ReplaceAllStringFunc(name, func(s string) string { + // Convert `MyType[path/to.SubType]` to `MyType[SubType]`. + parts := strings.Split(s, ".") + return parts[len(parts)-1] + }) + // Remove square brackets. name = strings.ReplaceAll(name, "[", "") name = strings.ReplaceAll(name, "]", "") diff --git a/schema_test.go b/schema_test.go index e88e7ea5..2e6b1128 100644 --- a/schema_test.go +++ b/schema_test.go @@ -505,6 +505,20 @@ func TestSchemaGenericNaming(t *testing.T) { }`, string(b)) } +func TestSchemaGenericNamingFromModule(t *testing.T) { + type SchemaGeneric[T any] struct { + Value T `json:"value"` + } + + r := huma.NewMapRegistry("#/components/schemas/", huma.DefaultSchemaNamer) + s := r.Schema(reflect.TypeOf(SchemaGeneric[time.Time]{}), true, "") + + b, _ := json.Marshal(s) + assert.JSONEq(t, `{ + "$ref": "#/components/schemas/SchemaGenericTime" + }`, string(b)) +} + type OmittableNullable[T any] struct { //nolint: musttag Sent bool Null bool