diff --git a/API.md b/API.md
deleted file mode 100644
index 6730d62..0000000
--- a/API.md
+++ /dev/null
@@ -1,1490 +0,0 @@
-# API examples
-
-_Generated from annotated public API comments. Do not edit this file directly._
-
-This guide covers functions and methods with repository examples. See [pkg.go.dev](https://pkg.go.dev/github.com/goforj/web) for the complete API, including exported types, configuration fields, constants, and variables.
-
-## API Index
-
-| Group | Functions |
-|------:|:-----------|
-| **Adapter** | [Adapter.Echo](#echoweb-adapter-echo) · [Adapter.Router](#echoweb-adapter-router) · [Adapter.ServeHTTP](#echoweb-adapter-servehttp) · [New](#echoweb-new) · [NewServer](#echoweb-newserver) · [Server.Router](#echoweb-server-router) · [Server.Serve](#echoweb-server-serve) · [Server.ServeHTTP](#echoweb-server-servehttp) · [UnwrapContext](#echoweb-unwrapcontext) · [UnwrapWebSocketConn](#echoweb-unwrapwebsocketconn) · [Wrap](#echoweb-wrap) |
-| **Indexing** | [Run](#webindex-run) |
-| **Middleware
Auth** | [BasicAuth](#webmiddleware-basicauth) · [BasicAuthWithConfig](#webmiddleware-basicauthwithconfig) · [CSRF](#webmiddleware-csrf) · [CSRFWithConfig](#webmiddleware-csrfwithconfig) · [CreateExtractors](#webmiddleware-createextractors) · [KeyAuth](#webmiddleware-keyauth) · [KeyAuthWithConfig](#webmiddleware-keyauthwithconfig) |
-| **Middleware
Compression** | [Compress](#webmiddleware-compress) · [Decompress](#webmiddleware-decompress) · [DecompressWithConfig](#webmiddleware-decompresswithconfig) · [Gzip](#webmiddleware-gzip) · [GzipWithConfig](#webmiddleware-gzipwithconfig) |
-| **Middleware
Method Override** | [MethodFromForm](#webmiddleware-methodfromform) · [MethodFromHeader](#webmiddleware-methodfromheader) · [MethodFromQuery](#webmiddleware-methodfromquery) · [MethodOverride](#webmiddleware-methodoverride) · [MethodOverrideWithConfig](#webmiddleware-methodoverridewithconfig) |
-| **Middleware
Path Rewriting** | [AddTrailingSlash](#webmiddleware-addtrailingslash) · [AddTrailingSlashWithConfig](#webmiddleware-addtrailingslashwithconfig) · [RemoveTrailingSlash](#webmiddleware-removetrailingslash) · [RemoveTrailingSlashWithConfig](#webmiddleware-removetrailingslashwithconfig) · [Rewrite](#webmiddleware-rewrite) · [RewriteWithConfig](#webmiddleware-rewritewithconfig) |
-| **Middleware
Payloads** | [BodyDump](#webmiddleware-bodydump) · [BodyDumpWithConfig](#webmiddleware-bodydumpwithconfig) · [BodyLimit](#webmiddleware-bodylimit) · [BodyLimitWithConfig](#webmiddleware-bodylimitwithconfig) · [ErrorBodyDump](#webmiddleware-errorbodydump) · [ErrorBodyDumpWithConfig](#webmiddleware-errorbodydumpwithconfig) |
-| **Middleware
Proxying** | [NewRandomBalancer](#webmiddleware-newrandombalancer) · [NewRoundRobinBalancer](#webmiddleware-newroundrobinbalancer) · [Proxy](#webmiddleware-proxy) · [ProxyWithConfig](#webmiddleware-proxywithconfig) |
-| **Middleware
Rate Limiting** | [NewRateLimiterMemoryStore](#webmiddleware-newratelimitermemorystore) · [NewRateLimiterMemoryStoreWithConfig](#webmiddleware-newratelimitermemorystorewithconfig) · [RateLimiter](#webmiddleware-ratelimiter) · [RateLimiterMemoryStore.Allow](#webmiddleware-ratelimitermemorystore-allow) · [RateLimiterWithConfig](#webmiddleware-ratelimiterwithconfig) |
-| **Middleware
Redirects** | [HTTPSNonWWWRedirect](#webmiddleware-httpsnonwwwredirect) · [HTTPSNonWWWRedirectWithConfig](#webmiddleware-httpsnonwwwredirectwithconfig) · [HTTPSRedirect](#webmiddleware-httpsredirect) · [HTTPSRedirectWithConfig](#webmiddleware-httpsredirectwithconfig) · [HTTPSWWWRedirect](#webmiddleware-httpswwwredirect) · [HTTPSWWWRedirectWithConfig](#webmiddleware-httpswwwredirectwithconfig) · [NonWWWRedirect](#webmiddleware-nonwwwredirect) · [NonWWWRedirectWithConfig](#webmiddleware-nonwwwredirectwithconfig) · [WWWRedirect](#webmiddleware-wwwredirect) · [WWWRedirectWithConfig](#webmiddleware-wwwredirectwithconfig) |
-| **Middleware
Reliability** | [Recover](#webmiddleware-recover) · [RecoverWithConfig](#webmiddleware-recoverwithconfig) |
-| **Middleware
Request Lifecycle** | [ContextTimeout](#webmiddleware-contexttimeout) · [ContextTimeoutWithConfig](#webmiddleware-contexttimeoutwithconfig) · [DefaultSkipper](#webmiddleware-defaultskipper) · [RequestID](#webmiddleware-requestid) · [RequestIDWithConfig](#webmiddleware-requestidwithconfig) · [RequestLoggerWithConfig](#webmiddleware-requestloggerwithconfig) · [Timeout](#webmiddleware-timeout) · [TimeoutWithConfig](#webmiddleware-timeoutwithconfig) |
-| **Middleware
Security** | [CORS](#webmiddleware-cors) · [CORSWithConfig](#webmiddleware-corswithconfig) · [Secure](#webmiddleware-secure) · [SecureWithConfig](#webmiddleware-securewithconfig) |
-| **Middleware
Static Files** | [Static](#webmiddleware-static) · [StaticWithConfig](#webmiddleware-staticwithconfig) |
-| **Prometheus** | [Default](#webprometheus-default) · [Handler](#webprometheus-handler) · [Metrics.Handler](#webprometheus-metrics-handler) · [Metrics.Middleware](#webprometheus-metrics-middleware) · [Middleware](#webprometheus-middleware) · [MustNew](#webprometheus-mustnew) · [New](#webprometheus-new) · [RunPushGatewayGatherer](#webprometheus-runpushgatewaygatherer) · [WriteGatheredMetrics](#webprometheus-writegatheredmetrics) |
-| **Route Reporting** | [BuildRouteEntries](#buildrouteentries) · [RenderRouteTable](#renderroutetable) |
-| **Routing** | [MountRouter](#mountrouter) · [NewRoute](#newroute) · [NewRouteGroup](#newroutegroup) · [NewWebSocketRoute](#newwebsocketroute) · [RegisterRoutes](#registerroutes) · [Route.Handler](#route-handler) · [Route.HandlerName](#route-handlername) · [Route.IsWebSocket](#route-iswebsocket) · [Route.Method](#route-method) · [Route.MiddlewareNames](#route-middlewarenames) · [Route.Middlewares](#route-middlewares) · [Route.Path](#route-path) · [Route.WebSocketHandler](#route-websockethandler) · [Route.WithMiddlewareNames](#route-withmiddlewarenames) · [RouteGroup.MiddlewareNames](#routegroup-middlewarenames) · [RouteGroup.Middlewares](#routegroup-middlewares) · [RouteGroup.RoutePrefix](#routegroup-routeprefix) · [RouteGroup.Routes](#routegroup-routes) · [RouteGroup.WithMiddlewareNames](#routegroup-withmiddlewarenames) |
-| **Testing** | [NewContext](#webtest-newcontext) |
-
-
-## Examples
-
-_Generated from public API comments and examples._
-
-### Adapter
-
-#### echoweb.Adapter.Echo
-
-Echo returns the underlying Echo engine.
-
-```go
-adapter := echoweb.New()
-fmt.Println(adapter.Echo() != nil)
-// true
-```
-
-#### echoweb.Adapter.Router
-
-Router returns the app-facing router contract.
-
-```go
-adapter := echoweb.New()
-fmt.Println(adapter.Router() != nil)
-// true
-```
-
-#### echoweb.Adapter.ServeHTTP
-
-ServeHTTP exposes the adapter as a standard http.Handler.
-
-```go
-adapter := echoweb.New()
-adapter.Router().GET("/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) })
-rr := httptest.NewRecorder()
-req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
-adapter.ServeHTTP(rr, req)
-fmt.Println(rr.Code)
-// 204
-```
-
-#### echoweb.New
-
-New creates a new Echo-backed web adapter.
-
-```go
-adapter := echoweb.New()
-fmt.Println(adapter.Router() != nil, adapter.Echo() != nil)
-// true true
-```
-
-#### echoweb.NewServer
-
-NewServer creates an Echo-backed server from web route groups and mounts.
-
-```go
-server, err := echoweb.NewServer(echoweb.ServerConfig{
- RouteGroups: []web.RouteGroup{
- web.NewRouteGroup("/api", []web.Route{
- web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }),
- }),
- },
-})
-
-fmt.Println(err == nil, server.Router() != nil)
-// true true
-```
-
-#### echoweb.Server.Router
-
-Router exposes the app-facing router contract.
-
-```go
-server, _ := echoweb.NewServer(echoweb.ServerConfig{})
-fmt.Println(server.Router() != nil)
-// true
-```
-
-#### echoweb.Server.Serve
-
-Serve starts the server and gracefully shuts it down when ctx is cancelled.
-
-```go
-server, _ := echoweb.NewServer(echoweb.ServerConfig{Addr: "127.0.0.1:0"})
-ctx, cancel := context.WithCancel(context.Background())
-cancel()
-fmt.Println(server.Serve(ctx) == nil)
-// true
-```
-
-#### echoweb.Server.ServeHTTP
-
-ServeHTTP exposes the server as an http.Handler for tests and local probing.
-
-```go
-server, _ := echoweb.NewServer(echoweb.ServerConfig{
- RouteGroups: []web.RouteGroup{
- web.NewRouteGroup("/api", []web.Route{
- web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }),
- }),
- },
-})
-
-rr := httptest.NewRecorder()
-req := httptest.NewRequest(http.MethodGet, "/api/healthz", nil)
-server.ServeHTTP(rr, req)
-fmt.Println(rr.Code)
-// 204
-```
-
-#### echoweb.UnwrapContext
-
-UnwrapContext returns the underlying Echo context when the web.Context came from this adapter.
-
-```go
-adapter := echoweb.New()
-
-adapter.Router().GET("/healthz", func(c web.Context) error {
- _, ok := echoweb.UnwrapContext(c)
- fmt.Println(ok)
- return c.NoContent(http.StatusOK)
-})
-
-rr := httptest.NewRecorder()
-req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
-adapter.ServeHTTP(rr, req)
-// true
-```
-
-#### echoweb.UnwrapWebSocketConn
-
-UnwrapWebSocketConn returns the underlying gorilla websocket connection.
-
-```go
-_, ok := echoweb.UnwrapWebSocketConn(nil)
-fmt.Println(ok)
-// false
-```
-
-#### echoweb.Wrap
-
-Wrap exposes an existing Echo engine through the web.Router contract.
-
-```go
-adapter := echoweb.Wrap(nil)
-fmt.Println(adapter.Echo() != nil)
-// true
-```
-
-### Indexing
-
-#### webindex.Run
-
-Run indexes API metadata from source and writes artifacts.
-
-```go
-manifest, err := webindex.Run(context.Background(), webindex.IndexOptions{
- Root: ".",
- OutPath: "webindex.json",
-})
-
-fmt.Println(err == nil, manifest.Version != "")
-// true true
-```
-
-### Auth Middleware
-
-#### webmiddleware.BasicAuth
-
-BasicAuth returns basic auth middleware.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.BasicAuth(func(user, pass string, c web.Context) (bool, error) {
- return user == "demo" && pass == "secret", nil
-}))
-
-router.GET("/admin", func(c web.Context) error {
- return c.Text(200, "welcome")
-})
-```
-
-#### webmiddleware.BasicAuthWithConfig
-
-BasicAuthWithConfig returns basic auth middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.BasicAuthWithConfig(webmiddleware.BasicAuthConfig{
- Realm: "Admin",
- Validator: func(user, pass string, c web.Context) (bool, error) {
- return user == "demo" && pass == "secret", nil
- },
-}))
-
-router.GET("/admin", func(c web.Context) error {
- return c.Text(200, "welcome")
-})
-```
-
-#### webmiddleware.CSRF
-
-CSRF enables token-based CSRF protection.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.CSRF())
-
-router.POST("/settings", func(c web.Context) error {
- return c.NoContent(204)
-})
-```
-
-#### webmiddleware.CSRFWithConfig
-
-CSRFWithConfig enables token-based CSRF protection with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.CSRFWithConfig(webmiddleware.CSRFConfig{
- CookieName: "_csrf",
- TokenLookup: "header:X-CSRF-Token",
-}))
-
-router.POST("/settings", func(c web.Context) error {
- return c.NoContent(204)
-})
-```
-
-#### webmiddleware.CreateExtractors
-
-CreateExtractors creates extractors from a lookup definition.
-
-```go
-extractors, err := webmiddleware.CreateExtractors("header:X-API-Key,query:token")
-fmt.Println(err == nil, len(extractors))
-// true 2
-```
-
-#### webmiddleware.KeyAuth
-
-KeyAuth returns key auth middleware.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.KeyAuth(func(key string, c web.Context) (bool, error) {
- return key == "demo-key", nil
-}))
-
-router.GET("/api/reports", func(c web.Context) error {
- return c.JSON(200, map[string]any{"ready": true})
-})
-```
-
-#### webmiddleware.KeyAuthWithConfig
-
-KeyAuthWithConfig returns key auth middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.KeyAuthWithConfig(webmiddleware.KeyAuthConfig{
- KeyLookup: "query:api_key",
- Validator: func(key string, c web.Context) (bool, error) {
- return key == "demo-key", nil
- },
-}))
-
-router.GET("/api/reports", func(c web.Context) error {
- return c.JSON(200, map[string]any{"ready": true})
-})
-```
-
-### Compression Middleware
-
-#### webmiddleware.Compress
-
-Compress enables gzip response compression for clients that support it.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.Compress())
-
-router.GET("/reports", func(c web.Context) error {
- return c.Text(200, "large report response")
-})
-```
-
-#### webmiddleware.Decompress
-
-Decompress inflates gzip-encoded request bodies before handlers read them.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.Decompress())
-
-router.POST("/ingest", func(c web.Context) error {
- data, _ := io.ReadAll(c.Request().Body)
- return c.JSON(200, map[string]int{"bytes": len(data)})
-})
-```
-
-#### webmiddleware.DecompressWithConfig
-
-DecompressWithConfig inflates gzip-encoded request bodies with custom options.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.DecompressWithConfig(webmiddleware.DecompressConfig{
- Skipper: func(c web.Context) bool {
- return c.Path() == "/webhooks/raw"
- },
-}))
-
-router.POST("/ingest", func(c web.Context) error {
- return c.NoContent(202)
-})
-```
-
-#### webmiddleware.Gzip
-
-Gzip enables gzip response compression for clients that support it.
-
-```go
-router := echoweb.New().Router()
-
-router.GET("/feed", func(c web.Context) error {
- return c.Text(200, "large feed response")
-}, webmiddleware.Gzip())
-```
-
-#### webmiddleware.GzipWithConfig
-
-GzipWithConfig enables gzip response compression with custom options.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.GzipWithConfig(webmiddleware.GzipConfig{
- MinLength: 1024,
-}))
-```
-
-### Method Override Middleware
-
-#### webmiddleware.MethodFromForm
-
-MethodFromForm gets an override method from a form field.
-
-```go
-router := echoweb.New().Router()
-
-router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
- Getter: webmiddleware.MethodFromForm("_method"),
-}))
-```
-
-#### webmiddleware.MethodFromHeader
-
-MethodFromHeader gets an override method from a request header.
-
-```go
-router := echoweb.New().Router()
-
-router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
- Getter: webmiddleware.MethodFromHeader("X-HTTP-Method-Override"),
-}))
-```
-
-#### webmiddleware.MethodFromQuery
-
-MethodFromQuery gets an override method from a query parameter.
-
-```go
-router := echoweb.New().Router()
-
-router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
- Getter: webmiddleware.MethodFromQuery("_method"),
-}))
-```
-
-#### webmiddleware.MethodOverride
-
-MethodOverride returns method override middleware.
-
-```go
-router := echoweb.New().Router()
-router.Pre(webmiddleware.MethodOverride())
-
-router.PATCH("/articles/:id", func(c web.Context) error {
- return c.NoContent(204)
-})
-```
-
-#### webmiddleware.MethodOverrideWithConfig
-
-MethodOverrideWithConfig returns method override middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
- Getter: webmiddleware.MethodFromQuery("_method"),
-}))
-
-router.DELETE("/articles/:id", func(c web.Context) error {
- return c.NoContent(204)
-})
-```
-
-### Path Rewriting Middleware
-
-#### webmiddleware.AddTrailingSlash
-
-AddTrailingSlash adds a trailing slash to the request path.
-
-```go
-router := echoweb.New().Router()
-router.Pre(webmiddleware.AddTrailingSlash())
-
-router.GET("/docs/", func(c web.Context) error {
- return c.Text(200, "docs")
-})
-```
-
-#### webmiddleware.AddTrailingSlashWithConfig
-
-AddTrailingSlashWithConfig returns trailing-slash middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Pre(webmiddleware.AddTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{
- RedirectCode: 308,
-}))
-
-router.GET("/docs/", func(c web.Context) error {
- return c.Text(200, "docs")
-})
-```
-
-#### webmiddleware.RemoveTrailingSlash
-
-RemoveTrailingSlash removes the trailing slash from the request path.
-
-```go
-router := echoweb.New().Router()
-router.Pre(webmiddleware.RemoveTrailingSlash())
-
-router.GET("/docs", func(c web.Context) error {
- return c.Text(200, "docs")
-})
-```
-
-#### webmiddleware.RemoveTrailingSlashWithConfig
-
-RemoveTrailingSlashWithConfig returns remove-trailing-slash middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Pre(webmiddleware.RemoveTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{
- RedirectCode: 308,
-}))
-
-router.GET("/docs", func(c web.Context) error {
- return c.Text(200, "docs")
-})
-```
-
-#### webmiddleware.Rewrite
-
-Rewrite rewrites the request path using wildcard rules.
-
-```go
-router := echoweb.New().Router()
-
-router.Pre(webmiddleware.Rewrite(map[string]string{
- "/old/*": "/new/$1",
-}))
-
-router.GET("/new/:name", func(c web.Context) error {
- return c.Text(200, c.Param("name"))
-})
-```
-
-#### webmiddleware.RewriteWithConfig
-
-RewriteWithConfig rewrites the request path using wildcard and regex rules.
-
-```go
-router := echoweb.New().Router()
-
-router.Pre(webmiddleware.RewriteWithConfig(webmiddleware.RewriteConfig{
- Rules: map[string]string{"/old/*": "/v2/$1"},
-}))
-
-router.GET("/v2/:name", func(c web.Context) error {
- return c.Text(200, c.Param("name"))
-})
-```
-
-### Payloads Middleware
-
-#### webmiddleware.BodyDump
-
-BodyDump captures request and response payloads.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.BodyDump(func(c web.Context, reqBody, resBody []byte) {
- log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody))
-}))
-
-router.POST("/webhooks", func(c web.Context) error {
- return c.JSON(202, map[string]any{"queued": true})
-})
-```
-
-#### webmiddleware.BodyDumpWithConfig
-
-BodyDumpWithConfig captures request and response payloads with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.BodyDumpWithConfig(webmiddleware.BodyDumpConfig{
- Skipper: func(c web.Context) bool {
- return c.Path() == "/healthz"
- },
- Handler: func(c web.Context, reqBody, resBody []byte) {
- log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody))
- },
-}))
-```
-
-#### webmiddleware.BodyLimit
-
-BodyLimit returns middleware that limits request body size.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.BodyLimit("2MB"))
-
-router.POST("/uploads", func(c web.Context) error {
- return c.NoContent(204)
-})
-```
-
-#### webmiddleware.BodyLimitWithConfig
-
-BodyLimitWithConfig returns body limit middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.BodyLimitWithConfig(webmiddleware.BodyLimitConfig{
- Limit: "10MB",
-}))
-
-router.POST("/imports", func(c web.Context) error {
- return c.NoContent(202)
-})
-```
-
-#### webmiddleware.ErrorBodyDump
-
-ErrorBodyDump captures response bodies for non-2xx and non-3xx responses.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.ErrorBodyDump(func(c web.Context, status int, body []byte) {
- log.Printf("%s %s failed with %d", c.Method(), c.URI(), status)
-}))
-
-router.GET("/reports/:id", func(c web.Context) error {
- return c.Text(404, "report not found")
-})
-```
-
-#### webmiddleware.ErrorBodyDumpWithConfig
-
-ErrorBodyDumpWithConfig captures response bodies for non-success responses with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.ErrorBodyDumpWithConfig(webmiddleware.ErrorBodyDumpConfig{
- Skipper: func(c web.Context) bool {
- return c.Path() == "/healthz"
- },
- Handler: func(c web.Context, status int, body []byte) {
- log.Printf("%s %s failed with %d", c.Method(), c.URI(), status)
- },
-}))
-```
-
-### Proxying Middleware
-
-#### webmiddleware.NewRandomBalancer
-
-NewRandomBalancer creates a random proxy balancer.
-
-```go
-target, _ := url.Parse("http://localhost:8080")
-balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
-fmt.Println(balancer.Next(nil).URL.Host)
-// localhost:8080
-```
-
-#### webmiddleware.NewRoundRobinBalancer
-
-NewRoundRobinBalancer creates a round-robin proxy balancer.
-
-```go
-target, _ := url.Parse("http://localhost:8080")
-balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
-fmt.Println(balancer.Next(nil).URL.Host)
-// localhost:8080
-```
-
-#### webmiddleware.Proxy
-
-Proxy creates a proxy middleware.
-
-```go
-target, _ := url.Parse("http://localhost:8080")
-balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
-
-router := echoweb.New().Router()
-router.Use(webmiddleware.Proxy(balancer))
-```
-
-#### webmiddleware.ProxyWithConfig
-
-ProxyWithConfig creates a proxy middleware with config.
-
-```go
-target, _ := url.Parse("http://localhost:8080")
-balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
-
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.ProxyWithConfig(webmiddleware.ProxyConfig{
- Balancer: balancer,
- Rewrite: map[string]string{
- "/api/*": "/$1",
- },
-}))
-```
-
-### Rate Limiting Middleware
-
-#### webmiddleware.NewRateLimiterMemoryStore
-
-NewRateLimiterMemoryStore creates an in-memory rate limiter store.
-
-```go
-store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
-allowed1, _ := store.Allow("192.0.2.1")
-allowed2, _ := store.Allow("192.0.2.1")
-fmt.Println(allowed1, allowed2)
-// true false
-```
-
-#### webmiddleware.NewRateLimiterMemoryStoreWithConfig
-
-NewRateLimiterMemoryStoreWithConfig creates an in-memory rate limiter store with config.
-
-```go
-store := webmiddleware.NewRateLimiterMemoryStoreWithConfig(webmiddleware.RateLimiterMemoryStoreConfig{Rate: rate.Every(time.Second)})
-allowed, _ := store.Allow("192.0.2.1")
-fmt.Println(allowed)
-// true
-```
-
-#### webmiddleware.RateLimiter
-
-RateLimiter creates a rate limiting middleware.
-
-```go
-store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
-
-router := echoweb.New().Router()
-router.Use(webmiddleware.RateLimiter(store))
-
-router.POST("/api/messages", func(c web.Context) error {
- return c.NoContent(202)
-})
-```
-
-#### webmiddleware.RateLimiterMemoryStore.Allow
-
-Allow checks whether the given identifier is allowed through.
-
-```go
-store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
-allowed, err := store.Allow("127.0.0.1")
-fmt.Println(err == nil, allowed)
-// true true
-```
-
-#### webmiddleware.RateLimiterWithConfig
-
-RateLimiterWithConfig creates a rate limiting middleware with config.
-
-```go
-store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
-
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.RateLimiterWithConfig(webmiddleware.RateLimiterConfig{
- Store: store,
- IdentifierExtractor: func(c web.Context) (string, error) {
- return c.Header("X-Account-ID"), nil
- },
-}))
-```
-
-### Redirects Middleware
-
-#### webmiddleware.HTTPSNonWWWRedirect
-
-HTTPSNonWWWRedirect redirects to https without www.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.HTTPSNonWWWRedirect())
-```
-
-#### webmiddleware.HTTPSNonWWWRedirectWithConfig
-
-HTTPSNonWWWRedirectWithConfig returns HTTPS non-WWW redirect middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.HTTPSNonWWWRedirectWithConfig(webmiddleware.RedirectConfig{
- Code: 307,
-}))
-```
-
-#### webmiddleware.HTTPSRedirect
-
-HTTPSRedirect redirects http requests to https.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.HTTPSRedirect())
-
-router.GET("/docs", func(c web.Context) error {
- return c.Text(200, "docs")
-})
-```
-
-#### webmiddleware.HTTPSRedirectWithConfig
-
-HTTPSRedirectWithConfig returns HTTPS redirect middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.HTTPSRedirectWithConfig(webmiddleware.RedirectConfig{
- Code: 307,
-}))
-```
-
-#### webmiddleware.HTTPSWWWRedirect
-
-HTTPSWWWRedirect redirects to https + www.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.HTTPSWWWRedirect())
-```
-
-#### webmiddleware.HTTPSWWWRedirectWithConfig
-
-HTTPSWWWRedirectWithConfig returns HTTPS+WWW redirect middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.HTTPSWWWRedirectWithConfig(webmiddleware.RedirectConfig{
- Code: 307,
-}))
-```
-
-#### webmiddleware.NonWWWRedirect
-
-NonWWWRedirect redirects to the non-www host.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.NonWWWRedirect())
-```
-
-#### webmiddleware.NonWWWRedirectWithConfig
-
-NonWWWRedirectWithConfig returns non-WWW redirect middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.NonWWWRedirectWithConfig(webmiddleware.RedirectConfig{
- Code: 307,
-}))
-```
-
-#### webmiddleware.WWWRedirect
-
-WWWRedirect redirects to the www host.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.WWWRedirect())
-```
-
-#### webmiddleware.WWWRedirectWithConfig
-
-WWWRedirectWithConfig returns WWW redirect middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.WWWRedirectWithConfig(webmiddleware.RedirectConfig{
- Code: 307,
-}))
-```
-
-### Reliability Middleware
-
-#### webmiddleware.Recover
-
-Recover returns middleware that recovers panics from the handler chain.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.Recover())
-
-router.GET("/panic", func(c web.Context) error {
- panic("boom")
-})
-```
-
-#### webmiddleware.RecoverWithConfig
-
-RecoverWithConfig returns recover middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.RecoverWithConfig(webmiddleware.RecoverConfig{
- DisableStack: true,
- HandleError: func(c web.Context, err error, stack []byte) error {
- return c.JSON(500, map[string]any{"error": "internal server error"})
- },
-}))
-```
-
-### Request Lifecycle Middleware
-
-#### webmiddleware.ContextTimeout
-
-ContextTimeout sets a timeout on the request context.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.ContextTimeout(2 * time.Second))
-
-router.GET("/reports", func(c web.Context) error {
- return c.JSON(200, map[string]any{"ready": true})
-})
-```
-
-#### webmiddleware.ContextTimeoutWithConfig
-
-ContextTimeoutWithConfig sets a timeout on the request context with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.ContextTimeoutWithConfig(webmiddleware.ContextTimeoutConfig{
- Timeout: time.Second,
-}))
-```
-
-#### webmiddleware.DefaultSkipper
-
-DefaultSkipper always runs the middleware.
-
-```go
-fmt.Println(webmiddleware.DefaultSkipper(nil))
-// false
-```
-
-#### webmiddleware.RequestID
-
-RequestID returns middleware that sets a request id header and context value.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.RequestID())
-
-router.GET("/healthz", func(c web.Context) error {
- return c.JSON(200, map[string]any{
- "request_id": c.Get("request_id"),
- })
-})
-```
-
-#### webmiddleware.RequestIDWithConfig
-
-RequestIDWithConfig returns RequestID middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.RequestIDWithConfig(webmiddleware.RequestIDConfig{
- TargetHeader: "X-Correlation-ID",
- ContextKey: "correlation_id",
-}))
-```
-
-#### webmiddleware.RequestLoggerWithConfig
-
-RequestLoggerWithConfig returns request logger middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.RequestLoggerWithConfig(webmiddleware.RequestLoggerConfig{
- LogValuesFunc: func(c web.Context, values webmiddleware.RequestLoggerValues) error {
- log.Printf("%s %s %d %s", values.Method, values.URI, values.Status, values.Latency)
- return nil
- },
-}))
-
-router.GET("/users/:id", func(c web.Context) error {
- return c.NoContent(204)
-})
-```
-
-#### webmiddleware.Timeout
-
-Timeout returns a response-timeout middleware.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.Timeout())
-
-router.GET("/healthz", func(c web.Context) error {
- return c.NoContent(204)
-})
-```
-
-#### webmiddleware.TimeoutWithConfig
-
-TimeoutWithConfig returns a response-timeout middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.TimeoutWithConfig(webmiddleware.TimeoutConfig{
- Timeout: time.Second,
- ErrorMessage: "request timed out",
-}))
-```
-
-### Security Middleware
-
-#### webmiddleware.CORS
-
-CORS returns Cross-Origin Resource Sharing middleware.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.CORS())
-
-router.GET("/api/healthz", func(c web.Context) error {
- return c.JSON(200, map[string]any{"ok": true})
-})
-```
-
-#### webmiddleware.CORSWithConfig
-
-CORSWithConfig returns CORS middleware with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.CORSWithConfig(webmiddleware.CORSConfig{
- AllowOrigins: []string{"https://app.example.com"},
- AllowMethods: []string{"GET", "POST", "PATCH"},
-}))
-
-router.GET("/api/healthz", func(c web.Context) error {
- return c.JSON(200, map[string]any{"ok": true})
-})
-```
-
-#### webmiddleware.Secure
-
-Secure sets security-oriented response headers.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.Secure())
-
-router.GET("/", func(c web.Context) error {
- return c.Text(200, "home")
-})
-```
-
-#### webmiddleware.SecureWithConfig
-
-SecureWithConfig sets security-oriented response headers with config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.SecureWithConfig(webmiddleware.SecureConfig{
- ReferrerPolicy: "same-origin",
- ContentSecurityPolicy: "default-src 'self'",
-}))
-```
-
-### Static Files Middleware
-
-#### webmiddleware.Static
-
-Static serves static content from the provided root.
-
-```go
-router := echoweb.New().Router()
-router.Use(webmiddleware.Static("public"))
-
-router.GET("/healthz", func(c web.Context) error {
- return c.NoContent(204)
-})
-```
-
-#### webmiddleware.StaticWithConfig
-
-StaticWithConfig serves static content using config.
-
-```go
-router := echoweb.New().Router()
-
-router.Use(webmiddleware.StaticWithConfig(webmiddleware.StaticConfig{
- Root: "public",
- HTML5: true,
-}))
-```
-
-### Prometheus
-
-#### webprometheus.Default
-
-Default returns the package-level Prometheus metrics instance.
-
-```go
-fmt.Println(webprometheus.Default() == webprometheus.Default())
-// true
-```
-
-#### webprometheus.Handler
-
-Handler returns the package-level Prometheus scrape handler.
-
-```go
-registry := prometheus.NewRegistry()
-counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"})
-registry.MustRegister(counter)
-counter.Inc()
-metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry})
-recorder := httptest.NewRecorder()
-ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil)
-_ = metrics.Handler()(ctx)
-fmt.Println(strings.Contains(recorder.Body.String(), "demo_total"))
-// true
-```
-
-#### webprometheus.Metrics.Handler
-
-Handler exposes the configured Prometheus metrics as a web.Handler.
-
-```go
-registry := prometheus.NewRegistry()
-counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"})
-registry.MustRegister(counter)
-counter.Inc()
-metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry})
-recorder := httptest.NewRecorder()
-ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil)
-_ = metrics.Handler()(ctx)
-fmt.Println(strings.Contains(recorder.Body.String(), "demo_total"))
-// true
-```
-
-#### webprometheus.Metrics.Middleware
-
-Middleware records Prometheus metrics for each request.
-
-```go
-registry := prometheus.NewRegistry()
-metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"})
-handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) })
-ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil)
-_ = handler(ctx)
-out := &bytes.Buffer{}
-_ = webprometheus.WriteGatheredMetrics(out, registry)
-fmt.Println(strings.Contains(out.String(), "example_requests_total"))
-// true
-```
-
-#### webprometheus.Middleware
-
-Middleware returns the package-level Prometheus middleware.
-
-```go
-registry := prometheus.NewRegistry()
-metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"})
-handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) })
-ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil)
-_ = handler(ctx)
-out := &bytes.Buffer{}
-_ = webprometheus.WriteGatheredMetrics(out, registry)
-fmt.Println(strings.Contains(out.String(), "example_requests_total"))
-// true
-```
-
-#### webprometheus.MustNew
-
-MustNew creates a Metrics instance and panics on registration errors.
-
-```go
-metrics := webprometheus.MustNew(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: prometheus.NewRegistry()})
-fmt.Println(metrics != nil)
-// true
-```
-
-#### webprometheus.New
-
-New creates a Metrics instance backed by Prometheus collectors.
-
-```go
-metrics, err := webprometheus.New(webprometheus.Config{Namespace: "app"})
-_ = metrics
-fmt.Println(err == nil)
-// true
-```
-
-#### webprometheus.RunPushGatewayGatherer
-
-RunPushGatewayGatherer starts pushing collected metrics until the context finishes.
-
-```go
-err := webprometheus.RunPushGatewayGatherer(context.Background(), webprometheus.PushGatewayConfig{})
-fmt.Println(err != nil)
-// true
-```
-
-#### webprometheus.WriteGatheredMetrics
-
-WriteGatheredMetrics gathers collected metrics and writes them to the given writer.
-
-```go
-var buf bytes.Buffer
-err := webprometheus.WriteGatheredMetrics(&buf, prometheus.NewRegistry())
-fmt.Println(err == nil)
-// true
-```
-
-### Route Reporting
-
-#### BuildRouteEntries
-
-BuildRouteEntries builds a sorted slice of route entries from registered groups and extra entries.
-
-```go
-entries := web.BuildRouteEntries([]web.RouteGroup{
- web.NewRouteGroup("/api", []web.Route{
- web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
- }),
-})
-fmt.Println(entries[0].Path, entries[0].Methods[0])
-// /api/healthz GET
-```
-
-#### RenderRouteTable
-
-RenderRouteTable renders a route table using simple ASCII borders and ANSI colors.
-
-```go
-table := web.RenderRouteTable([]web.RouteEntry{{
- Path: "/api/healthz",
- Handler: "monitoring.Healthz",
- Methods: []string{"GET"},
-}})
-fmt.Println(strings.Contains(table, "/api/healthz"))
-// true
-```
-
-### Routing
-
-#### MountRouter
-
-MountRouter applies mount-style router configuration in declaration order.
-
-```go
-adapter := echoweb.New()
-
-err := web.MountRouter(adapter.Router(), []web.RouterMount{
- func(r web.Router) error {
- r.GET("/healthz", func(c web.Context) error { return nil })
- return nil
- },
-})
-
-fmt.Println(err == nil)
-// true
-```
-
-#### NewRoute
-
-NewRoute creates a new route using the app-facing web handler contract directly.
-
-```go
-route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error {
- return c.NoContent(http.StatusOK)
-})
-
-fmt.Println(route.Method(), route.Path())
-// GET /healthz
-```
-
-#### NewRouteGroup
-
-NewRouteGroup wraps routes and their accompanied web middleware.
-
-```go
-group := web.NewRouteGroup("/api", []web.Route{
- web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
-})
-
-fmt.Println(group.RoutePrefix(), len(group.Routes()))
-// /api 1
-```
-
-#### NewWebSocketRoute
-
-NewWebSocketRoute creates a websocket route using the app-facing websocket handler contract.
-
-```go
-route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error {
- return nil
-})
-
-fmt.Println(route.IsWebSocket())
-// true
-```
-
-#### RegisterRoutes
-
-RegisterRoutes registers route groups onto a router.
-
-```go
-adapter := echoweb.New()
-
-groups := []web.RouteGroup{
- web.NewRouteGroup("/api", []web.Route{
- web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
- }),
-}
-
-err := web.RegisterRoutes(adapter.Router(), groups)
-fmt.Println(err == nil)
-// true
-```
-
-#### Route.Handler
-
-Handler returns the route handler.
-
-```go
-route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error {
- return c.NoContent(http.StatusCreated)
-})
-
-ctx := webtest.NewContext(nil, nil, "/healthz", nil)
-_ = route.Handler()(ctx)
-fmt.Println(ctx.StatusCode())
-// 201
-```
-
-#### Route.HandlerName
-
-HandlerName returns the original handler name for route reporting.
-
-```go
-route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil })
-fmt.Println(route.HandlerName() != "")
-// true
-```
-
-#### Route.IsWebSocket
-
-IsWebSocket reports whether this route upgrades to a websocket connection.
-
-```go
-route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error { return nil })
-fmt.Println(route.IsWebSocket())
-// true
-```
-
-#### Route.Method
-
-Method returns the HTTP method.
-
-```go
-route := web.NewRoute(http.MethodPost, "/users", func(c web.Context) error { return nil })
-fmt.Println(route.Method())
-// POST
-```
-
-#### Route.MiddlewareNames
-
-MiddlewareNames returns original middleware names for route reporting.
-
-```go
-route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth")
-fmt.Println(route.MiddlewareNames()[0])
-// auth
-```
-
-#### Route.Middlewares
-
-Middlewares returns the route middleware slice.
-
-```go
-route := web.NewRoute(
-
-http.MethodGet,
-"/healthz",
-func(c web.Context) error { return nil },
-func(next web.Handler) web.Handler { return next },
-
-)
-fmt.Println(len(route.Middlewares()))
-// 1
-```
-
-#### Route.Path
-
-Path returns the path of the route.
-
-```go
-route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil })
-fmt.Println(route.Path())
-// /healthz
-```
-
-#### Route.WebSocketHandler
-
-WebSocketHandler returns the websocket route handler.
-
-```go
-route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error {
- c.Set("ready", true)
- return nil
-})
-
-ctx := webtest.NewContext(nil, nil, "/ws", nil)
-err := route.WebSocketHandler()(ctx, nil)
-fmt.Println(err == nil, ctx.Get("ready"))
-// true true
-```
-
-#### Route.WithMiddlewareNames
-
-WithMiddlewareNames attaches reporting-only middleware names to the route.
-
-```go
-route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth", "trace")
-fmt.Println(len(route.MiddlewareNames()))
-// 2
-```
-
-#### RouteGroup.MiddlewareNames
-
-MiddlewareNames returns original middleware names for route reporting.
-
-```go
-group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth")
-fmt.Println(group.MiddlewareNames()[0])
-// auth
-```
-
-#### RouteGroup.Middlewares
-
-Middlewares returns the middleware slice for the group.
-
-```go
-group := web.NewRouteGroup("/api", nil, func(next web.Handler) web.Handler { return next })
-fmt.Println(len(group.Middlewares()))
-// 1
-```
-
-#### RouteGroup.RoutePrefix
-
-RoutePrefix returns the group prefix.
-
-```go
-group := web.NewRouteGroup("/api", nil)
-fmt.Println(group.RoutePrefix())
-// /api
-```
-
-#### RouteGroup.Routes
-
-Routes returns the routes in the group.
-
-```go
-group := web.NewRouteGroup("/api", []web.Route{
- web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
-})
-
-fmt.Println(len(group.Routes()))
-// 1
-```
-
-#### RouteGroup.WithMiddlewareNames
-
-WithMiddlewareNames attaches reporting-only middleware names to the group.
-
-```go
-group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth", "trace")
-fmt.Println(len(group.MiddlewareNames()))
-// 2
-```
-
-### Testing
-
-#### webtest.NewContext
-
-NewContext creates a new test context around the provided request/recorder pair.
-
-```go
-req := httptest.NewRequest(http.MethodGet, "/users/42?expand=roles", nil)
-ctx := webtest.NewContext(req, nil, "/users/:id", webtest.PathParams{"id": "42"})
-fmt.Println(ctx.Param("id"), ctx.Query("expand"))
-// 42 roles
-```
diff --git a/README.md b/README.md
index 1c17858..eacec4a 100644
--- a/README.md
+++ b/README.md
@@ -274,10 +274,1491 @@ adapter.Echo().IPExtractor = echo.ExtractIPDirect()
Behind a trusted proxy, configure `echo.ExtractIPFromXFFHeader` or `echo.ExtractIPFromRealIPHeader` with trust options that match the deployment, and ensure the edge proxy removes client-supplied forwarding headers before adding its own.
-## Reference
+## API
-The [complete API documentation](https://pkg.go.dev/github.com/goforj/web) includes every exported type, field, function, and method.
+## API Index
-The [generated API examples](API.md) collect the functions and methods annotated with runnable examples in this repository.
+| Group | Functions |
+|------:|:-----------|
+| **Adapter** | [Adapter.Echo](#echoweb-adapter-echo) · [Adapter.Router](#echoweb-adapter-router) · [Adapter.ServeHTTP](#echoweb-adapter-servehttp) · [New](#echoweb-new) · [NewServer](#echoweb-newserver) · [Server.Router](#echoweb-server-router) · [Server.Serve](#echoweb-server-serve) · [Server.ServeHTTP](#echoweb-server-servehttp) · [UnwrapContext](#echoweb-unwrapcontext) · [UnwrapWebSocketConn](#echoweb-unwrapwebsocketconn) · [Wrap](#echoweb-wrap) |
+| **Indexing** | [Run](#webindex-run) |
+| **Middleware
Auth** | [BasicAuth](#webmiddleware-basicauth) · [BasicAuthWithConfig](#webmiddleware-basicauthwithconfig) · [CSRF](#webmiddleware-csrf) · [CSRFWithConfig](#webmiddleware-csrfwithconfig) · [CreateExtractors](#webmiddleware-createextractors) · [KeyAuth](#webmiddleware-keyauth) · [KeyAuthWithConfig](#webmiddleware-keyauthwithconfig) |
+| **Middleware
Compression** | [Compress](#webmiddleware-compress) · [Decompress](#webmiddleware-decompress) · [DecompressWithConfig](#webmiddleware-decompresswithconfig) · [Gzip](#webmiddleware-gzip) · [GzipWithConfig](#webmiddleware-gzipwithconfig) |
+| **Middleware
Method Override** | [MethodFromForm](#webmiddleware-methodfromform) · [MethodFromHeader](#webmiddleware-methodfromheader) · [MethodFromQuery](#webmiddleware-methodfromquery) · [MethodOverride](#webmiddleware-methodoverride) · [MethodOverrideWithConfig](#webmiddleware-methodoverridewithconfig) |
+| **Middleware
Path Rewriting** | [AddTrailingSlash](#webmiddleware-addtrailingslash) · [AddTrailingSlashWithConfig](#webmiddleware-addtrailingslashwithconfig) · [RemoveTrailingSlash](#webmiddleware-removetrailingslash) · [RemoveTrailingSlashWithConfig](#webmiddleware-removetrailingslashwithconfig) · [Rewrite](#webmiddleware-rewrite) · [RewriteWithConfig](#webmiddleware-rewritewithconfig) |
+| **Middleware
Payloads** | [BodyDump](#webmiddleware-bodydump) · [BodyDumpWithConfig](#webmiddleware-bodydumpwithconfig) · [BodyLimit](#webmiddleware-bodylimit) · [BodyLimitWithConfig](#webmiddleware-bodylimitwithconfig) · [ErrorBodyDump](#webmiddleware-errorbodydump) · [ErrorBodyDumpWithConfig](#webmiddleware-errorbodydumpwithconfig) |
+| **Middleware
Proxying** | [NewRandomBalancer](#webmiddleware-newrandombalancer) · [NewRoundRobinBalancer](#webmiddleware-newroundrobinbalancer) · [Proxy](#webmiddleware-proxy) · [ProxyWithConfig](#webmiddleware-proxywithconfig) |
+| **Middleware
Rate Limiting** | [NewRateLimiterMemoryStore](#webmiddleware-newratelimitermemorystore) · [NewRateLimiterMemoryStoreWithConfig](#webmiddleware-newratelimitermemorystorewithconfig) · [RateLimiter](#webmiddleware-ratelimiter) · [RateLimiterMemoryStore.Allow](#webmiddleware-ratelimitermemorystore-allow) · [RateLimiterWithConfig](#webmiddleware-ratelimiterwithconfig) |
+| **Middleware
Redirects** | [HTTPSNonWWWRedirect](#webmiddleware-httpsnonwwwredirect) · [HTTPSNonWWWRedirectWithConfig](#webmiddleware-httpsnonwwwredirectwithconfig) · [HTTPSRedirect](#webmiddleware-httpsredirect) · [HTTPSRedirectWithConfig](#webmiddleware-httpsredirectwithconfig) · [HTTPSWWWRedirect](#webmiddleware-httpswwwredirect) · [HTTPSWWWRedirectWithConfig](#webmiddleware-httpswwwredirectwithconfig) · [NonWWWRedirect](#webmiddleware-nonwwwredirect) · [NonWWWRedirectWithConfig](#webmiddleware-nonwwwredirectwithconfig) · [WWWRedirect](#webmiddleware-wwwredirect) · [WWWRedirectWithConfig](#webmiddleware-wwwredirectwithconfig) |
+| **Middleware
Reliability** | [Recover](#webmiddleware-recover) · [RecoverWithConfig](#webmiddleware-recoverwithconfig) |
+| **Middleware
Request Lifecycle** | [ContextTimeout](#webmiddleware-contexttimeout) · [ContextTimeoutWithConfig](#webmiddleware-contexttimeoutwithconfig) · [DefaultSkipper](#webmiddleware-defaultskipper) · [RequestID](#webmiddleware-requestid) · [RequestIDWithConfig](#webmiddleware-requestidwithconfig) · [RequestLoggerWithConfig](#webmiddleware-requestloggerwithconfig) · [Timeout](#webmiddleware-timeout) · [TimeoutWithConfig](#webmiddleware-timeoutwithconfig) |
+| **Middleware
Security** | [CORS](#webmiddleware-cors) · [CORSWithConfig](#webmiddleware-corswithconfig) · [Secure](#webmiddleware-secure) · [SecureWithConfig](#webmiddleware-securewithconfig) |
+| **Middleware
Static Files** | [Static](#webmiddleware-static) · [StaticWithConfig](#webmiddleware-staticwithconfig) |
+| **Prometheus** | [Default](#webprometheus-default) · [Handler](#webprometheus-handler) · [Metrics.Handler](#webprometheus-metrics-handler) · [Metrics.Middleware](#webprometheus-metrics-middleware) · [Middleware](#webprometheus-middleware) · [MustNew](#webprometheus-mustnew) · [New](#webprometheus-new) · [RunPushGatewayGatherer](#webprometheus-runpushgatewaygatherer) · [WriteGatheredMetrics](#webprometheus-writegatheredmetrics) |
+| **Route Reporting** | [BuildRouteEntries](#buildrouteentries) · [RenderRouteTable](#renderroutetable) |
+| **Routing** | [MountRouter](#mountrouter) · [NewRoute](#newroute) · [NewRouteGroup](#newroutegroup) · [NewWebSocketRoute](#newwebsocketroute) · [RegisterRoutes](#registerroutes) · [Route.Handler](#route-handler) · [Route.HandlerName](#route-handlername) · [Route.IsWebSocket](#route-iswebsocket) · [Route.Method](#route-method) · [Route.MiddlewareNames](#route-middlewarenames) · [Route.Middlewares](#route-middlewares) · [Route.Path](#route-path) · [Route.WebSocketHandler](#route-websockethandler) · [Route.WithMiddlewareNames](#route-withmiddlewarenames) · [RouteGroup.MiddlewareNames](#routegroup-middlewarenames) · [RouteGroup.Middlewares](#routegroup-middlewares) · [RouteGroup.RoutePrefix](#routegroup-routeprefix) · [RouteGroup.Routes](#routegroup-routes) · [RouteGroup.WithMiddlewareNames](#routegroup-withmiddlewarenames) |
+| **Testing** | [NewContext](#webtest-newcontext) |
+
+
+## API Reference
+
+_Generated from public API comments and examples._
+
+### Adapter
+
+#### echoweb.Adapter.Echo
+
+Echo returns the underlying Echo engine.
+
+```go
+adapter := echoweb.New()
+fmt.Println(adapter.Echo() != nil)
+// true
+```
+
+#### echoweb.Adapter.Router
+
+Router returns the app-facing router contract.
+
+```go
+adapter := echoweb.New()
+fmt.Println(adapter.Router() != nil)
+// true
+```
+
+#### echoweb.Adapter.ServeHTTP
+
+ServeHTTP exposes the adapter as a standard http.Handler.
+
+```go
+adapter := echoweb.New()
+adapter.Router().GET("/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) })
+rr := httptest.NewRecorder()
+req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
+adapter.ServeHTTP(rr, req)
+fmt.Println(rr.Code)
+// 204
+```
+
+#### echoweb.New
+
+New creates a new Echo-backed web adapter.
+
+```go
+adapter := echoweb.New()
+fmt.Println(adapter.Router() != nil, adapter.Echo() != nil)
+// true true
+```
+
+#### echoweb.NewServer
+
+NewServer creates an Echo-backed server from web route groups and mounts.
+
+```go
+server, err := echoweb.NewServer(echoweb.ServerConfig{
+ RouteGroups: []web.RouteGroup{
+ web.NewRouteGroup("/api", []web.Route{
+ web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }),
+ }),
+ },
+})
+
+fmt.Println(err == nil, server.Router() != nil)
+// true true
+```
+
+#### echoweb.Server.Router
+
+Router exposes the app-facing router contract.
+
+```go
+server, _ := echoweb.NewServer(echoweb.ServerConfig{})
+fmt.Println(server.Router() != nil)
+// true
+```
+
+#### echoweb.Server.Serve
+
+Serve starts the server and gracefully shuts it down when ctx is cancelled.
+
+```go
+server, _ := echoweb.NewServer(echoweb.ServerConfig{Addr: "127.0.0.1:0"})
+ctx, cancel := context.WithCancel(context.Background())
+cancel()
+fmt.Println(server.Serve(ctx) == nil)
+// true
+```
+
+#### echoweb.Server.ServeHTTP
+
+ServeHTTP exposes the server as an http.Handler for tests and local probing.
+
+```go
+server, _ := echoweb.NewServer(echoweb.ServerConfig{
+ RouteGroups: []web.RouteGroup{
+ web.NewRouteGroup("/api", []web.Route{
+ web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }),
+ }),
+ },
+})
+
+rr := httptest.NewRecorder()
+req := httptest.NewRequest(http.MethodGet, "/api/healthz", nil)
+server.ServeHTTP(rr, req)
+fmt.Println(rr.Code)
+// 204
+```
+
+#### echoweb.UnwrapContext
+
+UnwrapContext returns the underlying Echo context when the web.Context came from this adapter.
+
+```go
+adapter := echoweb.New()
+
+adapter.Router().GET("/healthz", func(c web.Context) error {
+ _, ok := echoweb.UnwrapContext(c)
+ fmt.Println(ok)
+ return c.NoContent(http.StatusOK)
+})
+
+rr := httptest.NewRecorder()
+req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
+adapter.ServeHTTP(rr, req)
+// true
+```
+
+#### echoweb.UnwrapWebSocketConn
+
+UnwrapWebSocketConn returns the underlying gorilla websocket connection.
+
+```go
+_, ok := echoweb.UnwrapWebSocketConn(nil)
+fmt.Println(ok)
+// false
+```
+
+#### echoweb.Wrap
+
+Wrap exposes an existing Echo engine through the web.Router contract.
+
+```go
+adapter := echoweb.Wrap(nil)
+fmt.Println(adapter.Echo() != nil)
+// true
+```
+
+### Indexing
+
+#### webindex.Run
+
+Run indexes API metadata from source and writes artifacts.
+
+```go
+manifest, err := webindex.Run(context.Background(), webindex.IndexOptions{
+ Root: ".",
+ OutPath: "webindex.json",
+})
+
+fmt.Println(err == nil, manifest.Version != "")
+// true true
+```
+
+### Auth Middleware
+
+#### webmiddleware.BasicAuth
+
+BasicAuth returns basic auth middleware.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.BasicAuth(func(user, pass string, c web.Context) (bool, error) {
+ return user == "demo" && pass == "secret", nil
+}))
+
+router.GET("/admin", func(c web.Context) error {
+ return c.Text(200, "welcome")
+})
+```
+
+#### webmiddleware.BasicAuthWithConfig
+
+BasicAuthWithConfig returns basic auth middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.BasicAuthWithConfig(webmiddleware.BasicAuthConfig{
+ Realm: "Admin",
+ Validator: func(user, pass string, c web.Context) (bool, error) {
+ return user == "demo" && pass == "secret", nil
+ },
+}))
+
+router.GET("/admin", func(c web.Context) error {
+ return c.Text(200, "welcome")
+})
+```
+
+#### webmiddleware.CSRF
+
+CSRF enables token-based CSRF protection.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.CSRF())
+
+router.POST("/settings", func(c web.Context) error {
+ return c.NoContent(204)
+})
+```
+
+#### webmiddleware.CSRFWithConfig
+
+CSRFWithConfig enables token-based CSRF protection with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.CSRFWithConfig(webmiddleware.CSRFConfig{
+ CookieName: "_csrf",
+ TokenLookup: "header:X-CSRF-Token",
+}))
+
+router.POST("/settings", func(c web.Context) error {
+ return c.NoContent(204)
+})
+```
+
+#### webmiddleware.CreateExtractors
+
+CreateExtractors creates extractors from a lookup definition.
+
+```go
+extractors, err := webmiddleware.CreateExtractors("header:X-API-Key,query:token")
+fmt.Println(err == nil, len(extractors))
+// true 2
+```
+
+#### webmiddleware.KeyAuth
+
+KeyAuth returns key auth middleware.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.KeyAuth(func(key string, c web.Context) (bool, error) {
+ return key == "demo-key", nil
+}))
+
+router.GET("/api/reports", func(c web.Context) error {
+ return c.JSON(200, map[string]any{"ready": true})
+})
+```
+
+#### webmiddleware.KeyAuthWithConfig
+
+KeyAuthWithConfig returns key auth middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.KeyAuthWithConfig(webmiddleware.KeyAuthConfig{
+ KeyLookup: "query:api_key",
+ Validator: func(key string, c web.Context) (bool, error) {
+ return key == "demo-key", nil
+ },
+}))
+
+router.GET("/api/reports", func(c web.Context) error {
+ return c.JSON(200, map[string]any{"ready": true})
+})
+```
+
+### Compression Middleware
+
+#### webmiddleware.Compress
+
+Compress enables gzip response compression for clients that support it.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.Compress())
+
+router.GET("/reports", func(c web.Context) error {
+ return c.Text(200, "large report response")
+})
+```
+
+#### webmiddleware.Decompress
+
+Decompress inflates gzip-encoded request bodies before handlers read them.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.Decompress())
+
+router.POST("/ingest", func(c web.Context) error {
+ data, _ := io.ReadAll(c.Request().Body)
+ return c.JSON(200, map[string]int{"bytes": len(data)})
+})
+```
+
+#### webmiddleware.DecompressWithConfig
+
+DecompressWithConfig inflates gzip-encoded request bodies with custom options.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.DecompressWithConfig(webmiddleware.DecompressConfig{
+ Skipper: func(c web.Context) bool {
+ return c.Path() == "/webhooks/raw"
+ },
+}))
+
+router.POST("/ingest", func(c web.Context) error {
+ return c.NoContent(202)
+})
+```
+
+#### webmiddleware.Gzip
+
+Gzip enables gzip response compression for clients that support it.
+
+```go
+router := echoweb.New().Router()
+
+router.GET("/feed", func(c web.Context) error {
+ return c.Text(200, "large feed response")
+}, webmiddleware.Gzip())
+```
+
+#### webmiddleware.GzipWithConfig
+
+GzipWithConfig enables gzip response compression with custom options.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.GzipWithConfig(webmiddleware.GzipConfig{
+ MinLength: 1024,
+}))
+```
+
+### Method Override Middleware
+
+#### webmiddleware.MethodFromForm
+
+MethodFromForm gets an override method from a form field.
+
+```go
+router := echoweb.New().Router()
+
+router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
+ Getter: webmiddleware.MethodFromForm("_method"),
+}))
+```
+
+#### webmiddleware.MethodFromHeader
+
+MethodFromHeader gets an override method from a request header.
+
+```go
+router := echoweb.New().Router()
+
+router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
+ Getter: webmiddleware.MethodFromHeader("X-HTTP-Method-Override"),
+}))
+```
+
+#### webmiddleware.MethodFromQuery
+
+MethodFromQuery gets an override method from a query parameter.
+
+```go
+router := echoweb.New().Router()
+
+router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
+ Getter: webmiddleware.MethodFromQuery("_method"),
+}))
+```
+
+#### webmiddleware.MethodOverride
+
+MethodOverride returns method override middleware.
+
+```go
+router := echoweb.New().Router()
+router.Pre(webmiddleware.MethodOverride())
+
+router.PATCH("/articles/:id", func(c web.Context) error {
+ return c.NoContent(204)
+})
+```
+
+#### webmiddleware.MethodOverrideWithConfig
+
+MethodOverrideWithConfig returns method override middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
+ Getter: webmiddleware.MethodFromQuery("_method"),
+}))
+
+router.DELETE("/articles/:id", func(c web.Context) error {
+ return c.NoContent(204)
+})
+```
+
+### Path Rewriting Middleware
+
+#### webmiddleware.AddTrailingSlash
+
+AddTrailingSlash adds a trailing slash to the request path.
+
+```go
+router := echoweb.New().Router()
+router.Pre(webmiddleware.AddTrailingSlash())
+
+router.GET("/docs/", func(c web.Context) error {
+ return c.Text(200, "docs")
+})
+```
+
+#### webmiddleware.AddTrailingSlashWithConfig
+
+AddTrailingSlashWithConfig returns trailing-slash middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Pre(webmiddleware.AddTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{
+ RedirectCode: 308,
+}))
+
+router.GET("/docs/", func(c web.Context) error {
+ return c.Text(200, "docs")
+})
+```
+
+#### webmiddleware.RemoveTrailingSlash
+
+RemoveTrailingSlash removes the trailing slash from the request path.
+
+```go
+router := echoweb.New().Router()
+router.Pre(webmiddleware.RemoveTrailingSlash())
+
+router.GET("/docs", func(c web.Context) error {
+ return c.Text(200, "docs")
+})
+```
+
+#### webmiddleware.RemoveTrailingSlashWithConfig
+
+RemoveTrailingSlashWithConfig returns remove-trailing-slash middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Pre(webmiddleware.RemoveTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{
+ RedirectCode: 308,
+}))
+
+router.GET("/docs", func(c web.Context) error {
+ return c.Text(200, "docs")
+})
+```
+
+#### webmiddleware.Rewrite
+
+Rewrite rewrites the request path using wildcard rules.
+
+```go
+router := echoweb.New().Router()
+
+router.Pre(webmiddleware.Rewrite(map[string]string{
+ "/old/*": "/new/$1",
+}))
+
+router.GET("/new/:name", func(c web.Context) error {
+ return c.Text(200, c.Param("name"))
+})
+```
+
+#### webmiddleware.RewriteWithConfig
+
+RewriteWithConfig rewrites the request path using wildcard and regex rules.
+
+```go
+router := echoweb.New().Router()
+
+router.Pre(webmiddleware.RewriteWithConfig(webmiddleware.RewriteConfig{
+ Rules: map[string]string{"/old/*": "/v2/$1"},
+}))
+
+router.GET("/v2/:name", func(c web.Context) error {
+ return c.Text(200, c.Param("name"))
+})
+```
+
+### Payloads Middleware
+
+#### webmiddleware.BodyDump
+
+BodyDump captures request and response payloads.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.BodyDump(func(c web.Context, reqBody, resBody []byte) {
+ log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody))
+}))
+
+router.POST("/webhooks", func(c web.Context) error {
+ return c.JSON(202, map[string]any{"queued": true})
+})
+```
+
+#### webmiddleware.BodyDumpWithConfig
+
+BodyDumpWithConfig captures request and response payloads with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.BodyDumpWithConfig(webmiddleware.BodyDumpConfig{
+ Skipper: func(c web.Context) bool {
+ return c.Path() == "/healthz"
+ },
+ Handler: func(c web.Context, reqBody, resBody []byte) {
+ log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody))
+ },
+}))
+```
+
+#### webmiddleware.BodyLimit
+
+BodyLimit returns middleware that limits request body size.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.BodyLimit("2MB"))
+
+router.POST("/uploads", func(c web.Context) error {
+ return c.NoContent(204)
+})
+```
+
+#### webmiddleware.BodyLimitWithConfig
+
+BodyLimitWithConfig returns body limit middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.BodyLimitWithConfig(webmiddleware.BodyLimitConfig{
+ Limit: "10MB",
+}))
+
+router.POST("/imports", func(c web.Context) error {
+ return c.NoContent(202)
+})
+```
+
+#### webmiddleware.ErrorBodyDump
+
+ErrorBodyDump captures response bodies for non-2xx and non-3xx responses.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.ErrorBodyDump(func(c web.Context, status int, body []byte) {
+ log.Printf("%s %s failed with %d", c.Method(), c.URI(), status)
+}))
+
+router.GET("/reports/:id", func(c web.Context) error {
+ return c.Text(404, "report not found")
+})
+```
+
+#### webmiddleware.ErrorBodyDumpWithConfig
+
+ErrorBodyDumpWithConfig captures response bodies for non-success responses with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.ErrorBodyDumpWithConfig(webmiddleware.ErrorBodyDumpConfig{
+ Skipper: func(c web.Context) bool {
+ return c.Path() == "/healthz"
+ },
+ Handler: func(c web.Context, status int, body []byte) {
+ log.Printf("%s %s failed with %d", c.Method(), c.URI(), status)
+ },
+}))
+```
+
+### Proxying Middleware
+
+#### webmiddleware.NewRandomBalancer
+
+NewRandomBalancer creates a random proxy balancer.
+
+```go
+target, _ := url.Parse("http://localhost:8080")
+balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
+fmt.Println(balancer.Next(nil).URL.Host)
+// localhost:8080
+```
+
+#### webmiddleware.NewRoundRobinBalancer
+
+NewRoundRobinBalancer creates a round-robin proxy balancer.
+
+```go
+target, _ := url.Parse("http://localhost:8080")
+balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
+fmt.Println(balancer.Next(nil).URL.Host)
+// localhost:8080
+```
+
+#### webmiddleware.Proxy
+
+Proxy creates a proxy middleware.
+
+```go
+target, _ := url.Parse("http://localhost:8080")
+balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
+
+router := echoweb.New().Router()
+router.Use(webmiddleware.Proxy(balancer))
+```
+
+#### webmiddleware.ProxyWithConfig
+
+ProxyWithConfig creates a proxy middleware with config.
+
+```go
+target, _ := url.Parse("http://localhost:8080")
+balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
+
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.ProxyWithConfig(webmiddleware.ProxyConfig{
+ Balancer: balancer,
+ Rewrite: map[string]string{
+ "/api/*": "/$1",
+ },
+}))
+```
+
+### Rate Limiting Middleware
+
+#### webmiddleware.NewRateLimiterMemoryStore
+
+NewRateLimiterMemoryStore creates an in-memory rate limiter store.
+
+```go
+store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
+allowed1, _ := store.Allow("192.0.2.1")
+allowed2, _ := store.Allow("192.0.2.1")
+fmt.Println(allowed1, allowed2)
+// true false
+```
+
+#### webmiddleware.NewRateLimiterMemoryStoreWithConfig
+
+NewRateLimiterMemoryStoreWithConfig creates an in-memory rate limiter store with config.
+
+```go
+store := webmiddleware.NewRateLimiterMemoryStoreWithConfig(webmiddleware.RateLimiterMemoryStoreConfig{Rate: rate.Every(time.Second)})
+allowed, _ := store.Allow("192.0.2.1")
+fmt.Println(allowed)
+// true
+```
+
+#### webmiddleware.RateLimiter
+
+RateLimiter creates a rate limiting middleware.
+
+```go
+store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
+
+router := echoweb.New().Router()
+router.Use(webmiddleware.RateLimiter(store))
+
+router.POST("/api/messages", func(c web.Context) error {
+ return c.NoContent(202)
+})
+```
+
+#### webmiddleware.RateLimiterMemoryStore.Allow
+
+Allow checks whether the given identifier is allowed through.
+
+```go
+store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
+allowed, err := store.Allow("127.0.0.1")
+fmt.Println(err == nil, allowed)
+// true true
+```
+
+#### webmiddleware.RateLimiterWithConfig
+
+RateLimiterWithConfig creates a rate limiting middleware with config.
+
+```go
+store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
+
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.RateLimiterWithConfig(webmiddleware.RateLimiterConfig{
+ Store: store,
+ IdentifierExtractor: func(c web.Context) (string, error) {
+ return c.Header("X-Account-ID"), nil
+ },
+}))
+```
+
+### Redirects Middleware
+
+#### webmiddleware.HTTPSNonWWWRedirect
+
+HTTPSNonWWWRedirect redirects to https without www.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.HTTPSNonWWWRedirect())
+```
+
+#### webmiddleware.HTTPSNonWWWRedirectWithConfig
+
+HTTPSNonWWWRedirectWithConfig returns HTTPS non-WWW redirect middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.HTTPSNonWWWRedirectWithConfig(webmiddleware.RedirectConfig{
+ Code: 307,
+}))
+```
+
+#### webmiddleware.HTTPSRedirect
+
+HTTPSRedirect redirects http requests to https.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.HTTPSRedirect())
+
+router.GET("/docs", func(c web.Context) error {
+ return c.Text(200, "docs")
+})
+```
+
+#### webmiddleware.HTTPSRedirectWithConfig
+
+HTTPSRedirectWithConfig returns HTTPS redirect middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.HTTPSRedirectWithConfig(webmiddleware.RedirectConfig{
+ Code: 307,
+}))
+```
+
+#### webmiddleware.HTTPSWWWRedirect
+
+HTTPSWWWRedirect redirects to https + www.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.HTTPSWWWRedirect())
+```
+
+#### webmiddleware.HTTPSWWWRedirectWithConfig
+
+HTTPSWWWRedirectWithConfig returns HTTPS+WWW redirect middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.HTTPSWWWRedirectWithConfig(webmiddleware.RedirectConfig{
+ Code: 307,
+}))
+```
+
+#### webmiddleware.NonWWWRedirect
+
+NonWWWRedirect redirects to the non-www host.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.NonWWWRedirect())
+```
+
+#### webmiddleware.NonWWWRedirectWithConfig
+
+NonWWWRedirectWithConfig returns non-WWW redirect middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.NonWWWRedirectWithConfig(webmiddleware.RedirectConfig{
+ Code: 307,
+}))
+```
+
+#### webmiddleware.WWWRedirect
+
+WWWRedirect redirects to the www host.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.WWWRedirect())
+```
+
+#### webmiddleware.WWWRedirectWithConfig
+
+WWWRedirectWithConfig returns WWW redirect middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.WWWRedirectWithConfig(webmiddleware.RedirectConfig{
+ Code: 307,
+}))
+```
+
+### Reliability Middleware
+
+#### webmiddleware.Recover
+
+Recover returns middleware that recovers panics from the handler chain.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.Recover())
+
+router.GET("/panic", func(c web.Context) error {
+ panic("boom")
+})
+```
+
+#### webmiddleware.RecoverWithConfig
+
+RecoverWithConfig returns recover middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.RecoverWithConfig(webmiddleware.RecoverConfig{
+ DisableStack: true,
+ HandleError: func(c web.Context, err error, stack []byte) error {
+ return c.JSON(500, map[string]any{"error": "internal server error"})
+ },
+}))
+```
+
+### Request Lifecycle Middleware
+
+#### webmiddleware.ContextTimeout
+
+ContextTimeout sets a timeout on the request context.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.ContextTimeout(2 * time.Second))
+
+router.GET("/reports", func(c web.Context) error {
+ return c.JSON(200, map[string]any{"ready": true})
+})
+```
+
+#### webmiddleware.ContextTimeoutWithConfig
+
+ContextTimeoutWithConfig sets a timeout on the request context with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.ContextTimeoutWithConfig(webmiddleware.ContextTimeoutConfig{
+ Timeout: time.Second,
+}))
+```
+
+#### webmiddleware.DefaultSkipper
+
+DefaultSkipper always runs the middleware.
+
+```go
+fmt.Println(webmiddleware.DefaultSkipper(nil))
+// false
+```
+
+#### webmiddleware.RequestID
+
+RequestID returns middleware that sets a request id header and context value.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.RequestID())
+
+router.GET("/healthz", func(c web.Context) error {
+ return c.JSON(200, map[string]any{
+ "request_id": c.Get("request_id"),
+ })
+})
+```
+
+#### webmiddleware.RequestIDWithConfig
+
+RequestIDWithConfig returns RequestID middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.RequestIDWithConfig(webmiddleware.RequestIDConfig{
+ TargetHeader: "X-Correlation-ID",
+ ContextKey: "correlation_id",
+}))
+```
+
+#### webmiddleware.RequestLoggerWithConfig
+
+RequestLoggerWithConfig returns request logger middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.RequestLoggerWithConfig(webmiddleware.RequestLoggerConfig{
+ LogValuesFunc: func(c web.Context, values webmiddleware.RequestLoggerValues) error {
+ log.Printf("%s %s %d %s", values.Method, values.URI, values.Status, values.Latency)
+ return nil
+ },
+}))
+
+router.GET("/users/:id", func(c web.Context) error {
+ return c.NoContent(204)
+})
+```
+
+#### webmiddleware.Timeout
+
+Timeout returns a response-timeout middleware.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.Timeout())
+
+router.GET("/healthz", func(c web.Context) error {
+ return c.NoContent(204)
+})
+```
+
+#### webmiddleware.TimeoutWithConfig
+
+TimeoutWithConfig returns a response-timeout middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.TimeoutWithConfig(webmiddleware.TimeoutConfig{
+ Timeout: time.Second,
+ ErrorMessage: "request timed out",
+}))
+```
+
+### Security Middleware
+
+#### webmiddleware.CORS
+
+CORS returns Cross-Origin Resource Sharing middleware.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.CORS())
+
+router.GET("/api/healthz", func(c web.Context) error {
+ return c.JSON(200, map[string]any{"ok": true})
+})
+```
+
+#### webmiddleware.CORSWithConfig
+
+CORSWithConfig returns CORS middleware with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.CORSWithConfig(webmiddleware.CORSConfig{
+ AllowOrigins: []string{"https://app.example.com"},
+ AllowMethods: []string{"GET", "POST", "PATCH"},
+}))
+
+router.GET("/api/healthz", func(c web.Context) error {
+ return c.JSON(200, map[string]any{"ok": true})
+})
+```
+
+#### webmiddleware.Secure
+
+Secure sets security-oriented response headers.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.Secure())
+
+router.GET("/", func(c web.Context) error {
+ return c.Text(200, "home")
+})
+```
+
+#### webmiddleware.SecureWithConfig
+
+SecureWithConfig sets security-oriented response headers with config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.SecureWithConfig(webmiddleware.SecureConfig{
+ ReferrerPolicy: "same-origin",
+ ContentSecurityPolicy: "default-src 'self'",
+}))
+```
+
+### Static Files Middleware
+
+#### webmiddleware.Static
+
+Static serves static content from the provided root.
+
+```go
+router := echoweb.New().Router()
+router.Use(webmiddleware.Static("public"))
+
+router.GET("/healthz", func(c web.Context) error {
+ return c.NoContent(204)
+})
+```
+
+#### webmiddleware.StaticWithConfig
+
+StaticWithConfig serves static content using config.
+
+```go
+router := echoweb.New().Router()
+
+router.Use(webmiddleware.StaticWithConfig(webmiddleware.StaticConfig{
+ Root: "public",
+ HTML5: true,
+}))
+```
+
+### Prometheus
+
+#### webprometheus.Default
+
+Default returns the package-level Prometheus metrics instance.
+
+```go
+fmt.Println(webprometheus.Default() == webprometheus.Default())
+// true
+```
+
+#### webprometheus.Handler
+
+Handler returns the package-level Prometheus scrape handler.
+
+```go
+registry := prometheus.NewRegistry()
+counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"})
+registry.MustRegister(counter)
+counter.Inc()
+metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry})
+recorder := httptest.NewRecorder()
+ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil)
+_ = metrics.Handler()(ctx)
+fmt.Println(strings.Contains(recorder.Body.String(), "demo_total"))
+// true
+```
+
+#### webprometheus.Metrics.Handler
+
+Handler exposes the configured Prometheus metrics as a web.Handler.
+
+```go
+registry := prometheus.NewRegistry()
+counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"})
+registry.MustRegister(counter)
+counter.Inc()
+metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry})
+recorder := httptest.NewRecorder()
+ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil)
+_ = metrics.Handler()(ctx)
+fmt.Println(strings.Contains(recorder.Body.String(), "demo_total"))
+// true
+```
+
+#### webprometheus.Metrics.Middleware
+
+Middleware records Prometheus metrics for each request.
+
+```go
+registry := prometheus.NewRegistry()
+metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"})
+handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) })
+ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil)
+_ = handler(ctx)
+out := &bytes.Buffer{}
+_ = webprometheus.WriteGatheredMetrics(out, registry)
+fmt.Println(strings.Contains(out.String(), "example_requests_total"))
+// true
+```
+
+#### webprometheus.Middleware
+
+Middleware returns the package-level Prometheus middleware.
+
+```go
+registry := prometheus.NewRegistry()
+metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"})
+handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) })
+ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil)
+_ = handler(ctx)
+out := &bytes.Buffer{}
+_ = webprometheus.WriteGatheredMetrics(out, registry)
+fmt.Println(strings.Contains(out.String(), "example_requests_total"))
+// true
+```
+
+#### webprometheus.MustNew
+
+MustNew creates a Metrics instance and panics on registration errors.
+
+```go
+metrics := webprometheus.MustNew(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: prometheus.NewRegistry()})
+fmt.Println(metrics != nil)
+// true
+```
+
+#### webprometheus.New
+
+New creates a Metrics instance backed by Prometheus collectors.
+
+```go
+metrics, err := webprometheus.New(webprometheus.Config{Namespace: "app"})
+_ = metrics
+fmt.Println(err == nil)
+// true
+```
+
+#### webprometheus.RunPushGatewayGatherer
+
+RunPushGatewayGatherer starts pushing collected metrics until the context finishes.
+
+```go
+err := webprometheus.RunPushGatewayGatherer(context.Background(), webprometheus.PushGatewayConfig{})
+fmt.Println(err != nil)
+// true
+```
+
+#### webprometheus.WriteGatheredMetrics
+
+WriteGatheredMetrics gathers collected metrics and writes them to the given writer.
+
+```go
+var buf bytes.Buffer
+err := webprometheus.WriteGatheredMetrics(&buf, prometheus.NewRegistry())
+fmt.Println(err == nil)
+// true
+```
+
+### Route Reporting
+
+#### BuildRouteEntries
+
+BuildRouteEntries builds a sorted slice of route entries from registered groups and extra entries.
+
+```go
+entries := web.BuildRouteEntries([]web.RouteGroup{
+ web.NewRouteGroup("/api", []web.Route{
+ web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
+ }),
+})
+fmt.Println(entries[0].Path, entries[0].Methods[0])
+// /api/healthz GET
+```
+
+#### RenderRouteTable
+
+RenderRouteTable renders a route table using simple ASCII borders and ANSI colors.
+
+```go
+table := web.RenderRouteTable([]web.RouteEntry{{
+ Path: "/api/healthz",
+ Handler: "monitoring.Healthz",
+ Methods: []string{"GET"},
+}})
+fmt.Println(strings.Contains(table, "/api/healthz"))
+// true
+```
+
+### Routing
+
+#### MountRouter
+
+MountRouter applies mount-style router configuration in declaration order.
+
+```go
+adapter := echoweb.New()
+
+err := web.MountRouter(adapter.Router(), []web.RouterMount{
+ func(r web.Router) error {
+ r.GET("/healthz", func(c web.Context) error { return nil })
+ return nil
+ },
+})
+
+fmt.Println(err == nil)
+// true
+```
+
+#### NewRoute
+
+NewRoute creates a new route using the app-facing web handler contract directly.
+
+```go
+route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error {
+ return c.NoContent(http.StatusOK)
+})
+
+fmt.Println(route.Method(), route.Path())
+// GET /healthz
+```
+
+#### NewRouteGroup
+
+NewRouteGroup wraps routes and their accompanied web middleware.
+
+```go
+group := web.NewRouteGroup("/api", []web.Route{
+ web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
+})
+
+fmt.Println(group.RoutePrefix(), len(group.Routes()))
+// /api 1
+```
+
+#### NewWebSocketRoute
+
+NewWebSocketRoute creates a websocket route using the app-facing websocket handler contract.
+
+```go
+route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error {
+ return nil
+})
+
+fmt.Println(route.IsWebSocket())
+// true
+```
+
+#### RegisterRoutes
+
+RegisterRoutes registers route groups onto a router.
+
+```go
+adapter := echoweb.New()
+
+groups := []web.RouteGroup{
+ web.NewRouteGroup("/api", []web.Route{
+ web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
+ }),
+}
+
+err := web.RegisterRoutes(adapter.Router(), groups)
+fmt.Println(err == nil)
+// true
+```
+
+#### Route.Handler
+
+Handler returns the route handler.
+
+```go
+route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error {
+ return c.NoContent(http.StatusCreated)
+})
+
+ctx := webtest.NewContext(nil, nil, "/healthz", nil)
+_ = route.Handler()(ctx)
+fmt.Println(ctx.StatusCode())
+// 201
+```
+
+#### Route.HandlerName
+
+HandlerName returns the original handler name for route reporting.
+
+```go
+route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil })
+fmt.Println(route.HandlerName() != "")
+// true
+```
+
+#### Route.IsWebSocket
+
+IsWebSocket reports whether this route upgrades to a websocket connection.
+
+```go
+route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error { return nil })
+fmt.Println(route.IsWebSocket())
+// true
+```
+
+#### Route.Method
+
+Method returns the HTTP method.
+
+```go
+route := web.NewRoute(http.MethodPost, "/users", func(c web.Context) error { return nil })
+fmt.Println(route.Method())
+// POST
+```
+
+#### Route.MiddlewareNames
+
+MiddlewareNames returns original middleware names for route reporting.
+
+```go
+route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth")
+fmt.Println(route.MiddlewareNames()[0])
+// auth
+```
+
+#### Route.Middlewares
+
+Middlewares returns the route middleware slice.
+
+```go
+route := web.NewRoute(
+
+http.MethodGet,
+"/healthz",
+func(c web.Context) error { return nil },
+func(next web.Handler) web.Handler { return next },
+
+)
+fmt.Println(len(route.Middlewares()))
+// 1
+```
+
+#### Route.Path
+
+Path returns the path of the route.
+
+```go
+route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil })
+fmt.Println(route.Path())
+// /healthz
+```
+
+#### Route.WebSocketHandler
+
+WebSocketHandler returns the websocket route handler.
+
+```go
+route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error {
+ c.Set("ready", true)
+ return nil
+})
+
+ctx := webtest.NewContext(nil, nil, "/ws", nil)
+err := route.WebSocketHandler()(ctx, nil)
+fmt.Println(err == nil, ctx.Get("ready"))
+// true true
+```
+
+#### Route.WithMiddlewareNames
+
+WithMiddlewareNames attaches reporting-only middleware names to the route.
+
+```go
+route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth", "trace")
+fmt.Println(len(route.MiddlewareNames()))
+// 2
+```
+
+#### RouteGroup.MiddlewareNames
+
+MiddlewareNames returns original middleware names for route reporting.
+
+```go
+group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth")
+fmt.Println(group.MiddlewareNames()[0])
+// auth
+```
+
+#### RouteGroup.Middlewares
+
+Middlewares returns the middleware slice for the group.
+
+```go
+group := web.NewRouteGroup("/api", nil, func(next web.Handler) web.Handler { return next })
+fmt.Println(len(group.Middlewares()))
+// 1
+```
+
+#### RouteGroup.RoutePrefix
+
+RoutePrefix returns the group prefix.
+
+```go
+group := web.NewRouteGroup("/api", nil)
+fmt.Println(group.RoutePrefix())
+// /api
+```
+
+#### RouteGroup.Routes
+
+Routes returns the routes in the group.
+
+```go
+group := web.NewRouteGroup("/api", []web.Route{
+ web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
+})
+
+fmt.Println(len(group.Routes()))
+// 1
+```
+
+#### RouteGroup.WithMiddlewareNames
+
+WithMiddlewareNames attaches reporting-only middleware names to the group.
+
+```go
+group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth", "trace")
+fmt.Println(len(group.MiddlewareNames()))
+// 2
+```
+
+### Testing
+
+#### webtest.NewContext
+
+NewContext creates a new test context around the provided request/recorder pair.
+
+```go
+req := httptest.NewRequest(http.MethodGet, "/users/42?expand=roles", nil)
+ctx := webtest.NewContext(req, nil, "/users/:id", webtest.PathParams{"id": "42"})
+fmt.Println(ctx.Param("id"), ctx.Query("expand"))
+// 42 roles
+```
diff --git a/docs/readme/main.go b/docs/readme/main.go
index 8fa2789..252296a 100644
--- a/docs/readme/main.go
+++ b/docs/readme/main.go
@@ -25,10 +25,10 @@ func main() {
fmt.Println("Error:", err)
os.Exit(1)
}
- fmt.Println("✔ API.md and README reference updated")
+ fmt.Println("✔ API index and examples updated in README.md")
}
-// run regenerates the annotated API guide while keeping the README focused on onboarding.
+// run regenerates the annotated API index and examples inside the README marker pair.
func run() error {
root, err := findRoot()
if err != nil {
@@ -46,28 +46,11 @@ func run() error {
return err
}
- readmeReference := strings.Join([]string{
- "The [complete API documentation](https://pkg.go.dev/github.com/goforj/web) includes every exported type, field, function, and method.",
- "The [generated API examples](API.md) collect the functions and methods annotated with runnable examples in this repository.",
- }, "\n\n") + "\n"
- out, err := replaceSection(string(data), apiStart, apiEnd, readmeReference)
+ out, err := replaceSection(string(data), apiStart, apiEnd, renderAPI(funcs))
if err != nil {
return err
}
- apiDocument := strings.Join([]string{
- "# API examples",
- "",
- "_Generated from annotated public API comments. Do not edit this file directly._",
- "",
- "This guide covers functions and methods with repository examples. See [pkg.go.dev](https://pkg.go.dev/github.com/goforj/web) for the complete API, including exported types, configuration fields, constants, and variables.",
- "",
- renderAPI(funcs),
- }, "\n")
- if err := os.WriteFile(filepath.Join(root, "API.md"), []byte(apiDocument), 0o644); err != nil {
- return err
- }
-
return os.WriteFile(readmePath, []byte(out), 0o644)
}
@@ -388,7 +371,7 @@ func renderAPI(funcs []*FuncDoc) string {
}
buf.WriteString("\n\n")
- buf.WriteString("## Examples\n\n")
+ buf.WriteString("## API Reference\n\n")
buf.WriteString("_Generated from public API comments and examples._\n\n")
for _, group := range groupNames {