diff --git a/adapters/adapters_test.go b/adapters/adapters_test.go index 3949b922..c8caea3d 100644 --- a/adapters/adapters_test.go +++ b/adapters/adapters_test.go @@ -90,18 +90,34 @@ func TestAdapters(t *testing.T) { return huma.DefaultConfig("Test", "1.0.0") } + wrap := func(h huma.API, isFiber bool) huma.API { + h.UseMiddleware(func(ctx huma.Context, next func(huma.Context)) { + assert.Nil(t, ctx.TLS()) + v := ctx.Version() + if !isFiber { + assert.Equal(t, "HTTP/1.1", v.Proto) + assert.Equal(t, 1, v.ProtoMajor) + assert.Equal(t, 1, v.ProtoMinor) + } else { + assert.Equal(t, "http", v.Proto) + } + next(ctx) + }) + return h + } + for _, adapter := range []struct { name string new func() huma.API }{ - {"chi", func() huma.API { return humachi.New(chi.NewMux(), config()) }}, - {"echo", func() huma.API { return humaecho.New(echo.New(), config()) }}, - {"fiber", func() huma.API { return humafiber.New(fiber.New(), config()) }}, - {"gin", func() huma.API { return humagin.New(gin.New(), config()) }}, - {"httprouter", func() huma.API { return humahttprouter.New(httprouter.New(), config()) }}, - {"mux", func() huma.API { return humamux.New(mux.NewRouter(), config()) }}, - {"bunrouter", func() huma.API { return humabunrouter.New(bunrouter.New(), config()) }}, - {"bunroutercompat", func() huma.API { return humabunrouter.NewCompat(bunrouter.New().Compat(), config()) }}, + {"chi", func() huma.API { return wrap(humachi.New(chi.NewMux(), config()), false) }}, + {"echo", func() huma.API { return wrap(humaecho.New(echo.New(), config()), false) }}, + {"fiber", func() huma.API { return wrap(humafiber.New(fiber.New(), config()), true) }}, + {"gin", func() huma.API { return wrap(humagin.New(gin.New(), config()), false) }}, + {"httprouter", func() huma.API { return wrap(humahttprouter.New(httprouter.New(), config()), false) }}, + {"mux", func() huma.API { return wrap(humamux.New(mux.NewRouter(), config()), false) }}, + {"bunrouter", func() huma.API { return wrap(humabunrouter.New(bunrouter.New(), config()), false) }}, + {"bunroutercompat", func() huma.API { return wrap(humabunrouter.NewCompat(bunrouter.New().Compat(), config()), false) }}, } { t.Run(adapter.name, func(t *testing.T) { testAdapter(t, adapter.new()) diff --git a/adapters/humabunrouter/humabunrouter.go b/adapters/humabunrouter/humabunrouter.go index 6790b861..8fb1cdca 100644 --- a/adapters/humabunrouter/humabunrouter.go +++ b/adapters/humabunrouter/humabunrouter.go @@ -2,6 +2,7 @@ package humabunrouter import ( "context" + "crypto/tls" "fmt" "io" "mime/multipart" @@ -107,6 +108,18 @@ func (c *bunContext) BodyWriter() io.Writer { return c.w } +func (c *bunContext) TLS() *tls.ConnectionState { + return c.r.TLS +} + +func (c *bunContext) Version() huma.ProtoVersion { + return huma.ProtoVersion{ + Proto: c.r.Proto, + ProtoMajor: c.r.ProtoMajor, + ProtoMinor: c.r.ProtoMinor, + } +} + // NewContext creates a new Huma context from an HTTP request and response. func NewContext(op *huma.Operation, r bunrouter.Request, w http.ResponseWriter) huma.Context { return &bunContext{op: op, r: r, w: w} @@ -198,6 +211,18 @@ func (c *bunCompatContext) BodyWriter() io.Writer { return c.w } +func (c *bunCompatContext) TLS() *tls.ConnectionState { + return c.r.TLS +} + +func (c *bunCompatContext) Version() huma.ProtoVersion { + return huma.ProtoVersion{ + Proto: c.r.Proto, + ProtoMajor: c.r.ProtoMajor, + ProtoMinor: c.r.ProtoMinor, + } +} + // NewCompatContext creates a new Huma context from an HTTP request and response. func NewCompatContext(op *huma.Operation, r *http.Request, w http.ResponseWriter) huma.Context { return &bunCompatContext{op: op, r: r, w: w} diff --git a/adapters/humachi/humachi.go b/adapters/humachi/humachi.go index ba8a551a..2f4be07c 100644 --- a/adapters/humachi/humachi.go +++ b/adapters/humachi/humachi.go @@ -2,6 +2,7 @@ package humachi import ( "context" + "crypto/tls" "io" "mime/multipart" "net/http" @@ -106,6 +107,18 @@ func (c *chiContext) BodyWriter() io.Writer { return c.w } +func (c *chiContext) TLS() *tls.ConnectionState { + return c.r.TLS +} + +func (c *chiContext) Version() huma.ProtoVersion { + return huma.ProtoVersion{ + Proto: c.r.Proto, + ProtoMajor: c.r.ProtoMajor, + ProtoMinor: c.r.ProtoMinor, + } +} + // NewContext creates a new Huma context from an HTTP request and response. func NewContext(op *huma.Operation, r *http.Request, w http.ResponseWriter) huma.Context { return &chiContext{op: op, r: r, w: w} diff --git a/adapters/humaecho/humaecho.go b/adapters/humaecho/humaecho.go index c183f3ba..ece42e73 100644 --- a/adapters/humaecho/humaecho.go +++ b/adapters/humaecho/humaecho.go @@ -2,6 +2,7 @@ package humaecho import ( "context" + "crypto/tls" "io" "mime/multipart" "net/http" @@ -104,6 +105,19 @@ func (c *echoCtx) BodyWriter() io.Writer { return c.orig.Response() } +func (c *echoCtx) TLS() *tls.ConnectionState { + return c.orig.Request().TLS +} + +func (c *echoCtx) Version() huma.ProtoVersion { + r := c.orig.Request() + return huma.ProtoVersion{ + Proto: r.Proto, + ProtoMajor: r.ProtoMajor, + ProtoMinor: r.ProtoMinor, + } +} + type router interface { Add(method, path string, handler echo.HandlerFunc, middlewares ...echo.MiddlewareFunc) *echo.Route } diff --git a/adapters/humafiber/humafiber.go b/adapters/humafiber/humafiber.go index 85e81f35..3703be2b 100644 --- a/adapters/humafiber/humafiber.go +++ b/adapters/humafiber/humafiber.go @@ -3,6 +3,7 @@ package humafiber import ( "bytes" "context" + "crypto/tls" "io" "mime/multipart" "net/http" @@ -111,6 +112,16 @@ func (c *fiberCtx) BodyWriter() io.Writer { return c.orig } +func (c *fiberCtx) TLS() *tls.ConnectionState { + return c.orig.Context().TLSConnectionState() +} + +func (c *fiberCtx) Version() huma.ProtoVersion { + return huma.ProtoVersion{ + Proto: c.orig.Protocol(), + } +} + type router interface { Add(method, path string, handlers ...fiber.Handler) fiber.Router } diff --git a/adapters/humaflow/humaflow.go b/adapters/humaflow/humaflow.go index fcea34e8..d4d5452a 100644 --- a/adapters/humaflow/humaflow.go +++ b/adapters/humaflow/humaflow.go @@ -2,6 +2,7 @@ package humaflow import ( "context" + "crypto/tls" "io" "mime/multipart" "net/http" @@ -103,6 +104,18 @@ func (c *goContext) BodyWriter() io.Writer { return c.w } +func (c *goContext) TLS() *tls.ConnectionState { + return c.r.TLS +} + +func (c *goContext) Version() huma.ProtoVersion { + return huma.ProtoVersion{ + Proto: c.r.Proto, + ProtoMajor: c.r.ProtoMajor, + ProtoMinor: c.r.ProtoMinor, + } +} + // NewContext creates a new Huma context from an HTTP request and response. func NewContext(op *huma.Operation, r *http.Request, w http.ResponseWriter) huma.Context { return &goContext{op: op, r: r, w: w} diff --git a/adapters/humagin/humagin.go b/adapters/humagin/humagin.go index ef437866..64762cc4 100644 --- a/adapters/humagin/humagin.go +++ b/adapters/humagin/humagin.go @@ -2,6 +2,7 @@ package humagin import ( "context" + "crypto/tls" "io" "mime/multipart" "net/http" @@ -104,6 +105,18 @@ func (c *ginCtx) BodyWriter() io.Writer { return c.orig.Writer } +func (c *ginCtx) TLS() *tls.ConnectionState { + return c.orig.Request.TLS +} + +func (c *ginCtx) Version() huma.ProtoVersion { + return huma.ProtoVersion{ + Proto: c.orig.Request.Proto, + ProtoMajor: c.orig.Request.ProtoMajor, + ProtoMinor: c.orig.Request.ProtoMinor, + } +} + // Router is an interface that wraps the Gin router's Handle method. type Router interface { Handle(string, string, ...gin.HandlerFunc) gin.IRoutes diff --git a/adapters/humago/humago.go b/adapters/humago/humago.go index 12693404..41b23ba9 100644 --- a/adapters/humago/humago.go +++ b/adapters/humago/humago.go @@ -2,6 +2,7 @@ package humago import ( "context" + "crypto/tls" "io" "mime/multipart" "net/http" @@ -112,6 +113,18 @@ func (c *goContext) BodyWriter() io.Writer { return c.w } +func (c *goContext) TLS() *tls.ConnectionState { + return c.r.TLS +} + +func (c *goContext) Version() huma.ProtoVersion { + return huma.ProtoVersion{ + Proto: c.r.Proto, + ProtoMajor: c.r.ProtoMajor, + ProtoMinor: c.r.ProtoMinor, + } +} + // NewContext creates a new Huma context from an HTTP request and response. func NewContext(op *huma.Operation, r *http.Request, w http.ResponseWriter) huma.Context { return &goContext{op: op, r: r, w: w} diff --git a/adapters/humahttprouter/humahttprouter.go b/adapters/humahttprouter/humahttprouter.go index b464e222..56730423 100644 --- a/adapters/humahttprouter/humahttprouter.go +++ b/adapters/humahttprouter/humahttprouter.go @@ -2,6 +2,7 @@ package humahttprouter import ( "context" + "crypto/tls" "io" "mime/multipart" "net/http" @@ -107,6 +108,18 @@ func (c *httprouterContext) BodyWriter() io.Writer { return c.w } +func (c *httprouterContext) TLS() *tls.ConnectionState { + return c.r.TLS +} + +func (c *httprouterContext) Version() huma.ProtoVersion { + return huma.ProtoVersion{ + Proto: c.r.Proto, + ProtoMajor: c.r.ProtoMajor, + ProtoMinor: c.r.ProtoMinor, + } +} + type httprouterAdapter struct { router *httprouter.Router } diff --git a/adapters/humamux/humamux.go b/adapters/humamux/humamux.go index 1f2654a5..7525b43d 100644 --- a/adapters/humamux/humamux.go +++ b/adapters/humamux/humamux.go @@ -2,6 +2,7 @@ package humamux import ( "context" + "crypto/tls" "io" "mime/multipart" "net/http" @@ -63,6 +64,18 @@ func (c *gmuxContext) Header(name string) string { return c.r.Header.Get(name) } +func (c *gmuxContext) TLS() *tls.ConnectionState { + return c.r.TLS +} + +func (c *gmuxContext) Version() huma.ProtoVersion { + return huma.ProtoVersion{ + Proto: c.r.Proto, + ProtoMajor: c.r.ProtoMajor, + ProtoMinor: c.r.ProtoMinor, + } +} + func (c *gmuxContext) EachHeader(cb func(name, value string)) { for name, values := range c.r.Header { for _, value := range values { diff --git a/api.go b/api.go index 60ac05e0..a49a9314 100644 --- a/api.go +++ b/api.go @@ -2,6 +2,7 @@ package huma import ( "context" + "crypto/tls" "encoding/json" "errors" "fmt" @@ -66,6 +67,12 @@ type Context interface { // Context returns the underlying request context. Context() context.Context + // TLS / SSL connection information. + TLS() *tls.ConnectionState + + // Version of the HTTP protocol as text and integers. + Version() ProtoVersion + // Method returns the HTTP method for the request. Method() string @@ -117,6 +124,13 @@ type Context interface { BodyWriter() io.Writer } +// Represent http protocol version +type ProtoVersion struct { + Proto string + ProtoMajor int + ProtoMinor int +} + type ( humaContext Context subContext struct {