Help us shape the future of Go! Take the 2026 Go Developer Survey

zinc

package module
v0.7.3 Latest Latest
Warning

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

Go to latest
Published: Oct 5, 2026 License: MIT Imports: 42 Imported by: 1

README

Zinc

Release Go Reference Go Report Card License

Galvanize net/http.

Zinc is a fast, thin application layer for Go's standard HTTP stack. It adds routing, binding, validation, central error handling, response helpers, and production middleware, and it describes your API as an OpenAPI 3.1 spec built from the types you already write. A Zinc app is still an http.Handler, so everything you already use with net/http keeps working.

Documentation · Quick start · Middleware · API reference

Install

go get github.com/0mjs/zinc

Zinc requires Go 1.25 or newer.

A small API

package main

import (
	"log"
	"net/http"
	"sync"

	"github.com/0mjs/zinc"
	"github.com/0mjs/zinc/middleware/recover"
	"github.com/0mjs/zinc/middleware/requestid"
)

type User struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

// Handlers run concurrently, so shared state needs a lock.
var (
	mu    sync.RWMutex
	users = map[string]User{"42": {ID: "42", Name: "Ada"}}
)

func main() {
	app := zinc.New()
	app.Use(recover.New(), requestid.New())

	api := app.Group("/api")

	api.Get("/users/{id}", func(c *zinc.Context) error {
		mu.RLock()
		user, ok := users[c.Param("id")]
		mu.RUnlock()
		if !ok {
			return zinc.NotFound("user not found")
		}
		return c.JSON(user)
	})

	api.Post("/users", func(c *zinc.Context) error {
		var user User
		if err := c.Bind().JSON(&user); err != nil {
			return err // 400 with the failing field
		}
		mu.Lock()
		users[user.ID] = user
		mu.Unlock()
		return c.Status(http.StatusCreated).JSON(user)
	})

	log.Fatal(app.Listen(":8080"))
}
curl localhost:8080/api/users/42
# {"id":"42","name":"Ada"}

curl localhost:8080/api/users/7
# {"error":{"status":404,"message":"user not found"}}

Handlers return errors, and one error handler turns them into responses. Invalid or conflicting routes fail at startup rather than at request time.

Typed handlers

A handler's signature can be its request contract. Zinc binds and validates the input, calls the function, and writes the output:

type CreateUser struct {
	OrgID string `path:"org"`
	Email string `json:"email"`
}

api.Post("/orgs/{org}/users", zinc.Typed(func(c *zinc.Context, in CreateUser) (User, error) {
	return users.Create(c.Context(), in)
})).Status(http.StatusCreated)

A value that doesn't parse is a 400 naming the field, a validation failure is a 422, and a typed handler allocates no more than the same code written by hand.

OpenAPI from your types

Every app serves an OpenAPI 3.1 spec at /openapi.json and a browsable reference page at /docs. The spec comes from the code above: no comments, annotations or generator. For this struct:

type CreateUser struct {
	OrgID  string `path:"org"`
	DryRun bool   `query:"dry_run"`
	Email  string `json:"email" validate:"required,email"`
}

Zinc writes, among the rest:

"parameters": [
  { "name": "org", "in": "path", "required": true, "schema": { "type": "string" } },
  { "name": "dry_run", "in": "query", "schema": { "type": "boolean" } }
],
"CreateUserBody": {
  "type": "object",
  "properties": { "email": { "type": "string", "format": "email" } },
  "required": ["email"]
}

with 201, 400, 422 and 500 responses and their bodies. The email rule in the spec is the rule a request is checked against: {"email":"nope"} gets a 422 naming the field. The spec only states rules that something enforces, and mistakes such as two routes OpenAPI can't tell apart fail at startup. app.Validate() checks everything before the server takes traffic.

Output types, response headers, Route.Errors, checked examples, security schemes, RFC 9457 problem details, and hooks for anything else are in the OpenAPI guide. For a private API, turn the spec off or put it behind auth. Not supported: YAML output and polymorphic (oneOf) types. Coming from Huma or Fuego? Here's how they compare.

What you get

  • Routing: a radix router with groups, parameters, catch-alls, and clear precedence: static, then parameter, then catch-all.
  • Binding: path, query, header, form, multipart, JSON, and XML input, validated by validate tags with built-in rules and pattern tags, or a validator you plug in. YAML, TOML, or a faster JSON library plug in per app with one map entry, and Zinc itself requires no other module.
  • OpenAPI: a 3.1 spec and a /docs page from your types, with validation rules that match what's enforced, checked in CI against 147 scenarios and a generated client.
  • Responses: JSON, text, files, streams, templates, and redirects.
  • Errors: JSON error responses by default, short constructors like zinc.NotFound("…"), and domain errors that choose their own status. Internal error text never reaches clients.
  • Middleware: security, observability, limits, and transport, listed below.
  • Replaceable parts: swap the validator, renderer, error handler, or JSON library, and add body formats, through zinc.Config.

Still net/http

Zinc wraps the standard library rather than replacing it:

// Run it on a server you configure.
server := &http.Server{Addr: ":8080", Handler: app, ReadHeaderTimeout: 5 * time.Second}

// Serve a route with any standard handler.
app.HandleHTTP("GET /metrics", promhttp.Handler())

// Wrap the app in standard middleware, or just one group.
app.UseHTTP(otelhttp.NewMiddleware("api"))
admin := app.Group("/admin").UseHTTP(basicAuth)

// Hand a whole subtree to an existing handler. The prefix is stripped.
app.Mount("/legacy", legacyMux)

Inside a handler, c.Request() and c.Writer() give you the underlying request and response writer.

Middleware

Each middleware is its own package under github.com/0mjs/zinc/middleware, with a New function that takes an optional Config, and none adds a dependency. Middleware that needs a third-party library lives in github.com/0mjs/contrib.

Family Packages, in chain order
Observe OpenTelemetry, requestid, logger, prometheus, healthcheck, bodydump
Contain recover, timeout, bodylimit, limiter
Shape redirect, trailingslash, rewrite, methodoverride
Guard secure, cors, contenttype, session, csrf, basicauth, keyauth, casbin, contrib jwtauth
Carry decompress, compress, nocache, headers, proxy, pprof, apidocs

OpenTelemetry uses the standard otelhttp package through UseHTTP. The middleware docs cover configuration for each.

Performance

Zinc is scored against Gin and Echo on 93 scenarios, and against the bare routers BunRouter and Chi on the 66 routing ones, with every framework measured together in one run on an Apple M1 Pro. Every request pool holds 10,000 distinct paths, so nothing is answered from a cache. For 0.7.0, Zinc was fastest by 3% or more in 63 of 93 against the frameworks, with 6 more tied within 3%, and 5.7% from the fastest on average; against the routers, 32 of 66 with 12 ties. The benchmark report has every scenario, every loss, the environment, and the commands. Results vary by workload and machine, so run the suite against the revision you deploy.

Good to know

  • *zinc.Context is pooled and valid only during its request. Copy values out before starting background work. Use c.Request().Context() when that work should be cancelled with the request.
  • Use app.TryHandle(spec) for routes that come from configuration or plugins. It returns an error instead of panicking.

Project status

Zinc is pre-1.0. Pin a release, and check the release notes and the 0.4 migration guide when you upgrade.

Bug reports and focused proposals are welcome in GitHub Issues. Read CONTRIBUTING.md before opening a pull request.

License

MIT

Documentation

Overview

Example

A route returns an error or writes a response. Zinc is an http.Handler, so app.Listen(":8080") serves it, or any net/http server can.

package main

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

	"github.com/0mjs/zinc"
)

// do sends a request to app and prints the status and body, as a client
// would see them.
func do(app *zinc.App, method, target, body string) {
	r := httptest.NewRequest(method, target, strings.NewReader(body))
	if body != "" {
		r.Header.Set("Content-Type", "application/json")
	}
	w := httptest.NewRecorder()
	app.ServeHTTP(w, r)
	fmt.Println(w.Code, strings.TrimSpace(w.Body.String()))
}

func main() {
	app := zinc.New()
	app.Get("/hello/{name}", func(c *zinc.Context) error {
		return c.JSON(zinc.Map{"message": "Hello, " + c.Param("name") + "!"})
	})

	do(app, "GET", "/hello/ada", "")
}
Output:
200 {"message":"Hello, ada!"}

Index

Examples

Constants

View Source
const (
	DefaultBodyLimit       int64 = 4 << 20
	DefaultReadTimeout           = 5 * time.Second
	DefaultWriteTimeout          = 10 * time.Second
	DefaultIdleTimeout           = 120 * time.Second
	DefaultShutdownTimeout       = 10 * time.Second
	DefaultProxyHeader           = "X-Forwarded-For"

	// Deprecated: Zinc has no route cache since 0.5. Kept so code that
	// names it still compiles.
	DefaultRouteCacheSize = 0
)

Defaults applied to zero-valued Config fields.

