Skip to content

Unable to create a wrapper func that also requires the *http.Request object #811

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

Open
sinloss opened this issue Apr 30, 2025 · 3 comments · May be fixed by #813
Open

Unable to create a wrapper func that also requires the *http.Request object #811

sinloss opened this issue Apr 30, 2025 · 3 comments · May be fixed by #813

Comments

@sinloss
Copy link

sinloss commented Apr 30, 2025

Hi there, I'm trying to create a resuable wrapper function that can do some common logic against the *http.Request. Below is my attempt:

// RequestAware will try to capture the http.Request from the huma.Context by implementing the huma.Resolver interface
type RequestAware struct {
	*http.Request
}

func (i *RequestAware) Resolve(ctx huma.Context) []error {
	i.Request, _ = humago.Unwrap(ctx)
	return nil
}

// Here I'm struggling to get the Input value
type Blended[I any] struct {
	RequestAware
	Input I
}

// The wrapper function
func Wrapper[I, O any](
	handler func(context.Context, *I) (*O, error),
) func(context.Context, *Blended[I]) (*O, error) {
	return func(ctx context.Context, t *Blended[I]) (*O, error) {
		doSomethingWithRequest(t.Request)
		return handler(ctx, &t.Input)
	}
}

In the above example, I'm struggling to get the Input value. The type [I] in my first registered handler, is like this:

struct {
    Body struct {
        V int `json:"v"`
    }
}

I can however successfully capture the *http.Request. Is it because that the I is not embedded in the Blended struct ? But there's no way to embed a generic type parameter currently AFAIK.
Can someone please help me, guide me out of this storm, please! Thank you beforehand!

@sinloss
Copy link
Author

sinloss commented May 2, 2025

I've noticed that slices and maps will be recursively processed (

huma/huma.go

Lines 442 to 447 in eb497ee

if f.Anonymous || recurseFields || deref(f.Type).Kind() != reflect.Struct {
// Always process embedded structs and named fields which are not
// structs. If `recurseFields` is true then we also process named
// struct fields recursively.
visited[t] = struct{}{}
_findInType(f.Type, fi, result, onType, onField, recurseFields, visited, ignore...)
and

huma/huma.go

Lines 451 to 454 in eb497ee

case reflect.Slice:
_findInType(t.Elem(), path, result, onType, onField, recurseFields, visited, ignore...)
case reflect.Map:
_findInType(t.Elem(), path, result, onType, onField, recurseFields, visited, ignore...)
), so I modified the Blended type to this:

type Blended[P, B any] struct {
	RequestAware
	Params []P
	Body   B
}

This approach can capture the Body and can generate proper documentations about the headers, querys and other params though, it can not successfully set the demanded params.
After some investigation, the reason I've found seems to be this:

huma/huma.go

Lines 292 to 299 in eb497ee

case reflect.Slice:
for j := 0; j < current.Len(); j++ {
r.every(current.Index(j), path, v, f)
}
case reflect.Map:
for _, k := range current.MapKeys() {
r.every(current.MapIndex(k), path, v, f)
}

To set the slice or the map, it requires the current slice/map value to be initialized with proper sizes, which is not likely to serve the toplevel type to contain further param definitions, as per:

huma/huma.go

Lines 668 to 684 in eb497ee

var input I
// Get the validation dependencies from the shared pool.
deps := validatePool.Get().(*validateDeps)
defer func() {
deps.pb.Reset()
deps.res.Reset()
validatePool.Put(deps)
}()
pb := deps.pb
res := deps.res
errStatus := http.StatusUnprocessableEntity
var cookies map[string]*http.Cookie
v := reflect.ValueOf(&input).Elem()
The toplevel value is create via

huma/huma.go

Line 684 in eb497ee

v := reflect.ValueOf(&input).Elem()

It seems to be a dead end here.

Which got me thinking that if there's a tag to instruct the _findInType function to dive deeper in

huma/huma.go

Lines 442 to 448 in eb497ee

if f.Anonymous || recurseFields || deref(f.Type).Kind() != reflect.Struct {
// Always process embedded structs and named fields which are not
// structs. If `recurseFields` is true then we also process named
// struct fields recursively.
visited[t] = struct{}{}
_findInType(f.Type, fi, result, onType, onField, recurseFields, visited, ignore...)
delete(visited, t)
like, for example, `recursive:"true"`, could this problem be happily solved?
And this will allow arbitary composing of input structures with generic types... while not changing any existing behaviours.

@sinloss sinloss linked a pull request May 3, 2025 that will close this issue
@sinloss sinloss changed the title How to create a wrapper func that also requires the *http.Request object? Unable to create a wrapper func that also requires the *http.Request object May 11, 2025
@sinloss
Copy link
Author

sinloss commented May 11, 2025

With the change proposed by #813 , I can modify the Blended type to this:

type Blended[P, B any] struct {
	RequestAware
	Params P `recursive:"true"`
	Body   B
}

And it works as expected.

@sinloss
Copy link
Author

sinloss commented May 19, 2025

Kindly ping.

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

Successfully merging a pull request may close this issue.

1 participant