Skip to content

Commit

Permalink
fix: support v1 middleware, add example
Browse files Browse the repository at this point in the history
  • Loading branch information
danielgtaylor committed Oct 20, 2023
1 parent b2be402 commit 9637414
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
Expand Down
28 changes: 27 additions & 1 deletion adapters/humachi/humachi.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import (

"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/queryparam"
chiV4 "github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
)

type chiContext struct {
op *huma.Operation
r *http.Request
w http.ResponseWriter
v4 bool
}

func (c *chiContext) Operation() *huma.Operation {
Expand All @@ -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 {
Expand Down Expand Up @@ -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})
}
77 changes: 77 additions & 0 deletions examples/v1-middleware/main.go
Original file line number Diff line number Diff line change
@@ -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()
}

0 comments on commit 9637414

Please sign in to comment.