View Source
const (
	HeaderAuthorization                   = "Authorization"
	HeaderProxyAuthenticate               = "Proxy-Authenticate"
	HeaderProxyAuthorization              = "Proxy-Authorization"
	HeaderWWWAuthenticate                 = "WWW-Authenticate"
	HeaderAge                             = "Age"
	HeaderCacheControl                    = "Cache-Control"
	HeaderClearSiteData                   = "Clear-Site-Data"
	HeaderExpires                         = "Expires"
	HeaderPragma                          = "Pragma"
	HeaderWarning                         = "Warning"
	HeaderAcceptCH                        = "Accept-CH"
	HeaderAcceptCHLifetime                = "Accept-CH-Lifetime"
	HeaderContentDPR                      = "Content-DPR"
	HeaderDPR                             = "DPR"
	HeaderEarlyData                       = "Early-Data"
	HeaderSaveData                        = "Save-Data"
	HeaderViewportWidth                   = "Viewport-Width"
	HeaderWidth                           = "Width"
	HeaderETag                            = "ETag"
	HeaderIfMatch                         = "If-Match"
	HeaderIfModifiedSince                 = "If-Modified-Since"
	HeaderIfNoneMatch                     = "If-None-Match"
	HeaderIfUnmodifiedSince               = "If-Unmodified-Since"
	HeaderLastModified                    = "Last-Modified"
	HeaderVary                            = "Vary"
	HeaderConnection                      = "Connection"
	HeaderKeepAlive                       = "Keep-Alive"
	HeaderAccept                          = "Accept"
	HeaderAcceptCharset                   = "Accept-Charset"
	HeaderAcceptEncoding                  = "Accept-Encoding"
	HeaderAcceptLanguage                  = "Accept-Language"
	HeaderCookie                          = "Cookie"
	HeaderExpect                          = "Expect"
	HeaderMaxForwards                     = "Max-Forwards"
	HeaderSetCookie                       = "Set-Cookie"
	HeaderSecFetchSite                    = "Sec-Fetch-Site"
	HeaderAccessControlAllowCredentials   = "Access-Control-Allow-Credentials"
	HeaderAccessControlAllowHeaders       = "Access-Control-Allow-Headers"
	HeaderAccessControlAllowMethods       = "Access-Control-Allow-Methods"
	HeaderAccessControlAllowOrigin        = "Access-Control-Allow-Origin"
	HeaderAccessControlExposeHeaders      = "Access-Control-Expose-Headers"
	HeaderAccessControlMaxAge             = "Access-Control-Max-Age"
	HeaderAccessControlRequestHeaders     = "Access-Control-Request-Headers"
	HeaderAccessControlRequestMethod      = "Access-Control-Request-Method"
	HeaderOrigin                          = "Origin"
	HeaderTimingAllowOrigin               = "Timing-Allow-Origin"
	HeaderXPermittedCrossDomainPolicies   = "X-Permitted-Cross-Domain-Policies"
	HeaderDNT                             = "DNT"
	HeaderTk                              = "Tk"
	HeaderContentDisposition              = "Content-Disposition"
	HeaderContentEncoding                 = "Content-Encoding"
	HeaderContentLanguage                 = "Content-Language"
	HeaderContentLength                   = "Content-Length"
	HeaderContentLocation                 = "Content-Location"
	HeaderContentType                     = "Content-Type"
	HeaderForwarded                       = "Forwarded"
	HeaderVia                             = "Via"
	HeaderXForwardedFor                   = "X-Forwarded-For"
	HeaderXForwardedHost                  = "X-Forwarded-Host"
	HeaderXForwardedProto                 = "X-Forwarded-Proto"
	HeaderXForwardedProtocol              = "X-Forwarded-Protocol"
	HeaderXForwardedSsl                   = "X-Forwarded-Ssl"
	HeaderXUrlScheme                      = "X-Url-Scheme"
	HeaderLocation                        = "Location"
	HeaderFrom                            = "From"
	HeaderHost                            = "Host"
	HeaderReferer                         = "Referer"
	HeaderReferrerPolicy                  = "Referrer-Policy"
	HeaderUserAgent                       = "User-Agent"
	HeaderAllow                           = "Allow"
	HeaderServer                          = "Server"
	HeaderAcceptRanges                    = "Accept-Ranges"
	HeaderContentRange                    = "Content-Range"
	HeaderIfRange                         = "If-Range"
	HeaderRange                           = "Range"
	HeaderContentSecurityPolicy           = "Content-Security-Policy"
	HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
	HeaderCrossOriginResourcePolicy       = "Cross-Origin-Resource-Policy"
	HeaderExpectCT                        = "Expect-CT"
	HeaderFeaturePolicy                   = "Feature-Policy"
	HeaderPublicKeyPins                   = "Public-Key-Pins"
	HeaderPublicKeyPinsReportOnly         = "Public-Key-Pins-Report-Only"
	HeaderStrictTransportSecurity         = "Strict-Transport-Security"
	HeaderUpgradeInsecureRequests         = "Upgrade-Insecure-Requests"
	HeaderXContentTypeOptions             = "X-Content-Type-Options"
	HeaderXDownloadOptions                = "X-Download-Options"
	HeaderXFrameOptions                   = "X-Frame-Options"
	HeaderXPoweredBy                      = "X-Powered-By"
	HeaderXCSRFToken                      = "X-CSRF-Token"
	HeaderXXSSProtection                  = "X-XSS-Protection"
	HeaderLastEventID                     = "Last-Event-ID"
	HeaderNEL                             = "NEL"
	HeaderPingFrom                        = "Ping-From"
	HeaderPingTo                          = "Ping-To"
	HeaderReportTo                        = "Report-To"
	HeaderTE                              = "TE"
	HeaderTrailer                         = "Trailer"
	HeaderTransferEncoding                = "Transfer-Encoding"
	HeaderSecWebSocketAccept              = "Sec-WebSocket-Accept"
	HeaderSecWebSocketExtensions          = "Sec-WebSocket-Extensions"
	HeaderSecWebSocketKey                 = "Sec-WebSocket-Key"
	HeaderSecWebSocketProtocol            = "Sec-WebSocket-Protocol"
	HeaderSecWebSocketVersion             = "Sec-WebSocket-Version"
	HeaderAcceptPatch                     = "Accept-Patch"
	HeaderAcceptPushPolicy                = "Accept-Push-Policy"
	HeaderAcceptSignature                 = "Accept-Signature"
	HeaderAltSvc                          = "Alt-Svc"
	HeaderDate                            = "Date"
	HeaderIndex                           = "Index"
	HeaderLargeAllocation                 = "Large-Allocation"
	HeaderLink                            = "Link"
	HeaderPushPolicy                      = "Push-Policy"
	HeaderRetryAfter                      = "Retry-After"
	HeaderServerTiming                    = "Server-Timing"
	HeaderSignature                       = "Signature"
	HeaderSignedHeaders                   = "Signed-Headers"
	HeaderSourceMap                       = "SourceMap"
	HeaderUpgrade                         = "Upgrade"
	HeaderXDNSPrefetchControl             = "X-DNS-Prefetch-Control"
	HeaderXPingback                       = "X-Pingback"
	HeaderXRequestID                      = "X-Request-ID"
	HeaderXRequestedWith                  = "X-Requested-With"
	HeaderXRobotsTag                      = "X-Robots-Tag"
	HeaderXUACompatible                   = "X-UA-Compatible"
)

Common HTTP header names used by Zinc and its middleware.

View Source
const (
	MethodGet     = "GET"     // RFC 7231, 4.3.1
	MethodHead    = "HEAD"    // RFC 7231, 4.3.2
	MethodPost    = "POST"    // RFC 7231, 4.3.3
	MethodPut     = "PUT"     // RFC 7231, 4.3.4
	MethodPatch   = "PATCH"   // RFC 5789
	MethodDelete  = "DELETE"  // RFC 7231, 4.3.5
	MethodConnect = "CONNECT" // RFC 7231, 4.3.6
	MethodOptions = "OPTIONS" // RFC 7231, 4.3.7
	MethodTrace   = "TRACE"   // RFC 7231, 4.3.8

)

Standard HTTP method names supported by the dedicated router fast paths.

View Source
const (
	StatusContinue                      = 100 // RFC 7231, 6.2.1
	StatusSwitchingProtocols            = 101 // RFC 7231, 6.2.2
	StatusProcessing                    = 102 // RFC 2518, 10.1
	StatusEarlyHints                    = 103 // RFC 8297
	StatusOK                            = 200 // RFC 7231, 6.3.1
	StatusCreated                       = 201 // RFC 7231, 6.3.2
	StatusAccepted                      = 202 // RFC 7231, 6.3.3
	StatusNonAuthoritativeInformation   = 203 // RFC 7231, 6.3.4
	StatusNoContent                     = 204 // RFC 7231, 6.3.5
	StatusResetContent                  = 205 // RFC 7231, 6.3.6
	StatusPartialContent                = 206 // RFC 7233, 4.1
	StatusMultiStatus                   = 207 // RFC 4918, 11.1
	StatusAlreadyReported               = 208 // RFC 5842, 7.1
	StatusIMUsed                        = 226 // RFC 3229, 10.4.1
	StatusMultipleChoices               = 300 // RFC 7231, 6.4.1
	StatusMovedPermanently              = 301 // RFC 7231, 6.4.2
	StatusFound                         = 302 // RFC 7231, 6.4.3
	StatusSeeOther                      = 303 // RFC 7231, 6.4.4
	StatusNotModified                   = 304 // RFC 7232, 4.1
	StatusUseProxy                      = 305 // RFC 7231, 6.4.5
	StatusTemporaryRedirect             = 307 // RFC 7231, 6.4.7
	StatusPermanentRedirect             = 308 // RFC 7538, 3
	StatusBadRequest                    = 400 // RFC 7231, 6.5.1
	StatusUnauthorized                  = 401 // RFC 7235, 3.1
	StatusPaymentRequired               = 402 // RFC 7231, 6.5.2
	StatusForbidden                     = 403 // RFC 7231, 6.5.3
	StatusNotFound                      = 404 // RFC 7231, 6.5.4
	StatusMethodNotAllowed              = 405 // RFC 7231, 6.5.5
	StatusNotAcceptable                 = 406 // RFC 7231, 6.5.6
	StatusProxyAuthRequired             = 407 // RFC 7235, 3.2
	StatusRequestTimeout                = 408 // RFC 7231, 6.5.7
	StatusConflict                      = 409 // RFC 7231, 6.5.8
	StatusGone                          = 410 // RFC 7231, 6.5.9
	StatusLengthRequired                = 411 // RFC 7231, 6.5.10
	StatusPreconditionFailed            = 412 // RFC 7232, 4.2
	StatusRequestEntityTooLarge         = 413 // RFC 7231, 6.5.11
	StatusRequestURITooLong             = 414 // RFC 7231, 6.5.12
	StatusUnsupportedMediaType          = 415 // RFC 7231, 6.5.13
	StatusRequestedRangeNotSatisfiable  = 416 // RFC 7233, 4.4
	StatusExpectationFailed             = 417 // RFC 7231, 6.5.14
	StatusTeapot                        = 418 // RFC 7168, 2.3.3
	StatusMisdirectedRequest            = 421 // RFC 7540, 9.1.2
	StatusUnprocessableEntity           = 422 // RFC 4918, 11.2
	StatusLocked                        = 423 // RFC 4918, 11.3
	StatusFailedDependency              = 424 // RFC 4918, 11.4
	StatusTooEarly                      = 425 // RFC 8470, 5.2.
	StatusUpgradeRequired               = 426 // RFC 7231, 6.5.15
	StatusPreconditionRequired          = 428 // RFC 6585, 3
	StatusTooManyRequests               = 429 // RFC 6585, 4
	StatusRequestHeaderFieldsTooLarge   = 431 // RFC 6585, 5
	StatusUnavailableForLegalReasons    = 451 // RFC 7725, 3
	StatusInternalServerError           = 500 // RFC 7231, 6.6.1
	StatusNotImplemented                = 501 // RFC 7231, 6.6.2
	StatusBadGateway                    = 502 // RFC 7231, 6.6.3
	StatusServiceUnavailable            = 503 // RFC 7231, 6.6.4
	StatusGatewayTimeout                = 504 // RFC 7231, 6.6.5
	StatusHTTPVersionNotSupported       = 505 // RFC 7231, 6.6.6
	StatusVariantAlsoNegotiates         = 506 // RFC 2295, 8.1
	StatusInsufficientStorage           = 507 // RFC 4918, 11.5
	StatusLoopDetected                  = 508 // RFC 5842, 7.2
	StatusNotExtended                   = 510 // RFC 2774, 7
	StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)

HTTP status codes, including commonly used WebDAV and extension statuses.

View Source
const (
	MIMEJSON        = "application/json; charset=utf-8"
	MIMEXML         = "application/xml; charset=utf-8"
	MIMEHTML        = "text/html; charset=utf-8"
	MIMEText        = "text/plain; charset=utf-8"
	MIMEEventStream = "text/event-stream"
	MIMEOctetStream = "application/octet-stream"
)

MIME types for Context.Data and response headers. The text types include the UTF-8 charset.

View Source
const DefaultDocsPath = "/docs"

DefaultDocsPath is where an app serves its API reference page unless Config.DocsPath says otherwise.

View Source
const DefaultListenAddr = ":8080"

DefaultListenAddr is used when Listen receives no address or an empty one.

View Source
const DefaultOpenAPIPath = "/openapi.json"

DefaultOpenAPIPath is where an app serves its OpenAPI spec unless Config.OpenAPIPath says otherwise.

View Source
const Version = "0.7.3"

Version is the current version of Zinc. This should be updated for each release.

Variables

View Source
var (
	ErrBadRequest                    = NewError(StatusBadRequest)
	ErrUnauthorized                  = NewError(StatusUnauthorized)
	ErrPaymentRequired               = NewError(StatusPaymentRequired)
	ErrForbidden                     = NewError(StatusForbidden)
	ErrNotFound                      = NewError(StatusNotFound)
	ErrMethodNotAllowed              = NewError(StatusMethodNotAllowed)
	ErrNotAcceptable                 = NewError(StatusNotAcceptable)
	ErrProxyAuthRequired             = NewError(StatusProxyAuthRequired)
	ErrRequestTimeout                = NewError(StatusRequestTimeout)
	ErrConflict                      = NewError(StatusConflict)
	ErrGone                          = NewError(StatusGone)
	ErrLengthRequired                = NewError(StatusLengthRequired)
	ErrPreconditionFailed            = NewError(StatusPreconditionFailed)
	ErrRequestEntityTooLarge         = NewError(StatusRequestEntityTooLarge)
	ErrRequestURITooLong             = NewError(StatusRequestURITooLong)
	ErrUnsupportedMediaType          = NewError(StatusUnsupportedMediaType)
	ErrRequestedRangeNotSatisfiable  = NewError(StatusRequestedRangeNotSatisfiable)
	ErrExpectationFailed             = NewError(StatusExpectationFailed)
	ErrTeapot                        = NewError(StatusTeapot)
	ErrMisdirectedRequest            = NewError(StatusMisdirectedRequest)
	ErrUnprocessableEntity           = NewError(StatusUnprocessableEntity)
	ErrLocked                        = NewError(StatusLocked)
	ErrFailedDependency              = NewError(StatusFailedDependency)
	ErrTooEarly                      = NewError(StatusTooEarly)
	ErrUpgradeRequired               = NewError(StatusUpgradeRequired)
	ErrPreconditionRequired          = NewError(StatusPreconditionRequired)
	ErrTooManyRequests               = NewError(StatusTooManyRequests)
	ErrRequestHeaderFieldsTooLarge   = NewError(StatusRequestHeaderFieldsTooLarge)
	ErrUnavailableForLegalReasons    = NewError(StatusUnavailableForLegalReasons)
	ErrInternalServerError           = NewError(StatusInternalServerError)
	ErrNotImplemented                = NewError(StatusNotImplemented)
	ErrBadGateway                    = NewError(StatusBadGateway)
	ErrServiceUnavailable            = NewError(StatusServiceUnavailable)
	ErrGatewayTimeout                = NewError(StatusGatewayTimeout)
	ErrHTTPVersionNotSupported       = NewError(StatusHTTPVersionNotSupported)
	ErrVariantAlsoNegotiates         = NewError(StatusVariantAlsoNegotiates)
	ErrInsufficientStorage           = NewError(StatusInsufficientStorage)
	ErrLoopDetected                  = NewError(StatusLoopDetected)
	ErrNotExtended                   = NewError(StatusNotExtended)
	ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired)
)

