Audience: developers applying api-toolkit to common HTTP API tasks. Recipes are
either runnable contrib examples or inline package sketches. Runnable examples
name a contrib/examples/... source and a run command; inline sketches show the
minimum package usage to adapt inside an application.
Run runnable examples from the contrib module unless a recipe says otherwise:
cd contrib
go run ./examples/<name>Purpose: accept JSON, validate fields, and return Problem Details for bad input.
-
Source type: Runnable contrib example (
contrib/examples/minimal). -
Prerequisites: none.
-
Example source:
contrib/examples/minimal. -
Run command:
cd contrib && go run ./examples/minimal. -
Request:
curl -s -X POST http://localhost:8080/widgets \
-H 'Content-Type: application/json' \
-d '{"name":"starter","quantity":1}'- Expected response shape:
201JSON withid,name, andquantity. - Production caveat: keep strict JSON decoding and field-level errors at the edge so downstream services do not need to guess validation shape.
Purpose: bind request input into typed structs while preserving the toolkit's Problem Details validation shape.
-
Source type: Inline sketch using the stable
bindingpackage. -
Prerequisites: none.
-
Example source: use
github.com/aatuh/api-toolkit/v4/bindingin any HTTP handler. -
Request:
curl -s -X POST 'http://localhost:8080/widgets?dry_run=true' \
-H 'Content-Type: application/json' \
-d '{"name":"starter","quantity":1}'- Handler sketch:
type createWidgetBody struct {
Name string `json:"name" required:"true"`
Quantity int `json:"quantity" required:"true"`
}
type createWidgetQuery struct {
DryRun bool `query:"dry_run"`
}
body, err := binding.DecodeJSON[createWidgetBody](r, binding.JSONConfig{})
if err != nil {
binding.WriteValidationProblem(w, err)
return
}
query, err := binding.DecodeQuery[createWidgetQuery](r, binding.QueryConfig{})
if err != nil {
binding.WriteValidationProblem(w, err)
return
}- Expected response shape: invalid input returns
application/problem+jsonwithvalidation.fields. - Production caveat: keep route-specific semantic validation in the application layer; binding handles transport shape, conversion, and required-field errors.
Purpose: apply the strict API profile with secure headers, timeout, tracing, and explicit system endpoint choices.
-
Source type: Runnable contrib example (
contrib/examples/secure-profile). -
Prerequisites: none.
-
Example source:
contrib/examples/secure-profile. -
Run command:
cd contrib && go run ./examples/secure-profile. -
Request:
curl -i http://localhost:8080/hello- Expected response shape:
200JSON{"status":"ok"}plus security headers. - Production caveat: cross-origin isolation can break embeds or CDN-backed assets; enable it only when you control embedded resources.
Purpose: wire Clerk JWT middleware and an explicit authorizer check.
-
Source type: Runnable contrib example (
contrib/examples/auth). -
Prerequisites: replace the placeholder Clerk issuer, JWKS URL, and audience with a real tenant before using the route successfully.
-
Example source:
contrib/examples/auth. -
Run command:
cd contrib && go run ./examples/auth. -
Request:
curl -i http://localhost:8080/admin \
-H 'Authorization: Bearer <jwt>'- Expected response shape:
200JSON{"role":"admin"}for an allowed subject, otherwise Problem Details401or403. - Production caveat: do not keep placeholder identity-provider URLs in deployed configuration.
Purpose: keep public probes separate from operator-only pprof, detailed health, and metrics routes. Web, mobile, and desktop clients should never need direct access to operator-only endpoints.
-
Source type: Inline sketch.
-
Prerequisites: provide an application-owned
requireAdminmiddleware or internal-network wrapper. -
Public routes:
bootstrap.MountSystemEndpointsToWithAdminmounts public health, docs, and version routes normally on the provided router. -
Admin routes: the same helper mounts detailed health, metrics, and pprof behind the wrapper at startup.
-
Manual split-router wiring: use
RegisterPublicRoutesTo(publicRouter)for public probes andRegisterAdminDetailedHealthRoute(adminRouter, requireAdmin)for detailed dependency output.
err := bootstrap.MountSystemEndpointsToWithAdmin(router, bootstrap.SystemEndpoints{
Health: healthHandler,
Metrics: bootstrap.PrometheusMetricsHandler(),
Pprof: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
}, bootstrap.SystemEndpointAdminOptions{
RequireAdmin: requireAdmin,
EnablePprof: true,
})
if err != nil {
return err
}Purpose: authenticate service-to-service or automation calls with API keys and explicit scopes.
-
Source type: Runnable contrib example (
contrib/examples/api-key). -
Prerequisites: use the demo key only for local testing.
-
Example source:
contrib/examples/api-key. -
Run command:
cd contrib && go run ./examples/api-key. -
Request:
curl -s http://localhost:8080/admin \
-H 'Authorization: ApiKey demo-admin-key' \
-H 'Accept: application/json'- Route contract: the example registers the
/adminoperation once throughroutecontracts, generates theAdminResponseschema from a Go struct, appliesApiKeyAuthscope metadata, enforcesAcceptnegotiation, decodes query input withbinding, uses catalog-backed Problem Details for validation errors, and serves the generated document at/openapi.json. - Expected response shape:
200JSON withprincipal_idandscopes; missing or invalid keys return Problem Details401, and missing scopes return Problem Details403. - Production caveat: keep key storage, hashing, rotation, and revocation in application-owned verifier code; the stable middleware extracts credentials, calls the verifier, writes auth context, and enforces scopes.
Purpose: keep runtime route registration, OpenAPI operation metadata, schema components, content negotiation, and error behavior in one declaration path.
-
Source type: Inline sketch.
-
Prerequisites: use a router that supports the common
Get,Post,Put, andDeleteregistration methods. -
Packages:
github.com/aatuh/api-toolkit/v4/routecontracts,github.com/aatuh/api-toolkit/v4/specs,github.com/aatuh/api-toolkit/v4/negotiation, andgithub.com/aatuh/api-toolkit/v4/httpx. -
Handler sketch:
specRegistry := specs.NewRegistry(specs.Info{Title: "Widget API", Version: "v1"})
_ = specs.RegisterSchemaFrom[widgetResponse](specRegistry, "Widget", specs.SchemaOptions{})
contracts := routecontracts.NewRegistry(router, specRegistry)
_ = contracts.Get("/widgets/{id}", specs.Operation{
Summary: "Read widget",
Responses: map[int]specs.Response{
http.StatusOK: {
Content: map[string]specs.MediaType{
"application/json": {SchemaRef: "#/components/schemas/Widget"},
},
},
},
}, widgetHandler, negotiation.RequireAccept("application/json"))- Expected response shape: the router serves the handler and
/openapi.jsoncan describe the same method, path, responses, schemas, and media types. - Production caveat: keep generated schemas simple and review OpenAPI diffs when changing public response structs.
Purpose: catch drift between route registration, OpenAPI operations, response metadata, security metadata, and typed problem catalogs.
-
Source type: Inline test sketch.
-
Prerequisites: register routes through
routecontractsand operations throughspecs.Registry. -
Package:
github.com/aatuh/api-toolkit/v4/contracttest. -
Test sketch:
contracttest.AssertRegistryValid(t, routeRegistry)
contracttest.AssertRouteCoverage(t, routeRegistry, http.MethodGet, "/widgets")
contracttest.AssertOperationHasResponse(t, specRegistry, http.MethodGet, "/widgets", http.StatusOK)
contracttest.AssertOperationHasSecurity(t, specRegistry, http.MethodGet, "/widgets", "ApiKeyAuth")
contracttest.AssertAllOperationsHaveOperationID(t, specRegistry)
contracttest.AssertUniqueOperationIDs(t, specRegistry)
contracttest.AssertProblemCatalogHas(t, httpx.DefaultProblemCatalog(), httpx.ProblemCode(httpx.TypeBadRequest))- Expected result: tests fail when a runtime route, documented response, security requirement, stable operation identity, or problem code is missing.
- Production caveat: golden OpenAPI tests should review diffs intentionally; the helper does not auto-update golden files.
Purpose: signal deprecated or sunsetting routes at runtime without changing response behavior.
-
Source type: Inline sketch.
-
Prerequisites: choose the deprecation and sunset dates from your published migration policy.
-
Package:
github.com/aatuh/api-toolkit/v4/middleware/deprecation. -
Handler sketch:
mw, _ := deprecation.New(deprecation.Config{
DeprecatedAt: time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
SunsetAt: time.Date(2026, 8, 2, 0, 0, 0, 0, time.UTC),
Links: []deprecation.Link{{
URL: "https://developer.example.com/deprecations/widgets-v1",
}},
})
router.Get("/v1/widgets", mw.Handler(widgetsHandler).ServeHTTP)- Expected response shape: unchanged route body plus
Deprecation,Sunset, andLinkheaders. - Production caveat: headers are only hints; keep the human migration guide accurate and monitor deprecated-route traffic separately.
Purpose: authorize a route through a Cedar policy engine with request context.
-
Source type: Runnable contrib example (
contrib/examples/policy). -
Prerequisites: none; the example embeds a demo policy.
-
Example source:
contrib/examples/policy. -
Run command:
cd contrib && go run ./examples/policy. -
Request:
curl -s http://localhost:8080/docs/doc_123- Expected response shape:
200JSON withidwhen the policy allows the request. - Production caveat: load policies through your deployment process and fail startup if policy parsing fails.
Purpose: use the idempotency middleware to make safe retries for POST, PUT, or PATCH operations.
-
Source type: Runnable contrib example (
contrib/examples/idempotency). -
Prerequisites: none; the example uses an in-memory store and a fake billing provider.
-
Example source:
contrib/examples/idempotency. -
Run command:
cd contrib && go run ./examples/idempotency. -
Request:
curl -s -X POST http://localhost:8080/checkout \
-H 'Idempotency-Key: key-1' \
-H 'Content-Type: application/json' \
-d '{"amount":500,"currency":"eur"}'- Expected response shape:
200JSON checkout session withidandurl; repeat the same request and key to replay the stored response. - Production caveat: use a durable store such as Redis, apply auth and tenant middleware before idempotency, set
Options.StorageKeyFunctoidempotency.TenantScopedStorageKeyFunc()for shared multi-tenant storage, and exclude streaming or large-download routes withShouldHandle.
Purpose: verify HMAC signatures, cap body size, parse the event, and return an acknowledgement.
-
Source type: Runnable contrib example (
contrib/examples/webhook). -
Prerequisites: use the demo secret only for local testing.
-
Example source:
contrib/examples/webhook. -
Run command:
cd contrib && go run ./examples/webhook. -
Request:
body='{"id":"evt_123","type":"payment.succeeded"}'
sig=$(printf '%s' "$body" | openssl dgst -sha256 -hmac 'demo-secret' -binary | xxd -p -c 256)
curl -s -X POST http://localhost:8080/webhooks/payment \
-H "X-Signature: $sig" \
-H 'Content-Type: application/json' \
-d "$body"- Expected response shape:
202JSON{"status":"accepted"}for a valid signature. - Production caveat: keep provider secrets in secret storage, reject unsigned or oversized payloads before JSON parsing, and add replay protection in application code.
Purpose: build a JSON event request with an HMAC signature that a webhooks receiver can verify.
-
Source type: Inline sketch with receiver code related to
contrib/examples/webhook. -
Prerequisites: share the demo secret only for local testing.
-
Example source:
contrib/examples/webhook. -
Handler sketch:
signer, _ := webhooks.NewHMACSHA256Signer(webhooks.HMACSignerConfig{Secret: []byte("demo-secret")})
req, err := webhooks.BuildSignedRequest(ctx, webhooks.OutgoingEvent[paymentEvent]{
ID: "evt_123",
Type: "payment.succeeded",
Payload: event,
}, webhooks.SignedRequestConfig{
URL: "https://api.example.com/webhooks/payment",
Signer: signer,
})- Expected request shape:
POSTJSON withX-Signature,X-Webhook-Event-ID, andX-Webhook-Timestampheaders. - Production caveat: delivery scheduling, retries, replay protection, and idempotency remain application-owned.
Purpose: cap request size and stream multipart upload metadata without buffering the full file in memory.
-
Source type: Runnable contrib example (
contrib/examples/file-upload). -
Prerequisites: a local file to upload.
-
Example source:
contrib/examples/file-upload. -
Run command:
cd contrib && go run ./examples/file-upload. -
Request:
printf 'hello\n' > /tmp/api-toolkit-upload.txt
curl -s -X POST http://localhost:8080/upload \
-F 'file=@/tmp/api-toolkit-upload.txt;type=text/plain'- Expected response shape:
200JSON withfilename,content_type, andbytes. - Production caveat: store uploads outside the request path when possible and scan untrusted content before later use.
Purpose: bound list queries and return field-level validation errors for invalid limits.
-
Source type: Runnable contrib example (
contrib/examples/pagination). -
Prerequisites: none.
-
Example source:
contrib/examples/pagination. -
Run command:
cd contrib && go run ./examples/pagination. -
Request:
curl -s 'http://localhost:8080/items?limit=3&offset=2'- Expected response shape:
200JSON withitemsand optionalnext_offset. - Production caveat: keep maximum limits low enough for the backing store and prefer checked parser APIs when validation errors matter.
Purpose: parse list endpoint query parameters for sorting, filtering, sparse fieldsets, and includes before applying application-owned query logic.
-
Source type: Inline sketch.
-
Prerequisites: define allowed sort and filter fields for the route.
-
Package:
github.com/aatuh/api-toolkit/v4/queryparams. -
Request:
curl -s 'http://localhost:8080/widgets?sort=name,-created_at&filter[status]=active&filter[created_at][gte]=2026-01-01&fields=id,name&include=owner'- Handler sketch:
sorts, err := queryparams.ParseSort(r.URL.Query(), queryparams.SortConfig{AllowedFields: []string{"name", "created_at"}})
if err != nil {
binding.WriteValidationProblem(w, err)
return
}
filters, err := queryparams.ParseFilters(r.URL.Query(), queryparams.FilterConfig{Fields: []queryparams.FilterField{
{Name: "status"},
{Name: "created_at", Operators: []queryparams.FilterOperator{queryparams.FilterOperatorGreaterThanOrEqual}},
}})
if err != nil {
binding.WriteValidationProblem(w, err)
return
}- Expected behavior: unknown sort fields, filter fields, or filter operators return field-level validation errors.
- Production caveat: parsed query parameters are transport contracts only; translate them to SQL, search, or service-layer requests in application code.
Purpose: return 202 Accepted for long-running work and expose a pollable operation resource.
-
Source type: Inline sketch.
-
Prerequisites: provide an application-owned operation store.
-
Package:
github.com/aatuh/api-toolkit/v4/operations. -
Handler sketch:
operations.WriteAccepted(w, operations.AcceptedConfig{
ID: "op_123",
Location: "/operations/op_123",
RetryAfter: 5 * time.Second,
})
router.Get("/operations/{id}", operations.PollHandler(operations.PollConfig[result]{
Store: store,
OperationID: func(r *http.Request) string {
return chi.URLParam(r, "id")
},
}).ServeHTTP)- Expected response shape: accepted writes return
202withLocation; poll responses return operation statepending,running,succeeded,failed, orcanceled. - Production caveat: workers, queues, persistence, cancellation, and retry policy remain application-owned.
Purpose: use ETags and Last-Modified validators for cache-friendly reads and optimistic concurrency on writes.
-
Source type: Inline sketch.
-
Prerequisites: compute a stable representation validator from your resource version or response body.
-
Package:
github.com/aatuh/api-toolkit/v4/httpcache. -
Handler sketch:
validators := httpcache.Validators{
ETag: httpcache.StrongETag(widget.Version),
LastModified: widget.UpdatedAt,
}
if decision := httpcache.EvaluateRead(r, validators); decision.NotModified {
httpcache.WriteNotModified(w, validators)
return
}
httpcache.SetValidators(w, validators)
httpx.WriteJSON(w, http.StatusOK, widget)- Expected response shape: matching
If-None-MatchorIf-Modified-Sincereturns304with validators and no body; failed write preconditions can return412Problem Details. - Production caveat: only use strong ETags for write preconditions when they represent the exact mutable resource version.
Purpose: return signed cursor metadata for stable forward pagination without exposing database offsets.
-
Source type: Inline sketch.
-
Prerequisites: provide an application secret for HMAC signing; rotate it through your normal secret-management process.
-
Example source: combine
github.com/aatuh/api-toolkit/v4/endpoints/listwith your list handler. -
Request:
curl -s 'http://localhost:8080/items?limit=25&cursor=<cursor>'- Handler sketch:
codec, _ := list.NewHMACCursorCodec([]byte("replace-with-secret"))
query, err := list.ParseCursorQueryChecked(r, list.CursorQueryConfig{
DefaultLimit: 25,
MaxLimit: 100,
Codec: codec,
})
if err != nil {
httpx.WriteProblem(w, httpx.Problem{
Type: "https://example.com/problems/invalid-query",
Title: "Invalid query",
Status: http.StatusBadRequest,
})
return
}
nextCursor, _ := codec.Encode(map[string]string{"after_id": "item_123"}, time.Now().Add(15*time.Minute))
httpx.WriteJSON(w, http.StatusOK, list.NewCursorResponse(items, list.CursorMeta{
Limit: query.Limit,
NextCursor: nextCursor,
}))- Expected response shape:
200JSON withitemsand cursormeta, includinglimitand optionalnext_cursor. - Production caveat: cursor values are signed but not encrypted; keep sensitive data out of cursor payloads.
Purpose: generate handler skeletons from OpenAPI and validate requests and responses.
-
Source type: Runnable contrib example (
contrib/examples/spec-first). -
Prerequisites: run generation from the example directory before changing generated code.
-
Example source:
contrib/examples/spec-first. -
Run command:
cd contrib/examples/spec-first && go generate ./... && go run .. -
Request:
curl -s -X POST http://localhost:8080/pets \
-H 'Content-Type: application/json' \
-d '{"name":"Milo"}'- Expected response shape:
201JSON pet withidandname; invalid names returnapplication/problem+jsonwith avalidation.fieldsentry. - Production caveat: treat
openapi.jsonas the source of truth and review generated-file changes with the implementation change that caused them.
Purpose: register reusable schemas, responses, and security schemes for generated route contracts.
-
Source type: Inline sketch.
-
Prerequisites: keep schema names stable once clients consume the generated OpenAPI document.
-
Package:
github.com/aatuh/api-toolkit/v4/specs. -
Registry sketch:
registry := specs.NewRegistry(specs.Info{Title: "Widget API", Version: "v1"})
registry.RegisterSchema("Widget", map[string]any{"type": "object"})
registry.RegisterSecurityScheme("ApiKeyAuth", specs.SecurityScheme{
Type: "apiKey",
Name: "X-API-Key",
In: "header",
})
registry.SetSecurity([]specs.SecurityRequirement{{Name: "ApiKeyAuth"}})
registry.RegisterResponse("Problem", specs.Response{
Description: "Problem Details",
Content: map[string]specs.MediaType{
"application/problem+json": {SchemaRef: "#/components/schemas/Problem"},
},
})- Expected response shape:
/openapi.jsonincludes deterministic top-levelsecurity,components.schemas,components.responses, andcomponents.securitySchemes. - Production caveat: route registration and runtime middleware still need to be kept in sync by tests or review.
Purpose: show SSRF host allowlisting, retry budget controls, circuit breaking, and bulkhead limits.
-
Source type: Runnable contrib example (
contrib/examples/outbound). -
Prerequisites: replace
api.example.comwith an allowed real upstream before expecting a successful request. -
Example source:
contrib/examples/outbound. -
Run command:
cd contrib && go run ./examples/outbound. -
Request example: the program issues a guarded
GET https://api.example.com/health. -
Expected response shape: no local HTTP endpoint; success means the outbound call returns and the response body closes.
-
Production caveat: do not broaden
AllowedHosts,AllowedPorts, or retryable methods without reviewing replay and SSRF risk.
Purpose: keep runtime middleware aligned with the route contract instead of repeating deprecation, negotiation, auth, and quota metadata in handlers.
-
Source type: Inline sketch.
-
Prerequisites: register routes through
routecontracts.NewRegistryWithOptions. -
Packages:
github.com/aatuh/api-toolkit/v4/routecontracts,github.com/aatuh/api-toolkit/v4/routepolicy, andgithub.com/aatuh/api-toolkit/v4/specs. -
Handler sketch:
policy := routepolicy.New(routepolicy.Config{
EnableDeprecation: true,
EnableNegotiation: true,
EmitPolicyExtension: true,
Auth: func(op specs.Operation) (func(http.Handler) http.Handler, error) {
return requireScopes(op.Scopes...), nil
},
})
contracts := routecontracts.NewRegistryWithOptions(router, specRegistry, routecontracts.Options{
Policies: []routecontracts.Policy{policy},
})
err := contracts.Get("/widgets", specs.Operation{
Summary: "List widgets",
Scopes: []string{"widgets:read"},
Deprecated: true,
Responses: map[int]specs.Response{
200: {Content: map[string]specs.MediaType{"application/json": {SchemaRef: "#/components/schemas/WidgetList"}}},
},
}, listWidgets)- Expected behavior: policy middleware is derived from the operation and route-specific middleware can still wrap it when needed.
- Production caveat: keep storage-backed auth, idempotency, and quota decisions in application-owned middleware factories;
routepolicyonly derives when to apply them.
Purpose: publish the same typed Problem Details catalog in runtime error handling and OpenAPI components.
-
Source type: Inline sketch.
-
Prerequisites: create or use an
httpx.ProblemCatalog. -
Packages:
github.com/aatuh/api-toolkit/v4/httpxandgithub.com/aatuh/api-toolkit/v4/specs. -
Registry sketch:
registry := specs.NewRegistry(specs.Info{Title: "Widget API", Version: "v1"})
specs.RegisterProblemCatalog(registry, httpx.DefaultProblemCatalog())
operation := specs.Operation{Responses: map[int]specs.Response{
400: specs.ValidationProblemResponse("Invalid request"),
500: specs.ProblemResponse("Internal error"),
}}- Expected behavior:
components.schemas.Problem,components.schemas.ValidationProblem, and catalog-backed response components appear deterministically only after explicit registration. - Production caveat: keep stable machine-readable problem codes in the catalog before exposing them in published client contracts.
Purpose: expose quota information to API clients without changing limiter storage or decision ownership.
-
Source type: Inline sketch.
-
Prerequisites: use
middleware/ratelimit. -
Package:
github.com/aatuh/api-toolkit/v4/middleware/ratelimit. -
Middleware sketch:
limiter, err := ratelimit.New(ratelimit.Options{
Capacity: 100,
RefillRate: 10,
HeaderConfig: ratelimit.DefaultHeaderConfig(),
})
if err != nil {
return err
}
handler := limiter.Handler(next)- Expected behavior: allowed and denied responses include standard quota headers when header emission is enabled; existing limiter behavior is unchanged when
HeaderConfigis left empty. - Production caveat: distributed quota accounting still belongs in an app-owned or adapter-owned limiter.
Purpose: standardize idempotency-key validation, request hashing, conflict responses, and replayed 202 Accepted responses.
-
Source type: Inline sketch.
-
Prerequisites: use application-owned storage or
middleware/idempotencyfor reservation/replay ownership. -
Package:
github.com/aatuh/api-toolkit/v4/idempotent. -
Handler sketch:
key, err := idempotent.RequireKey(r, "")
if err != nil {
httpx.WriteProblem(w, http.StatusBadRequest, httpx.Problem{Title: "Missing idempotency key", Detail: err.Error()})
return
}
hash := idempotent.RequestHash(r, body)
if conflictsExistingRequest(key, hash) {
idempotent.WriteConflict(w, "idempotency key was reused with a different request")
return
}
idempotent.WriteAcceptedReplay(w, idempotent.AsyncConfig{ID: "op_123", Location: "/operations/op_123", RetryAfter: 2 * time.Second})- Expected behavior: clients can distinguish new accepts, replayed accepts, and conflicting request reuse with consistent headers and Problem Details.
- Production caveat: persistence, key TTLs, and response replay storage remain application or middleware concerns.
Purpose: require event ids, reject timestamps outside a replay window, and document outbound delivery attempts/results.
-
Source type: Inline sketch.
-
Prerequisites: configure a webhook verifier and timestamp-producing sender.
-
Package:
github.com/aatuh/api-toolkit/v4/webhooks. -
Receiver sketch:
receiver := webhooks.Receiver[myEvent]{Config: webhooks.ReceiverConfig[myEvent]{
Verifier: verifier,
Replay: webhooks.ReplayConfig{
Tolerance: 5 * time.Minute,
RequireEventID: true,
},
Handle: handleEvent,
}}- Expected behavior: missing event ids, missing timestamps, invalid timestamps,
verifier failures, and timestamps outside the configured skew window return
Problem Details before the event is handled. Verifier failures use a generic
client-facing detail unless
VerificationErrorDetailsupplies safe text. - Production caveat: replay databases, duplicate suppression, delivery queues, and retry persistence remain outside core.
Purpose: decode multipart forms with required files, size limits, content-type allowlists, and Problem Details field errors.
-
Source type: Inline sketch.
-
Prerequisites: clients send
multipart/form-data. -
Package:
github.com/aatuh/api-toolkit/v4/upload. -
Handler sketch:
form, err := upload.DecodeMultipart(r, upload.Config{
MaxRequestBytes: 20 << 20,
MaxFileBytes: upload.MaxFileBytes(5 << 20),
RequiredFiles: []string{"avatar"},
AllowedContentTypes: upload.AllowedContentTypes("image/png", "image/jpeg"),
})
if err != nil {
upload.WriteValidationProblem(w, err)
return
}
file, err := upload.RequireFile(form, "avatar")
if err != nil {
upload.WriteValidationProblem(w, err)
return
}- Expected behavior: missing, oversized, malformed, or disallowed files return
application/problem+jsonwith validation field errors. - Production caveat: virus scanning, object storage, image transcoding, and persistence stay in application code.
Purpose: keep provider-specific token validation outside core while standardizing claims, scope checks, and OpenAPI security schemes.
-
Source type: Inline sketch.
-
Prerequisites: provide an application-owned
oauth2.Validatorimplementation. -
Package:
github.com/aatuh/api-toolkit/contrib/v4/oauth2. -
Handler sketch:
token, ok := oauth2.BearerToken(r)
if !ok {
httpx.WriteProblem(w, http.StatusUnauthorized, httpx.Problem{Title: "Unauthorized"})
return
}
claims, err := validator.ValidateToken(r.Context(), token)
if err != nil || oauth2.RequireScopes(claims, "widgets:write") != nil {
httpx.WriteProblem(w, http.StatusForbidden, httpx.Problem{Title: "Forbidden"})
return
}
ctx := authorization.WithActor(r.Context(), claims.Actor())
ctx = authorization.WithScope(ctx, claims.AuthorizationScope())- Expected behavior: OAuth2 scope enforcement and route contract security metadata can share the same provider-neutral scope strings.
- Production caveat: JWKS fetching, provider quirks, token caching, and issuer-specific validation remain application or adapter concerns.
Purpose: make application tests assert API contracts without reimplementing Problem Details, pagination, and OpenAPI golden checks.
-
Source type: Inline test sketch.
-
Prerequisites: use
httptest.ResponseRecorder. -
Package:
github.com/aatuh/api-toolkit/v4/apitest. -
Test sketch:
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
apitest.AssertProblem(t, recorder, http.StatusBadRequest)
apitest.AssertValidationFields(t, recorder, "name")
apitest.AssertRateLimitHeaders(t, recorder)
apitest.AssertOpenAPIGolden(t, generated, golden)- Expected behavior: failed assertions report the mismatched HTTP contract directly in the test failure.
- Production caveat: these helpers are for tests only; runtime code should use
httpx,binding,upload,operations, and the middleware packages directly.
Purpose: consume api-toolkit-shaped APIs without generating a service-specific SDK.
-
Source type: Inline client sketch.
-
Prerequisites: use
net/httpclients. -
Package:
github.com/aatuh/api-toolkit/v4/apiclient. -
Client sketch:
client := &http.Client{Transport: apiclient.APIKeyTransport{Key: apiKey}}
result, resp, err := apiclient.DoJSON[widgetResponse](ctx, client, http.MethodPost, endpoint, createWidgetRequest{Name: "starter"})
if problem, ok := err.(*apiclient.ProblemError); ok {
log.Printf("api problem: %s", problem.Problem.Title)
}
if retryAfter, ok := apiclient.RetryAfter(resp.Header); ok {
_ = retryAfter
}- Expected behavior: clients can decode Problem Details, set precondition headers, sign webhook requests, add API keys, and iterate cursor pages with small composable helpers.
- Production caveat: retries, persistence, service-specific resources, and generated SDKs remain outside core.