Skip to content
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

Allow anonymous fields to be used as request input parameters #617

Open
lsdch opened this issue Oct 18, 2024 · 2 comments
Open

Allow anonymous fields to be used as request input parameters #617

lsdch opened this issue Oct 18, 2024 · 2 comments

Comments

@lsdch
Copy link
Contributor

lsdch commented Oct 18, 2024

Hi! Would you consider adding support for anonymous fields in request input parameters ?

My use case is the implementation of generic operation handlers such as :

type InputUUID UUID

func (i InputUUID) Identifier() UUID {
	return i
}

func GetHandler[
	OperationInput GetInputInterface[Item, ID],
	Item any,
	ID any,
](
	find models.ItemFinder[ID, Item],
) func(context.Context, OperationInput) (*GetHandlerOutput[Item], error) {
  return func(ctx context.Context, input OperationInput) (*GetHandlerOutput[Item], error) {
      item, err := find(input.DB(), input.Identifier())
      return &GetHandlerOutput[Item]{Body: item}, err
  }
}

router.Register(accountAPI, "GetPendingUserRequest",
          huma.Operation{
	          Path:        "/pending/{uuid}",
	          Method:      http.MethodGet,
	          Summary:     "Get pending user request",
          }, GetHandler[*struct {
	          resolvers.AccessRestricted[resolvers.Admin]
	          InputUUID `path:"uuid" format:"uuid"` // Anonymous field parameter
          }](people.GetPendingUserRequest))
@danielgtaylor
Copy link
Owner

@lsdch this example seems incomplete (I'm not sure what GetInputInterface is for example). Can you help me understand the advantage of this? Like what is the difference between what you propose and something like:

Identifier InputUUID `path:"uuid" format:"uuid"`

Then rather than input.Identifier() you would use input.Identifier. I'm guessing the difference has to do with that interface type, but what is the reason for trying to do it this way?

@lsdch
Copy link
Contributor Author

lsdch commented Nov 5, 2024

Hey, sorry about the late response !

So I have a generic GetHandler function that returns a Huma handler to fetch an item using an identifier + a function that handles the DB call (in this example people.GetPendingUserRequest). I use it to limit the amount of boilerplate for this kind of operations and lean towards more declarative code.

The challenge is that item identifiers may have different type (e.g. UUID, string) and different input source (e.g. {uuid}, {code}, {email}), so that I need to be able to configure that when declaring the endpoint.
This is why I have these interfaces that are indeed missing from my example:

type IdentifierInput[T any] interface {
	Identifier() T
}
type GetInputInterface[Item any, ID any] interface {
	IdentifierInput[ID] // The identifier of the item to get
        // ... some resolvers to handle things like access control
}

Because of how Go is designed, I could not access the identifier from inside GetHandler if I declare it as a named field. However using interfaces + embedded fields I can just get the identifer directly from the operation input.

func GetHandler[
	OperationInput GetInputInterface[Item, ID],
	Item any,
	ID any,
](
	find models.ItemFinder[ID, Item],
) func(context.Context, OperationInput) (*GetHandlerOutput[Item], error) {
        // Huma handler
	return func(ctx context.Context, input OperationInput) (*GetHandlerOutput[Item], error) {
		item, err := find(input.DB(), input.Identifier()) // getting the identifier directly from the input
		// ... boilerplate
		return &GetHandlerOutput[Item]{Body: item}, err
	}
}

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

No branches or pull requests

2 participants