Reusable HTTP errors. Modifier methods return independent copies and never mutate these package-level values.

View Source
var (
	ErrTemplateEngineNotConfigured = errors.New("template engine is not configured")
	ErrTemplateNameRequired        = errors.New("template name is required")
	ErrTemplateNotFound            = errors.New("template not found")
)

Template renderer configuration and lookup errors.

View Source
var ErrResponseAlreadySent = errors.New("response already sent")

ErrResponseAlreadySent is returned when a handler attempts a second terminal response write. SSE is the deliberate exception and supports multiple events.

Functions

func BuiltinRules added in v0.7.0

func BuiltinRules() []string

BuiltinRules are the validate-tag rules Zinc enforces itself when Config.Validator is nil. They mean what they mean to go-playground/validator, so a struct's tags work with either:

required, omitempty, min, max, len, gt, gte, lt, lte, oneof,
email, uuid, uuid4, url, uri, http_url

min, max, len, gt, gte, lt and lte count the characters of a string and the elements of a slice or map, and compare a number's value exactly, as the field's own type. A pointer that isn't nil counts as set, and omitempty skips only the rules after it. Nested structs are validated too, including those in slices and maps and those of a recursive type, as deep as the value goes, as the spec describes them; go-playground/validator needs dive for that.

func DefaultErrorHandler added in v0.4.0

func DefaultErrorHandler(c *Context, err error)

DefaultErrorHandler writes err as a JSON error body, newline-terminated like every Context.JSON response:

{"error":{"status":404,"message":"user not found"}}

It adds "fields" for binding and validation errors and "details" for HTTPError details. Messages from HTTPError and from 4xx StatusCoders are sent as written; any other error is reported only by its status text, so internal error text never reaches the client.

func Form added in v0.4.0

func Form[T any](c *Context, name string) (T, error)

Form returns the named form value parsed as T, with the semantics of FormValue. A missing or unparsable value is a *BindError, answered with 400. A body over the limit is ErrRequestEntityTooLarge, and a form that can't be parsed is a *BindError carrying the parse error.

func FormOr added in v0.4.0

func FormOr[T any](c *Context, name string, fallback T) T

FormOr returns the named form value parsed as T, or fallback when the value is missing or does not parse.

func MustValue added in v0.4.0

func MustValue[T any](c *Context, key any) T

MustValue returns the request-scoped value stored under key, and panics when it is missing or has another type. Use it for values an earlier middleware always sets.

func Param added in v0.4.0

func Param[T any](c *Context, name string) (T, error)

Param returns the named route parameter parsed as T. A value that does not parse is a *BindError, which the default error handler answers with 400 and the parameter's name:

id, err := zinc.Param[int64](c, "id")
if err != nil {
	return err
}

T may be a string, bool, integer, or float type, a type implementing encoding.TextUnmarshaler, or a pointer to one of those.

func ProblemErrors added in v0.6.4

func ProblemErrors(c *Context, err error)

ProblemErrors is an ErrorHandler that writes RFC 9457 problem details, as application/problem+json:

{"type":"about:blank","title":"Not Found","status":404,"detail":"pet not found"}

Invalid fields from binding or validation are listed under "errors", and an HTTPError's Details become members of the object. Status resolution and message safety match DefaultErrorHandler, and the OpenAPI spec describes error responses as problem details when it's the app's ErrorHandler.

func Query added in v0.4.0

func Query[T any](c *Context, name string) (T, error)

Query returns the first query value for name parsed as T. A missing or unparsable value is a *BindError, answered with 400. Use QueryOr for an optional value.

func QueryOr added in v0.4.0

func QueryOr[T any](c *Context, name string, fallback T) T

QueryOr returns the first query value for name parsed as T, or fallback when the value is missing or does not parse. T is inferred from fallback:

page := zinc.QueryOr(c, "page", 1)

func StatusCode added in v0.4.0

func StatusCode(err error) int

StatusCode reports the HTTP status the default error handler sends for err: the code of an HTTPError anywhere in its chain, otherwise the status of the first StatusCoder, otherwise 500. It returns 0 for a nil error.

func TextErrors added in v0.4.0

func TextErrors(c *Context, err error)

TextErrors is an ErrorHandler that writes plain-text error bodies, as Zinc 0.3 did. Status resolution and message safety match DefaultErrorHandler.

func Value added in v0.4.0

func Value[T any](c *Context, key any) (T, bool)

Value returns the request-scoped value stored under key by Set, if it has type T.

user, ok := zinc.Value[*User](c, userKey)

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App is Zinc's application, router, middleware registry, and http.Handler. Configure routes and middleware before serving; registration is not safe concurrently with requests.

func New

func New(config ...Config) *App

New creates an App. With no Config, or with a zero-valued field, Zinc's defaults apply; see Config.

func (*App) AcquireContext added in v0.1.0

func (a *App) AcquireContext(w http.ResponseWriter, r *http.Request) *Context

AcquireContext creates an application-bound Context for advanced integrations. The caller must eventually pass it to ReleaseContext.

func (*App) Add

func (a *App) Add(method, path string, handlers ...HandlerFunc) Route

Add registers handlers and panics when the route declaration is invalid.

func (*App) All

func (a *App) All(path string, handlers ...HandlerFunc)

All registers handlers for Zinc's standard method set.

func (*App) Close added in v0.3.0

func (a *App) Close() error

Close stops the active server and releases confined static roots. Call it after externally managed servers have stopped serving this App.

func (*App) Connect

func (a *App) Connect(path string, handlers ...HandlerFunc) Route

Connect registers a CONNECT route.

func (*App) Delete

func (a *App) Delete(path string, handlers ...HandlerFunc) Route

Delete registers a DELETE route.

func (*App) Document added in v0.7.1

func (a *App) Document(docs ...MiddlewareDoc)

Document adds descriptions of the app's middleware, which runs on every route, to every route in the spec. See MiddlewareDoc.

func (*App) File

func (a *App) File(path, file string) Route

File serves one operating-system file at path.

func (*App) FileFS

func (a *App) FileFS(path, file string, filesystem fs.FS) Route

FileFS serves one file from filesystem at path.

func (*App) FindRoute added in v0.1.0

func (a *App) FindRoute(method, path string) (RouteInfo, bool)

FindRoute resolves metadata without invoking the route handler. It finds the route a request would run: a HEAD request with no HEAD route finds the GET route, unless automatic HEAD is off.

func (*App) Get

func (a *App) Get(path string, handlers ...HandlerFunc) Route

Get registers a GET route.

func (*App) Group

func (a *App) Group(prefix string, handlers ...HandlerFunc) *Group

Group creates a route group rooted at prefix.

Example

A group shares a prefix and middleware with every route registered in it.

package main

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

	"github.com/0mjs/zinc"
)

// do sends a request to app and prints the status and body, as a client
// would see them.
func do(app *zinc.App, method, target, body string) {
	r := httptest.NewRequest(method, target, strings.NewReader(body))
	if body != "" {
		r.Header.Set("Content-Type", "application/json")
	}
	w := httptest.NewRecorder()
	app.ServeHTTP(w, r)
	fmt.Println(w.Code, strings.TrimSpace(w.Body.String()))
}

func main() {
	app := zinc.New()
	requireKey := func(c *zinc.Context) error {
		if c.Header("X-API-Key") != "secret" {
			return zinc.Unauthorized("missing or wrong API key")
		}
		return c.Next()
	}
	api := app.Group("/api", requireKey)
	api.Get("/status", func(c *zinc.Context) error { return c.JSON(zinc.Map{"ok": true}) })

	do(app, "GET", "/api/status", "")
}
Output:
401 {"error":{"status":401,"message":"missing or wrong API key"}}

func (*App) HandleHTTP added in v0.2.0

func (a *App) HandleHTTP(pattern string, handler http.Handler) Route

HandleHTTP registers a standard net/http handler using a "METHOD /path" pattern, such as "GET /metrics". Matched parameters are available through http.Request.PathValue inside the standard handler, and the route's pattern through http.Request.Pattern, as Wrap describes.

func (*App) Handler

func (a *App) Handler() http.Handler

Handler returns the App as a standard-library handler.

func (*App) Head

func (a *App) Head(path string, handlers ...HandlerFunc) Route

Head registers a HEAD route.

func (*App) Listen

func (a *App) Listen(addr ...string) error

Listen starts the app on addr, defaulting to :8080 when addr is omitted.

func (*App) ListenContext added in v0.4.0

func (a *App) ListenContext(ctx context.Context, addr string) error

ListenContext serves on addr until ctx ends, then shuts down gracefully: it stops accepting connections and waits up to Config.ShutdownTimeout for in-flight requests. It returns nil after a clean shutdown. Pair it with signal.NotifyContext to stop on SIGINT or SIGTERM; Zinc installs no signal handlers itself.

func (*App) ListenTLS

func (a *App) ListenTLS(addr, certFile, keyFile string) error

ListenTLS starts an HTTPS server using certificate and key files.

func (*App) Match

func (a *App) Match(methods []string, path string, handlers ...HandlerFunc)

Match registers the same handler chain for each method.

func (*App) MethodNotAllowed

func (a *App) MethodNotAllowed(handler HandlerFunc)

MethodNotAllowed replaces the application-wide 405 handler.

func (*App) Mount

func (a *App) Mount(prefix string, h http.Handler)

Mount delegates a path subtree to h. More specific mounts take precedence.

func (*App) NotFound

func (a *App) NotFound(handler HandlerFunc)

NotFound replaces the application-wide 404 handler.

func (*App) OpenAPI added in v0.6.0

func (a *App) OpenAPI(path string, cfg OpenAPIConfig, middleware ...HandlerFunc) Route

OpenAPI serves the app's OpenAPI 3.1 spec as JSON at path, with GET, and returns the route, in place of the spec an app serves at Config.OpenAPIPath. Use it to protect the spec with middleware, or to serve several specs.

app.OpenAPI("/openapi.json", zinc.OpenAPIConfig{Title: "Shop", Version: "1.4.0"})

The spec is built on the first request and sent as pre-encoded bytes after that. Routes registered later are picked up on the next request. The spec route itself is hidden from the spec. It panics when a security scheme is missing a field its type needs, or cfg.Security names a scheme cfg.SecuritySchemes doesn't define; a route naming an unknown scheme makes the request fail with a 500 and the error.

Hiding the spec isn't access control: protect it, and the API, with auth middleware if the API is private.

func (*App) OpenAPISpec added in v0.6.0

func (a *App) OpenAPISpec(cfg OpenAPIConfig) ([]byte, error)

OpenAPISpec returns the app's OpenAPI 3.1 spec as JSON. Every registered route is in it, except hidden ones, mounts and static files, and methods OpenAPI 3.1 can't express, such as PURGE. Typed handlers are described fully; other handlers add detail with Route.Input, Route.Output and the other Route methods. The output is the same for the same routes.

Example

Every app serves its OpenAPI 3.1 spec at /openapi.json. OpenAPISpec builds it in code, for tests or to write it to a file.

package main

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

	"github.com/0mjs/zinc"
)

type CreateUser struct {
	Org   string `path:"org"`
	Email string `json:"email" validate:"required,email"`
}

type User struct {
	ID    int    `json:"id"`
	Org   string `json:"org"`
	Email string `json:"email"`
}

func main() {
	app := zinc.New()
	app.Post("/orgs/{org}/users", zinc.Typed(func(c *zinc.Context, in CreateUser) (User, error) {
		return User{}, nil
	})).Status(http.StatusCreated)

	spec, err := app.OpenAPISpec(zinc.OpenAPIConfig{Title: "Users", Version: "1.0.0"})
	if err != nil {
		panic(err)
	}
	var doc struct {
		OpenAPI string                                `json:"openapi"`
		Paths   map[string]map[string]json.RawMessage `json:"paths"`
	}
	_ = json.Unmarshal(spec, &doc)
	fmt.Println(doc.OpenAPI)
	for path, ops := range doc.Paths {
		for method := range ops {
			fmt.Println(method, path)
		}
	}
}
Output:
3.1.0
post /orgs/{org}/users

func (*App) Options

func (a *App) Options(path string, handlers ...HandlerFunc) Route

Options registers an OPTIONS route.

func (*App) Patch

func (a *App) Patch(path string, handlers ...HandlerFunc) Route

Patch registers a PATCH route.

func (*App) Post

func (a *App) Post(path string, handlers ...HandlerFunc) Route

Post registers a POST route.

func (*App) Put

func (a *App) Put(path string, handlers ...HandlerFunc) Route

Put registers a PUT route.

func (*App) ReleaseContext added in v0.1.0

func (a *App) ReleaseContext(c *Context)

ReleaseContext returns a manually acquired Context to its pool.

func (*App) Route

func (a *App) Route(prefix string, fn func(*Group), handlers ...HandlerFunc) *Group

Route configures and returns a group through fn.

func (*App) RouteByName added in v0.1.0

func (a *App) RouteByName(name string) (RouteInfo, bool)

RouteByName finds route metadata by its unique name.

func (*App) RouteNotFound added in v0.1.0

func (a *App) RouteNotFound(path string, handlers ...HandlerFunc)

RouteNotFound registers a path-specific handler used when no method matches.

func (*App) Routes

func (a *App) Routes() []RouteInfo

Routes returns registered routes and mounts in registration order.

func (*App) Serve

func (a *App) Serve(ln net.Listener) error

Serve accepts HTTP requests from an existing listener.

func (*App) ServeHTTP

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler. Standard HTTP middleware wraps the application here; Zinc middleware and routing continue through serveHTTP.

func (*App) Shutdown

func (a *App) Shutdown(ctx context.Context) error

Shutdown gracefully stops the active server and releases confined static roots after requests drain. It is a no-op before serving.

func (*App) Static

func (a *App) Static(prefix, root string, opts ...StaticOption)

Static serves root from the operating-system filesystem below prefix. The confined directory handle is retained after its first use and released by Shutdown or Close.

func (*App) StaticFS

func (a *App) StaticFS(prefix string, filesystem fs.FS, opts ...StaticOption)

StaticFS serves filesystem below prefix. It panics if filesystem is nil.

func (*App) Trace

func (a *App) Trace(path string, handlers ...HandlerFunc) Route

Trace registers a TRACE route.

func (*App) TryHandle added in v0.2.0

func (a *App) TryHandle(spec RouteSpec) error

TryHandle registers a route whose declaration came from dynamic input.

func (*App) URL added in v0.1.0

func (a *App) URL(name string, params ...string) (string, error)

URL builds a named route URL. Segment parameters are escaped and must be non-empty without slashes; catch-all values keep their slashes and escape '?', '#', and '%'. The URL routes back to the same route and values, or URL returns an error: for example, beside GET /users/me, the value "me" for GET /users/{id} has no URL, and under default routing neither has an empty catch-all value for GET /files/{path...} beside GET /files.

func (*App) Use

func (a *App) Use(handlers ...HandlerFunc)

Use appends Zinc middleware in registration order.

func (*App) UseHTTP added in v0.2.0

func (a *App) UseHTTP(middleware ...HTTPMiddleware)

UseHTTP wraps the whole application in standard net/http middleware. Middleware runs in registration order, with the first middleware outermost.

func (*App) UsePrefix

func (a *App) UsePrefix(prefix string, handlers ...HandlerFunc)

UsePrefix applies Zinc middleware only to requests below a segment boundary.

func (*App) Validate added in v0.0.63

func (a *App) Validate() error

Validate checks the app's routes and spec without serving a request, so a mistake fails a test or a deploy rather than the first request that reaches it. Call it after registering routes:

if err := app.Validate(); err != nil {
	log.Fatal(err)
}

It builds every spec the app serves, which fails on routes OpenAPI can't tell apart, unknown security schemes and examples that don't match their types, and it reports what registration can't panic over:

  • a header-tagged output field that's still in the JSON body, which sends no header, unless the type is also the route's input;
  • an openapi tag word other than readonly, writeonly and deprecated;
  • a validate rule the validator doesn't enforce, on a type given to Route.Input or Route.Output.

The error lists every problem, one per line; nil means none.

type Bind added in v0.1.1

type Bind struct {
	// contains filtered or unexported fields
}

Bind exposes source-specific binding for a Context.

func (*Bind) All added in v0.1.1

func (b *Bind) All(v any) error

All binds the body, then query values, then path parameters, and validates v. Later sources win, so a path or query value is never replaced by a body key that matches the same field.

func (*Bind) Body added in v0.1.1

func (b *Bind) Body(v any) error

Body binds the request body according to its Content-Type.

func (*Bind) Cookie added in v0.6.0

func (b *Bind) Cookie(v any) error

Cookie binds request cookies into fields tagged cookie, then validates v.

func (*Bind) Form added in v0.1.1

func (b *Bind) Form(v any) error

Form binds URL-encoded or multipart form values, then validates v.

func (*Bind) Header added in v0.1.1

func (b *Bind) Header(v any) error

Header binds request headers, then validates v.

func (*Bind) JSON added in v0.1.1

func (b *Bind) JSON(v any) error

JSON decodes and validates a non-empty JSON request body.

func (*Bind) Path added in v0.1.1

func (b *Bind) Path(v any) error

Path binds route parameters, then validates v.

func (*Bind) Query added in v0.1.1

func (b *Bind) Query(v any) error

Query binds query values, then validates v.

func (*Bind) Text added in v0.1.1

func (b *Bind) Text(v any) error

Text decodes and validates a non-empty plain-text request body.

func (*Bind) XML added in v0.1.1

func (b *Bind) XML(v any) error

XML decodes and validates a non-empty XML request body.

type BindError added in v0.1.0

type BindError struct {
	// Source is "path", "query", "header", "cookie", "form", or "body".
	Source string
	// Field is the Go struct field, for logs.
	Field string
	// Name is the request-facing name: the tag value, or the JSON field path.
	Name string
	// Reason is a client-safe description of what the value must be, such as
	// "must be an integer". It is empty when there is nothing safe to say.
	Reason string
	Err    error
}

BindError identifies the request source and struct field that failed.

func (*BindError) Error added in v0.1.0

func (e *BindError) Error() string

Error formats the binding source, field, and underlying error.

func (*BindError) StatusCode added in v0.4.0

func (*BindError) StatusCode() int

StatusCode implements StatusCoder: a binding failure is the client's error.

func (*BindError) Unwrap added in v0.1.0

func (e *BindError) Unwrap() error

Unwrap exposes the underlying conversion or decoding error.

type Bytes added in v0.6.3

type Bytes struct {
	Type string
	Data []byte
}

Bytes is sent with media type Type, or application/octet-stream when Type is empty.

type Config

type Config struct {
	// ServerHeader is written to every response when non-empty.
	ServerHeader string

	// CaseSensitive makes literal route segments case-sensitive.
	CaseSensitive bool
	// StrictRouting treats a trailing slash as part of the route identity.
	StrictRouting bool
	// DisableAutoHead stops HEAD requests from being served by a matching GET
	// route when no HEAD route exists.
	DisableAutoHead bool
	// DisableAutoOptions stops OPTIONS requests from being answered with the
	// methods registered for a path.
	DisableAutoOptions bool
	// DisableMethodNotAllowed answers a method mismatch with 404 instead of
	// 405 and an Allow header.
	DisableMethodNotAllowed bool

	// BodyLimit is the maximum request body size accepted by binding helpers
	// and form parsing. 0 means DefaultBodyLimit; negative means no limit.
	BodyLimit int64
	// ReadTimeout bounds reading the request, including its body.
	// 0 means DefaultReadTimeout; negative means no timeout.
	ReadTimeout time.Duration
	// WriteTimeout bounds writing a response, or one event of a stream.
	// 0 means DefaultWriteTimeout; negative means no timeout.
	WriteTimeout time.Duration
	// IdleTimeout controls keep-alive connection lifetime.
	// 0 means DefaultIdleTimeout; negative means no timeout.
	IdleTimeout time.Duration
	// ShutdownTimeout bounds how long ListenContext waits for in-flight
	// requests after its context ends. 0 means DefaultShutdownTimeout;
	// negative means wait until they finish.
	ShutdownTimeout time.Duration

	// CookieSameSite is applied to cookies written by SetCookie and
	// ClearCookie that don't set SameSite themselves. Zero leaves them unset.
	CookieSameSite http.SameSite

	// ProxyHeader identifies the forwarding header used by Context.IP.
	// Empty means DefaultProxyHeader.
	ProxyHeader string
	// TrustedProxies contains IPs/CIDRs trusted in the forwarded chain.
	// Entries are copied and validated at construction.
	TrustedProxies []string

	// Decoders read request bodies in other media types, keyed by media
	// type, such as "application/yaml": yaml.Unmarshal. Binding picks one by
	// the request's Content-Type. An entry for "application/json" or
	// "application/xml" replaces the built-in decoder.
	Decoders map[string]Decoder
	// Encoders write responses in other media types, for Context.Encode and
	// Context.Negotiate. An entry for "application/json" or
	// "application/xml" replaces the built-in encoder, including for
	// Context.JSON and Context.XML.
	Encoders map[string]Encoder
	// Validator checks bound input, after binding and through
	// Context.Validate. Nil means Zinc checks validate tags itself, with
	// BuiltinRules. A RuleSetValidator declares the rules it enforces, the
	// only ones the OpenAPI spec claims.
	Validator Validator
	// ValidateResponses checks each typed handler's output with the same
	// rules before it's sent, and answers 500 when it fails, so a response
	// that breaks the spec's contract shows up in development and tests.
	// It costs a check per response; leave it off in production.
	ValidateResponses bool
	// Renderer provides named template rendering.
	Renderer Renderer
	// ErrorHandler receives errors returned by handlers and middleware.
	// Nil means DefaultErrorHandler.
	ErrorHandler ErrorHandler

	// OpenAPIPath is where the app serves its OpenAPI spec, for GET and HEAD
	// requests that no route or mount answers. Empty means
	// DefaultOpenAPIPath; "-" serves no spec, as does calling App.OpenAPI.
	// The spec lists every route that isn't hidden, so for a private API,
	// protect it with middleware or turn it off.
	OpenAPIPath string
	// OpenAPI describes the API in the served spec: its title, version,
	// security schemes and more.
	OpenAPI OpenAPIConfig
	// DocsPath is where the app serves a browsable reference page for its
	// spec, for GET and HEAD requests no route or mount answers. Empty means
	// DefaultDocsPath; "-" serves none, and neither does an app that serves
	// no spec. The page loads Scalar from a CDN; middleware/apidocs serves
	// it elsewhere, with another renderer, or from your own files.
	DocsPath string

	// Deprecated: RouteCacheSize is ignored. Zinc 0.5 removed the route
	// cache: the route tree matches faster than the cache could look a path
	// up on realistic traffic, and the cache slowed parallel requests.
	RouteCacheSize int
}

