这是indexloc提供的服务,不要输入任何密码
Skip to content

Conversation

@danielgtaylor
Copy link
Owner

This change is a major refactor / partial rewrite of Huma which tries to accomplish a few things:

  • Simplify writing handler functions
  • Support request and response streaming
  • Compatibility with standard library and popular third-party libraries
    • context.Context
    • http.ResponseWriter
    • io.Reader
    • io.Writer
    • middleware func(next http.Handler) http.Handler
  • Per-resource (aka prefixed) middleware
  • Replaces complicated/confusing dependencies with resolvable input structs

See the updated README.md for more details.

@danielgtaylor danielgtaylor added the enhancement New feature or request label Sep 10, 2020
@danielgtaylor danielgtaylor self-assigned this Sep 10, 2020
return len(c.errors) > 0
}

func (c *hcontext) WriteHeader(status int) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never understood why methods with this purpose are called "WriteHeader" in golang-world. You're not writing a "header" (as in a request or response header), you are writing a status code (or status line). I find the name confusing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I guess this implements http.ResponseWriter so 🤷

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is purely to match up with the standard library http.ResponseWriter interface so we don't really control it 😢

error.go Outdated
// ErrorDetail provides details about a specific error.
type ErrorDetail struct {
Message string `json:"message,omitempty"`
Location string `json:"location,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of Location? Specifically, is it a source code location? Because an end-user won't be able to do anything with that information (other than providing it in a support ticket, in which case some sort of error code would probably be better anyway).

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some additional documentation. This is actually the location in the input as a JSONPath type link and can refer to request parameters (path/query/header) or the request body. For example:

{
  "status": 400,
  "title": "Bad Request",
  "detail": "Request input did not pass validation",
  "errors": [
    {
      "message": "Could not parse int",
      "location": "path.thing-id",
      "value": "abc"
    },
    {
      "message": "Field is required",
      "location": "body.tags[1].description"
    },
    {
      "message": "Must match pattern ^[a-z0-9-]+$",
      "location": "body.slug",
      "value": "foo_bar"
    }
  ]
}

Comment on lines 16 to 18
// NewRouter creates a new test router. It includes a logger attached to the
// test so if it fails you will see additional output. There is no recovery
// middleware so panics will get caught by the test runner.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually find that very annoying, because I think that if a single test fails you'll get log spew from all the tests (making debugging harder). This would be even worse for table-drive tests where maybe just one case is failing. I would like to be able to at least have the option to not attach a logger.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a couple of options for this:

  1. Call humatest.NewRouter for each test in the table, then you get only the single failing log output.
  2. Ignore the humatest package and just do huma.New instead and you get a blank router without any middleware.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any examples of this in the existing code? I'd be good to see a setup that could be used to demonstrate this for future testing.

)

// OpenTracing provides a middleware for cross-service tracing support.
func OpenTracing(next http.Handler) http.Handler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to see an example of how this integrates with the datadog tracer API.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is all you have to do:

// Start a Datadog tracer, optionally providing a set of options,
// returning an opentracing.Tracer which wraps it.
t := opentracer.New(tracer.WithAgentAddr("host:port"))

// Use it with the Opentracing API. The (already started) Datadog tracer
// may be used in parallel with the Opentracing API if desired.
opentracing.SetGlobalTracer(t)

https://github.com/DataDog/dd-trace-go/blob/v1/ddtrace/opentracer/example_test.go#L15-L23

## Resources

Huma APIs are composed of resources and sub-resources attached to a router. A resource refers to a unique URI on which operations can be performed. Huma resources can have dependencies, security requirements, parameters, response headers, and responses attached to them which are all applied to every operation and sub-resource.
Huma APIs are composed of resources and sub-resources attached to a router. A resource refers to a unique URI on which operations can be performed. Huma resources can have middleware attached to them, which run before operation handlers.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which run before operation handlers.
Is my understanding correct that the middleware executes to completion, then the operation handler is executed?

No change requested, just looking to understand: I am mentally contrasting it with a gRPC interceptor where the middleware calls the handler so it can also take action on the handler response.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the same concept. Each middleware takes a next handler and calls next.ServeHTTP(w, r) on it, and at the end of the chain next is just the handler function itself.

WriteModel(status int, model interface{})
}

type hcontext struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a very informative type name; what is meant by hcontext?

Copy link
Owner Author

@danielgtaylor danielgtaylor Sep 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name context is taken by a standard library package, so we append the name of this package. I guess that means it stands for "Huma context". It was just a way to prevent shadowing and is an internal implementation detail of the public huma.Context interface.

It's a pretty common pattern but by no means the only way to handle this. Open to other naming suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants