chirouter

package
v0.2.76 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 9 Imported by: 7

Documentation

Overview

Package chirouter provides instrumentation for chi.Router.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func PathToURLValues

func PathToURLValues(r *http.Request) (url.Values, error)

PathToURLValues is a decoder function for parameters in path.

Example
package main

import (
	"bytes"
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/go-chi/chi/v5"
	"github.com/swaggest/rest"
	"github.com/swaggest/rest/chirouter"
	"github.com/swaggest/rest/request"
)

func main() {
	// Instantiate decoder factory with gorillamux.PathToURLValues.
	// Single factory can be used to create multiple request decoders.
	decoderFactory := request.NewDecoderFactory()
	decoderFactory.ApplyDefaults = true
	decoderFactory.SetDecoderFunc(rest.ParamInPath, chirouter.PathToURLValues)

	// Define request structure for your HTTP handler.
	type myRequest struct {
		Query1    int     `query:"query1"`
		Path1     string  `path:"path1"`
		Path2     int     `path:"path2"`
		Header1   float64 `header:"X-Header-1"`
		FormData1 bool    `formData:"formData1"`
		FormData2 string  `formData:"formData2"`
	}

	// Create decoder for that request structure.
	dec := decoderFactory.MakeDecoder(http.MethodPost, myRequest{}, nil)

	router := chi.NewRouter()

	// Now in router handler you can decode *http.Request into a Go structure.
	router.Handle("/foo/{path1}/bar/{path2}", http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		var in myRequest

		_ = dec.Decode(r, &in, nil)

		fmt.Printf("%+v\n", in)
	}))

	// Serving example URL.
	w := httptest.NewRecorder()

	req, _ := http.NewRequest(http.MethodPost, `/foo/a%2Fbc/bar/123?query1=321`,
		bytes.NewBufferString("formData1=true&formData2=def"))

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("X-Header-1", "1.23")

	router.ServeHTTP(w, req)
}
Output:
{Query1:321 Path1:a/bc Path2:123 Header1:1.23 FormData1:true FormData2:def}

Types

type OpenAPICollector added in v0.2.76

type OpenAPICollector struct {
	// Collector is an actual OpenAPI collector.
	Collector *openapi.Collector

	// OperationExtractor allows flexible extraction of OpenAPI information.
	OperationExtractor func(h http.Handler) func(oc oapi.OperationContext) error
}

OpenAPICollector is a wrapper for openapi.Collector tailored to walk chi router.

func NewOpenAPICollector added in v0.2.76

func NewOpenAPICollector(r oapi.Reflector) *OpenAPICollector

NewOpenAPICollector creates route walker for chi, that collects OpenAPI operations.

Example
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"
	"github.com/swaggest/openapi-go"
	"github.com/swaggest/openapi-go/openapi3"
	"github.com/swaggest/rest"
	"github.com/swaggest/rest/chirouter"
	"github.com/swaggest/rest/nethttp"
	"github.com/swaggest/rest/request"
)

// Define request structure for your HTTP handler.
type myRequest struct {
	Query1    int     `query:"query1"`
	Path1     string  `path:"path1"`
	Path2     int     `path:"path2"`
	Header1   float64 `header:"X-Header-1"`
	FormData1 bool    `formData:"formData1"`
	FormData2 string  `formData:"formData2"`
}

type myResp struct {
	Sum    float64 `json:"sum"`
	Concat string  `json:"concat"`
}

func newMyHandler() *myHandler {
	decoderFactory := request.NewDecoderFactory()
	decoderFactory.ApplyDefaults = true
	decoderFactory.SetDecoderFunc(rest.ParamInPath, chirouter.PathToURLValues)

	return &myHandler{
		dec: decoderFactory.MakeDecoder(http.MethodGet, myRequest{}, nil),
	}
}

type myHandler struct {
	// Automated request decoding is not required to collect OpenAPI schema,
	// but it is good to have to establish a single source of truth and to simplify request reading.
	dec nethttp.RequestDecoder
}

func (m *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var in myRequest

	if err := m.dec.Decode(r, &in, nil); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)

		return
	}

	// Serve request.
	out := myResp{
		Sum:    in.Header1 + float64(in.Path2) + float64(in.Query1),
		Concat: in.Path1 + in.FormData2 + strconv.FormatBool(in.FormData1),
	}

	j, err := json.Marshal(out)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)

		return
	}

	_, _ = w.Write(j)
}

// SetupOpenAPIOperation declares OpenAPI schema for the handler.
func (m *myHandler) SetupOpenAPIOperation(oc openapi.OperationContext) error {
	oc.SetTags("My Tag")
	oc.SetSummary("My Summary")
	oc.SetDescription("This endpoint aggregates request in structured way.")

	oc.AddReqStructure(myRequest{})
	oc.AddRespStructure(myResp{})
	oc.AddRespStructure(nil, openapi.WithContentType("text/html"), openapi.WithHTTPStatus(http.StatusBadRequest))
	oc.AddRespStructure(nil, openapi.WithContentType("text/html"), openapi.WithHTTPStatus(http.StatusInternalServerError))

	return nil
}