Config holds the application and server configuration. The zero value of every field is the default, so a literal only needs the fields it changes:

app := zinc.New(zinc.Config{BodyLimit: 64 << 20})

For limits and timeouts, 0 selects the default and a negative value turns the limit off.

type Context

type Context struct {
	// contains filtered or unexported fields
}

Context carries request-scoped state through Zinc handlers. Contexts are pooled and must not be retained or used after the handler returns.

func (*Context) Accepts added in v0.1.2

func (c *Context) Accepts(types ...string) string

Accepts returns the best offered type allowed by the request Accept header.

func (*Context) AppendHeader

func (c *Context) AppendHeader(key string, values ...string) *Context

AppendHeader adds response header values without replacing existing ones.

func (*Context) Attachment

func (c *Context) Attachment(filePath string, name ...string) error

Attachment serves a file with an attachment Content-Disposition.

func (*Context) Bind

func (c *Context) Bind() *Bind

Bind returns the request binder facade for this context.

func (*Context) BodyBytes

func (c *Context) BodyBytes() ([]byte, error)

BodyBytes reads and caches the request body, returning a copy owned by the caller. The configured application body limit is enforced.

func (*Context) BodyLimit added in v0.3.0

func (c *Context) BodyLimit() int64

BodyLimit returns the configured binding budget, or zero without an app. Middleware can use it for limits on transformed request bodies.

func (*Context) BodyString

func (c *Context) BodyString() (string, error)

BodyString reads and caches the request body as a string.

func (*Context) ClearCookie

func (c *Context) ClearCookie(cookie *http.Cookie)

ClearCookie tells the client to delete cookie. Name, Path, and Domain must match the cookie being removed; Path defaults to "/".

c.ClearCookie(&http.Cookie{Name: "session"})

func (*Context) ContentType added in v0.1.2

func (c *Context) ContentType() string

ContentType returns the normalized media type without parameters.

func (*Context) Context

func (c *Context) Context() stdctx.Context

Context returns the request's standard-library context.

func (*Context) Cookie

func (c *Context) Cookie(name string) (*http.Cookie, error)

Cookie returns the named request cookie.

func (*Context) Cookies

func (c *Context) Cookies() []*http.Cookie

Cookies returns all request cookies.

func (*Context) Data

func (c *Context) Data(contentType string, b []byte) error

Data writes bytes with the supplied content type.

func (*Context) Encode added in v0.4.0

func (c *Context) Encode(mediaType string, v any) error

Encode writes v as mediaType, using the Encoder configured for it, or the built-in JSON or XML encoding. It returns an error for a media type the app has no encoder for.

func (*Context) File

func (c *Context) File(filePath string) error

File serves a file from the operating-system filesystem.

func (*Context) FileFS

func (c *Context) FileFS(filePath string, filesystem fs.FS) error

FileFS serves a file from filesystem using fs.ValidPath semantics.

func (*Context) FormFile

func (c *Context) FormFile(name string) (*multipart.FileHeader, error)

FormFile returns the first uploaded file header and closes the opened part.

func (*Context) FormFiles

func (c *Context) FormFiles(name string) ([]*multipart.FileHeader, error)

FormFiles returns all uploaded file headers for name.

func (*Context) FormValue

func (c *Context) FormValue(name string) string

FormValue returns the first form value using net/http form parsing semantics.

func (*Context) FullPath

func (c *Context) FullPath() string

FullPath returns the registered route pattern matched by this request.

func (*Context) Get

func (c *Context) Get(key any) (any, bool)

Get retrieves a request-scoped value.

func (*Context) HTML

func (c *Context) HTML(data string) error

HTML writes an HTML response.

func (*Context) HandleError added in v0.4.0

func (c *Context) HandleError(err error)

HandleError sends err to the application's error handler now instead of returning it. Middleware that must observe the final response, such as a logger, uses it; a handler normally just returns the error. The error handler runs once per request, so a later return of the same failure is ignored.

func (*Context) Header added in v0.4.0

func (c *Context) Header(key string) string

Header returns the first request header value for key.

func (*Context) IP

func (c *Context) IP() string

IP returns the first client address from IPs.

func (*Context) IPs

func (c *Context) IPs() []string

IPs returns the verified suffix of the forwarded address chain for trusted peers; otherwise it returns the direct remote address.

func (*Context) Inline added in v0.1.2

func (c *Context) Inline(filePath string, name ...string) error

Inline serves a file with an inline Content-Disposition.

func (*Context) IsPreflight

func (c *Context) IsPreflight() bool

IsPreflight reports whether the request is a CORS preflight.

func (*Context) IsWebSocket

func (c *Context) IsWebSocket() bool

IsWebSocket reports whether the request asks to upgrade to WebSocket.

func (*Context) JSON

func (c *Context) JSON(v any) error

JSON writes v as JSON, using the app's JSON encoder when one is configured.

func (*Context) JSONPretty

func (c *Context) JSONPretty(v any, indent string) error

JSONPretty writes v as JSON indented by indent.

func (*Context) LastError added in v0.0.73

func (c *Context) LastError() error

LastError returns the most recent error passed to HandleError.

func (*Context) Location

func (c *Context) Location(location string) *Context

Location sets the response Location header.

func (*Context) Method

func (c *Context) Method() string

Method returns the request method.

func (*Context) MultipartForm

func (c *Context) MultipartForm() (*multipart.Form, error)

MultipartForm parses multipart input with net/http's 32 MiB in-memory threshold; larger file parts may be stored in temporary files.

func (*Context) Negotiate added in v0.1.2

func (c *Context) Negotiate(offers map[string]any) error

Negotiate writes the offered representation that best matches the Accept header, using the status set by Status, or returns ErrNotAcceptable. The response depends on Accept, so Negotiate adds Accept to Vary, keeping the Vary entries already set.

func (*Context) Next

func (c *Context) Next() error

Next advances the middleware chain by one handler. Middleware may perform work before and after the call to wrap downstream execution.

func (*Context) NoContent

func (c *Context) NoContent() error

NoContent writes the selected status, defaulting to 204.

func (*Context) OriginalURL

func (c *Context) OriginalURL() string

OriginalURL returns the request URI as received, before any SetPath or rewrite middleware changed it. Use c.Request().URL for the current target.

func (*Context) Param

func (c *Context) Param(name string) string

Param returns a named route parameter. Values remain slices of the request path until accessed so routing itself does not allocate parameter strings.

func (*Context) Path

func (c *Context) Path() string

Path returns the current URL path.

func (*Context) Query

func (c *Context) Query(name string) string

Query returns the first query value for name.

func (*Context) QueryArray added in v0.1.2

func (c *Context) QueryArray(name string) []string

QueryArray returns a copy of all query values for name.

func (*Context) QueryMap added in v0.1.2

func (c *Context) QueryMap(name string) map[string]string

QueryMap collects bracketed query keys such as filter[name].

func (*Context) QueryValues

func (c *Context) QueryValues() url.Values

QueryValues parses and caches the request query values.

func (*Context) Redirect

func (c *Context) Redirect(location string) error

Redirect sends location with 302 Found, or with the redirect status chosen by Status, such as c.Status(http.StatusMovedPermanently).Redirect("/new").

func (*Context) RemoteIP

func (c *Context) RemoteIP() string

RemoteIP returns the direct network peer, independent of proxy headers.

func (*Context) Render

func (c *Context) Render(name string, data any) error

Render executes the configured renderer and writes the result as HTML.

func (*Context) Request

func (c *Context) Request() *http.Request

Request returns the current net/http request.

func (*Context) Route

func (c *Context) Route() RouteInfo

Route returns metadata for the matched route.

func (*Context) SSE added in v0.0.71

func (c *Context) SSE(event Event) error

SSE writes and flushes one event, keeping the event-stream response open for more calls. Config.WriteTimeout bounds each event rather than the whole stream, so a stream may run for as long as the handler keeps sending.

func (*Context) SaveFile

func (c *Context) SaveFile(file *multipart.FileHeader, dst string) error

SaveFile copies an uploaded file to dst, creating parent directories.

func (*Context) Scheme

func (c *Context) Scheme() string

Scheme returns http or https. Forwarded protocol headers are trusted only when the direct peer matches Config.TrustedProxies.

func (*Context) Secure

func (c *Context) Secure() bool

Secure reports whether Scheme resolves to https.

func (*Context) Send

func (c *Context) Send(data any) error

Send chooses a response representation from the dynamic value type.

func (*Context) Set

func (c *Context) Set(key any, value any)

Set stores a request-scoped value.

func (*Context) SetContext

func (c *Context) SetContext(ctx stdctx.Context)

SetContext replaces the standard-library context carried by the request.

func (*Context) SetCookie

func (c *Context) SetCookie(cookie *http.Cookie)

SetCookie adds cookie to the response.

func (*Context) SetHeader

func (c *Context) SetHeader(key, value string) *Context

SetHeader replaces a response header value.

func (*Context) SetPath

func (c *Context) SetPath(path string)

SetPath rewrites URL.Path, URL.RawPath, and RequestURI together.

func (*Context) SetRequest

func (c *Context) SetRequest(r *http.Request)

SetRequest replaces the request and invalidates request-derived caches.

func (*Context) SetWriter

func (c *Context) SetWriter(w http.ResponseWriter)

SetWriter replaces the response writer used by subsequent handlers.

func (*Context) Status

func (c *Context) Status(code int) *Context

Status selects the status code for the next response write.

func (*Context) Stream

func (c *Context) Stream(contentType string, r io.Reader) error

Stream copies r to the response without buffering it in Zinc.

func (*Context) String

func (c *Context) String(data string) error

String writes a plain-text response without converting data to []byte.

func (*Context) Type

func (c *Context) Type(ext string) *Context

Type sets Content-Type from a filename extension.

func (*Context) Validate

func (c *Context) Validate(v any) error

Validate checks v, a pointer to a struct, as binding does. Enum values, from an enum tag or an EnumProvider type, are always checked. Then the configured Validator runs, or, when there's none, Zinc checks v's validate tags with BuiltinRules. A failure is a *ValidationError, which the default error handler answers with 422, unless the validator's error already carries a status.

func (*Context) Vary

func (c *Context) Vary(fields ...string) *Context

Vary appends fields to the response Vary header.

func (*Context) Writer

func (c *Context) Writer() http.ResponseWriter

Writer returns the current response writer, including any middleware wrapper.

func (*Context) XML

func (c *Context) XML(v any) error

XML writes v as XML, using the app's XML encoder when one is configured.

type Decoder added in v0.4.0

type Decoder func(data []byte, v any) error

Decoder reads a request body into v. It has the shape of json.Unmarshal, so any library's Unmarshal function can be used directly:

zinc.Config{Decoders: map[string]zinc.Decoder{"application/yaml": yaml.Unmarshal}}

type Encoder added in v0.4.0

type Encoder func(v any) ([]byte, error)

Encoder turns v into a response body. It has the shape of json.Marshal, so any library's Marshal function can be used directly.

type EnumProvider added in v0.6.0

type EnumProvider interface {
	Enum() []any
}

EnumProvider lets a named type list the values it can take. The type becomes a component with an enum, shared by every field of the type:

type Kind string

func (Kind) Enum() []any { return []any{"cat", "dog"} }

Zinc checks the values on input, whatever the validator.

type ErrorHandler

type ErrorHandler func(*Context, error)

ErrorHandler handles an error returned from a Zinc handler.

type Event added in v0.4.0

type Event struct {
	Event string
	ID    string
	Retry time.Duration
	Data  any
}

Event describes one Server-Sent Events message.

type File added in v0.6.3

type File struct {
	Path string
	FS   fs.FS
	Name string
}

File serves a file from Path on disk, or from FS when FS is set, with the semantics of Context.File and Context.FileFS: ranges, conditional requests and a Content-Type from the extension. A non-empty Name makes it a download with that file name, as Context.Attachment does.

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group applies a shared path prefix and middleware chain to related routes.

func (*Group) Add

func (g *Group) Add(method, routePath string, handlers ...HandlerFunc) Route

Add registers handlers and panics when the route declaration is invalid.

func (*Group) All

func (g *Group) All(path string, handlers ...HandlerFunc)

