From 9637414b3b8acb1083e4c2055e00e0cb0a179e5b Mon Sep 17 00:00:00 2001 From: "Daniel G. Taylor" Date: Fri, 20 Oct 2023 09:13:03 -0700 Subject: [PATCH 1/2] fix: support v1 middleware, add example --- README.md | 2 +- adapters/humachi/humachi.go | 28 ++++++++++++- examples/v1-middleware/main.go | 77 ++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 examples/v1-middleware/main.go diff --git a/README.md b/README.md index 2179009b..3c272d46 100644 --- a/README.md +++ b/README.md @@ -1030,7 +1030,7 @@ The `huma.Register` function is a highly-optimized wrapper around the low-level ## Migrating from Huma v1 1. Import `github.com/danielgtaylor/huma/v2` instead of `github.com/danielgtaylor/huma`. -1. Use the `humachi` adapter as v1 uses Chi under the hood +1. Use the `humachi.NewV4` adapter as Huma v1 uses Chi v4 under the hood 1. Attach your middleware to the `chi` instance. 1. Replace resource & operation creation with `huma.Register` 1. Rewrite handlers to be like `func(context.Context, *Input) (*Output, error)` diff --git a/adapters/humachi/humachi.go b/adapters/humachi/humachi.go index c8ccdd02..e99f3560 100644 --- a/adapters/humachi/humachi.go +++ b/adapters/humachi/humachi.go @@ -10,6 +10,7 @@ import ( "github.com/danielgtaylor/huma/v2" "github.com/danielgtaylor/huma/v2/queryparam" + chiV4 "github.com/go-chi/chi" "github.com/go-chi/chi/v5" ) @@ -17,6 +18,7 @@ type chiContext struct { op *huma.Operation r *http.Request w http.ResponseWriter + v4 bool } func (c *chiContext) Operation() *huma.Operation { @@ -40,7 +42,11 @@ func (c *chiContext) URL() url.URL { } func (c *chiContext) Param(name string) string { - return chi.URLParam(c.r, name) + if !c.v4 { + return chi.URLParam(c.r, name) + } + + return chiV4.URLParam(c.r, name) } func (c *chiContext) Query(name string) string { @@ -102,6 +108,26 @@ func (a *chiAdapter) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.router.ServeHTTP(w, r) } +// New creates a new Huma API using the latest v5.x.x version of Chi. func New(r chi.Router, config huma.Config) huma.API { return huma.NewAPI(config, &chiAdapter{router: r}) } + +type chiAdapterV4 struct { + router chiV4.Router +} + +func (a *chiAdapterV4) Handle(op *huma.Operation, handler func(huma.Context)) { + a.router.MethodFunc(op.Method, op.Path, func(w http.ResponseWriter, r *http.Request) { + handler(&chiContext{op: op, r: r, w: w, v4: true}) + }) +} + +func (a *chiAdapterV4) ServeHTTP(w http.ResponseWriter, r *http.Request) { + a.router.ServeHTTP(w, r) +} + +// NewV4 creates a new Huma API using the older v4.x.x version of Chi. +func NewV4(r chiV4.Router, config huma.Config) huma.API { + return huma.NewAPI(config, &chiAdapterV4{router: r}) +} diff --git a/examples/v1-middleware/main.go b/examples/v1-middleware/main.go new file mode 100644 index 00000000..192a9ab7 --- /dev/null +++ b/examples/v1-middleware/main.go @@ -0,0 +1,77 @@ +// An example showing how to use Huma v1 middleware in a Huma v2 project. +package main + +import ( + "context" + "fmt" + "log" + "net/http" + + "github.com/danielgtaylor/huma/middleware" + "github.com/danielgtaylor/huma/v2" + "github.com/danielgtaylor/huma/v2/adapters/humachi" + "github.com/go-chi/chi" +) + +// Options for the CLI. +type Options struct { + Port int `help:"Port to listen on" default:"8888"` +} + +// GreetingInput represents the greeting operation request. +type GreetingInput struct { + Name string `path:"name" doc:"Name to greet"` +} + +// GreetingOutput represents the greeting operation response. +type GreetingOutput struct { + Body struct { + Message string `json:"message" doc:"Greeting message" example:"Hello, world!"` + } +} + +func main() { + // Create a CLI app which takes a port option. + cli := huma.NewCLI(func(hooks huma.Hooks, opts *Options) { + // Create a Chi v4.x.x router instance. + router := chi.NewMux() + + // Attach the Huma v1 middleware to the router. + router.Use(middleware.DefaultChain) + + // Create the API + config := huma.DefaultConfig("My API", "1.0.0") + api := humachi.NewV4(router, config) + + // Register GET /greeting/{name} + huma.Register(api, huma.Operation{ + OperationID: "get-greeting", + Summary: "Get a greeting", + Method: http.MethodGet, + Path: "/greeting/{name}", + }, func(ctx context.Context, input *GreetingInput) (*GreetingOutput, error) { + resp := &GreetingOutput{} + resp.Body.Message = fmt.Sprintf("Hello, %s!", input.Name) + return resp, nil + }) + + srv := &http.Server{ + Addr: fmt.Sprintf("%s:%d", "localhost", opts.Port), + Handler: router, + } + + // Tell the CLI how to start your router. + hooks.OnStart(func() { + // Start the server + log.Printf("Server is running with: host:%v port:%v\n", "localhost", opts.Port) + + err := srv.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } + }) + }) + + // Run the CLI. When passed no commands, it starts the server. + cli.Run() +} From 4813d3fdeac04774a386a6766a4fd1133b12b143 Mon Sep 17 00:00:00 2001 From: "Daniel G. Taylor" Date: Fri, 20 Oct 2023 09:15:08 -0700 Subject: [PATCH 2/2] fix: deps --- go.mod | 6 +++++- go.sum | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 638f7397..ced6e9cf 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 github.com/fxamacker/cbor/v2 v2.4.0 github.com/gin-gonic/gin v1.9.1 + github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/chi/v5 v5.0.10 github.com/goccy/go-yaml v1.11.0 github.com/gofiber/fiber/v2 v2.50.0 @@ -37,7 +38,6 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect @@ -57,6 +57,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -75,6 +76,9 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + go.uber.org/zap v1.21.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/sys v0.13.0 // indirect diff --git a/go.sum b/go.sum index 66029b33..9b463a6a 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,7 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -378,6 +379,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -495,11 +497,15 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= @@ -938,6 +944,7 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=