这是indexloc提供的服务,不要输入任何密码
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions adapters/humamux/humagmux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,63 @@ func BenchmarkHumaGorillaMux(b *testing.B) {
}
}
}

func TestOperationWithoutIDAndInlineRequestTypeDefinitionNotPanics(t *testing.T) {
r := mux.NewRouter()
api := New(r, huma.DefaultConfig("Test", "1.0.0"))

var (
op1 = huma.Operation{Method: http.MethodGet, Path: "/1"} // no OperationID
op2 = huma.Operation{Method: http.MethodGet, Path: "/2"} // no OperationID
)

huma.Register(api, op1, func(ctx context.Context, i *struct { // inline request input type definition
Body struct {
Test1 string // field name varying
}
},
) (*struct{}, error,
) {
return nil, nil
})

huma.Register(api, op2, func(ctx context.Context, i *struct { // inline request input type definition
Body struct {
Test2 string // field name varying
}
},
) (*struct{}, error,
) {
return nil, nil
})
}

func TestOperationWithoutIDAndInlineResponseTypeDefinitionNotPanics(t *testing.T) {
r := mux.NewRouter()
api := New(r, huma.DefaultConfig("", ""))

var (
op1 = huma.Operation{Method: http.MethodGet, Path: "/1"} // no OperationID
op2 = huma.Operation{Method: http.MethodGet, Path: "/2"} // no OperationID
)

huma.Register(api, op1, func(ctx context.Context, i *struct{},
) (*struct { // inline response output type definition
Body struct {
Test1 string // field name varying
}
}, error,
) {
return nil, nil
})

huma.Register(api, op2, func(ctx context.Context, i *struct{},
) (*struct { // inline response output type definition
Body struct {
Test2 string // field name varying
}
}, error,
) {
return nil, nil
})
}
34 changes: 27 additions & 7 deletions huma.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ import (

var errDeadlineUnsupported = fmt.Errorf("%w", http.ErrNotSupported)

var bodyCallbackType = reflect.TypeOf(func(Context) {})
var cookieType = reflect.TypeOf((*http.Cookie)(nil)).Elem()
var fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
var stringType = reflect.TypeOf("")
var (
bodyCallbackType = reflect.TypeOf(func(Context) {})
cookieType = reflect.TypeOf((*http.Cookie)(nil)).Elem()
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
stringType = reflect.TypeOf("")
)

// SetReadDeadline is a utility to set the read deadline on a response writer,
// if possible. If not, it will not incur any allocations (unlike the stdlib
Expand Down Expand Up @@ -714,7 +716,7 @@ func Register[I, O any](api API, op Operation, handler func(context.Context, *I)
}
}

var receiver = f
receiver := f
if f.Addr().Type().Implements(reflect.TypeFor[ParamWrapper]()) {
receiver = f.Addr().Interface().(ParamWrapper).Receiver()
}
Expand Down Expand Up @@ -1221,7 +1223,7 @@ func setRequestBodyFromBody(op *Operation, registry Registry, fBody reflect.Stru
op.RequestBody.Content[contentType] = &MediaType{}
}
if op.RequestBody.Content[contentType].Schema == nil {
hint := getHint(inputType, fBody.Name, op.OperationID+"Request")
hint := getHint(inputType, fBody.Name, getDefaultHint(op.OperationID, registry, inputType, "Request"))
if nameHint := fBody.Tag.Get("nameHint"); nameHint != "" {
hint = nameHint
}
Expand All @@ -1230,6 +1232,24 @@ func setRequestBodyFromBody(op *Operation, registry Registry, fBody reflect.Stru
}
}

// getDefaultHint checks if the hint already exists in the registry and returns a new hint if it does.
//
// It suffixes the hint with an increasing number if it already exists, starting from 1.
//
// However, the operationID takes precedence if it is not empty.
func getDefaultHint(operationID string, registry Registry, inputType reflect.Type, defaultPrefix string) string {
if operationID != "" {
return operationID + defaultPrefix
}

defaultHint := defaultPrefix
for i := 1; registry.NameExistsInSchema(inputType, defaultHint); i++ {
defaultHint = defaultPrefix + strconv.Itoa(i)
}

return defaultHint
}

type rawBodyType int

const (
Expand Down Expand Up @@ -1359,7 +1379,7 @@ func processOutputType(outputType reflect.Type, op *Operation, registry Registry
op.Responses[statusStr].Headers = map[string]*Param{}
}
if !outBodyFunc {
hint := getHint(outputType, f.Name, op.OperationID+"Response")
hint := getHint(outputType, f.Name, getDefaultHint(op.OperationID, registry, outputType, "Response"))
if nameHint := f.Tag.Get("nameHint"); nameHint != "" {
hint = nameHint
}
Expand Down
6 changes: 6 additions & 0 deletions registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
// schemas to exist while being flexible enough to support other use cases
// like only inline objects (no refs) or always using refs for structs.
type Registry interface {
NameExistsInSchema(t reflect.Type, hint string) bool
Schema(t reflect.Type, allowRef bool, hint string) *Schema
SchemaFromRef(ref string) *Schema
TypeFromRef(ref string) reflect.Type
Expand Down Expand Up @@ -160,6 +161,11 @@ func (r *mapRegistry) RegisterTypeAlias(t reflect.Type, alias reflect.Type) {
r.aliases[t] = alias
}

func (r *mapRegistry) NameExistsInSchema(t reflect.Type, hint string) bool {
_, ok := r.schemas[r.namer(t, hint)]
return ok
}

// NewMapRegistry creates a new registry that stores schemas in a map and
// returns references to them using the given prefix.
func NewMapRegistry(prefix string, namer func(t reflect.Type, hint string) string) Registry {
Expand Down