All registers handlers for Zinc's standard method set.

func (*Group) Connect

func (g *Group) Connect(path string, handlers ...HandlerFunc) Route

Connect registers a CONNECT route.

func (*Group) Delete

func (g *Group) Delete(path string, handlers ...HandlerFunc) Route

Delete registers a DELETE route.

func (*Group) Document added in v0.7.1

func (g *Group) Document(docs ...MiddlewareDoc) *Group

Document adds descriptions of the group's middleware to every route registered in the group from now on, and its child groups' routes. Like Use, it panics once the group has routes.

func (*Group) File

func (g *Group) File(routePath, file string) Route

File serves one operating-system file below the group.

func (*Group) FileFS

func (g *Group) FileFS(routePath, file string, filesystem fs.FS) Route

FileFS serves one file from an fs.FS below the group.

func (*Group) Get

func (g *Group) Get(path string, handlers ...HandlerFunc) Route

Get registers a GET route.

func (*Group) Group

func (g *Group) Group(prefix string, handlers ...HandlerFunc) *Group

Group creates a child that inherits the parent's middleware in order.

func (*Group) HandleHTTP added in v0.2.0

func (g *Group) HandleHTTP(pattern string, handler http.Handler) Route

HandleHTTP registers a standard net/http handler below the group prefix.

func (*Group) Head

func (g *Group) Head(path string, handlers ...HandlerFunc) Route

Head registers a HEAD route.

func (*Group) Hidden added in v0.6.0

func (g *Group) Hidden() *Group

Hidden leaves every route registered in the group from now on, and its child groups' routes, out of the spec. Like Use, it panics once the group has routes.

func (*Group) Match

func (g *Group) Match(methods []string, routePath string, handlers ...HandlerFunc)

Match registers the same handler chain for each method.

func (*Group) Mount

func (g *Group) Mount(prefix string, h http.Handler)

Mount delegates a subtree below the group to a standard HTTP handler.

func (*Group) Options

func (g *Group) Options(path string, handlers ...HandlerFunc) Route

Options registers an OPTIONS route.

func (*Group) Patch

func (g *Group) Patch(path string, handlers ...HandlerFunc) Route

Patch registers a PATCH route.

func (*Group) Post

func (g *Group) Post(path string, handlers ...HandlerFunc) Route

Post registers a POST route.

func (*Group) Put

func (g *Group) Put(path string, handlers ...HandlerFunc) Route

Put registers a PUT route.

func (*Group) Route

func (g *Group) Route(prefix string, fn func(*Group), handlers ...HandlerFunc) *Group

Route configures and returns a child group through fn.

func (*Group) RouteNotFound added in v0.1.0

func (g *Group) RouteNotFound(routePath string, handlers ...HandlerFunc)

RouteNotFound registers a path-specific 404 handler below the group.

func (*Group) Security added in v0.6.0

func (g *Group) Security(schemes ...string) *Group

Security names the security schemes that protect every route registered in the group from now on, and its child groups. A route can replace them with Route.Security. Like Use, it panics once the group has routes.

func (*Group) SecurityAll added in v0.6.0

func (g *Group) SecurityAll(schemes ...string) *Group

SecurityAll is Security for schemes that must all pass together.

func (*Group) Static

func (g *Group) Static(prefix, root string, opts ...StaticOption)

Static serves a filesystem directory below the group.

func (*Group) StaticFS

func (g *Group) StaticFS(prefix string, filesystem fs.FS, opts ...StaticOption)

StaticFS serves an fs.FS below the group. It panics if filesystem is nil.

func (*Group) Tags added in v0.6.0

func (g *Group) Tags(tags ...string) *Group

Tags adds tags to every route registered in the group from now on, and to its child groups. Like Use, it panics once the group has routes or child groups, so the order of calls can't silently change the spec.

func (*Group) Trace

func (g *Group) Trace(path string, handlers ...HandlerFunc) Route

Trace registers a TRACE route.

func (*Group) TryHandle added in v0.2.0

func (g *Group) TryHandle(spec RouteSpec) error

TryHandle registers a dynamically defined route below the group prefix.

func (*Group) Use

func (g *Group) Use(handlers ...HandlerFunc) *Group

Use appends middleware to the group. It panics once the group has routes, mounts, static files, or child groups, because those have already captured the chain and would silently run without the new middleware.

func (*Group) UseHTTP added in v0.4.0

func (g *Group) UseHTTP(middleware ...HTTPMiddleware) *Group

UseHTTP appends standard net/http middleware to the group. Each runs inside the group's earlier middleware, as FromHTTP describes.

type HTML added in v0.6.3

type HTML string

HTML is sent as text/html.

type HTTPError

type HTTPError struct {
	Code    int
	Message string
	Cause   error
	// Details are written to the response body by the default error handler.
	Details Map
	Headers http.Header
}

HTTPError represents an HTTP error with a status code and optional message.

func BadRequest added in v0.4.0

func BadRequest(message string) *HTTPError

BadRequest returns a 400 error with a client-facing message.

func Conflict added in v0.4.0

func Conflict(message string) *HTTPError

Conflict returns a 409 error with a client-facing message.

func Forbidden added in v0.4.0

func Forbidden(message string) *HTTPError

Forbidden returns a 403 error with a client-facing message.

func Gone added in v0.4.0

func Gone(message string) *HTTPError

Gone returns a 410 error with a client-facing message.

func InternalServerError added in v0.4.0

func InternalServerError(message string) *HTTPError

InternalServerError returns a 500 error with a client-facing message.

func NewError

func NewError(code int, message ...string) *HTTPError

NewError creates an HTTPError for code, with an optional client-facing message. Without one, the status text is used.

func NotFound added in v0.4.0

func NotFound(message string) *HTTPError

NotFound returns a 404 error with a client-facing message.

Example

A handler returns an error, and one error handler writes every failure.

package main

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

	"github.com/0mjs/zinc"
)

// do sends a request to app and prints the status and body, as a client
// would see them.
func do(app *zinc.App, method, target, body string) {
	r := httptest.NewRequest(method, target, strings.NewReader(body))
	if body != "" {
		r.Header.Set("Content-Type", "application/json")
	}
	w := httptest.NewRecorder()
	app.ServeHTTP(w, r)
	fmt.Println(w.Code, strings.TrimSpace(w.Body.String()))
}

func main() {
	app := zinc.New()
	app.Get("/users/{id}", func(c *zinc.Context) error {
		return zinc.NotFound("user not found")
	})

	do(app, "GET", "/users/42", "")
}
Output:
404 {"error":{"status":404,"message":"user not found"}}

func ServiceUnavailable added in v0.4.0

func ServiceUnavailable(message string) *HTTPError

ServiceUnavailable returns a 503 error with a client-facing message.

func TooManyRequests added in v0.4.0

func TooManyRequests(message string) *HTTPError

TooManyRequests returns a 429 error with a client-facing message.

func Unauthorized added in v0.4.0

func Unauthorized(message string) *HTTPError

Unauthorized returns a 401 error with a client-facing message.

func UnprocessableEntity added in v0.4.0

func UnprocessableEntity(message string) *HTTPError

UnprocessableEntity returns a 422 error with a client-facing message.

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) Is added in v0.4.0

func (e *HTTPError) Is(target error) bool

Is matches HTTP errors by status, so errors.Is(err, ErrNotFound) holds for any 404, including copies made by the With methods. A target that carries a message matches only errors with the same status and message.

func (*HTTPError) StatusCode added in v0.4.0

func (e *HTTPError) StatusCode() int

StatusCode implements StatusCoder.

func (*HTTPError) Unwrap added in v0.1.0

func (e *HTTPError) Unwrap() error

Unwrap exposes the underlying cause for errors.Is and errors.As.

func (*HTTPError) WithDetail added in v0.4.0

func (e *HTTPError) WithDetail(key string, value any) *HTTPError

WithDetail returns a copy with one detail, written to the response body by the default error handler.

func (*HTTPError) WithHeader added in v0.1.0

func (e *HTTPError) WithHeader(key, value string) *HTTPError

WithHeader returns a copy that adds a response header.

func (*HTTPError) Wrap added in v0.4.0

func (e *HTTPError) Wrap(cause error) *HTTPError

Wrap returns a copy that wraps cause. The cause is for logs and errors.Is; it is never sent to the client.

type HTTPMiddleware added in v0.2.0

type HTTPMiddleware func(http.Handler) http.Handler

HTTPMiddleware is the standard net/http middleware shape.

type HandlerFunc

type HandlerFunc func(*Context) error

HandlerFunc handles one request and returns failures to the App error handler.

func Typed added in v0.4.0

func Typed[In, Out any](fn func(*Context, In) (Out, error)) HandlerFunc

Typed adapts a function whose signature is the request contract into a handler. The input is bound from the request, validated, and passed in; the output is written as JSON:

type CreateUser struct {
	OrgID  string `path:"org"`
	DryRun bool   `query:"dry_run"`
	Email  string `json:"email" validate:"required,email"`
}

api.Post("/orgs/{org}/users", zinc.Typed(func(c *zinc.Context, in CreateUser) (User, error) {
	return users.Create(c.Context(), in)
})).Status(http.StatusCreated)

In must be a struct; use struct{} for a handler without input. Its fields bind like Bind().All, from the body and then tagged header, query, and path fields, so a value from the URL or a header is never replaced by a body key. A failure is a *BindError (400). The configured Validator then runs, and a failure is a *ValidationError (422). An error returned by fn goes to the error handler like any other.

The response status is the one fn or middleware sets with c.Status, or else the one declared with Route.Status, or else 200, 204 for NoContent and 302 for Redirect. The spec documents the same status. A status of 200 set with c.Status counts as unset on a route without a declared status. Out is written as JSON, except for Zinc's output types: NoContent, Text, HTML, Bytes, File, Stream and Redirect, each written and documented as what it is. Use NoContent as Out for a response without a body. If fn writes the response itself, its output is ignored.

A field of Out tagged header is sent as that response header, and needs json:"-" so it isn't also in the body:

type Created struct {
	Location string `header:"Location" json:"-"`
	ID       string `json:"id"`
}

An empty string, nil pointer or slice, or zero time.Time sends nothing; a slice sends a value per element. A *http.Cookie or []*http.Cookie field tagged header:"Set-Cookie" sets cookies.

Example

A typed handler's input binds from the request and is validated before the function runs; its output is the response. Both describe the route in the OpenAPI spec.

package main

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

	"github.com/0mjs/zinc"
)

// do sends a request to app and prints the status and body, as a client
// would see them.
func do(app *zinc.App, method, target, body string) {
	r := httptest.NewRequest(method, target, strings.NewReader(body))
	if body != "" {
		r.Header.Set("Content-Type", "application/json")
	}
	w := httptest.NewRecorder()
	app.ServeHTTP(w, r)
	fmt.Println(w.Code, strings.TrimSpace(w.Body.String()))
}

type CreateUser struct {
	Org   string `path:"org"`
	Email string `json:"email" validate:"required,email"`
}

type User struct {
	ID    int    `json:"id"`
	Org   string `json:"org"`
	Email string `json:"email"`
}

func main() {
	app := zinc.New()
	app.Post("/orgs/{org}/users", zinc.Typed(func(c *zinc.Context, in CreateUser) (User, error) {
		return User{ID: 1, Org: in.Org, Email: in.Email}, nil
	})).Status(http.StatusCreated)

	do(app, "POST", "/orgs/acme/users", `{"email":"ada@example.com"}`)
	do(app, "POST", "/orgs/acme/users", `{"email":"nope"}`)
}
Output:
201 {"id":1,"org":"acme","email":"ada@example.com"}
422 {"error":{"status":422,"message":"validation failed","fields":{"email":"must be an email address"}}}

func Wrap

func Wrap(h http.Handler) HandlerFunc

Wrap adapts a standard net/http handler to HandlerFunc. Immediately before it runs, the matched route is published on the request as http.ServeMux would: http.Request.PathValue returns each parameter, and http.Request.Pattern is the route's method and full path, such as "GET /users/{id}" (a GET route's pattern also for a HEAD request it answers).

func WrapFunc

func WrapFunc(fn http.HandlerFunc) HandlerFunc

