huma icon indicating copy to clipboard operation
huma copied to clipboard

Nested header on input

Open victoraugustolls opened this issue 11 months ago • 13 comments

I see that it is a common pattern with Huma that the body, both in the input and output, is to be created inside the nested struct field Body. Is it possible to do the same with headers and path/query parameters? Something like:

type MyInput struct {
    Headers struct {
        UserIP string `header:"user-ip"`
    }
    Body struct {
        Name string `json:"name"`
    }
}

victoraugustolls avatar Mar 12 '24 16:03 victoraugustolls

@victoraugustolls this isn't currently possible. They are just added at the top level, like:

type MyInput struct {
    UserIP  string `header:"User-Ip"`
    Header2 string `header:"Header2"`
    Header3 int    `header:"Header3"`
    Body struct {
        Name string `json:"name"`
    }
}

Any reason you need additional grouping?

danielgtaylor avatar Mar 12 '24 16:03 danielgtaylor

I was thinking about header reuse, which allows structures to be reused on inputs. Do you think it makes sense?

victoraugustolls avatar Mar 12 '24 18:03 victoraugustolls

I really like the grouping idea myself. It follows your established pattern to use Body. If we are upvoting..I'd love this as a feature.

bclements avatar Mar 12 '24 18:03 bclements

@victoraugustolls you can use composition in you case, like:

type MyInputHeaders struct {
    UserIP  string `header:"User-Ip"`
    Header2 string `header:"Header2"`
    Header3 int    `header:"Header3"`
}
type MyInput struct {
   MyInputHeaders
   Body struct {
        Name string `json:"name"`
   }
}

It's a not perfect solution, but I already use this method for each paginated lists in my APIs and it's work well

Insei avatar Mar 12 '24 19:03 Insei

I now we can it just not feels that follows a pattern as with the body like @bclements mentioned!

One example of how this can help is when we are accessing the values of the input. Imagine a case where I have everything: headers, body, path/query parameters...

In such a case I know I am using a body variable because I access it with input.Body.Value, but this is not true for the rest! In this scenario the user needs to look at the input in the case that it does not know by heart.

victoraugustolls avatar Mar 12 '24 19:03 victoraugustolls

If @danielgtaylor agrees with it, I can open a PR implementing this for other structures other than the body!

victoraugustolls avatar Mar 12 '24 20:03 victoraugustolls

I'm not opposed to the idea; seems several people are interested in it as a code organization strategy. It'll likely be slightly less efficient as reflection is used to set the fields and you have to go into the Headers struct but it's a tiny difference. As long as it is optional this seems like it would be a nice feature to have.

danielgtaylor avatar Mar 13 '24 02:03 danielgtaylor

@danielgtaylor I'm working on a pull request for it, but I might only be able to finish it next week!

victoraugustolls avatar Mar 14 '24 14:03 victoraugustolls

I'd actually like to make Body optional, and I think it might make sense to make both Body and Headers optional. I don't think it'd be too confusing to intuit what each parameter does from the tags, eg all header are headers, json are clearly body, (path and query things, right?), and anything left over can probably be assumed to be body.

It shouldn't make much difference to the reflection. If Headers is defined, look there for headers, otherwise look at the top level. If Body is defined, look there for body fields, otherwise look at the top level.

ssoroka avatar Apr 15 '24 01:04 ssoroka

@danielgtaylor given @ssoroka comment, I also feel like that for every possible part of the request we could have it in the root of the struct or in special fields like Body, Header, Query and Param. Do we need a discussion for this or would you be okay with this proposal?

victoraugustolls avatar Apr 17 '24 12:04 victoraugustolls

@victoraugustolls @ssoroka I'm not sure this will work for the body. In a scenario like this the JSON unmarshal call would not know what to do:

type ExampleRequest struct {
  Part1 struct {
    Field1 string `json:"field1"`
  }
  Part2 struct {
    Field2 string `json:"field2"`
  }
}

There's also the complication that fields without a json tag are still unmarshaled, and we need to support other formats like CBOR/YAML/etc.

danielgtaylor avatar Apr 17 '24 16:04 danielgtaylor

@danielgtaylor fair enough, did not think it through before tagging you! Thanks for the quick response, will work on the previous approach. I had some rough recent couple of weeks at works but I think I can take the time now :)

victoraugustolls avatar Apr 19 '24 01:04 victoraugustolls

I'm currently investigating the best approach to set nested fields inside the input using reflection. I tried to modify the least I could in the existing code, but that makes me create a lot of turnarounds, so some refactor in existing code might be needed to implement this in a cleaner manner.

This is the part of the code that @danielgtaylor mentioned in #393 so I'm handling it with care!

victoraugustolls avatar May 15 '24 16:05 victoraugustolls