func main() {
	// Your router does not need special instrumentation.
	router := chi.NewRouter()

	// If handler implements chirouter.OpenAPIPreparer, it will contribute detailed information to OpenAPI document.
	router.Method(http.MethodGet, "/foo/{path1}/bar/{path2}", newMyHandler())

	// If handler does not implement chirouter.OpenAPIPreparer, it will be exposed as incomplete.
	router.Post("/uninstrumented-handler/{path-item}", func(w http.ResponseWriter, r *http.Request) {})

	// A plain http.HandlerFunc can't implement chirouter.OpenAPIPreparer (funcs can't have methods),
	// but it can still get full documentation via Collector.AnnotateOperation, keyed by method and pattern.
	router.Get("/func-handler/{path-item}", func(w http.ResponseWriter, r *http.Request) {})

	// Setup OpenAPI schema.
	refl := openapi3.NewReflector()
	refl.SpecSchema().SetTitle("Sample API")
	refl.SpecSchema().SetVersion("v1.2.3")
	refl.SpecSchema().SetDescription("This is an example.")

	// Walk the router with OpenAPI collector.
	c := chirouter.NewOpenAPICollector(refl)

	// AnnotateOperation is the quickest way to document a func handler, but the method+pattern
	// key is a second copy of the route: if the route changes and this string is not updated to
	// match, the annotation silently stops applying (falls back to "Incomplete").
	c.Collector.AnnotateOperation(http.MethodGet, "/func-handler/{path-item}", func(oc openapi.OperationContext) error {
		oc.SetSummary("Func Handler")
		oc.AddReqStructure(struct {
			PathItem string `path:"path-item"`
		}{})
		oc.AddRespStructure(myResp{})

		return nil
	})

	// Collector.Describe avoids that duplication: the route registration itself stays the single
	// source of truth. Use it for func handlers you register yourself, especially routes built
	// programmatically/in bulk, where keeping a second, string-keyed AnnotateOperation call in
	// sync would be error-prone. Describe returns http.Handler, so register it with a method
	// that accepts one, such as Method, rather than Get/Post/etc which require http.HandlerFunc.
	router.Method(http.MethodGet, "/identified-func/{path-item}", c.Describe(
		func(w http.ResponseWriter, r *http.Request) {},
		func(oc openapi.OperationContext) error {
			oc.SetSummary("Identified Func Handler")
			oc.AddReqStructure(struct {
				PathItem string `path:"path-item"`
			}{})
			oc.AddRespStructure(myResp{})

			return nil
		},
	))

	_ = chi.Walk(router, c.Walker)

	// Get the resulting schema.
	yml, _ := refl.Spec.MarshalYAML()
	fmt.Println(string(yml))

}
Output:
openapi: 3.0.3
info:
  description: This is an example.
  title: Sample API
  version: v1.2.3
paths:
  /foo/{path1}/bar/{path2}:
    get:
      description: This endpoint aggregates request in structured way.
      parameters:
      - in: query
        name: query1
        schema:
          type: integer
      - in: path
        name: path1
        required: true
        schema:
          type: string
      - in: path
        name: path2
        required: true
        schema:
          type: integer
      - in: header
        name: X-Header-1
        schema:
          format: double
          type: number
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChirouterTestMyResp'
          description: OK
        "400":
          content:
            text/html:
              schema:
                type: string
          description: Bad Request
        "500":
          content:
            text/html:
              schema:
                type: string
          description: Internal Server Error
      summary: My Summary
      tags:
      - My Tag
  /func-handler/{path-item}:
    get:
      parameters:
      - in: path
        name: path-item
        required: true
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChirouterTestMyResp'
          description: OK
      summary: Func Handler
  /identified-func/{path-item}:
    get:
      parameters:
      - in: path
        name: path-item
        required: true
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChirouterTestMyResp'
          description: OK
      summary: Identified Func Handler
  /uninstrumented-handler/{path-item}:
    post:
      description: Information about this operation was obtained using only HTTP method
        and path pattern. It may be incomplete and/or inaccurate.
      parameters:
      - in: path
        name: path-item
        required: true
        schema:
          type: string
      responses:
        "200":
          content:
            text/html:
              schema:
                type: string
          description: OK
      tags:
      - Incomplete
components:
  schemas:
    ChirouterTestMyResp:
      properties:
        concat:
          type: string
        sum:
          format: double
          type: number
      type: object

func (*OpenAPICollector) Describe added in v0.2.76

func (dc *OpenAPICollector) Describe(h http.HandlerFunc, setup func(oc oapi.OperationContext) error) http.Handler