WrapFunc adapts a standard http.HandlerFunc to HandlerFunc.

type HeaderDoc added in v0.7.1

type HeaderDoc struct {
	// Name is the header's name, spelled as the spec should show it.
	Name        string
	Description string
	// Required says requests the middleware runs on must send it.
	Required bool
}

HeaderDoc is a request header a middleware reads.

type Map

type Map map[string]any

Map is a concise map type for dynamic JSON and template data.

type Middleware

type Middleware = HandlerFunc

Middleware is a HandlerFunc that calls Context.Next to continue the chain.

func FromHTTP added in v0.4.0

func FromHTTP(middleware HTTPMiddleware) Middleware

FromHTTP adapts standard net/http middleware to Zinc middleware, so it can run on a group or a single route instead of the whole app:

admin := app.Group("/admin", zinc.FromHTTP(basicAuth))

The middleware is built once. If it replaces the request or wraps the response writer before calling next, the rest of the Zinc chain uses the replacements. An error from that chain is written by the error handler before the standard middleware returns, so middleware that observes the response, such as a logger, sees its status; the error is still returned to outer Zinc middleware.

A middleware that wraps the ResponseWriter must expose the writer it wraps through an Unwrap() http.ResponseWriter method, the convention http.ResponseController relies on. Zinc uses it to find the request's Context without allocating.

On a group or route, the route has matched before the middleware runs, so the request carries it as http.ServeMux would set it: http.Request.PathValue returns each parameter and http.Request.Pattern is the route's "METHOD /path". On the app, through App.Use, the middleware runs before routing and sees neither.

func Skip added in v0.4.0

func Skip(skip func(*Context) bool, mw Middleware) Middleware

Skip runs mw except when skip reports true for the request, in which case the chain continues without it. It replaces the Skipper option each middleware used to carry:

app.Use(zinc.Skip(isHealthCheck, logger.New()))

type MiddlewareDoc added in v0.7.1

type MiddlewareDoc struct {
	// Methods limits the description to requests with these methods, such
	// as the unsafe ones CSRF protection checks. Empty means every method.
	Methods []string
	// Security holds credentials the middleware checks on every request it
	// covers, such as a CSRF token, by scheme name. Each is added to the
	// spec's security schemes and required together with the route's own
	// security, so a generated client sets it once rather than on every
	// call. A scheme of the same name in OpenAPIConfig.SecuritySchemes must
	// be the same. Unlike a route's security, it adds no 401 or 403; list
	// what the middleware answers in Errors.
	Security map[string]OpenAPISecurityScheme
	// Headers are request headers the middleware reads, other than
	// credentials: each is a parameter callers pass.
	Headers []HeaderDoc
	// Errors are statuses it answers by returning an error, described with
	// the body the error handler writes, as Route.Errors does.
	Errors []int
}

MiddlewareDoc describes, for the OpenAPI spec, what a middleware adds to the routes it runs on: the credentials and request headers it reads and the errors it can answer. A middleware is a plain function, so Zinc can't see this for itself. Pass it to App.Document, Group.Document or Route.Document beside the middleware:

api.Use(csrf.New())
api.Document(csrf.Doc())

type NoContent added in v0.4.0

type NoContent struct{}

NoContent is the output type of a typed handler that sends no body. The route answers 204 No Content, or the status declared with Route.Status, even 200.

type OpenAPIConfig added in v0.6.0

type OpenAPIConfig struct {
	// Title names the API. It defaults to the last element of the main
	// module's path, or "API".
	Title string
	// Version is the API's version, not Zinc's. It defaults to the main
	// module's version, or "0.0.0" for a development build.
	Version string
	// Description may use Markdown.
	Description string
	// TermsOfService is a URL.
	TermsOfService string
	Contact        *OpenAPIContact
	License        *OpenAPILicense
	// ExternalDocs links to documentation beyond the spec.
	ExternalDocs *OpenAPIExternalDocs
	// Servers lists base URLs the API is served from.
	Servers []OpenAPIServer
	// Tags describes tags, in the order docs pages list them. Tags that
	// routes use but Tags leaves out follow, in the order routes use them.
	Tags []OpenAPITag
	// SecuritySchemes defines the schemes Route.Security, Group.Security and
	// Security refer to, by name.
	SecuritySchemes map[string]OpenAPISecurityScheme
	// Security names the schemes that protect every route that doesn't set
	// its own with Route.Security or Group.Security. Any one of them is
	// enough. A name can carry a scope: "oauth:pets:read".
	Security []string
	// NoAuthResponses leaves out the 401, and the 403 for a route that needs
	// scopes, that Zinc adds to every secured route.
	NoAuthResponses bool
	// Schemas gives a schema to a type Zinc can't describe and you can't add
	// a SchemaProvider to, such as a type from another module:
	//
	//	Schemas: map[reflect.Type]map[string]any{
	//		reflect.TypeFor[decimal.Decimal](): {"type": "string", "format": "decimal"},
	//	}
	Schemas map[reflect.Type]map[string]any
	// Extensions adds specification extensions to the spec's root, such as
	// "x-logo". Each key must start with "x-".
	Extensions map[string]any
	// Mutate edits the finished spec, as decoded JSON, for what Zinc has no
	// option for. It runs after Extensions and every Route.Operation hook.
	// The result is checked again: it must still be OpenAPI 3.1 with a title
	// and version, and every local $ref must resolve.
	Mutate func(spec map[string]any) error
}

OpenAPIConfig describes the API as a whole in the OpenAPI spec. Every field is optional.

type OpenAPIContact added in v0.6.0

type OpenAPIContact struct {
	Name  string `json:"name,omitempty"`
	URL   string `json:"url,omitempty"`
	Email string `json:"email,omitempty"`
}

OpenAPIContact is who to contact about the API.

type OpenAPIExternalDocs added in v0.6.0

type OpenAPIExternalDocs struct {
	Description string `json:"description,omitempty"`
	URL         string `json:"url"`
}

OpenAPIExternalDocs links to documentation outside the spec.

type OpenAPILicense added in v0.6.0

type OpenAPILicense struct {
	Name       string `json:"name"`
	Identifier string `json:"identifier,omitempty"`
	URL        string `json:"url,omitempty"`
}

OpenAPILicense is the API's license. Name is required; set Identifier, an SPDX expression such as "MIT", or URL, not both.

type OpenAPIOAuthFlow added in v0.6.0

type OpenAPIOAuthFlow struct {
	AuthorizationURL string `json:"authorizationUrl,omitempty"`
	TokenURL         string `json:"tokenUrl,omitempty"`
	RefreshURL       string `json:"refreshUrl,omitempty"`
	// Scopes maps each scope the flow grants to a short description.
	Scopes map[string]string `json:"scopes"`
}

OpenAPIOAuthFlow is one OAuth 2 flow.

type OpenAPIOAuthFlows added in v0.6.0

type OpenAPIOAuthFlows struct {
	// AuthorizationCode needs AuthorizationURL and TokenURL.
	AuthorizationCode *OpenAPIOAuthFlow `json:"authorizationCode,omitempty"`
	// ClientCredentials needs TokenURL.
	ClientCredentials *OpenAPIOAuthFlow `json:"clientCredentials,omitempty"`
	// Password needs TokenURL.
	Password *OpenAPIOAuthFlow `json:"password,omitempty"`
	// Implicit needs AuthorizationURL.
	Implicit *OpenAPIOAuthFlow `json:"implicit,omitempty"`
}

OpenAPIOAuthFlows lists the OAuth 2 flows a scheme supports. Set at least one.

type OpenAPISecurityScheme added in v0.6.0

type OpenAPISecurityScheme struct {
	// Type is "http", "apiKey", "oauth2", "openIdConnect" or "mutualTLS".
	Type string `json:"type"`
	// Scheme is the HTTP authentication scheme for type "http", such as
	// "bearer" or "basic".
	Scheme string `json:"scheme,omitempty"`
	// BearerFormat hints at the token format for scheme "bearer", such as "JWT".
	BearerFormat string `json:"bearerFormat,omitempty"`
	// In is where an "apiKey" is sent: "header", "query" or "cookie".
	In string `json:"in,omitempty"`
	// Name is the header, query parameter or cookie that carries an "apiKey".
	Name string `json:"name,omitempty"`
	// Flows lists the flows an "oauth2" scheme supports; it needs at least
	// one.
	Flows *OpenAPIOAuthFlows `json:"flows,omitempty"`
	// OpenIDConnectURL is the discovery URL for type "openIdConnect".
	OpenIDConnectURL string `json:"openIdConnectUrl,omitempty"`
	Description      string `json:"description,omitempty"`
}

OpenAPISecurityScheme describes one way a client authenticates.

zinc.OpenAPISecurityScheme{Type: "http", Scheme: "bearer", BearerFormat: "JWT"}
zinc.OpenAPISecurityScheme{Type: "apiKey", In: "header", Name: "X-API-Key"}
zinc.OpenAPISecurityScheme{Type: "oauth2", Flows: &zinc.OpenAPIOAuthFlows{
	ClientCredentials: &zinc.OpenAPIOAuthFlow{TokenURL: "https://id.example.com/token"},
}}

A scheme missing a field its type needs, such as an "oauth2" scheme without flows, makes the spec fail to build.

type OpenAPIServer added in v0.6.0

type OpenAPIServer struct {
	URL         string `json:"url"`
	Description string `json:"description,omitempty"`
}

OpenAPIServer is a base URL the API is served from.

type OpenAPITag added in v0.6.0

type OpenAPITag struct {
	Name         string               `json:"name"`
	Description  string               `json:"description,omitempty"`
	ExternalDocs *OpenAPIExternalDocs `json:"externalDocs,omitempty"`
}

OpenAPITag describes a tag routes use.

type Redirect added in v0.6.3

type Redirect string

Redirect sends the client to its URL with the route's status, set with Route.Status, or 302 Found.

type Renderer

type Renderer interface {
	Render(w io.Writer, name string, data any, c *Context) error
}

Renderer renders named templates into a response writer.

type ResponseWriter added in v0.1.2

type ResponseWriter interface {
	http.ResponseWriter
	Status() int
	BytesWritten() int
	Written() bool
}

ResponseWriter records status and body size while retaining http.ResponseWriter.

func WrapResponseWriter added in v0.1.2

func WrapResponseWriter(w http.ResponseWriter) ResponseWriter

WrapResponseWriter wraps w or returns it unchanged when already instrumented.

type Route

type Route struct {
	// contains filtered or unexported fields
}

Route is a registered route. Registration methods return it so the route can be named for URL building:

app.Get("/users/{id}", showUser).Name("users.show")

func (Route) Consumes added in v0.6.3

func (r Route) Consumes(mediaTypes ...string) Route

Consumes declares the media types the route accepts as a request body, such as .Consumes("text/csv"), in place of the ones Zinc infers from the input type.

func (Route) Deprecated added in v0.6.0

func (r Route) Deprecated() Route

Deprecated marks the route as deprecated in the OpenAPI spec. The route still serves requests.

func (Route) Description added in v0.6.0

func (r Route) Description(description string) Route

Description sets the route's longer description in the OpenAPI spec. It may use Markdown.

func (Route) Document added in v0.7.1

func (r Route) Document(docs ...MiddlewareDoc) Route

Document adds descriptions of the route's own middleware, the handlers before its last, to the spec.

func (Route) Errors added in v0.6.0

func (r Route) Errors(statuses ...int) Route

Errors declares error statuses the route can answer by returning an error, such as zinc.NewError(http.StatusConflict, ...). The spec describes each with the body the error handler writes: Zinc's error envelope with the default handler, or the status alone with a custom one. Use Response for a status the handler writes itself.

func (Route) Example added in v0.6.4

func (r Route) Example(status int, name string, value any) Route

Example adds a named example of the response sent with status, shown in the spec beside its schema:

app.Get("/pets/{id}", zinc.Typed(getPet)).
	Example(200, "a cat", Pet{ID: "7", Name: "Tom"}).
	Errors(404).
	Example(404, "unknown id", zinc.NewError(404, "pet not found"))

value is what the route sends for it, as a Go value: the output type for the success status, the type given to Response for a declared status, or an *HTTPError for an error status, shown as the app's ErrorHandler writes it. The spec fails to build when value doesn't match the response it's for. An example with the same status and name replaces the earlier one.

func (Route) Hidden added in v0.6.0

func (r Route) Hidden() Route

Hidden leaves the route out of the OpenAPI spec. The route still serves requests.

func (Route) Input added in v0.6.0

func (r Route) Input(v any) Route

Input declares the request type of a route whose handler isn't Typed, so the OpenAPI spec can describe its parameters and body. Pass a value of the type, such as CreateUser{}. It panics on a Typed route, whose input is already known.

func (Route) Name added in v0.4.0

func (r Route) Name(name string) Route

Name gives the route a unique name, used by App.URL, App.RouteByName, and RouteInfo.Name. Like other registration mistakes, a duplicate name panics; routes from configuration should use TryHandle, which returns the error.

func (Route) Operation added in v0.7.0

func (r Route) Operation(hook func(op map[string]any)) Route

Operation adds a hook that edits the route's operation in the spec, as decoded JSON, for what no Route method sets, such as an extension:

app.Get("/pets", listPets).Operation(func(op map[string]any) {
	op["x-rate-limit"] = 100
})

Hooks run in the order added, before OpenAPIConfig.Mutate, and the spec is checked again after them.

func (Route) Output added in v0.6.0

func (r Route) Output(v any) Route

Output declares the success-response type of a route whose handler isn't Typed, such as User{}. It panics on a Typed route, whose output is already known.

func (Route) Produces added in v0.6.3

func (r Route) Produces(status int, mediaTypes ...string) Route

Produces declares the media types the route sends with status, such as .Produces(200, "text/csv"). Use it where Zinc can't tell from the route: a plain handler, or a Bytes, File or Stream output, whose media type is chosen when the handler runs. It applies to the success status or a status declared with Response. Calling it again for a status replaces its types.

func (Route) RequestExample added in v0.6.4

func (r Route) RequestExample(name string, value any) Route

RequestExample adds a named example of the request body, a value of the route's input type. Fields bound from the path, query, headers or cookies are left out, as the body doesn't carry them, and so are nil fields.

func (Route) Response added in v0.6.0

func (r Route) Response(status int, v any) Route

Response declares another response the route can send, such as a 409 with a ConflictBody{}. A nil value means the response has no body. Declaring the same status again replaces it.

func (Route) Security added in v0.6.0

func (r Route) Security(schemes ...string) Route

Security names the security schemes that protect the route, replacing its group's. Any one of them is enough; SecurityAll needs them all. A name can carry a scope after a colon, such as "oauth:pets:read". With no names, the route is marked public.

func (Route) SecurityAll added in v0.6.0

func (r Route) SecurityAll(schemes ...string) Route

SecurityAll is Security for schemes that must all pass together, such as an API key and a bearer token, replacing the group's.

func (Route) Status added in v0.4.0

func (r Route) Status(code int) Route

Status sets the route's success status, such as http.StatusCreated for a route that creates a resource. It's the status of whatever the route writes, for typed and plain handlers alike, unless a handler or middleware sets another with Context.Status, or returns an error. The spec documents it. It panics unless code is a success status (2xx) or a redirect (301, 302, 303, 307 or 308), the status a Redirect output or Context.Redirect sends. On a route whose output is Redirect, it panics unless code is a redirect.

func (Route) Summary added in v0.6.0

func (r Route) Summary(summary string) Route

Summary sets the route's one-line summary in the OpenAPI spec.

func (Route) Tags added in v0.6.0

func (r Route) Tags(tags ...string) Route

Tags adds tags that group the route in the OpenAPI spec, after any from its group.

type RouteInfo

type RouteInfo struct {
	Name    string
	Method  string
	Path    string
	Params  []string
	Mounted bool
	Handler string
	// Status is the success status declared with Route.Status, or 0.
	Status int
	// Builtin marks an endpoint the app serves itself: the OpenAPI spec and
	// the reference page.
	Builtin bool
}

RouteInfo is an immutable snapshot of registered route metadata.

type RouteSpec added in v0.1.0

type RouteSpec struct {
	Name    string
	Method  string
	Path    string
	Handler HandlerFunc
}

RouteSpec describes a route supplied by configuration, plugins, or generated code.

type RuleSetValidator added in v0.7.0

type RuleSetValidator interface {
	Validator
	RuleSet() []string
}

RuleSetValidator is a Validator that declares the validate-tag rules it enforces, such as "required", "min" and "email". The OpenAPI spec claims only those rules, and a typed handler whose input or output uses any other rule panics when it's registered, so a rule is never silently unenforced. A Validator without RuleSet makes no claims: the spec lists no validate rules, and nothing is checked at registration.

type SchemaNamer added in v0.6.4

type SchemaNamer interface {
	OpenAPIName() string
}

SchemaNamer lets a named type choose its component name in the OpenAPI spec, in place of its Go name, such as "Pet" for a struct named petRecord or a generic Page[Pet], or "UserStatus" for an enum type named Status. A struct's input variant adds Input to the name, and its request-body variant Body, unless the name already ends with it. If two types claim one name, the later is qualified by its package, as with Go names.

type SchemaProvider added in v0.6.0

type SchemaProvider interface {
	OpenAPISchema() map[string]any
}

SchemaProvider lets a type describe itself in the OpenAPI spec when the schema Zinc derives from its Go shape would be wrong, such as a type with a custom MarshalJSON. OpenAPISchema returns a JSON Schema 2020-12 object, such as map[string]any{"type": "string", "format": "date"}.

type StaticConfig

type StaticConfig struct {
	Browse bool
	Index  string
}

StaticConfig controls directory serving and index behaviour.

type StaticOption

type StaticOption func(*StaticConfig)

StaticOption mutates static-file configuration during registration.

func WithStaticBrowse

func WithStaticBrowse(browse bool) StaticOption

WithStaticBrowse enables or disables directory listings.

func WithStaticIndex

func WithStaticIndex(index string) StaticOption

WithStaticIndex changes the filename served for directory requests.

type StatusCoder added in v0.4.0

type StatusCoder interface {
	StatusCode() int
}

StatusCoder is implemented by errors that choose their own HTTP status, so a handler can return a domain error and let the error handler map it:

func (ErrUserNotFound) StatusCode() int { return http.StatusNotFound }

For statuses below 500, the error's own message is sent to the client.

type Stream added in v0.6.3

type Stream struct {
	Type   string
	Reader io.Reader
}

Stream copies Reader to the response with media type Type, or application/octet-stream when Type is empty. A Reader that's also an io.Closer is closed after the copy.

type TemplateExecutor added in v0.0.76

type TemplateExecutor interface {
	ExecuteTemplate(w io.Writer, name string, data any) error
}

TemplateExecutor is implemented by html/template and text/template sets.

type TemplateRenderer added in v0.0.76

type TemplateRenderer struct {
	Engine TemplateExecutor
	// contains filtered or unexported fields
}

TemplateRenderer adapts parsed templates to Zinc's Renderer contract.

func NewHTMLTemplateRenderer added in v0.0.76

func NewHTMLTemplateRenderer(engine *htmltemplate.Template, opts ...TemplateRendererOption) *TemplateRenderer

NewHTMLTemplateRenderer wraps an html/template set.

func NewTemplateRenderer added in v0.0.76

func NewTemplateRenderer(engine TemplateExecutor, opts ...TemplateRendererOption) *TemplateRenderer

NewTemplateRenderer wraps a parsed template set.

func NewTextTemplateRenderer added in v0.0.76

func NewTextTemplateRenderer(engine *texttemplate.Template, opts ...TemplateRendererOption) *TemplateRenderer

NewTextTemplateRenderer wraps a text/template set.

func (*TemplateRenderer) Render added in v0.0.76

func (r *TemplateRenderer) Render(w io.Writer, name string, data any, _ *Context) error

Render executes the first matching exact or suffixed template name.

type TemplateRendererOption added in v0.0.76

type TemplateRendererOption func(*TemplateRenderer)

TemplateRendererOption configures template name resolution.

func WithTemplateSuffixes added in v0.0.76

func WithTemplateSuffixes(suffixes ...string) TemplateRendererOption

WithTemplateSuffixes configures fallback suffixes tried after an exact name.

type Text added in v0.6.3

type Text string

Text is sent as text/plain.

type ValidationError added in v0.0.63

type ValidationError struct {
	Err error
}

ValidationError wraps a failure returned by the configured Validator. The default error handler answers it with 422, adding the field messages when the validator's error has a Fields() map[string]string method.

func (*ValidationError) Error added in v0.4.0

func (e *ValidationError) Error() string

func (*ValidationError) Fields added in v0.4.0

func (e *ValidationError) Fields() map[string]string

Fields returns per-field messages from the validator's error, or nil.

func (*ValidationError) StatusCode added in v0.4.0

func (*ValidationError) StatusCode() int

StatusCode implements StatusCoder.

func (*ValidationError) Unwrap added in v0.4.0

func (e *ValidationError) Unwrap() error

Unwrap exposes the validator's error.

type Validator

type Validator interface {
	Validate(any) error
}

Validator validates a value after binding.

Directories

Path Synopsis
internal
docspage
Package docspage renders the API reference page for an OpenAPI spec.
Package docspage renders the API reference page for an OpenAPI spec.
marks
Package marks lets Zinc's own middleware tell Zinc what it needs at registration: to run before routing, such as rewrite, or to answer CORS preflight requests, such as cors.
Package marks lets Zinc's own middleware tell Zinc what it needs at registration: to run before routing, such as rewrite, or to answer CORS preflight requests, such as cors.
Package middleware is the home of Zinc's standard middleware.
Package middleware is the home of Zinc's standard middleware.
apidocs
Package apidocs serves a browsable reference page for an OpenAPI spec, such as the one every Zinc app serves at /openapi.json.
Package apidocs serves a browsable reference page for an OpenAPI spec, such as the one every Zinc app serves at /openapi.json.
compress
Package compress compresses responses for clients that accept gzip.
Package compress compresses responses for clients that accept gzip.
contenttype
Package contenttype rejects request bodies of media types or content codings the application does not accept.
Package contenttype rejects request bodies of media types or content codings the application does not accept.
headers
Package headers sets response headers and selects middleware by request header.
Package headers sets response headers and selects middleware by request header.
healthcheck
Package healthcheck answers a health endpoint before routing work.
Package healthcheck answers a health endpoint before routing work.
internal/shared
Package shared holds helpers used by more than one Zinc middleware package.
Package shared holds helpers used by more than one Zinc middleware package.
internal/testutil
Package testutil holds helpers shared by middleware tests.
Package testutil holds helpers shared by middleware tests.
limiter
Package limiter bounds how fast, or how many at once, requests reach downstream handlers.
Package limiter bounds how fast, or how many at once, requests reach downstream handlers.
logger
Package logger logs one line per request.
Package logger logs one line per request.
nocache
Package nocache tells browsers and intermediaries not to cache responses.
Package nocache tells browsers and intermediaries not to cache responses.
pprof
Package pprof serves the net/http/pprof profiling endpoints.
Package pprof serves the net/http/pprof profiling endpoints.
prometheus
Package prometheus records request counts and durations and serves them in the Prometheus text format, without depending on the Prometheus client.
Package prometheus records request counts and durations and serves them in the Prometheus text format, without depending on the Prometheus client.
proxy
Package proxy forwards requests to upstream servers.
Package proxy forwards requests to upstream servers.
redirect
Package redirect answers matching paths with a redirect.
Package redirect answers matching paths with a redirect.
requestid
Package requestid gives every request an identifier, reusing the one the client or an upstream proxy sent.
Package requestid gives every request an identifier, reusing the one the client or an upstream proxy sent.
rewrite
Package rewrite routes matching paths as other paths.
Package rewrite routes matching paths as other paths.
session
Package session keeps string values in a signed client-side cookie.
Package session keeps string values in a signed client-side cookie.
trailingslash
Package trailingslash normalizes trailing slashes in request paths.
Package trailingslash normalizes trailing slashes in request paths.

Jump to

Keyboard shortcuts

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