From d422d6f8541652d973089bf2dcba144efa1e1bba Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Mon, 19 Oct 2026 12:14:34 +0200 Subject: [PATCH 1/5] docs: cite celeris symbols, not line numbers, on the remaining twelve pages Observability, routing, SSE, middleware content, middleware, engines, testing, net/http interop, streaming and three one-locator pages. The bare middleware-content citations (compress, etag, cache, protobuf, swagger) get the celeris/middleware/ path. The ratchet allowlist is empty. Refs #891 item 8 Refs #891 item 10 --- scripts/docs-symbol-citations.test.ts | 12 ----- src/content/docs/binding-and-validation.md | 2 +- src/content/docs/context-api-reference.md | 2 +- src/content/docs/engines.md | 38 +++++++------- src/content/docs/middleware-auth.md | 2 +- src/content/docs/middleware-content.md | 36 ++++++------- src/content/docs/middleware.md | 38 +++++++------- src/content/docs/net-http-interop.md | 32 ++++++------ src/content/docs/observability.md | 60 +++++++++++----------- src/content/docs/routing.md | 52 +++++++++---------- src/content/docs/sse.md | 46 ++++++++--------- src/content/docs/streaming.md | 28 +++++----- src/content/docs/testing.md | 32 ++++++------ 13 files changed, 184 insertions(+), 196 deletions(-) diff --git a/scripts/docs-symbol-citations.test.ts b/scripts/docs-symbol-citations.test.ts index 93c857d..a335c98 100644 --- a/scripts/docs-symbol-citations.test.ts +++ b/scripts/docs-symbol-citations.test.ts @@ -37,18 +37,6 @@ export function continuationLines(text: string): number[] { } const ALLOW: Record = { - "binding-and-validation.md": 1, - "context-api-reference.md": 1, - "engines.md": 20, - "middleware-auth.md": 1, - "middleware-content.md": 25, - "middleware.md": 21, - "net-http-interop.md": 16, - "observability.md": 31, - "routing.md": 28, - "sse.md": 26, - "streaming.md": 15, - "testing.md": 17, }; function pages(): string[] { diff --git a/src/content/docs/binding-and-validation.md b/src/content/docs/binding-and-validation.md index 5a4bdb0..8cead36 100644 --- a/src/content/docs/binding-and-validation.md +++ b/src/content/docs/binding-and-validation.md @@ -210,7 +210,7 @@ or validating input. Use `c.Bind*` plus your own field checks, as shown above. **The package is EXPERIMENTAL.** Its package documentation says so, and it is outside the compatibility promise in the Compatibility section of celeris's [GOVERNANCE.md](https://github.com/goceleris/celeris/blob/main/GOVERNANCE.md#compatibility) -(`celeris/validation/doc.go:22-31`): the counter names, the `Counters` fields, the +(`celeris/validation/doc.go`): the counter names, the `Counters` fields, the socket's JSON and the functions may change or go away in a minor release, and the release notes say when they do. diff --git a/src/content/docs/context-api-reference.md b/src/content/docs/context-api-reference.md index d36f648..8f171ec 100644 --- a/src/content/docs/context-api-reference.md +++ b/src/content/docs/context-api-reference.md @@ -453,7 +453,7 @@ func handler(c *celeris.Context) error { ## The `Cookie` type -`SetCookie` takes a `*celeris.Cookie` (`celeris/types.go:48-71`): +`SetCookie` takes a `*celeris.Cookie` (`celeris/types.go` (`Cookie`)): | Field | Type | Notes | | ----- | ---- | ----- | diff --git a/src/content/docs/engines.md b/src/content/docs/engines.md index cb6e60b..6c98ea1 100644 --- a/src/content/docs/engines.md +++ b/src/content/docs/engines.md @@ -14,7 +14,7 @@ difference between good and exceptional throughput. You select an engine with one field, `Config.Engine` (`celeris/config.go`). The zero value is the right answer on almost every box: **Adaptive on Linux, Std -everywhere else** (`celeris/internal/resource/config.go:13-19`). This page explains the four +everywhere else** (`celeris/internal/resource/config.go` (`defaultEngine`)). This page explains the four engines, the adaptive controller that picks between them, how the engine relates to protocol and to async dispatch, and the introspection surface for observing it all at runtime. @@ -75,8 +75,8 @@ modern Linux (epoll predates all supported kernels) and the safest native choice when io_uring is unavailable or you want to pin behaviour. Epoll is at throughput parity with io_uring for most request/response workloads — you are not trading latency for compatibility by choosing it. Epoll is also the engine that implements -zero-copy `sendfile(2)` for static-file responses (`celeris/internal/engine/engine.go:73-99`, -`celeris/internal/engine/capability.go:38-44`). +zero-copy `sendfile(2)` for static-file responses (`celeris/internal/engine/engine.go` (`SendfileCapable`), +`celeris/internal/engine/capability.go` (`CapabilityProfile.Sendfile`)). ### IOUring @@ -134,7 +134,7 @@ io_uring, epoll, and adaptive depend on Linux kernel facilities, so they cannot elsewhere. The distinction worth internalising: - **Leaving `Engine` unset off Linux silently selects Std.** The default resolves - per-platform (`celeris/internal/resource/config.go:13-19`). This is the intended fallback — + per-platform (`celeris/internal/resource/config.go` (`defaultEngine`)). This is the intended fallback — your code runs unchanged on a Mac. - **Explicitly setting a native engine off Linux is a validation error.** It is *not* silently downgraded. `Config.Validate` returns `engine requires @@ -170,8 +170,8 @@ and let Adaptive/Std resolve automatically. | Accept control (Pause/Resume) | Yes | Yes | — | | Driver event-loop colocation | Yes | Yes | — | -Sources: `celeris/internal/engine/capability.go`, `celeris/internal/engine/engine.go:38-99`, -`celeris/server.go:736-861`, `celeris/context_response.go:1423-1446`. +Sources: `celeris/internal/engine/capability.go`, `celeris/internal/engine/engine.go` (`SendfileCapable`), +`celeris/server.go` (`Server.EventLoopProvider`), `celeris/context_response.go` (`Context.EngineSupportsAsyncDetach`). ## The adaptive controller @@ -206,7 +206,7 @@ Established connections now transplant between engines on a switch (see above), the **start** engine no longer fixes the keep-alive throughput ceiling — but the steady-state concurrency is still unknowable when the server binds, and a good start avoids an unnecessary early switch. `WorkloadHint` -(`celeris/config.go:45-62`) is the config-level way to bias that start decision. It +(`celeris/config.go` (`WorkloadHint`)) is the config-level way to bias that start decision. It affects **nothing but the Adaptive engine's start choice**; on Epoll, IOUring, and Std it is ignored. @@ -236,7 +236,7 @@ Adaptive falls back to starting on epoll. Every time the adaptive controller changes strategy it increments a counter you can read from the metrics collector. `Server.Collector().Snapshot()` returns a `Snapshot` whose `EngineSwitches` field counts the switches since start -(`celeris/observe/collector.go:34-51`). +(`celeris/observe/collector.go` (`Snapshot`)). ```go snap := s.Collector().Snapshot() @@ -321,7 +321,7 @@ underneath a still-running goroutine. Celeris exposes this difference so streaming middleware can adapt rather than break. `Context.EngineSupportsAsyncDetach()` reports whether the active engine can keep the connection alive after the handler returns -(`celeris/context_response.go:1423-1446`): +(`celeris/context_response.go` (`Context.EngineSupportsAsyncDetach`)): ```go func streamHandler(c *celeris.Context) error { @@ -357,7 +357,7 @@ your own streaming transport. > WebSocket has a deeper engine integration than SSE: native engines provide an > *engine-integrated* upgrade path (`UpgradeWebSocket`, `WSReadPauser`, > `WSRawWriteFn`) with TCP-level backpressure; on Std these return false/nil and the -> middleware falls back to `Context.Hijack` (`celeris/context_response.go:1289-1410`). +> middleware falls back to `Context.Hijack` (`celeris/context_response.go` (`Context.Hijack`)). > The middleware handles this fallback transparently. ## Engine selection in practice @@ -415,7 +415,7 @@ The running engine exposes a read-only surface for observability and control. ### `EngineInfo` and `EngineType` `Server.EngineInfo()` returns the active engine's type and a metrics snapshot, or -`nil` before `Start` (`celeris/server.go:802-812`). On Adaptive, `Type` reflects the +`nil` before `Start` (`celeris/server.go` (`Server.EngineInfo`)). On Adaptive, `Type` reflects the engine that is *currently active*, so you can see which sub-engine the controller has selected. @@ -434,7 +434,7 @@ if info := s.EngineInfo(); info != nil { | `Metrics` | `EngineMetrics` | A point-in-time snapshot of the counters below. | `EngineType` has a `String()` method that returns `"io_uring"`, `"epoll"`, -`"adaptive"`, or `"std"` (`celeris/internal/engine/enginetype.go:20-33`). +`"adaptive"`, or `"std"` (`celeris/internal/engine/enginetype.go` (`EngineType.String`)). ### `EngineMetrics` fields @@ -460,7 +460,7 @@ These are the counters the adaptive controller reads to derive its load signals error rate (see [The adaptive controller](#the-adaptive-controller)). They are also re-exported on the metrics `Snapshot` as `EngineMetrics`, alongside `RequestsTotal`, `ErrorsTotal`, `ActiveConns`, `EngineSwitches`, latency buckets, and CPU -utilisation (`celeris/observe/collector.go:34-51`). +utilisation (`celeris/observe/collector.go` (`Snapshot`)). A request rate is not one of the counters: take two snapshots and divide the `RequestCount` difference by the time between them. @@ -482,9 +482,9 @@ if m.RequestCount > 0 { To stop accepting new connections while continuing to serve existing ones — useful for graceful load shedding or coordinated draining — call `Server.PauseAccept()` -and later `Server.ResumeAccept()` (`celeris/server.go:814-861`). These work on the +and later `Server.ResumeAccept()` (`celeris/server.go` (`Server.PauseAccept`)). These work on the native engines. The **std engine does not support accept control**: both return -`celeris.ErrAcceptControlNotSupported` (`celeris/errors.go:29-31`), as does calling +`celeris.ErrAcceptControlNotSupported` (`celeris/errors.go` (`ErrAcceptControlNotSupported`)), as does calling them before `Start`. ```go @@ -501,7 +501,7 @@ _ = s.ResumeAccept() `Server.EventLoopProvider()` returns the engine's per-worker event-loop provider, or `nil` if the engine does not expose one — which is the case for the **std** -fallback (`celeris/server.go:736-758`). This is the integration point that lets +fallback (`celeris/server.go` (`Server.EventLoopProvider`)). This is the integration point that lets Celeris database and cache drivers register their own sockets on the *same* worker event loops as the HTTP path, so a DB round-trip is driven by the very thread that owns the request's connection — no cross-thread handoff, NUMA-local buffers. @@ -518,7 +518,7 @@ lists them as not covered). The provider exposes `NumWorkers()` and `WorkerLoop(n)`; the per-worker `WorkerLoop` surface (`RegisterConn`, `UnregisterConn`, `Write`, `CPUID`) is -documented in `celeris/internal/engine/provider.go:32-104`. You normally don't call this +documented in `celeris/internal/engine/provider.go` (`EventLoopProvider`). You normally don't call this yourself — a Celeris driver opened `WithEngine(srv)` consumes it for you. When the provider is `nil` (std engine), drivers fall back to a standalone mini event loop. A nil check and a call such as `p.NumWorkers()` need no name for the type: @@ -534,7 +534,7 @@ if p := s.EventLoopProvider(); p != nil { > For drivers to pick their fast netpoll-park path, the server's *effective* async > state must be on (server `AsyncHandlers: true`, or routes marked `.Async()` / > `.UsesDriver()` registered **before** the driver is opened). `Server.AsyncHandlers()` -> reports the effective state (`celeris/server.go:760-800`). See +> reports the effective state (`celeris/server.go` (`Server.AsyncHandlers`)). See > [Routing](/docs/routing#dispatch-mode-async-sync-usesdriver) for the ordering rule. ## Common pitfalls @@ -557,7 +557,7 @@ if p := s.EventLoopProvider(); p != nil { it fails to start. - **Forgetting to call `done()` after `Detach`.** The returned function *must* run (typically `defer done()` in the streaming goroutine) or the `*Context` leaks from - its pool permanently (`celeris/context_response.go:1448-1505`). + its pool permanently (`celeris/context_response.go` (`Context.Detach`)). ## FAQ diff --git a/src/content/docs/middleware-auth.md b/src/content/docs/middleware-auth.md index c41c802..c25a373 100644 --- a/src/content/docs/middleware-auth.md +++ b/src/content/docs/middleware-auth.md @@ -51,7 +51,7 @@ config shape so you can swap or stack them without relearning the surface. ### One sentinel across all stacks: `ErrUnauthorized` Every auth package re-exports the same canonical 401 error, -`celeris.ErrUnauthorized` (defined in `celeris/errors.go:44`). `basicauth.ErrUnauthorized`, +`celeris.ErrUnauthorized` (defined in `celeris/errors.go` (`ErrUnauthorized`)). `basicauth.ErrUnauthorized`, `keyauth.ErrUnauthorized`, and `jwt.ErrUnauthorized` are all aliases of it, so a single `errors.Is` check matches a failure from *any* layer of a mixed stack: diff --git a/src/content/docs/middleware-content.md b/src/content/docs/middleware-content.md index e3a935a..ca21491 100644 --- a/src/content/docs/middleware-content.md +++ b/src/content/docs/middleware-content.md @@ -69,7 +69,7 @@ s.Use(compress.New(compress.Config{ | `Skip` | `func(*Context) bool` | `nil` | Return `true` to skip compression for a request. | | `SkipPaths` | `[]string` | `nil` | Exact-match paths to skip. | -Source: `compress/config.go:30-74`. +Source: `celeris/middleware/compress/config.go` (`Config`). ### Per-codec levels @@ -95,7 +95,7 @@ s.Use(compress.New(compress.Config{ })) ``` -Source: `compress/config.go:11-28`, `compress/config.go:109-146`. +Source: `celeris/middleware/compress/config.go` (`Level`), `celeris/middleware/compress/config.go` (`Config.validate`). ### When compression is skipped @@ -117,7 +117,7 @@ Even when the client accepts a codec, `compress` flushes the body **uncompressed If compression itself errors, the middleware degrades gracefully: it flushes the original uncompressed body so the client never sees a blank page. -Source: `compress/compress.go:116-234`. +Source: `celeris/middleware/compress/compress.go` (`New`). ### Install order relative to `etag` @@ -166,7 +166,7 @@ content-negotiated or transfer-encoded (e.g. compressed) — and as noted above, | `Skip` | `func(*Context) bool` | `nil` | Return `true` to skip ETag handling. | | `SkipPaths` | `[]string` | `nil` | Exact-match paths to skip. | -Source: `etag/config.go:5-24`. +Source: `celeris/middleware/etag/config.go` (`Config`). ### Custom hash function @@ -203,7 +203,7 @@ before `Range` (RFC 9110 §13.2.2). If a downstream handler or middleware (for example the `static` file middleware) **already** set an `ETag` header, `etag` reuses that tag verbatim instead of -hashing the body — so you never get a double tag. Source: `etag/etag.go:17-111`. +hashing the body — so you never get a double tag. Source: `celeris/middleware/etag/etag.go` (`New`). ### It must be the innermost transform @@ -247,7 +247,7 @@ store transport error sets `X-Cache: ERROR` and passes through uncached. Source: | `Skip` | `func(*Context) bool` | `nil` | Return `true` to skip caching for a request. | | `SkipPaths` | `[]string` | `nil` | Exact-match paths to skip. | -Source: `cache/config.go:11-95`. +Source: `celeris/middleware/cache/config.go` (`Config`). ### Cache keys and `VaryHeaders` @@ -274,7 +274,7 @@ s.Use(cache.New(cache.Config{ })) ``` -Source: `cache/cache.go:318-361`. +Source: `celeris/middleware/cache/cache.go` (`defaultKeyGenerator`). ### Singleflight @@ -300,7 +300,7 @@ leaves out, is the default: before celeris v1.6.0 a `Config` that did not set `Singleflight: true` silently turned coalescing off, and `RespectCacheControl: false` had no effect ([celeris#922](https://github.com/goceleris/celeris/issues/922)). -Source: `cache/cache.go:87-145`. +Source: `celeris/middleware/cache/cache.go` (`New`). ### Honoring `Cache-Control` @@ -324,7 +324,7 @@ s.GET("/me", func(c *celeris.Context) error { `Set-Cookie` is excluded from the stored header set by default, so a cached response won't leak one user's session cookie to another. Source: -`cache/cache.go:193-233`, `cache/config.go:125-127`. +`celeris/middleware/cache/cache.go` (`capture`), `celeris/middleware/cache/config.go` (`applyDefaults`). ### Invalidation @@ -358,7 +358,7 @@ s.POST("/users/:id", func(c *celeris.Context) error { > your invalidation calls. The middleware does not expose the store it created > internally, so to invalidate you must own the reference. -Source: `cache/cache.go:413-429`. +Source: `celeris/middleware/cache/cache.go` (`Invalidate`). ### Pluggable stores @@ -376,7 +376,7 @@ defer store.Close() // stops the cleanup goroutine s.Use(cache.New(cache.Config{Store: store})) ``` -`MemoryStore` fields (`cache/store.go:14-32`): +`MemoryStore` fields (`celeris/middleware/cache/store.go` (`MemoryStoreConfig`)): | Field | Default | Meaning | | ----------------- | ------------------ | --------------------------------------------------------- | @@ -449,7 +449,7 @@ Source: `celeris/middleware/protobuf/protobuf.go`, `protobuf/middleware.go`, | `protobuf.ErrInvalidProtoBuf`| unmarshal failed (wraps the underlying error) | | `protobuf.ErrNotProtoBuf` | `Bind` saw a non-protobuf `Content-Type` | -Source: `protobuf/config.go:9-25`. +Source: `celeris/middleware/protobuf/config.go` (`ContentType`). ### Package-level functions @@ -489,7 +489,7 @@ s.GET("/users/:id", func(c *celeris.Context) error { `Respond` writes the *exact* protobuf variant the client preferred (`application/x-protobuf` vs `application/protobuf`) and honors `q=0` to exclude a -form. Source: `protobuf/protobuf.go:30-134`. +form. Source: `celeris/middleware/protobuf/protobuf.go` (`Write`). ### The middleware just stores config @@ -518,7 +518,7 @@ s.POST("/strict", func(c *celeris.Context) error { `Content-Type` is protobuf and returns `ErrNotProtoBuf` otherwise, then unmarshals with the stored options. If the middleware wasn't installed, `FromContext` falls back to default options, so it's always safe to call. Source: -`protobuf/middleware.go:14-60`, `protobuf/config.go:55-68`. +`celeris/middleware/protobuf/middleware.go` (`New`), `celeris/middleware/protobuf/config.go` (`defaultConfig`). ## OpenAPI docs (Swagger) @@ -582,7 +582,7 @@ required (the middleware **panics at construction** if neither is set): If you don't set `SpecFile`, the content type of an inline spec is sniffed from its first non-whitespace byte (`{`/`[` → JSON, else YAML). Source: -`swagger/config.go:145-162`, `swagger/config.go:306-330`, `swagger/swagger.go:176-187`. +`celeris/middleware/swagger/config.go` (`Config`), `celeris/middleware/swagger/config.go` (`detectSpecContentType`), `celeris/middleware/swagger/swagger.go` (`New`). ### Config reference @@ -600,7 +600,7 @@ its first non-whitespace byte (`{`/`[` → JSON, else YAML). Source: | `Skip` | `func(*Context) bool` | `nil` | Skip predicate. | | `SkipPaths` | `[]string` | `nil` | Exact-match paths to skip. | -Source: `swagger/config.go:113-238`. +Source: `celeris/middleware/swagger/config.go` (`Config`). ### UI options @@ -632,7 +632,7 @@ s.Use(swagger.New(swagger.Config{ `DefaultModelsExpandDepth` is a `*int` so the middleware can tell "unset" (use the UI default of 1) from an explicit `0`. Use `swagger.IntPtr` to set it. Source: -`swagger/config.go:24-86`, `swagger/config.go:240-245`. +`celeris/middleware/swagger/config.go` (`UIConfig`), `celeris/middleware/swagger/config.go` (`IntPtr`). ### OAuth2 with PKCE @@ -699,7 +699,7 @@ s.Use(swagger.New(swagger.Config{ })) ``` -Source: `swagger/config.go:170-188`, `swagger/swagger.go:281-372`. +Source: `celeris/middleware/swagger/config.go` (`Config.Options`), `celeris/middleware/swagger/swagger.go` (`buildPage`). ### Assets: embedded, CDN or self-hosted diff --git a/src/content/docs/middleware.md b/src/content/docs/middleware.md index 42fffab..716f2a5 100644 --- a/src/content/docs/middleware.md +++ b/src/content/docs/middleware.md @@ -38,7 +38,7 @@ s.Use(Timing) `Context.Next()` advances to and runs the next handler in the chain, returning the **first non-nil error** from anything downstream and short-circuiting the rest -(`celeris/context.go:360-386`). It also stops once a handler has answered the request +(`celeris/context.go` (`Context.Next`)). It also stops once a handler has answered the request (see [Global](#global--serveruse)). A middleware can inspect or swallow that error simply by choosing what to return. @@ -49,8 +49,8 @@ There are three ways to stop the chain early: | Technique | What happens | | -------------------------------- | --------------------------------------------------------------------------- | | **Return without calling `Next`**| Downstream handlers do not run when you return an error, or when you return `nil` after answering the request (a handler that answers ends the chain, see [Global](#global--serveruse)). Returning `nil` without having answered lets the chain continue. | -| **`c.Abort()`** | Sets the chain index past the end so no pending handler runs. Writes nothing on its own — write a response first. (`celeris/context.go:395-400`) | -| **`c.AbortWithStatus(code)`** | Calls `Abort()` and sends an empty-body status code. Returns the error for propagation. (`celeris/context.go:402-407`) | +| **`c.Abort()`** | Sets the chain index past the end so no pending handler runs. Writes nothing on its own — write a response first. (`celeris/context.go` (`Context.Abort`)) | +| **`c.AbortWithStatus(code)`** | Calls `Abort()` and sends an empty-body status code. Returns the error for propagation. (`celeris/context.go` (`Context.AbortWithStatus`)) | ```go // A guard that rejects unauthenticated requests and stops the chain. @@ -63,7 +63,7 @@ func RequireAuth(c *celeris.Context) error { } ``` -`c.IsAborted()` reports whether the chain was aborted (`celeris/context.go:409-412`), +`c.IsAborted()` reports whether the chain was aborted (`celeris/context.go` (`Context.IsAborted`)), which downstream middleware can check before doing expensive work. > Returning an error and aborting are different things. Returning a non-nil error @@ -102,8 +102,8 @@ func listItems(c *celeris.Context) error { Note `GetString` returns `(string, bool)`, not a bare string — unlike Gin's `c.GetString`. For non-string values use `Set` / `Get`, which round-trip an `any` -you type-assert at the read site (`celeris/context.go:577-600` for `GetString`, -`celeris/context.go:481-485` for `Set`, `celeris/context.go:487-508` for `Get`). +you type-assert at the read site (`celeris/context.go` (`Context.GetString`) for `GetString`, +`celeris/context.go` (`Context.Set`) for `Set`, `celeris/context.go` (`Context.Get`) for `Get`). ## Install points @@ -123,7 +123,7 @@ is fixed, so install order in your setup code matters. `Pre` registers middleware that runs **before the router matches the request**, so it can mutate the request method, path, scheme, host, or client IP before the -handler is even chosen (`celeris/server.go:211`). This is the only layer that can +handler is even chosen (`celeris/server.go` (`Server.Pre`)). This is the only layer that can rewrite *what gets routed*. ```go @@ -141,7 +141,7 @@ s.Pre(redirect.HTTPSRedirect()) ### Global — `Server.Use` `Use` registers global middleware that runs for every matched route, in registration -order, outermost first (`celeris/server.go:196`). +order, outermost first (`celeris/server.go` (`Server.Use`)). A handler that answers the request ends the chain: once it has written the response, had it captured by a buffering middleware (`etag`, `compress`, `cache`) @@ -187,7 +187,7 @@ hold an engine worker for one, and with `AsyncHandlers: false` they always run i > route is registered, so calling `Use` after a `GET`/`POST`/etc. would silently > give some routes the middleware and others not. Celeris panics to surface this: > *"Server.Use called after routes were registered…"*. Put all `s.Use` calls above -> your first route. (Source: `celeris/server.go:197-199`.) +> your first route. (Source: `celeris/server.go` (`Server.Use`).) ### Per-group — `Group.Use` @@ -196,7 +196,7 @@ server-level middleware but **before** the route's own handlers. You can pass it `Group(prefix, mw...)` or add it with `g.Use(mw...)`. Unlike the server, a group's `Use` does **not** panic if called late — it simply applies only to routes registered afterward, so add all `Use` calls before registering routes -(`celeris/group.go:71`). +(`celeris/group.go` (`RouteGroup.Use`)). ```go api := s.Group("/api", requestid.New()) // group middleware at creation @@ -211,7 +211,7 @@ Sub-groups inherit a **copy** of the parent's middleware plus their own. See Every registration method is variadic; the **last** handler is the terminal handler and any **leading** handlers are per-route middleware that run in order before it -(`celeris/server.go:158-164`). +(`celeris/server.go` (`Server.handle`)). ```go // auditLog and requireAdmin run before deleteUser, in that order. @@ -222,7 +222,7 @@ s.DELETE("/users/:id", auditLog, requireAdmin, deleteUser) `Route.Use` prepends middleware to a single route's chain, inserting it **just before** the terminal handler. It panics if the route has no handlers -(`celeris/router.go:196-220`). +(`celeris/router.go` (`Route.Use`)). ```go r := s.GET("/admin", adminDashboard) @@ -238,7 +238,7 @@ Pre → server Use → group Use → per-call leading handlers → Route ``` Within each band, handlers run in the order you added them. The whole chain is one -flat slice (`celeris/group.go:19-26`), so on the way back out it unwinds in reverse: +flat slice (`celeris/group.go` (`RouteGroup.handle`)), so on the way back out it unwinds in reverse: the terminal handler returns first, then `Route.Use`, then the leading handlers, and so on out to the outermost `s.Use`. @@ -504,7 +504,7 @@ treatment — including mounting Celeris under a `net/http` server — see ### Wrap a `func(http.Handler) http.Handler` `adapters.WrapMiddleware` adapts any standard net/http middleware into a Celeris -`HandlerFunc` (`celeris/middleware/adapters/adapters.go:32`). The adapted middleware +`HandlerFunc` (`celeris/middleware/adapters/adapters.go` (`WrapMiddleware`)). The adapted middleware gets a reconstructed `*http.Request`; when it calls the inner handler (`next.ServeHTTP`), the Celeris chain continues via `c.Next()`. If it short-circuits (e.g. returns an early 403 without calling the inner handler), the captured response @@ -538,7 +538,7 @@ s.Use(adapters.WrapMiddleware(StdlibAuth)) For a stdlib *handler* (not middleware), use the core bridges `celeris.Adapt(http.Handler)` and `celeris.AdaptFunc(http.HandlerFunc)` -(`celeris/bridge.go:16`, `celeris/bridge.go:51`): +(`celeris/bridge.go` (`Adapt`), `celeris/bridge.go` (`AdaptFunc`)): ```go s.GET("/legacy", celeris.AdaptFunc(legacyHandlerFunc)) @@ -547,7 +547,7 @@ s.Handle("GET", "/proxy/*path", celeris.Adapt(someHTTPHandler)) `adapters` also ships a ready-made reverse proxy: `adapters.ReverseProxy(target, opts...)` wraps `httputil.ReverseProxy` and sets `X-Forwarded-*` headers -automatically (`celeris/middleware/adapters/adapters.go:210`). +automatically (`celeris/middleware/adapters/adapters.go` (`ReverseProxy`)). ## Server-level error and fallback handlers @@ -556,9 +556,9 @@ Three `*Server` hooks complete the request lifecycle. All must be set before | Hook | Fires when | Source | | --------------------------- | --------------------------------------------------------------- | ----------------------- | -| `OnError(fn)` | An unhandled error reaches the safety net after all middleware. | `celeris/server.go:302` | -| `NotFound(handler)` | No route matches the request path, and no global middleware answered it. | `celeris/server.go:281` | -| `MethodNotAllowed(handler)` | The path matches but the method doesn't (`Allow` header is set automatically), and no global middleware answered it. | `celeris/server.go:293` | +| `OnError(fn)` | An unhandled error reaches the safety net after all middleware. | `celeris/server.go` (`Server.OnError`) | +| `NotFound(handler)` | No route matches the request path, and no global middleware answered it. | `celeris/server.go` (`Server.NotFound`) | +| `MethodNotAllowed(handler)` | The path matches but the method doesn't (`Allow` header is set automatically), and no global middleware answered it. | `celeris/server.go` (`Server.MethodNotAllowed`) | ```go s.OnError(func(c *celeris.Context, err error) { diff --git a/src/content/docs/net-http-interop.md b/src/content/docs/net-http-interop.md index ebb2fde..d4fa68e 100644 --- a/src/content/docs/net-http-interop.md +++ b/src/content/docs/net-http-interop.md @@ -53,7 +53,7 @@ import ( `celeris.Adapt` turns any `http.Handler` into a `celeris.HandlerFunc` you can pass to `s.GET`, `s.POST`, a route group, `s.Use`, or any other place a handler is -expected. Source: `celeris/bridge.go:16`. +expected. Source: `celeris/bridge.go` (`Adapt`). ```go func Adapt(h http.Handler) celeris.HandlerFunc @@ -110,7 +110,7 @@ s.GET("/", homeHandler) // already ported to Celeris `celeris.AdaptFunc` is a convenience wrapper, exactly equivalent to `Adapt(http.HandlerFunc(h))`. Use it when you have a bare function rather than a -type that already implements `http.Handler`. Source: `celeris/bridge.go:51`. +type that already implements `http.Handler`. Source: `celeris/bridge.go` (`AdaptFunc`). ```go func AdaptFunc(h http.HandlerFunc) celeris.HandlerFunc @@ -148,7 +148,7 @@ The two hard limits to remember: than 100 MB, the underlying `Write` returns an error to the stdlib handler; `Adapt` does not inspect this and still replays the bytes buffered before the limit with the handler's own status (200 unless it called `WriteHeader`), so the - client may receive a truncated response. Source: `celeris/bridge.go:109-117`. + client may receive a truncated response. Source: `celeris/bridge.go` (`bridgeResponseWriter.Write`). > The reconstructed `http.ResponseWriter` does **not** implement `http.Hijacker` > or `http.Flusher`. A handler that type-asserts for either (WebSocket upgrade, @@ -160,7 +160,7 @@ The two hard limits to remember: `celeris.ToHandler` is the reverse of `Adapt`: it wraps a `celeris.HandlerFunc` so it satisfies the standard `http.Handler` interface. This is what you reach for when you want to mount a Celeris handler inside a stdlib router, or — most -commonly — drive it from `net/http/httptest`. Source: `celeris/stdlib.go:22`. +commonly — drive it from `net/http/httptest`. Source: `celeris/stdlib.go` (`ToHandler`). ```go func ToHandler(h celeris.HandlerFunc) http.Handler @@ -214,11 +214,11 @@ Two limits mirror the other direction: - The **request body is read fully into memory** and capped at 100 MB; a larger body returns `413 Request Entity Too Large`, and a read error returns `400`. - Source: `celeris/stdlib.go:47-61`. + Source: `celeris/stdlib.go` (`ToHandler`). - If the Celeris handler returns a non-nil `error` and nothing has been written yet, `ToHandler` responds with `500 Internal Server Error`. A panic in the handler is recovered and also yields a `500` (when the response is not already - flushed). Source: `celeris/stdlib.go:73-89`. + flushed). Source: `celeris/stdlib.go` (`ToHandler`). ## Wrap net/http middleware @@ -226,7 +226,7 @@ The most valuable adapter for migration is `adapters.WrapMiddleware`. It adapts the ubiquitous `func(http.Handler) http.Handler` middleware idiom — used by `rs/cors`, `gorilla/csrf`, `gorilla/handlers`, and countless in-house libraries — into a `celeris.HandlerFunc` you can register with `s.Use`, on a group, or on a -single route. Source: `celeris/middleware/adapters/adapters.go:32`. +single route. Source: `celeris/middleware/adapters/adapters.go` (`WrapMiddleware`). ```go func WrapMiddleware(mw func(http.Handler) http.Handler) celeris.HandlerFunc @@ -300,13 +300,13 @@ s.Use(adapters.WrapMiddleware(requireKey)) Panics raised inside the Celeris chain are propagated back out *after* the stdlib middleware returns, so your Celeris recovery middleware sees the original panic value rather than having it swallowed by a `recover()` inside the stdlib -middleware. Source: `celeris/middleware/adapters/adapters.go:57-72`. +middleware. Source: `celeris/middleware/adapters/adapters.go` (`WrapMiddleware`). ### Limitations of wrapped middleware - **`nil` panics.** `WrapMiddleware(nil)` panics with `adapters: WrapMiddleware argument must not be nil`. Source: - `celeris/middleware/adapters/adapters.go:33-35`. + `celeris/middleware/adapters/adapters.go` (`WrapMiddleware`). - **No `Hijacker` / `Flusher`.** The capturing response writer implements neither, so middleware that needs WebSocket upgrade or streaming flush will not work through `WrapMiddleware`. Implement those natively. @@ -316,13 +316,13 @@ middleware. Source: `celeris/middleware/adapters/adapters.go:57-72`. - **Do not double up with native equivalents.** Running, say, `rs/cors` via `WrapMiddleware` *and* the native `celeris/middleware/cors` in the same chain produces duplicate `Access-Control-*` headers and conflicting preflight - handling. Pick one. Source: `celeris/middleware/adapters/doc.go:10-13`. + handling. Pick one. Source: `celeris/middleware/adapters/doc.go`. ## Built-in reverse proxy `adapters.ReverseProxy` wraps the standard library's `net/http/httputil` `ReverseProxy` into a `celeris.HandlerFunc`, so forwarding to a backend is a -one-liner. Source: `celeris/middleware/adapters/adapters.go:210`. +one-liner. Source: `celeris/middleware/adapters/adapters.go` (`ReverseProxy`). ```go func ReverseProxy(target *url.URL, opts ...adapters.Option) celeris.HandlerFunc @@ -338,7 +338,7 @@ s.Any("/api/*path", adapters.ReverseProxy(target)) The proxy automatically sets `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto` on the outbound request (via `httputil.ProxyRequest.SetXForwarded`). It **panics if `target` is `nil`**. -Source: `celeris/middleware/adapters/adapters.go:210-237`. +Source: `celeris/middleware/adapters/adapters.go` (`ReverseProxy`). ### Options @@ -382,11 +382,11 @@ s.Any("/api/*path", proxy) ``` > `ReverseProxy` delegates to `celeris.Adapt` (Source: -> `celeris/middleware/adapters/adapters.go:236`), so it inherits the same **buffering** +> `celeris/middleware/adapters/adapters.go` (`ReverseProxy`)), so it inherits the same **buffering** > behaviour: the backend response is read fully into memory (100 MB cap) before it > reaches the client. **Streaming responses — SSE, WebSocket upgrade, or large > downloads — are not supported through this proxy.** Source: -> `celeris/bridge.go:92-117`. +> `celeris/bridge.go` (`maxBridgeResponseBytes`). ## Caveats @@ -397,7 +397,7 @@ performance feature. Know the trade-offs before you commit to one. routes through a capturing `http.ResponseWriter`. `WrapMiddleware` costs roughly 8–15 heap allocations per request. On a hot path served thousands of times per second, that overhead is measurable. Source: - `celeris/middleware/adapters/adapters.go:104-152` (`buildRequest`). + `celeris/middleware/adapters/adapters.go` (`buildRequest`). - **No zero-copy, no streaming.** `Adapt`, `WrapMiddleware`, and `ReverseProxy` all buffer the full response in memory (100 MB cap). `ToHandler` buffers the request body the same way. Nothing flushes incrementally. @@ -438,7 +438,7 @@ a catch-all route and migrate endpoints out over time. **Does `Adapt` pass the request context through?** Yes. The reconstructed `*http.Request` uses the Celeris request context (`c.Context()`), so deadlines, cancellation, and context values flow into the -stdlib handler. Source: `celeris/bridge.go:67`. +stdlib handler. Source: `celeris/bridge.go` (`buildHTTPRequest`). **How do I test a Celeris handler with `httptest`?** Wrap it with `celeris.ToHandler` and call `ServeHTTP` against an diff --git a/src/content/docs/observability.md b/src/content/docs/observability.md index 6a0eeae..321ac54 100644 --- a/src/content/docs/observability.md +++ b/src/content/docs/observability.md @@ -52,14 +52,14 @@ affects what they see (see [Ordering](#recommended-ordering)). ## Built-in metrics — the Collector The server creates an `observe.Collector` **eagerly in `New`** unless you set -`Config.DisableMetrics: true` (`celeris/config.go:178-181`, `celeris/server.go:152-154`). +`Config.DisableMetrics: true` (`celeris/config.go` (`Config.DisableMetrics`), `celeris/server.go` (`New`)). It uses lock-free, cache-line-padded counters sharded per worker, so recording is cheap on the hot path. ### Getting the Collector `Server.Collector()` returns the live collector, or `nil` only when metrics are -disabled via `DisableMetrics` (`celeris/server.go:863-868`). Because the collector is +disabled via `DisableMetrics` (`celeris/server.go` (`Server.Collector`)). Because the collector is created in `New`, it is non-nil immediately — you do **not** need to wait for `Start`. Engine-derived fields (`ActiveConns`, `EngineMetrics`), however, stay zero until the server is running and the engine is wired: @@ -79,12 +79,12 @@ if col != nil { > **Pitfall:** `Collector()` returns `nil` only when `DisableMetrics` is true — > still nil-check it before dereferencing. The `middleware/debug` `/metrics` > endpoint returns `501 Not Implemented` when no collector is wired in -> (`celeris/middleware/debug/debug.go:197`). +> (`celeris/middleware/debug/debug.go` (`handleMetrics`)). ### Reading a Snapshot `Collector.Snapshot()` returns a point-in-time copy of all counters -(`celeris/observe/collector.go:181`). All fields are read-only values captured +(`celeris/observe/collector.go` (`Collector.Snapshot`)). All fields are read-only values captured at the moment of the call. | Field | Type | Meaning | @@ -101,7 +101,7 @@ at the moment of the call. The histogram is paired: `LatencyBuckets[i]` is the count of requests whose latency was `<= BucketBounds[i]` seconds (the final bucket is the overflow). The default bounds are `0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 5` -seconds (`celeris/observe/collector.go:11`). +seconds (`celeris/observe/collector.go` (`defaultBucketBounds`)). ```go snap := s.Collector().Snapshot() @@ -122,7 +122,7 @@ if snap.CPUUtilization >= 0 { ### EngineMetrics `Snapshot.EngineMetrics` (and `Server.EngineInfo().Metrics`) expose the I/O -engine's own counters (`celeris/observe/engine_metrics.go:22`). These are the same +engine's own counters (`celeris/observe/engine_metrics.go` (`EngineMetrics`)). These are the same counters the adaptive controller reads to pick an engine. | Field | Type | Meaning | @@ -142,7 +142,7 @@ counters the adaptive controller reads to pick an engine. If you only want engine-level info (not the request histogram), `Server.EngineInfo()` returns the active engine type and its metrics, or `nil` -if the server isn't started (`celeris/server.go:802-812`): +if the server isn't started (`celeris/server.go` (`Server.EngineInfo`)): ```go if info := s.EngineInfo(); info != nil { @@ -180,7 +180,7 @@ It is intentionally rich and zero-alloc on the steady-state path. ### The server-level logger `Config.Logger` is the server's structured logger; it defaults to -`slog.Default()` when nil (`celeris/config.go:262-263`). This logger is used by the +`slog.Default()` when nil (`celeris/config.go` (`Config.Logger`)). This logger is used by the server itself (and is the default sink for several middleware, including `recovery` — see [Error handling](/docs/error-handling)). Set it once at construction: @@ -197,9 +197,9 @@ s := celeris.New(celeris.Config{ ### Request access logs -Install `logger.New()` to log every request (`celeris/middleware/logger/logger.go:24`). +Install `logger.New()` to log every request (`celeris/middleware/logger/logger.go` (`New`)). With no config its `Output` defaults to `slog.Default()` and it uses the default -level mapping (`celeris/middleware/logger/config.go:209`): +level mapping (`celeris/middleware/logger/config.go` (`applyDefaults`)): ```go import "github.com/goceleris/celeris/middleware/logger" @@ -211,13 +211,13 @@ Every entry carries these base attributes (always emitted): `method`, `path`, `status`, `latency`, `bytes`. When present, the middleware also adds `client_ip`, `request_id` (from [Request IDs](#request-ids)), and `error` (the handler's returned error string). Source: -`celeris/middleware/logger/logger.go:116`. +`celeris/middleware/logger/logger.go` (`New`). #### Level mapping `Config.Level` maps a response status code to a `slog.Level`. The default maps 5xx → `Error`, 4xx → `Warn`, everything else → `Info` -(`celeris/middleware/logger/config.go:222`). Override it to, say, treat 404s as +(`celeris/middleware/logger/config.go` (`defaultLevel`)). Override it to, say, treat 404s as info: ```go @@ -282,7 +282,7 @@ s.Use(logger.New(logger.Config{ `CaptureRequestBody` and `CaptureResponseBody` log the request/response body (attrs `request_body` / `response_body`), each truncated to `MaxCaptureBytes` -(default 4096) (`celeris/middleware/logger/config.go:93`). Response capture +(default 4096) (`celeris/middleware/logger/config.go` (`Config.CaptureRequestBody`)). Response capture requires the response to be buffered, which the middleware enables via `c.CaptureResponse()` automatically. @@ -302,7 +302,7 @@ s.Use(logger.New(logger.Config{ This is the single most important thing to get right in the logger. **Header and form-field redaction have asymmetric defaults** -(`celeris/middleware/logger/config.go:107` and `:180`): +(`celeris/middleware/logger/config.go` (`Config.SensitiveHeaders`) and `:180`): | Setting | `nil` (unset) | `[]string{}` (empty) | non-empty | | --- | --- | --- | --- | @@ -323,15 +323,15 @@ s.Use(logger.New(logger.Config{ Helpers `logger.DefaultSensitiveHeaders()` and `logger.DefaultSensitiveFormFields()` return fresh copies of the built-in lists -(`celeris/middleware/logger/config.go:25` and `:55`). Redacted values are +(`celeris/middleware/logger/config.go` (`DefaultSensitiveHeaders`) and `:55`). Redacted values are replaced with `[REDACTED]`. Note that **every configured sensitive header is always emitted as `[REDACTED]`**, present or not — a constant-presence design that avoids leaking which headers a request carried -(`celeris/middleware/logger/logger.go:231`). +(`celeris/middleware/logger/logger.go` (`New`)). ### Presets — CLF and JSON -Two preset configs are provided (`celeris/middleware/logger/config.go:243`, +Two preset configs are provided (`celeris/middleware/logger/config.go` (`CLFConfig`), `:283`): ```go @@ -347,7 +347,7 @@ s.Use(logger.New(logger.JSONConfig())) `FastHandler` is a high-performance `slog.Handler` that formats records directly into a pooled buffer with zero steady-state allocations, producing -`slog.TextHandler`-compatible output (`celeris/middleware/logger/fasthandler.go:21`). +`slog.TextHandler`-compatible output (`celeris/middleware/logger/fasthandler.go` (`FastHandler`)). The logger middleware has a fast path that bypasses `slog.Record` entirely when the output uses `FastHandler`. @@ -380,14 +380,14 @@ output. When `FastHandler`'s writer is `io.Discard`, the handler reports `Enabled() == false` and short-circuits all formatting — letting you keep per-request instrumentation (request-id propagation, latency, redaction) with no log output, which is exactly what benchmarks use -(`celeris/middleware/logger/fasthandler.go:123`). +(`celeris/middleware/logger/fasthandler.go` (`NewFastHandler`)). ## Request IDs `middleware/requestid` assigns a unique ID to every request, stores it on the context, and echoes it in a response header. The `logger` middleware then automatically includes it as the `request_id` attr, so install requestid -**before** logger (`celeris/middleware/requestid/requestid.go:60`). +**before** logger (`celeris/middleware/requestid/requestid.go` (`New`)). ```go import "github.com/goceleris/celeris/middleware/requestid" @@ -419,10 +419,10 @@ func handler(c *celeris.Context) error { ``` - `requestid.FromContext(c)` reads the ID from the Celeris context store - (`celeris/middleware/requestid/requestid.go:38`). + (`celeris/middleware/requestid/requestid.go` (`FromContext`)). - `requestid.FromStdContext(ctx)` reads it from a stdlib `context.Context` — but **only works if `EnableStdContext` is true** (see below) - (`celeris/middleware/requestid/requestid.go:17`). + (`celeris/middleware/requestid/requestid.go` (`FromStdContext`)). - `requestid.ContextKey` is the store key (`"request_id"`). ### Configuration @@ -448,7 +448,7 @@ s.Use(requestid.New(requestid.Config{ ``` Inbound IDs are validated regardless: an ID must be 1–128 printable-ASCII bytes -or it is discarded and regenerated (`celeris/middleware/requestid/requestid.go:43`). +or it is discarded and regenerated (`celeris/middleware/requestid/requestid.go` (`validID`)). ## Prometheus (separate module) @@ -460,7 +460,7 @@ go get github.com/goceleris/celeris/middleware/metrics ``` It serves a Prometheus exposition endpoint (default `/metrics`) and records -per-request metrics (`celeris/middleware/metrics/metrics.go:30`): +per-request metrics (`celeris/middleware/metrics/metrics.go` (`New`)): ```go import "github.com/goceleris/celeris/middleware/metrics" @@ -540,7 +540,7 @@ go get github.com/goceleris/celeris/middleware/otel It creates a server span per request with W3C trace-context propagation and (optionally) OTel metrics, exporting to whatever providers you've configured -globally (`celeris/middleware/otel/otel.go:322`). By default it uses the global +globally (`celeris/middleware/otel/otel.go` (`New`)). By default it uses the global providers, so configure your tracer/meter/propagator once via the OTel SDK and just install the middleware: @@ -558,7 +558,7 @@ s.Use(otel.New()) Each request produces a server-kind span named `"METHOD /route"` with standard HTTP semantic-convention attributes (method, route, scheme, path, protocol version, server address, response status/size). The request ID is added as a -`request.id` span attribute when present (`celeris/middleware/otel/otel.go:484`). +`request.id` span attribute when present (`celeris/middleware/otel/otel.go` (`New`)). When metrics are enabled (the default), it also records `http.server.request.duration`, `http.server.active_requests`, `http.server.request.body.size`, and `http.server.response.body.size`. Their @@ -580,7 +580,7 @@ set `MetricServerAddress` where the `Host` is bounded upstream ### Reading the active span `otel.SpanFromContext(c)` returns the active span so you can add attributes or -events from a handler (`celeris/middleware/otel/otel.go:315-319`): +events from a handler (`celeris/middleware/otel/otel.go` (`SpanFromContext`)): ```go func handler(c *celeris.Context) error { @@ -613,7 +613,7 @@ func handler(c *celeris.Context) error { > `CollectClientIP`), and `url.query` is **never** added to spans because query > strings frequently carry tokens, emails, and session IDs. If you need the > query string, add it yourself via `CustomAttributes` -> (`celeris/middleware/otel/config.go:65`). +> (`celeris/middleware/otel/config.go` (`Config`)). ```go disableUA := false @@ -636,7 +636,7 @@ with `403` unless you replace `AuthFunc`. ### pprof `middleware/pprof` mounts the standard `net/http/pprof` handlers under -`/debug/pprof` (`celeris/middleware/pprof/pprof.go:32`). It is part of the main +`/debug/pprof` (`celeris/middleware/pprof/pprof.go` (`New`)). It is part of the main module (no extra `go get`). ```go @@ -665,7 +665,7 @@ Available endpoints: `index`, `cmdline`, `profile`, `symbol`, `trace`, ### debug — `/debug/celeris` JSON `middleware/debug` serves a JSON diagnostics tree under `/debug/celeris`, -loopback-only by default (`celeris/middleware/debug/debug.go:75`). Part of the +loopback-only by default (`celeris/middleware/debug/debug.go` (`New`)). Part of the main module. ```go diff --git a/src/content/docs/routing.md b/src/content/docs/routing.md index 52c80c0..68f78e1 100644 --- a/src/content/docs/routing.md +++ b/src/content/docs/routing.md @@ -36,7 +36,7 @@ s.OPTIONS("/users", optionsUser) The seven standard HTTP verbs each have a dedicated method on `*Server` (and on `*RouteGroup`): `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`. -Source: `celeris/server.go:223-264`. +Source: `celeris/server.go` (`Server.GET`). ### `HEAD` and `OPTIONS` are answered for you @@ -101,7 +101,7 @@ for _, r := range routes { } ``` -Source: `celeris/server.go:266-274`. +Source: `celeris/server.go` (`Server.Any`). ### `Handle` — custom and runtime-chosen methods @@ -116,7 +116,7 @@ method := resolveMethodFromConfig() s.Handle(method, "/rpc", rpcHandler) ``` -Source: `celeris/server.go:216-221`. +Source: `celeris/server.go` (`Server.Handle`). > The standard verb methods are just thin wrappers over `Handle`, so there is no > behavioural difference for `GET`/`POST`/etc. versus `Handle("GET", …)`. @@ -152,9 +152,9 @@ s.GET("/files/*path", func(c *celeris.Context) error { ``` The matcher prepends the slash to the remaining path -(`celeris/router_tree.go:206-208`). When the request is exactly the prefix **with +(`celeris/router_tree.go` (`search`)). When the request is exactly the prefix **with its trailing slash** (`/files/`), the catch-all matches with an empty value `""` -(`celeris/router_tree.go:174-178`). The bare prefix without the slash (`/files`) +(`celeris/router_tree.go` (`search`)). The bare prefix without the slash (`/files`) does **not** match `/files/*path` at all and falls through to the 404 handler — if you need `/files` to resolve too, register a separate route for it. @@ -163,7 +163,7 @@ you need `/files` to resolve too, register a separate route for it. When more than one pattern could match a request, Celeris always prefers the most specific: **static > parameter > catch-all**. This ordering is independent of the order in which you register the routes — the trie sorts children by specificity at -insert time (`celeris/router_tree.go:106`). +insert time (`celeris/router_tree.go` (`insertSorted`)). ```go s.GET("/users/:id", showUser) @@ -176,7 +176,7 @@ s.GET("/users/me", showCurrentUser) // registered AFTER, still wins for /users/m ### Slash normalisation The router collapses **consecutive** slashes before matching, so `//users///42` -resolves the same as `/users/42` (`celeris/router_tree.go:236-263`). When that +resolves the same as `/users/42` (`celeris/router_tree.go` (`cleanPath`)). When that collapse runs it also drops a single trailing slash, so `/users/42//` matches `/users/:id`. @@ -216,12 +216,12 @@ loudly at startup rather than silently mis-routing. Registration **panics** when | A catch-all name is empty | `/files/*` | `path contains empty catchAll name` | | A catch-all is not the last segment | `/files/*p/more` | `catchAll parameter must be the last path segment` | -Source: `celeris/router_tree.go:265-288` and `celeris/router.go:510-513`. +Source: `celeris/router_tree.go` (`validatePath`) and `celeris/router.go` (`router.addRouteWithAsync`). ## Reading path parameters Inside a handler, read captured parameters off the `*Context`. All accessors are in -`celeris/context_request.go:99-132`. +`celeris/context_request.go` (`Context.Param`). | Method | Returns | Behaviour | | ------------------------------------- | ---------------------- | ---------------------------------------------------------- | @@ -248,7 +248,7 @@ s.GET("/posts/:slug", func(c *celeris.Context) error { ### `FullPath` — the matched pattern `c.FullPath()` returns the **route pattern** that matched (e.g. `/users/:id`), not -the concrete request path (`celeris/context_request.go:36-44`). This is exactly what +the concrete request path (`celeris/context_request.go` (`Context.FullPath`)). This is exactly what you want for low-cardinality metric labels and structured-log fields — using the raw path would explode your label cardinality with one series per `id`. @@ -265,7 +265,7 @@ handler). ## Route groups `Server.Group(prefix, middleware...)` creates a `*RouteGroup` that shares a path -prefix and a middleware stack across many routes (`celeris/server.go:431-439`). Group +prefix and a middleware stack across many routes (`celeris/server.go` (`Server.Group`)). Group middleware runs **after** server-level middleware but **before** the route's own handlers. @@ -278,14 +278,14 @@ api.POST("/items", createItem) // → POST /api/items `*RouteGroup` exposes the same registration surface as `*Server`: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `Any`, `Handle`, and `Static` -(`celeris/group.go:77-133`). +(`celeris/group.go` (`RouteGroup.GET`)). ### Sub-groups inherit a copy of the parent `group.Group(prefix, mw...)` nests groups. The child concatenates prefixes and receives a **copy** of the parent's middleware (so later changes to the parent do not retroactively affect the child) plus the child's own middleware. The child also -inherits the parent's async/sync setting (`celeris/group.go:137-150`). +inherits the parent's async/sync setting (`celeris/group.go` (`RouteGroup.Group`)). ```go api := s.Group("/api") @@ -299,7 +299,7 @@ v1.GET("/users", listUsersV1) // → GET /api/v1/users, runs authMiddleware Group middleware chains are composed when each route is registered, so `Group.Use` only affects routes added **after** the call. Add all `Use` calls first, then -register routes (`celeris/group.go:71-74`). +register routes (`celeris/group.go` (`RouteGroup.Use`)). ```go api := s.Group("/api") @@ -311,7 +311,7 @@ api.GET("/items", listItems) > At the **server** level the same rule is enforced more strictly: `s.Use(...)` > **panics** if called after any route is registered, to surface the silent > inconsistency where some routes get the middleware and others don't -> (`celeris/server.go:197-199`). On a group it does not panic — it simply applies +> (`celeris/server.go` (`Server.Use`)). On a group it does not panic — it simply applies > only going forward — so be deliberate about ordering. ## The `*Route` handle @@ -323,8 +323,8 @@ methods must be called before `Start`. | Method | On duplicate name | Source | | ------------------------- | ----------------------------------------- | ----------------------- | -| `Name(name) *Route` | **panics**: `duplicate route name: …` | `celeris/router.go:163-177` | -| `TryName(name) error` | returns `ErrDuplicateRouteName` | `celeris/router.go:179-194` | +| `Name(name) *Route` | **panics**: `duplicate route name: …` | `celeris/router.go` (`Route.Name`) | +| `TryName(name) error` | returns `ErrDuplicateRouteName` | `celeris/router.go` (`Route.TryName`) | Use `Name` when a duplicate is a programming error you want to catch at startup; use `TryName` when names may legitimately collide (e.g. plugin-registered routes) and @@ -343,7 +343,7 @@ if err := s.GET("/posts/:id", showPost).TryName("post"); err != nil { ### Route-level middleware after registration `Route.Use` prepends middleware to a single route's chain, inserting it just before -the terminal handler (`celeris/router.go:196-220`). It panics if the route has no +the terminal handler (`celeris/router.go` (`Route.Use`)). It panics if the route has no handlers: ```go @@ -376,7 +376,7 @@ api.GET("/cached", cachedHandler).Sync() // …opt this one back to in `UsesDriver` is the recommended marker for routes that call a Celeris postgres/redis/memcached driver opened `WithEngine(srv)`: such drivers may complete faster than the adaptive promotion threshold, so an explicit mark guarantees the -handler is dispatched off the worker. Source: `celeris/router.go:222-275`. +handler is dispatched off the worker. Source: `celeris/router.go` (`Route.Async`). For the full dispatch model and `Config.AsyncHandlers`, see [Engines](/docs/engines). @@ -387,8 +387,8 @@ strings. There are two builders on `*Server`: | Method | Param substitution | Source | | ----------------------------------------- | -------------------------------- | ----------------------- | -| `URL(name, params...) (string, error)` | positional, in pattern order | `celeris/server.go:348` | -| `URLMap(name, map) (string, error)` | by parameter name | `celeris/server.go:378` | +| `URL(name, params...) (string, error)` | positional, in pattern order | `celeris/server.go` (`Server.URL`) | +| `URLMap(name, map) (string, error)` | by parameter name | `celeris/server.go` (`Server.URLMap`) | ```go s.GET("/users/:id/posts/:pid", showPost).Name("post") @@ -427,7 +427,7 @@ if errors.Is(err, celeris.ErrRouteNotFound) { ## Introspection `Server.Routes()` returns a `[]RouteInfo` describing every registered route, sorted -by method then path for deterministic output (`celeris/server.go:337-341`). Handy for +by method then path for deterministic output (`celeris/server.go` (`Server.Routes`)). Handy for startup logging, debug endpoints, or generating an API map. ```go @@ -436,7 +436,7 @@ for _, r := range s.Routes() { } ``` -`RouteInfo` fields (`celeris/server.go:40-46`): +`RouteInfo` fields (`celeris/server.go` (`RouteInfo`)): | Field | Type | Description | | -------------- | -------- | --------------------------------------------------------- | @@ -449,11 +449,11 @@ for _, r := range s.Routes() { - **Duplicate method + path silently overwrites.** Registering the same method and path twice keeps only the last handler and emits a `WARN` log (`celeris: duplicate route registration; previous handler overwritten`). It does - not panic, so it's easy to miss — watch your logs (`celeris/router.go:638-646`). + not panic, so it's easy to miss — watch your logs (`celeris/router.go` (`warnDuplicateRoute`)). - **Never `Sync()` a WebSocket or SSE route.** Handlers that hijack/detach the connection are async by construction; forcing them inline breaks them. The same warning applies to `Group.Sync()` over such routes - (`celeris/router.go:245-259`, `celeris/group.go:54-65`). + (`celeris/router.go` (`Route.Sync`), `celeris/group.go` (`RouteGroup.Sync`)). - **`s.Use(...)` after a route panics.** Move all server-level `Use` calls above your first route registration. (Group `Use` does not panic but silently applies only going forward.) @@ -475,7 +475,7 @@ has to be unique within a single pattern. **How do I serve a directory of files?** Use `s.Static(prefix, root)` (or `group.Static`). It registers a catch-all GET route with built-in path-traversal protection — you don't manage the `*path` parameter -yourself (`celeris/server.go:326-335`). +yourself (`celeris/server.go` (`Server.Static`)). **What happens on a path match with the wrong method?** The router returns the set of allowed methods; register a handler with diff --git a/src/content/docs/sse.md b/src/content/docs/sse.md index 6427723..44cd96f 100644 --- a/src/content/docs/sse.md +++ b/src/content/docs/sse.md @@ -50,7 +50,7 @@ s.GET("/events", sse.New(sse.Config{ (see [Routing](/docs/routing)). It works across **all** Celeris engines — std, epoll, and io_uring — because the middleware detaches the connection internally; you never manage the event-loop lifecycle yourself. Source: -`celeris/middleware/sse/sse.go:487`, `celeris/middleware/sse/sse.go:700-705`. +`celeris/middleware/sse/sse.go` (`New`). ## Basic usage @@ -121,7 +121,7 @@ The return contract for `Send` is worth internalising: - **nil error (queued mode)** — the event was *enqueued or dropped* per your slow-client policy; check `DroppedEvents()` to detect drops. -Source: `celeris/middleware/sse/sse.go:64-99`. +Source: `celeris/middleware/sse/sse.go` (`Client.Send`). > Always check the error from `Send` and bail out on non-nil. A failed write > cancels the client's context, so the next loop iteration would otherwise spin. @@ -179,8 +179,8 @@ sse.Config{ ``` Setting `HeartbeatInterval` to `0` keeps the default of 15 seconds; only a -**negative** value disables it. Source: `celeris/middleware/sse/config.go:51-58`, -`celeris/middleware/sse/sse.go:667-695`. +**negative** value disables it. Source: `celeris/middleware/sse/config.go` (`Config.HeartbeatInterval`), +`celeris/middleware/sse/sse.go` (`New`). ### Reconnect delay @@ -213,7 +213,7 @@ sse.New(sse.Config{ }) ``` -Source: `celeris/middleware/sse/sse.go:428-439`, `:560-562`. +Source: `celeris/middleware/sse/sse.go` (`New`), `:560-562`. **`HEAD` requests.** A `HEAD` to an SSE route is answered by its `GET` route (see [Routing](/docs/routing#head-and-options-are-answered-for-you)) and gets the @@ -250,7 +250,7 @@ sse.New(sse.Config{ ### `ClientPolicy` values -Source: `celeris/middleware/sse/config.go:24-39`. +Source: `celeris/middleware/sse/config.go` (`ClientPolicy`). | Policy | Effect when the queue is full | | -------------------- | ------------------------------------------------------------------------------------ | @@ -281,7 +281,7 @@ sse.Config{ // if client.QueueDepth() > 100 { ...degrade gracefully... } ``` -Source: `celeris/middleware/sse/sse.go:229-243`. +Source: `celeris/middleware/sse/sse.go` (`Client.DroppedEvents`). ## Fan-out with a Broker @@ -313,7 +313,7 @@ s.POST("/announce", func(c *celeris.Context) error { `Subscribe` returns an `unsubscribe` function that the handler **must** defer — it removes the subscriber and joins its drain goroutine. Calling it twice is safe. Subscribing to a closed broker is a no-op. Source: -`celeris/middleware/sse/broker.go:139-175`. +`celeris/middleware/sse/broker.go` (`Broker.Subscribe`). ### Broker API @@ -331,7 +331,7 @@ Source: `celeris/middleware/sse/broker.go`. ### `BrokerConfig` -All fields are optional. Source: `celeris/middleware/sse/broker.go:32-58`. +All fields are optional. Source: `celeris/middleware/sse/broker.go` (`BrokerConfig`). | Field | Type | Default | Purpose | | --------------------------- | ------------------------------------------ | ---------------------- | ------------------------------------------------------------------ | @@ -339,7 +339,7 @@ All fields are optional. Source: `celeris/middleware/sse/broker.go:32-58`. | `OnSlowSubscriber` | `func(*Client, *PreparedEvent) BrokerPolicy` | `BrokerPolicyDrop` | Policy when a subscriber's queue is full at publish time. | | `SlowSubscriberConcurrency` | `int` | `GOMAXPROCS*4` | Caps in-flight slow-path goroutines. Negative opts out (benchmarks only). | -`BrokerPolicy` mirrors `ClientPolicy` (source: `celeris/middleware/sse/broker.go:10-30`): +`BrokerPolicy` mirrors `ClientPolicy` (source: `celeris/middleware/sse/broker.go` (`BrokerPolicy`)): | Policy | Effect on a slow subscriber | | --------------------- | ------------------------------------------------------------------ | @@ -378,7 +378,7 @@ safe for concurrent reads once constructed. Per subscriber, events arrive in publish order (each drains a FIFO channel). **Across** subscribers there is no global ordering — fan-out is concurrent, so a fast subscriber may see event N before a slow one sees event N-1. Source: -`celeris/middleware/sse/broker.go:177-204`, `celeris/middleware/sse/broker.go:221-255`. +`celeris/middleware/sse/broker.go` (`Broker.drain`), `celeris/middleware/sse/broker.go` (`Broker.PublishPrepared`). ## Resumable streams (`Last-Event-ID`) @@ -410,7 +410,7 @@ Handler: func(client *sse.Client) { field with the store-assigned canonical ID** (so you don't manage IDs at all); - on an unknown cursor, falls through to a fresh start (details below). -Source: `celeris/middleware/sse/config.go:79-100`, `celeris/middleware/sse/sse.go:617-653`. +Source: `celeris/middleware/sse/config.go` (`Config.ReplayStore`), `celeris/middleware/sse/sse.go` (`New`). ### In-memory ring buffer @@ -432,7 +432,7 @@ s.GET("/feed", sse.New(sse.Config{ })) ``` -Source: `celeris/middleware/sse/replay_ring.go:15`. +Source: `celeris/middleware/sse/replay_ring.go` (`NewRingBuffer`). ### KV-backed store (durable / multi-instance) @@ -456,7 +456,7 @@ if err != nil { s.GET("/feed", sse.New(sse.Config{ReplayStore: replay, Handler: feedHandler})) ``` -`KVReplayStoreConfig` fields (source: `celeris/middleware/sse/replay_kv.go:32-79`): +`KVReplayStoreConfig` fields (source: `celeris/middleware/sse/replay_kv.go` (`KVReplayStoreConfig`)): | Field | Type | Default | Purpose | | ------------------------ | --------------- | -------------- | ------------------------------------------------------------------ | @@ -472,7 +472,7 @@ When the supplied KV also implements `store.Counter` (the in-memory KV does, via `Increment`; so does the Redis adapter via `INCR`), IDs are allocated atomically against the backend, giving every process a single monotonic ID space. Without `Counter`, the store falls back to a per-process counter and multi-instance setups -will see ID collisions. Source: `celeris/middleware/sse/replay_kv.go:81-124`. +will see ID collisions. Source: `celeris/middleware/sse/replay_kv.go` (`NewKVReplayStore`). #### `AsyncAppend` — latency vs. durability @@ -500,7 +500,7 @@ replay, _ := sse.NewKVReplayStore(sse.KVReplayStoreConfig{ }) ``` -Source: `celeris/middleware/sse/replay_kv.go:48-72`, `:160-190`. +Source: `celeris/middleware/sse/replay_kv.go` (`KVReplayStoreConfig`), `:160-190`. ### Unknown-cursor behaviour @@ -509,8 +509,8 @@ the retention window — the store's `Since` returns `sse.ErrLastIDUnknown`. The middleware treats this as a **fresh start**: it writes no replay events but still runs your `Handler`, and the original header value stays visible via `client.LastEventID()` so your handler can react (for example, send a "you may have -missed messages" marker). Source: `celeris/middleware/sse/replay.go:8-14`, -`celeris/middleware/sse/sse.go:623-639`. +missed messages" marker). Source: `celeris/middleware/sse/replay.go` (`ErrLastIDUnknown`), +`celeris/middleware/sse/sse.go` (`New`). ```go Handler: func(client *sse.Client) { @@ -526,7 +526,7 @@ Handler: func(client *sse.Client) { ## Event format `sse.Event` is the struct you hand to `Send`. Source: -`celeris/middleware/sse/event.go:13-30`. +`celeris/middleware/sse/event.go` (`Event`). | Field | Type | Wire output | Notes | | ------- | -------- | ------------------------------------ | -------------------------------------------------------------------- | @@ -547,7 +547,7 @@ buf := sse.FormatEvent(nil, sse.Event{Event: "ping", Data: "hi"}) // buf == "event: ping\ndata: hi\n\n" ``` -Source: `celeris/middleware/sse/event.go:34-62`. +Source: `celeris/middleware/sse/event.go` (`FormatEvent`). ## How it works across engines @@ -558,7 +558,7 @@ handler immediately; on the std engine it runs the stream inline (returning woul tell `net/http` the response is done). You don't have to think about any of this — the middleware calls `Context.Detach` and picks the right path internally. This is why the same `sse.Config` runs unchanged on all engines. Source: -`celeris/middleware/sse/sse.go:346-707`. For the underlying detach mechanics, see +`celeris/middleware/sse/sse.go` (`New`). For the underlying detach mechanics, see [Streaming](/docs/streaming). ### Server timeouts @@ -585,7 +585,7 @@ closing the connection, or a shutdown. See [Configuration](/docs/configuration#s flow while the handler is on the stack. If you publish into a `Client` from a spawned goroutine, join that goroutine **before** the handler returns. (The middleware guards against a panic, returning `ErrClientClosed`, but the - ownership is yours.) Source: `celeris/middleware/sse/sse.go:64-99`. + ownership is yours.) Source: `celeris/middleware/sse/sse.go` (`Client.Send`). - **Not deferring the broker's `unsubscribe`.** It both removes the subscriber and joins its drain goroutine; skipping it leaks a goroutine per connection. - **Treating `HeartbeatInterval: 0` as "disabled".** Zero means the 15s default; @@ -616,7 +616,7 @@ subscribed to several brokers at once. **Can a broker-subscribed client also use `MaxQueueDepth`?** Yes — they're independent layers. The broker writes via `WritePreparedEvent` (always synchronous), so the per-client queue only engages if something calls -`client.Send` directly. Source: `celeris/middleware/sse/prepared.go:26-42`. +`client.Send` directly. Source: `celeris/middleware/sse/prepared.go` (`Client.WritePreparedEvent`). **How do I detect that a client left?** Select on `client.Context().Done()`, or watch for a non-nil error from `Send`. Heartbeats turn a silently dead connection diff --git a/src/content/docs/streaming.md b/src/content/docs/streaming.md index 2ab02ad..43fbcab 100644 --- a/src/content/docs/streaming.md +++ b/src/content/docs/streaming.md @@ -51,7 +51,7 @@ if sw == nil { } ``` -Source: `celeris/context_response.go:1695-1742`. +Source: `celeris/context_response.go` (`appendClone`). `StreamWriter()` returns `nil` in two situations: @@ -66,7 +66,7 @@ Source: `celeris/context_response.go:1695-1742`. ### The StreamWriter API Once you hold a non-nil `*StreamWriter`, you drive the response with these methods. -Source: `celeris/context_response.go:1625-1693`. +Source: `celeris/context_response.go` (`Context.materializeRequestViews`). | Method | Returns | Purpose | | --------------------------------------- | ---------------- | ----------------------------------------------------------------------- | @@ -143,8 +143,8 @@ writers, and any other standard library writer. After you use a `StreamWriter`, `Context.IsWritten()` returns `true` and `Context.BytesWritten()` tracks the running total (it may keep increasing while the -stream is still in progress). Source: `celeris/context_response.go:1276`, -`celeris/context_response.go:1282`. +stream is still in progress). Source: `celeris/context_response.go` (`Context.IsWritten`), +`celeris/context_response.go` (`Context.BytesWritten`). ## The async-detach model @@ -163,7 +163,7 @@ engine — and Celeris exposes a single boolean to decide. `Context.EngineSupportsAsyncDetach()` reports whether the active engine can keep the connection alive after the handler returns. Source: -`celeris/context_response.go:1444`. +`celeris/context_response.go` (`Context.EngineSupportsAsyncDetach`). | Engine | `EngineSupportsAsyncDetach()` | Streaming strategy | | ----------------------- | ----------------------------- | ------------------------------------------------------------------ | @@ -179,7 +179,7 @@ handler returns, so the work must complete *before* you return. ### Detach `Context.Detach()` removes the `Context` from the handler chain's lifecycle and -returns a `done func()`. Source: `celeris/context_response.go:1471`. +returns a `done func()`. Source: `celeris/context_response.go` (`Context.Detach`). > **You MUST call `done()` exactly once when you are finished with the `Context`.** > If you don't, the `Context` is never released back to the pool — a permanent leak. @@ -259,8 +259,8 @@ skip the boilerplate. Despite their names, `Context.Stream` and `Context.StreamReader` are **not** incremental. They read the *entire* `io.Reader` into memory and then write it as a -single blob. Source: `celeris/context_response.go:1138` and -`celeris/context_response.go:1153`. +single blob. Source: `celeris/context_response.go` (`Context.Stream`) and +`celeris/context_response.go` (`Context.StreamReader`). ```go // Reads ALL of r into memory, then writes it. Not streaming. @@ -291,7 +291,7 @@ the only way to get true incremental output. When you need the raw TCP connection — to speak a non-HTTP protocol, or to implement an upgrade that the framework doesn't model — call `Context.Hijack()`. It returns the underlying `net.Conn` and hands you full ownership. Source: -`celeris/context_response.go:1304`. +`celeris/context_response.go` (`Context.Hijack`). ```go s.GET("/raw", func(c *celeris.Context) error { @@ -312,7 +312,7 @@ Rules and constraints: - **HTTP/1.1 only.** HTTP/2 multiplexes many logical streams over one TCP connection, so a single stream cannot take it over. `Hijack` on an HTTP/2 request returns - `celeris.ErrHijackNotSupported` (source: `celeris/errors.go:27`). Always check the + `celeris.ErrHijackNotSupported` (source: `celeris/errors.go` (`ErrHijackNotSupported`)). Always check the error. - **You own the connection.** After a successful `Hijack`, Celeris steps out completely. You are responsible for writing any status line/headers and for @@ -333,7 +333,7 @@ or rewrite a response after the handler produced it (loggers, compressors, ETag, response transforms). Application handlers can skip it. Celeris exposes two ways to intercept a response. Source: -`celeris/context_response.go:1196` and `celeris/context_response.go:1223`. +`celeris/context_response.go` (`Context.CaptureResponse`) and `celeris/context_response.go` (`Context.BufferResponse`). | Method | Effect | | ------------------------ | -------------------------------------------------------------------------------------------- | @@ -342,8 +342,8 @@ Celeris exposes two ways to intercept a response. Source: | `FlushResponse()` | Sends the buffered response. Depth-tracked: only the outermost layer's flush actually writes. | | `ResponseBody()` | The captured body (`[]byte`), or `nil` if capture was not enabled. | | `ResponseStatus()` / `ResponseContentType()` | The captured status code / `Content-Type`. | -| `IsWritten()` | `true` once a response has reached the wire. Source: `celeris/context_response.go:1276`. | -| `BytesWritten()` | Body size in bytes (running total while streaming). Source: `celeris/context_response.go:1282`. | +| `IsWritten()` | `true` once a response has reached the wire. Source: `celeris/context_response.go` (`Context.IsWritten`). | +| `BytesWritten()` | Body size in bytes (running total while streaming). Source: `celeris/context_response.go` (`Context.BytesWritten`). | `BufferResponse` is **depth-tracked**: several middleware layers can each call it, and the response is only sent when the outermost layer calls `FlushResponse`. A typical @@ -364,7 +364,7 @@ func transform(c *celeris.Context) error { ### StreamWriter is incompatible with buffering This is the crucial interaction for middleware authors. **While buffering is active, -`Context.StreamWriter()` returns `nil`.** Source: `celeris/context_response.go:1718-1723`. +`Context.StreamWriter()` returns `nil`.** Source: `celeris/context_response.go` (`StreamWriter`). Streaming writes bytes directly and irrevocably to the wire; buffering holds a response in memory so it can be discarded or rewritten. The two cannot coexist — a buffered diff --git a/src/content/docs/testing.md b/src/content/docs/testing.md index beed888..ef97ae8 100644 --- a/src/content/docs/testing.md +++ b/src/content/docs/testing.md @@ -29,8 +29,8 @@ There are two constructors. Both build a `*celeris.Context` and return a | `NewContext(method, path string, opts ...Option)` | `(*celeris.Context, *ResponseRecorder)` | You must `defer celeristest.ReleaseContext(ctx)` | | `NewContextT(t *testing.T, method, path, opts ...Option)` | `(*celeris.Context, *ResponseRecorder)` | Registers `t.Cleanup` automatically — no defer needed | -Source: `celeris/celeristest/celeristest.go:261` (`NewContextT`) and -`celeris/celeristest/celeristest.go:271` (`NewContext`). +Source: `celeris/celeristest/celeristest.go` (`NewContextT`) and +`celeris/celeristest/celeristest.go` (`NewContext`). `NewContextT` is the one to reach for in almost every test — it registers the release with `t.Cleanup`, so you can't forget it and you won't leak a pooled @@ -80,7 +80,7 @@ func TestHelloManual(t *testing.T) { ### The `ResponseRecorder` The recorder is a plain struct with three fields and two convenience methods -(`celeris/celeristest/celeristest.go:27`). It captures exactly one response — the +(`celeris/celeristest/celeristest.go` (`ResponseRecorder`)). It captures exactly one response — the last one the handler wrote. | Field / method | Type | Description | @@ -125,7 +125,7 @@ Everything about the simulated request — body, headers, query string, path params, auth, cookies, client address, protocol — is configured through `Option` values passed to the constructor. Each `With*` helper returns an `Option`; pass as many as you need, in any order. They are defined in -`celeris/celeristest/celeristest.go:134-228`. +`celeris/celeristest/celeristest.go` (`WithBody`). | Option | Effect on the test request | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------- | @@ -218,7 +218,7 @@ func TestSession(t *testing.T) { > `WithCookie` does **not** escape `;` or CR/LF inside the value — pass > well-formed values only. To exercise your server's handling of a *malformed* > cookie header, set the raw header yourself with `WithHeader("cookie", …)`. -> Source: `celeris/celeristest/celeristest.go:160-170`. +> Source: `celeris/celeristest/celeristest.go` (`WithContentType`). ### Client IP behind a proxy @@ -226,7 +226,7 @@ func TestSession(t *testing.T) { configured, it walks the chain right-to-left, skips entries inside those networks, and returns the first untrusted IP; without them it returns the **leftmost** XFF entry (legacy behaviour). It falls back to `X-Real-Ip`, then `""` -(`celeris/context_request.go:425-448`). To test the trusted-proxy path, combine +(`celeris/context_request.go` (`Context.ClientIP`)). To test the trusted-proxy path, combine `WithHeader` for the forwarded chain with `WithTrustedProxies`: ```go @@ -306,7 +306,7 @@ There are two independent things to assert on, and a well-rounded test checks bo ### Cleanup With `NewContextT` cleanup is automatic. With `NewContext` you are responsible for -calling `ReleaseContext` (`celeris/celeristest/celeristest.go:230-257`), which returns +calling `ReleaseContext` (`celeris/celeristest/celeristest.go` (`ReleaseContext`)), which returns the context, its stream, and the recorder to their pools: ```go @@ -321,7 +321,7 @@ handler panics. Do not access `ctx` or `rec` after release. ## Testing middleware Middleware is a handler that calls `c.Next()` to invoke the rest of the chain -(`celeris/context.go:372`). To test that interaction you need a real chain, which +(`celeris/context.go` (`Context.Next`)). To test that interaction you need a real chain, which is exactly what `WithHandlers` builds. List the handlers in execution order; the last one is the terminal handler: @@ -391,7 +391,7 @@ func TestRequireAuth_RejectsMissingToken(t *testing.T) { ## Testing error paths Celeris's idiomatic error is `*celeris.HTTPError`, which carries an HTTP status -code and an optional wrapped error (`celeris/errors.go:55`). Handlers return it to +code and an optional wrapped error (`celeris/errors.go` (`HTTPError`)). Handlers return it to signal a specific status; middleware and callers match on it with `errors.As` and `errors.Is`. (For the full model, see [Error handling](/docs/error-handling).) @@ -419,7 +419,7 @@ For the canonical sentinel errors that middleware packages share, match with `errors.Is`. Both `ErrUnauthorized` (401) and `ErrServiceUnavailable` (503) are `*HTTPError` values re-exported across the auth and traffic middleware so a single `errors.Is` matches regardless of which package produced them -(`celeris/errors.go:44` and `celeris/errors.go:50`): +(`celeris/errors.go` (`ErrUnauthorized`) and `celeris/errors.go` (`ErrServiceUnavailable`)): ```go if errors.Is(err, celeris.ErrUnauthorized) { @@ -431,7 +431,7 @@ if errors.Is(err, celeris.ErrServiceUnavailable) { ``` If your handler wraps a domain error, `errors.Is` reaches it through -`HTTPError.Unwrap` (`celeris/errors.go:82`): +`HTTPError.Unwrap` (`celeris/errors.go` (`HTTPError.Unwrap`)): ```go var ErrUserNotFound = errors.New("user not found") @@ -462,7 +462,7 @@ pooling and reset logic in `NewContext`/`ReleaseContext`. **They were removed in v1.6.0; use `celeristest`.** Code that calls them no longer compiles. `celeristest` now reaches the same logic through an internal package -(`celeris/celeristest/celeristest.go:325-359`), and its own API did not change. +(`celeris/celeristest/celeristest.go` (`NewContext`)), and its own API did not change. Each removed hook maps to a `celeristest` call: | Removed in v1.6.0 | Use… | @@ -540,17 +540,17 @@ func TestServerIntegration(t *testing.T) { Key APIs in play: - **`celeris.New(Config{Addr: ":0"})`** — `:0` asks the OS for any free port, - which keeps parallel tests from colliding on a fixed port (`celeris/config.go:75`). + which keeps parallel tests from colliding on a fixed port (`celeris/config.go` (`Config.Addr`)). - **`s.Addr() net.Addr`** — returns the listener's bound address, or `nil` if the server hasn't started yet. Use it to discover the OS-assigned port - (`celeris/server.go:726-734`). + (`celeris/server.go` (`Server.Addr`)). - **`s.Start() error`** — runs the accept loop; it blocks, so call it in a - goroutine (`celeris/server.go:445-462`). + goroutine (`celeris/server.go` (`Server.Start`)). - **`s.Shutdown(ctx) error`** — stops the engine and fires `OnShutdown` hooks, and returns `nil` if the server was never started. On every engine it waits for the in-flight requests and HTTP/2 streams to drain, bounded by `ctx`, then runs the hooks; if `ctx` is done first the hooks still run, with `ctx`, and it returns `ctx`'s error. - Always give it a bounded context (`celeris/server.go:546-617`). + Always give it a bounded context (`celeris/server.go` (`Server.Shutdown`)). > Routes must be registered **before** `Start` — handler chains are baked at > registration time, and the `*Server` is only safe for concurrent use after From 7397a34e4bf286439e4810f739e286be51f6c778 Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Mon, 19 Oct 2026 12:29:40 +0200 Subject: [PATCH 2/5] test: citation ratchet also requires the celeris/ prefix on every named source file (failing first) --- scripts/docs-symbol-citations.test.ts | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/scripts/docs-symbol-citations.test.ts b/scripts/docs-symbol-citations.test.ts index a335c98..701b66f 100644 --- a/scripts/docs-symbol-citations.test.ts +++ b/scripts/docs-symbol-citations.test.ts @@ -93,3 +93,44 @@ test("the continuation detector leaves listen addresses and plain prose alone", expect(continuationLines(lonely)).toEqual([]); for (const text of clean) expect(continuationLines(text)).toEqual([]); }); + +// Item 10: a celeris source file is always named with its celeris/ prefix and +// its full repository path. Two path-only shapes slip past the locator count: +// a path-qualified file without the prefix (`swagger/config.go`, also the +// shorthand `etag/config.go` after a full path) and a bare parenthesised file +// name ((server.go)). +const UNPREFIXED = /(?<=[`( ])(?!celeris\/)[a-z][\w-]*\/[\w./-]+\.go\b|\([a-z_]+\.go\)/; + +export function unprefixedLines(text: string): number[] { + const hits: number[] = []; + text.split("\n").forEach((line, i) => { + if (UNPREFIXED.test(line)) hits.push(i + 1); + }); + return hits; +} + +test("no page names a celeris source file without the celeris/ prefix", () => { + const hits: string[] = []; + for (const f of pages()) { + for (const n of unprefixedLines(readFileSync(join(DOCS, f), "utf8"))) hits.push(`${f}:${n}`); + } + expect(hits).toEqual([]); +}); + +test("the unprefixed-path detector flags both shapes and spares prefixed paths", () => { + for (const text of [ + "Source: `swagger/config.go`, `swagger/swagger.go`.", + "lives in `middleware/store/kv.go`.", + "(`driver/redis/options.go`):", + "`celeris.New(cfg)` (server.go) only allocates", + ]) { + expect(unprefixedLines(text)).not.toEqual([]); + } + for (const text of [ + "Source: `celeris/middleware/swagger/config.go` (`Config`).", + "Create a `main.go`:", + "`celeris.New(cfg)` (`celeris/server.go`) only allocates", + ]) { + expect(unprefixedLines(text)).toEqual([]); + } +}); From 591e3b210d2779878ccb9a90d9114865d1cc7c39 Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Mon, 19 Oct 2026 12:29:40 +0200 Subject: [PATCH 3/5] docs: replace leftover line-number fragments in observability and sse, and prefix path-only mentions in middleware-content and core-concepts --- src/content/docs/core-concepts.md | 18 +++++++++--------- src/content/docs/middleware-content.md | 17 +++++++++-------- src/content/docs/observability.md | 7 +++---- src/content/docs/sse.md | 4 ++-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/content/docs/core-concepts.md b/src/content/docs/core-concepts.md index e96f933..3f4fc35 100644 --- a/src/content/docs/core-concepts.md +++ b/src/content/docs/core-concepts.md @@ -41,7 +41,7 @@ func main() { ### `New` is cheap and non-blocking -`celeris.New(cfg)` (server.go) only allocates the `Server` struct, creates the +`celeris.New(cfg)` (`celeris/server.go`) only allocates the `Server` struct, creates the router, seeds the per-route async default from `Config.AsyncHandlers`, and — unless `Config.DisableMetrics` is set — eagerly constructs the metrics collector so that `Server.Collector()` returns non-nil before `Start`. **It does not bind a socket, @@ -65,7 +65,7 @@ constructs the CPU monitor, and installs the engine. Because it runs exactly once: - The **second** call to any `Start*` method returns `celeris.ErrAlreadyStarted` - (server.go). A `Server` is single-use; you cannot restart it after `Shutdown`. + (`celeris/server.go`). A `Server` is single-use; you cannot restart it after `Shutdown`. - Configuration validation errors and engine-initialization errors surface from the **first** `Start*` call, not from `New`. @@ -120,7 +120,7 @@ See [Configuration](/docs/configuration) for the full `Config` reference and ## 2. The handler contract Every handler and every piece of middleware in Celeris has the **same** type -(types.go): +(`celeris/types.go`): ```go type HandlerFunc func(*Context) error @@ -170,7 +170,7 @@ You install global middleware with `Server.Use`, per-group middleware with ## 3. The Context is pooled The `*Context` your handler receives is **obtained from a `sync.Pool`** -(context.go) and is reset and returned to that pool after your handler chain +(`celeris/context.go`) and is reset and returned to that pool after your handler chain completes. This is central to Celeris's allocation profile, and it imposes one rule you must internalise: @@ -198,12 +198,12 @@ the request (a Go `string` copy is just `x := c.Param("id")`). The two methods documented for cross-request lifetimes are explicit about it in source: -- `c.BodyCopy()` (context_request.go): "returns a copy of the request body that +- `c.BodyCopy()` (`celeris/context_request.go`): "returns a copy of the request body that is safe to retain after the handler returns. Use this instead of `Body()` when the body must outlive the request lifecycle (e.g., for async processing or logging)." `Body()` itself warns the slice "must not be modified or retained after the handler returns." -- `c.RequestHeaders()` (context_request.go): "returns all request headers as +- `c.RequestHeaders()` (`celeris/context_request.go`): "returns all request headers as key-value pairs. The returned slice is a copy safe for concurrent use." ```go @@ -241,7 +241,7 @@ classic footgun. ### Protocol — what's on the wire -`celeris.Protocol` (config.go) is **cleartext only** — Celeris does not terminate +`celeris.Protocol` (`celeris/config.go`) is **cleartext only** — Celeris does not terminate TLS itself; put it behind a TLS-terminating proxy or load balancer. | Protocol | Meaning | @@ -256,7 +256,7 @@ the protocol — enabled for `Auto`, disabled for `HTTP1` and `H2C`. ### Engine — how bytes move -`celeris.EngineType` (config.go) selects the I/O implementation. **The engine is +`celeris.EngineType` (`celeris/config.go`) selects the I/O implementation. **The engine is an I/O strategy, not a protocol** — every engine serves every supported protocol; they differ only in how they talk to the kernel. @@ -304,7 +304,7 @@ understanding before you build. I/O worker, you get goroutine-per-connection parallelism, matching `net/http`'s model. -The trade-off, per `Config.AsyncHandlers` go-doc (config.go): async costs a +The trade-off, per `Config.AsyncHandlers` go-doc (`celeris/config.go`): async costs a goroutine spawn (~100ns) plus scheduler overhead per request — a measured ~3–5% regression on a pure static-response benchmark. So: diff --git a/src/content/docs/middleware-content.md b/src/content/docs/middleware-content.md index ca21491..9d1f550 100644 --- a/src/content/docs/middleware-content.md +++ b/src/content/docs/middleware-content.md @@ -37,7 +37,7 @@ s := celeris.New(celeris.Config{Addr: ":8080"}) s.Use(compress.New()) // zstd → br → gzip, MinLength 256 ``` -Source: `celeris/middleware/compress/compress.go`, `compress/config.go`. +Source: `celeris/middleware/compress/compress.go`, `celeris/middleware/compress/config.go`. ### What it negotiates @@ -141,7 +141,7 @@ import "github.com/goceleris/celeris/middleware/etag" s.Use(etag.New()) // weak ETags, CRC-32 ``` -Source: `celeris/middleware/etag/etag.go`, `etag/config.go`. +Source: `celeris/middleware/etag/etag.go`, `celeris/middleware/etag/config.go`. ### Weak vs strong @@ -226,7 +226,7 @@ s.Use(cache.New()) // in-memory store, 1-minute TTL, GET/HEAD, 2xx only A hit sets `X-Cache: HIT`; a miss runs the handler and sets `X-Cache: MISS`. A store transport error sets `X-Cache: ERROR` and passes through uncached. Source: -`celeris/middleware/cache/cache.go`, `cache/config.go`, `cache/store.go`. +`celeris/middleware/cache/cache.go`, `celeris/middleware/cache/config.go`, `celeris/middleware/cache/store.go`. ### Config reference @@ -436,8 +436,8 @@ go get github.com/goceleris/celeris/middleware/protobuf import "github.com/goceleris/celeris/middleware/protobuf" ``` -Source: `celeris/middleware/protobuf/protobuf.go`, `protobuf/middleware.go`, -`protobuf/config.go`. +Source: `celeris/middleware/protobuf/protobuf.go`, `celeris/middleware/protobuf/middleware.go`, +`celeris/middleware/protobuf/config.go`. ### Content types and errors @@ -544,7 +544,7 @@ itself, and the redirect's `Location` is relative too. So the defaults also work behind a reverse proxy that publishes the app under another prefix and strips it (the browser asks for `/ext/swagger/`, the app sees `/swagger/`). -Source: `celeris/middleware/swagger/swagger.go`, `swagger/config.go`. +Source: `celeris/middleware/swagger/swagger.go`, `celeris/middleware/swagger/config.go`. ### Renderers @@ -675,7 +675,8 @@ request ends the chain ([celeris#927](https://github.com/goceleris/celeris/issue `OAuth2Config` fields: `ClientID`, `Realm`, `AppName`, `Scopes`, `DisablePKCE` (PKCE is on unless it is set; it replaces `UsePKCE`, which an `OAuth2Config` literal that left it out turned off). -Source: `swagger/config.go`, `swagger/swagger.go`, `swagger/assets.go`. +Source: `celeris/middleware/swagger/config.go`, `celeris/middleware/swagger/swagger.go`, +`celeris/middleware/swagger/assets.go`. ### Renderer-specific options @@ -791,7 +792,7 @@ page asked for `standalone.min.js`, a name the package does not publish: if you served Scalar's file under that name, serve it as `standalone.js` now. Swagger UI's OAuth2 redirect page is still served by the middleware. The page is written for the pinned versions above and carries no integrity hash, because the files are yours. `AssetsPath` and `CDN` -cannot both be set (`New` panics). Source: `swagger/assets.go`, `swagger/pins.go`. +cannot both be set (`New` panics). Source: `celeris/middleware/swagger/assets.go`, `celeris/middleware/swagger/pins.go`. See [Static files](/docs/static-files) for serving a directory. ## Common pitfalls diff --git a/src/content/docs/observability.md b/src/content/docs/observability.md index 321ac54..8e74235 100644 --- a/src/content/docs/observability.md +++ b/src/content/docs/observability.md @@ -302,7 +302,7 @@ s.Use(logger.New(logger.Config{ This is the single most important thing to get right in the logger. **Header and form-field redaction have asymmetric defaults** -(`celeris/middleware/logger/config.go` (`Config.SensitiveHeaders`) and `:180`): +(`celeris/middleware/logger/config.go` (`Config.SensitiveHeaders`, `Config.SensitiveFormFields`)): | Setting | `nil` (unset) | `[]string{}` (empty) | non-empty | | --- | --- | --- | --- | @@ -323,7 +323,7 @@ s.Use(logger.New(logger.Config{ Helpers `logger.DefaultSensitiveHeaders()` and `logger.DefaultSensitiveFormFields()` return fresh copies of the built-in lists -(`celeris/middleware/logger/config.go` (`DefaultSensitiveHeaders`) and `:55`). Redacted values are +(`celeris/middleware/logger/config.go` (`DefaultSensitiveHeaders`, `DefaultSensitiveFormFields`)). Redacted values are replaced with `[REDACTED]`. Note that **every configured sensitive header is always emitted as `[REDACTED]`**, present or not — a constant-presence design that avoids leaking which headers a request carried @@ -331,8 +331,7 @@ that avoids leaking which headers a request carried ### Presets — CLF and JSON -Two preset configs are provided (`celeris/middleware/logger/config.go` (`CLFConfig`), -`:283`): +Two preset configs are provided (`celeris/middleware/logger/config.go` (`CLFConfig`, `JSONConfig`)): ```go // Combined / Common Log Format — emits a single "clf" attr per request. diff --git a/src/content/docs/sse.md b/src/content/docs/sse.md index 44cd96f..735316a 100644 --- a/src/content/docs/sse.md +++ b/src/content/docs/sse.md @@ -213,7 +213,7 @@ sse.New(sse.Config{ }) ``` -Source: `celeris/middleware/sse/sse.go` (`New`), `:560-562`. +Source: `celeris/middleware/sse/sse.go` (`New`), `celeris/middleware/sse/config.go` (`Config.OnDisconnect`). **`HEAD` requests.** A `HEAD` to an SSE route is answered by its `GET` route (see [Routing](/docs/routing#head-and-options-are-answered-for-you)) and gets the @@ -500,7 +500,7 @@ replay, _ := sse.NewKVReplayStore(sse.KVReplayStoreConfig{ }) ``` -Source: `celeris/middleware/sse/replay_kv.go` (`KVReplayStoreConfig`), `:160-190`. +Source: `celeris/middleware/sse/replay_kv.go` (`KVReplayStoreConfig`, `kvStore.Append`). ### Unknown-cursor behaviour From 38615e2640117cde0effa8b4201f076bfdc7741e Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Mon, 19 Oct 2026 12:35:35 +0200 Subject: [PATCH 4/5] docs: engines capability sources name the three interfaces the old range covered --- src/content/docs/engines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/engines.md b/src/content/docs/engines.md index 6c98ea1..42aa203 100644 --- a/src/content/docs/engines.md +++ b/src/content/docs/engines.md @@ -170,7 +170,7 @@ and let Adaptive/Std resolve automatically. | Accept control (Pause/Resume) | Yes | Yes | — | | Driver event-loop colocation | Yes | Yes | — | -Sources: `celeris/internal/engine/capability.go`, `celeris/internal/engine/engine.go` (`SendfileCapable`), +Sources: `celeris/internal/engine/capability.go`, `celeris/internal/engine/engine.go` (`AcceptController`, `SwitchFreezer`, `SendfileCapable`), `celeris/server.go` (`Server.EventLoopProvider`), `celeris/context_response.go` (`Context.EngineSupportsAsyncDetach`). ## The adaptive controller From 499c0de10b9f519a1689908df9ed05f36695ec1c Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Mon, 19 Oct 2026 12:57:26 +0200 Subject: [PATCH 5/5] docs: citations name the declaration the sentence is about (StreamWriter, Respond, WithCookie, UsesDriver, FromContext) --- src/content/docs/engines.md | 2 +- src/content/docs/middleware-content.md | 4 ++-- src/content/docs/routing.md | 2 +- src/content/docs/streaming.md | 6 +++--- src/content/docs/testing.md | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/content/docs/engines.md b/src/content/docs/engines.md index 42aa203..e3ef14d 100644 --- a/src/content/docs/engines.md +++ b/src/content/docs/engines.md @@ -518,7 +518,7 @@ lists them as not covered). The provider exposes `NumWorkers()` and `WorkerLoop(n)`; the per-worker `WorkerLoop` surface (`RegisterConn`, `UnregisterConn`, `Write`, `CPUID`) is -documented in `celeris/internal/engine/provider.go` (`EventLoopProvider`). You normally don't call this +documented in `celeris/internal/engine/provider.go` (`EventLoopProvider`, `WorkerLoop`). You normally don't call this yourself — a Celeris driver opened `WithEngine(srv)` consumes it for you. When the provider is `nil` (std engine), drivers fall back to a standalone mini event loop. A nil check and a call such as `p.NumWorkers()` need no name for the type: diff --git a/src/content/docs/middleware-content.md b/src/content/docs/middleware-content.md index 9d1f550..64b333b 100644 --- a/src/content/docs/middleware-content.md +++ b/src/content/docs/middleware-content.md @@ -489,7 +489,7 @@ s.GET("/users/:id", func(c *celeris.Context) error { `Respond` writes the *exact* protobuf variant the client preferred (`application/x-protobuf` vs `application/protobuf`) and honors `q=0` to exclude a -form. Source: `celeris/middleware/protobuf/protobuf.go` (`Write`). +form. Source: `celeris/middleware/protobuf/protobuf.go` (`Respond`). ### The middleware just stores config @@ -518,7 +518,7 @@ s.POST("/strict", func(c *celeris.Context) error { `Content-Type` is protobuf and returns `ErrNotProtoBuf` otherwise, then unmarshals with the stored options. If the middleware wasn't installed, `FromContext` falls back to default options, so it's always safe to call. Source: -`celeris/middleware/protobuf/middleware.go` (`New`), `celeris/middleware/protobuf/config.go` (`defaultConfig`). +`celeris/middleware/protobuf/middleware.go` (`FromContext`), `celeris/middleware/protobuf/config.go` (`defaultConfig`). ## OpenAPI docs (Swagger) diff --git a/src/content/docs/routing.md b/src/content/docs/routing.md index 68f78e1..7e327b3 100644 --- a/src/content/docs/routing.md +++ b/src/content/docs/routing.md @@ -376,7 +376,7 @@ api.GET("/cached", cachedHandler).Sync() // …opt this one back to in `UsesDriver` is the recommended marker for routes that call a Celeris postgres/redis/memcached driver opened `WithEngine(srv)`: such drivers may complete faster than the adaptive promotion threshold, so an explicit mark guarantees the -handler is dispatched off the worker. Source: `celeris/router.go` (`Route.Async`). +handler is dispatched off the worker. Source: `celeris/router.go` (`Route.UsesDriver`). For the full dispatch model and `Config.AsyncHandlers`, see [Engines](/docs/engines). diff --git a/src/content/docs/streaming.md b/src/content/docs/streaming.md index 43fbcab..17700bc 100644 --- a/src/content/docs/streaming.md +++ b/src/content/docs/streaming.md @@ -51,7 +51,7 @@ if sw == nil { } ``` -Source: `celeris/context_response.go` (`appendClone`). +Source: `celeris/context_response.go` (`Context.StreamWriter`). `StreamWriter()` returns `nil` in two situations: @@ -66,7 +66,7 @@ Source: `celeris/context_response.go` (`appendClone`). ### The StreamWriter API Once you hold a non-nil `*StreamWriter`, you drive the response with these methods. -Source: `celeris/context_response.go` (`Context.materializeRequestViews`). +Source: `celeris/context_response.go` (`StreamWriter`). | Method | Returns | Purpose | | --------------------------------------- | ---------------- | ----------------------------------------------------------------------- | @@ -364,7 +364,7 @@ func transform(c *celeris.Context) error { ### StreamWriter is incompatible with buffering This is the crucial interaction for middleware authors. **While buffering is active, -`Context.StreamWriter()` returns `nil`.** Source: `celeris/context_response.go` (`StreamWriter`). +`Context.StreamWriter()` returns `nil`.** Source: `celeris/context_response.go` (`Context.StreamWriter`). Streaming writes bytes directly and irrevocably to the wire; buffering holds a response in memory so it can be discarded or rewritten. The two cannot coexist — a buffered diff --git a/src/content/docs/testing.md b/src/content/docs/testing.md index ef97ae8..32e654c 100644 --- a/src/content/docs/testing.md +++ b/src/content/docs/testing.md @@ -218,7 +218,7 @@ func TestSession(t *testing.T) { > `WithCookie` does **not** escape `;` or CR/LF inside the value — pass > well-formed values only. To exercise your server's handling of a *malformed* > cookie header, set the raw header yourself with `WithHeader("cookie", …)`. -> Source: `celeris/celeristest/celeristest.go` (`WithContentType`). +> Source: `celeris/celeristest/celeristest.go` (`WithCookie`). ### Client IP behind a proxy