Describe attaches OpenAPI documentation to a http.HandlerFunc. Unlike Collector.AnnotateOperation, which is keyed by a separately maintained method+pattern string that can drift from the actual route, Describe keeps the route registration itself as the single source of truth.

The result implements http.Handler, so register it with a method that accepts one, e.g. router.Method(http.MethodGet, pattern, c.Describe(h, setup)).

func (*OpenAPICollector) Walker added in v0.2.76

func (dc *OpenAPICollector) Walker(method, route string, handler http.Handler, _ ...func(http.Handler) http.Handler) error

Walker walks chi route tree and collects OpenAPI information.

Use it as chi.WalkFunc with chi.Walk.

type OpenAPIPreparer added in v0.2.76

type OpenAPIPreparer interface {
	SetupOpenAPIOperation(oc oapi.OperationContext) error
}

OpenAPIPreparer defines http.Handler with OpenAPI information.

type Wrapper

type Wrapper struct {
	chi.Router
	// contains filtered or unexported fields
}

Wrapper wraps chi.Router to enable unwrappable handlers in middlewares.

Middlewares can call nethttp.HandlerAs to inspect wrapped handlers.

func NewWrapper

func NewWrapper(r chi.Router) *Wrapper

NewWrapper creates router wrapper to upgrade middlewares processing.

func (*Wrapper) Connect

func (r *Wrapper) Connect(pattern string, handlerFn http.HandlerFunc)

Connect adds the route `pattern` that matches a CONNECT http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Delete

func (r *Wrapper) Delete(pattern string, handlerFn http.HandlerFunc)

Delete adds the route `pattern` that matches a DELETE http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Get

func (r *Wrapper) Get(pattern string, handlerFn http.HandlerFunc)

Get adds the route `pattern` that matches a GET http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Group

func (r *Wrapper) Group(fn func(r chi.Router)) chi.Router

Group adds a new inline-router along the current routing path, with a fresh middleware stack for the inline-router.

func (*Wrapper) Handle

func (r *Wrapper) Handle(pattern string, h http.Handler)

Handle adds routes for `basePattern` that matches all HTTP methods.

func (*Wrapper) HandlerFunc added in v0.2.61

func (r *Wrapper) HandlerFunc(h http.Handler) http.HandlerFunc

HandlerFunc prepares handler and returns its function.

Can be used as input for NotFound, MethodNotAllowed.

func (*Wrapper) Head

func (r *Wrapper) Head(pattern string, handlerFn http.HandlerFunc)

Head adds the route `pattern` that matches a HEAD http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Method

func (r *Wrapper) Method(method, pattern string, h http.Handler)

Method adds routes for `basePattern` that matches the `method` HTTP method.

func (*Wrapper) MethodFunc

func (r *Wrapper) MethodFunc(method, pattern string, handlerFn http.HandlerFunc)

MethodFunc adds the route `pattern` that matches `method` http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Mount

func (r *Wrapper) Mount(pattern string, h http.Handler)

Mount attaches another http.Handler along "./basePattern/*".

func (*Wrapper) Options

func (r *Wrapper) Options(pattern string, handlerFn http.HandlerFunc)

Options adds the route `pattern` that matches a OPTIONS http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Patch

func (r *Wrapper) Patch(pattern string, handlerFn http.HandlerFunc)

Patch adds the route `pattern` that matches a PATCH http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Post

func (r *Wrapper) Post(pattern string, handlerFn http.HandlerFunc)

Post adds the route `pattern` that matches a POST http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Put

func (r *Wrapper) Put(pattern string, handlerFn http.HandlerFunc)

Put adds the route `pattern` that matches a PUT http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Route

func (r *Wrapper) Route(pattern string, fn func(r chi.Router)) chi.Router

Route mounts a sub-router along a `basePattern` string.

func (*Wrapper) Trace

func (r *Wrapper) Trace(pattern string, handlerFn http.HandlerFunc)

Trace adds the route `pattern` that matches a TRACE http method to execute the `handlerFn` http.HandlerFunc.

func (*Wrapper) Use

func (r *Wrapper) Use(middlewares ...func(http.Handler) http.Handler)

Use appends one of more middlewares onto the Router stack.

func (Wrapper) With

func (r Wrapper) With(middlewares ...func(http.Handler) http.Handler) chi.Router

With adds inline middlewares for an endpoint handler.

func (*Wrapper) Wrap added in v0.2.28

func (r *Wrapper) Wrap(wraps ...func(handler http.Handler) http.Handler)

Wrap appends one or more wrappers that will be applied to handler before adding to Router. It is different from middleware in the sense that it is handler-centric, rather than request-centric. Wraps are invoked once for each added handler, they are not invoked for http requests. Wraps can leverage nethttp.HandlerAs to inspect and access deeper layers. For most cases Wrap can be safely used instead of Use, Use is mandatory for middlewares that affect routing (such as middleware.StripSlashes for example).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL