{"id":51152754,"url":"https://github.com/mrz1836/go-actions","last_synced_at":"2026-06-26T07:30:30.428Z","repository":{"id":367198458,"uuid":"1279548903","full_name":"mrz1836/go-actions","owner":"mrz1836","description":"🎬 Typed HTTP action framework with OpenAPI 3.1 generation for chi","archived":false,"fork":false,"pushed_at":"2026-06-25T02:09:57.000Z","size":1099,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-25T02:10:54.236Z","etag":null,"topics":["actions","chi","go","golang","http","router"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrz1836.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":".github/SECURITY.md","support":".github/SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":".github/AGENTS.md","dco":null,"cla":null},"funding":{"github":"mrz1836","custom":"https://mrz1818.com/?tab=tips\u0026utm_source=github\u0026utm_medium=sponsor-link\u0026utm_campaign=go-actions\u0026utm_term=go-actions\u0026utm_content=go-actions","buy_me_a_coffee":"mrz1818"}},"created_at":"2026-06-24T19:44:54.000Z","updated_at":"2026-06-25T02:05:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mrz1836/go-actions","commit_stats":null,"previous_names":["mrz1836/go-actions"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mrz1836/go-actions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrz1836%2Fgo-actions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrz1836%2Fgo-actions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrz1836%2Fgo-actions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrz1836%2Fgo-actions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrz1836","download_url":"https://codeload.github.com/mrz1836/go-actions/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrz1836%2Fgo-actions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34808043,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actions","chi","go","golang","http","router"],"created_at":"2026-06-26T07:30:29.859Z","updated_at":"2026-06-26T07:30:30.414Z","avatar_url":"https://github.com/mrz1836.png","language":"Go","funding_links":["https://github.com/sponsors/mrz1836","https://mrz1818.com/?tab=tips\u0026utm_source=github\u0026utm_medium=sponsor-link\u0026utm_campaign=go-actions\u0026utm_term=go-actions\u0026utm_content=go-actions","https://buymeacoffee.com/mrz1818"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 🎬\u0026nbsp;\u0026nbsp;go-actions\n\n**Typed HTTP actions for Go — one declaration, a handler and an OpenAPI 3.1 contract that can't drift**\n\n\u003cbr/\u003e\n\n\u003ca href=\"https://github.com/mrz1836/go-actions/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/release-pre/mrz1836/go-actions?include_prereleases\u0026style=flat-square\u0026logo=github\u0026color=black\" alt=\"Release\"\u003e\u003c/a\u003e\n\u003ca href=\"https://golang.org/\"\u003e\u003cimg src=\"https://img.shields.io/github/go-mod/go-version/mrz1836/go-actions?style=flat-square\u0026logo=go\u0026color=00ADD8\" alt=\"Go Version\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/mrz1836/go-actions/blob/master/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/mrz1836/go-actions?style=flat-square\u0026color=blue\" alt=\"License\"\u003e\u003c/a\u003e\n\n\u003cbr/\u003e\n\n\u003ctable align=\"center\" border=\"0\"\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"right\"\u003e\n       \u003ccode\u003eCI / CD\u003c/code\u003e \u0026nbsp;\u0026nbsp;\n    \u003c/td\u003e\n    \u003ctd align=\"left\"\u003e\n       \u003ca href=\"https://github.com/mrz1836/go-actions/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/mrz1836/go-actions/fortress.yml?branch=master\u0026label=build\u0026logo=github\u0026style=flat-square\" alt=\"Build\"\u003e\u003c/a\u003e\n       \u003ca href=\"https://github.com/mrz1836/go-actions/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/last-commit/mrz1836/go-actions?style=flat-square\u0026logo=git\u0026logoColor=white\u0026label=last%20update\" alt=\"Last Commit\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"right\"\u003e\n       \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp; \u003ccode\u003eQuality\u003c/code\u003e \u0026nbsp;\u0026nbsp;\n    \u003c/td\u003e\n    \u003ctd align=\"left\"\u003e\n       \u003ca href=\"https://goreportcard.com/report/github.com/mrz1836/go-actions\"\u003e\u003cimg src=\"https://goreportcard.com/badge/github.com/mrz1836/go-actions?style=flat-square\" alt=\"Go Report\"\u003e\u003c/a\u003e\n       \u003ca href=\"https://codecov.io/gh/mrz1836/go-actions\"\u003e\u003cimg src=\"https://codecov.io/gh/mrz1836/go-actions/branch/master/graph/badge.svg?style=flat-square\" alt=\"Coverage\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\n  \u003ctr\u003e\n    \u003ctd align=\"right\"\u003e\n       \u003ccode\u003eSecurity\u003c/code\u003e \u0026nbsp;\u0026nbsp;\n    \u003c/td\u003e\n    \u003ctd align=\"left\"\u003e\n       \u003ca href=\"https://scorecard.dev/viewer/?uri=github.com/mrz1836/go-actions\"\u003e\u003cimg src=\"https://api.scorecard.dev/projects/github.com/mrz1836/go-actions/badge?style=flat-square\" alt=\"Scorecard\"\u003e\u003c/a\u003e\n       \u003ca href=\".github/SECURITY.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/policy-active-success?style=flat-square\u0026logo=security\u0026logoColor=white\" alt=\"Security\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"right\"\u003e\n       \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp; \u003ccode\u003eDocs\u003c/code\u003e \u0026nbsp;\u0026nbsp;\n    \u003c/td\u003e\n    \u003ctd align=\"left\"\u003e\n       \u003ca href=\"https://pkg.go.dev/github.com/mrz1836/go-actions\"\u003e\u003cimg src=\"https://img.shields.io/badge/godoc-reference-blue?style=flat-square\u0026logo=go\u0026logoColor=white\" alt=\"Go Reference\"\u003e\u003c/a\u003e\n       \u003ca href=\"https://mrz1818.com/\"\u003e\u003cimg src=\"https://img.shields.io/badge/donate-bitcoin-ff9900?style=flat-square\u0026logo=bitcoin\" alt=\"Bitcoin\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c/div\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n### \u003ccode\u003eProject Navigation\u003c/code\u003e\n\n\u003c/div\u003e\n\n\u003ctable align=\"center\"\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\" width=\"33%\"\u003e\n       📦\u0026nbsp;\u003ca href=\"#-installation\"\u003e\u003ccode\u003eInstallation\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\" width=\"33%\"\u003e\n       ⚡\u0026nbsp;\u003ca href=\"#-quick-start\"\u003e\u003ccode\u003eQuick\u0026nbsp;Start\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\" width=\"33%\"\u003e\n       🧪\u0026nbsp;\u003ca href=\"#-examples--tests\"\u003e\u003ccode\u003eExamples\u0026nbsp;\u0026\u0026nbsp;Tests\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\n       📚\u0026nbsp;\u003ca href=\"#-documentation\"\u003e\u003ccode\u003eDocumentation\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\n      🛠️\u0026nbsp;\u003ca href=\"#-code-standards\"\u003e\u003ccode\u003eCode\u0026nbsp;Standards\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\n      📊\u0026nbsp;\u003ca href=\"#-benchmarks\"\u003e\u003ccode\u003eBenchmarks\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\n      🤖\u0026nbsp;\u003ca href=\"#-ai-usage--assistant-guidelines\"\u003e\u003ccode\u003eAI\u0026nbsp;Usage\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\n       ⚖️\u0026nbsp;\u003ca href=\"#-license\"\u003e\u003ccode\u003eLicense\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\n       👥\u0026nbsp;\u003ca href=\"#-maintainers\"\u003e\u003ccode\u003eMaintainers\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\u003cbr/\u003e\n\n## 🧩 About\n\n**go-actions** is a typed HTTP action framework with **OpenAPI 3.1 generation** for\n[chi](https://github.com/go-chi/chi). You declare a route **once** as a typed\n`Action[Req, Resp]`, and a `Registry` turns each declaration — all from the **same\nreflected types** — into:\n\n- an `http.HandlerFunc` (decode → validate → handle → encode),\n- a JSON Schema 2020-12,\n- an OpenAPI 3.1 document (JSON **and** YAML), and\n- a browsable HTML/Markdown action index.\n\nBecause every artifact derives from the same types, the **published contract cannot\ndrift from runtime behavior**. `Freeze()` enforces invariants at startup — unique IDs,\nunique method+path, and documented statuses — so a misconfigured route fails on boot,\nnot in production.\n\n- **One declaration, many artifacts** — handler, schema, OpenAPI, and docs all generated from one typed struct.\n- **Production-safe by default** — panic recovery, a 1 MiB request-body cap, request-id propagation, and JSON `404`/`405` responses are on out of the box, every one overridable.\n- **Composable middleware** — registry-wide (`WithMiddleware`) and per-action (`Action.Middleware`) using the standard chi/`net-http` signature, for auth, CORS, logging, and rate limiting.\n- **Observability hook** — one `WithObserver` callback receives each request's action id, status, latency, and error — the seam for access logs, metrics, and tracing.\n- **Documented auth** — declare OpenAPI `securitySchemes` and per-operation `security` (with `BearerAuth`/`APIKeyAuth` helpers) so the contract states how to authenticate.\n- **Pluggable error mapping** — decouple your domain errors from the wire shape with an `ErrorMapper`.\n- **Self-documenting** — serve `/openapi.json`, `/openapi.yaml`, and a browsable `/_actions` index straight from the registry.\n- **Struct-tag validation, with an escape hatch** — `required`, `min`, `max`, `oneof`, `uuid`, `email`, `e164`, `rfc3339` (the same tags feed the JSON Schema), plus a `Validatable` interface for rules a tag can't express.\n- **No domain coupling** — the core imports only the standard library, `go-chi/chi/v5`, `google/uuid`, and `gopkg.in/yaml.v3`.\n\n\u003e Why it matters: in 2026, your API contract is consumed by SDK generators, API\n\u003e gateways, and AI agents calling tools. A contract generated from the code that\n\u003e actually serves traffic is one you never have to hand-reconcile.\n\n\u003cbr/\u003e\n\n## 📦 Installation\n\n**go-actions** requires a [supported release of Go](https://golang.org/doc/devel/release.html#policy).\n```shell script\ngo get -u github.com/mrz1836/go-actions\n```\n\nGet the [MAGE-X](https://github.com/mrz1836/mage-x) build tool for development:\n```shell script\ngo install github.com/mrz1836/mage-x/cmd/magex@latest\n```\n\n\u003cbr/\u003e\n\n## ⚡ Quick Start\n\n### 1. Declare an action\n\nAn `Action[Req, Resp]` declares one route. Request fields bind from the JSON body or\nfrom `path`/`query`/`header` tags; `validate` tags are enforced before your handler runs.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"github.com/mrz1836/go-actions\"\n)\n\ntype createUserReq struct {\n\tName  string `json:\"name\"  validate:\"required,min=1,max=64\"`\n\tEmail string `json:\"email\" validate:\"required,email\"`\n}\n\ntype user struct {\n\tID    string `json:\"id\"`\n\tName  string `json:\"name\"`\n\tEmail string `json:\"email\"`\n}\n\nfunc createUser() actions.Action[createUserReq, actions.Created[user]] {\n\treturn actions.Action[createUserReq, actions.Created[user]]{\n\t\tID:      \"users.create\",\n\t\tMethod:  http.MethodPost,\n\t\tPath:    \"/users\",\n\t\tSummary: \"Create a user\",\n\t\tTags:    []string{\"users\"},\n\t\tStatuses: []actions.StatusDoc{\n\t\t\t{Code: http.StatusCreated, Description: \"the created user\"},\n\t\t\t{Code: http.StatusUnprocessableEntity, Description: \"invalid body\", Error: true},\n\t\t},\n\t\tHandle: func(_ context.Context, req createUserReq) (actions.Created[user], error) {\n\t\t\tu := user{ID: \"u_1\", Name: req.Name, Email: req.Email}\n\t\t\treturn actions.Created[user]{Body: u}, nil\n\t\t},\n\t}\n}\n```\n\nResponse envelopes set the documented status: `actions.Empty` → 204,\n`actions.Created[T]` → 201, `actions.Accepted[T]` → 202. Returning any other value\nencodes as 200. For full control, return `actions.Response[T]` to set a custom status\nand response headers (e.g. `Cache-Control`/`ETag`), `actions.Page[T]` for a\nconventional cursor-paginated list body, or `actions.List[T]` for a non-paginated\ncollection — `{\"items\": [...], \"meta\": {\"total\": N}}`. Build the latter with\n`actions.NewList(items)`, which sets the total and normalizes a nil slice to `[]`:\n\n```go\nHandle: func(ctx context.Context, _ listReq) (actions.List[user], error) {\n    return actions.NewList(store.all()), nil // 200 {\"items\":[...],\"meta\":{\"total\":N}}\n}\n```\n\nPath, query, and header parameters bind by struct tag (`path:`, `query:`,\n`header:`). Scalars convert automatically — `string`, `bool`, the integer and\nfloat kinds, and `time.Time` (parsed as RFC3339 or a bare `2006-01-02` date; a\nmalformed value yields a 422). Any of these may be a pointer, so an absent\noptional parameter stays `nil` rather than a zero value:\n\n```go\ntype listReq struct {\n    Active *bool      `json:\"-\" query:\"active\"` // nil when ?active= is absent\n    Since  *time.Time `json:\"-\" query:\"since\"`  // RFC3339; 422 if malformed\n    Limit  int        `json:\"-\" query:\"limit\" validate:\"max=100\"`\n}\n```\n\n### 2. Register, freeze, and mount\n\n```go\nfunc main() {\n\treg := actions.NewRegistry(actions.WithInfo(\n\t\t\"Users API\",\n\t\t\"Manage users.\",\n\t\t\"1.0.0\",\n\t))\n\n\tactions.Register(reg, createUser())\n\treg.Freeze() // validates declarations and builds the contract artifacts\n\n\thttp.Handle(\"/\", reg.Handler())\n\t_ = http.ListenAndServe(\":8080\", nil)\n}\n```\n\n`Register` is the only typed seam — after `Freeze()` the registry is sealed and further\n`Register` calls panic. `Handler()` returns an `http.Handler` mounting every action plus\nthe self-documentation endpoints.\n\n### 3. Serve the contract\n\n`Handler()` mounts three self-documenting endpoints automatically:\n\n| Endpoint         | Returns                                              |\n| ---------------- | --------------------------------------------------- |\n| `/openapi.json`  | the OpenAPI 3.1 document as JSON                     |\n| `/openapi.yaml`  | the same document as YAML                            |\n| `/_actions`      | a browsable HTML index (Markdown via `Accept`)      |\n\nThe `/_actions` index ships in core and is always on — no build tag required. You can\nalso reach the raw bytes directly with `reg.OpenAPIJSON()` and `reg.OpenAPIYAML()` (for\nexample, to write a committed snapshot).\n\n\u003cbr/\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003ePluggable error handling\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nHandlers return ordinary Go errors. An `ErrorMapper` translates them into the\ntransport-level `APIError`, decoupling the framework from your domain error model:\n\n```go\ntype APIError struct {\n\tStatus  int\n\tCode    string\n\tMessage string\n\tFields  []FieldError\n}\n\ntype ErrorMapper func(error) APIError\n```\n\nInstall one with `WithErrorMapper`:\n\n```go\nreg := actions.NewRegistry(actions.WithErrorMapper(func(err error) actions.APIError {\n\tif errors.Is(err, ErrNotFound) {\n\t\treturn actions.APIError{\n\t\t\tStatus:  http.StatusNotFound,\n\t\t\tCode:    actions.CodeNotFound,\n\t\t\tMessage: \"resource not found\",\n\t\t}\n\t}\n\treturn actions.APIError{\n\t\tStatus:  http.StatusInternalServerError,\n\t\tCode:    actions.CodeInternal,\n\t\tMessage: \"an internal error occurred\",\n\t}\n}))\n```\n\nThe default mapper passes an `*APIError` through unchanged (so handlers may return one\ndirectly) and maps every other error to a redacted 500, ensuring internal detail never\nreaches the wire. The error envelope is always\n`{\"error\": ..., \"code\": ..., \"request_id\": ...}`.\n\n**foundationx adapter** — the optional `foundationx` sub-package provides a ready-made\n`ErrorMapper` that wires the [`go-foundation`](https://github.com/mrz1836/go-foundation)\nerror model (`*ValidationError` → 422, `ErrNotFound` → 404). It lives in its own package\nso consumers that do not use `go-foundation` never pull it into their module graph:\n\n```go\nimport \"github.com/mrz1836/go-actions/foundationx\"\n\nreg := actions.NewRegistry(actions.WithErrorMapper(foundationx.NewErrorMapper()))\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eRegistry options\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\n| Option                          | Effect                                                              |\n| ------------------------------- | ------------------------------------------------------------------- |\n| `WithInfo(title, desc, version)`| Sets the OpenAPI `info` block; the title also names the `_actions` index. |\n| `WithErrorMapper(mapper)`       | Installs a custom error mapper (replaces the default generic one).  |\n| `WithStripPrefix(prefix)`       | Strips a namespace prefix from each action's `Path` when routing (e.g. when the registry is mounted under that prefix). |\n| `WithMiddleware(mw...)`         | Registry-wide middleware applied to every route (actions, self-docs, `404`/`405`). |\n| `WithMaxBodyBytes(n)`           | Caps the request body (default 1 MiB; `0` disables). Over-limit ⇒ `413`. |\n| `WithObserver(fn)`              | Per-request hook with action id, status, latency, and error.       |\n| `WithRequestIDGenerator(fn)`    | Overrides how a correlation id is minted when none is inbound (default UUIDv4). |\n| `WithNotFoundHandler(h)` / `WithMethodNotAllowedHandler(h)` | Override the JSON `404` / `405` defaults. |\n| `WithSecurityScheme(name, scheme)` | Declares an OpenAPI security scheme (`BearerAuth`/`APIKeyAuth` helpers). |\n| `WithSecurity(reqs...)`         | Sets registry-wide default security requirements.                  |\n| `WithServers(servers...)`       | Sets the OpenAPI `servers[]` block.                                 |\n| `WithOpenAPIVersion(v)`         | Declares the dialect: `\"3.1.0\"` (default) or `\"3.0.3\"`.            |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eProduction middleware \u0026 safety\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nEvery registry ships a built-in middleware chain wrapping the typed pipeline — all on by\ndefault and overridable:\n\n- **Panic recovery** — a panicking handler becomes a logged, redacted `500` (and a non-nil\n  `Observation.Err`); the connection is never torn down.\n- **Request-body cap** — bodies over `WithMaxBodyBytes` (default 1 MiB) are rejected with\n  `413`. Pass `0` to disable.\n- **Request-id propagation** — an inbound `X-Request-ID` / `X-Amzn-Request-Id` is reused,\n  otherwise one is generated; it is placed in the context (`actions.RequestIDFromContext`),\n  echoed on the response, and included in every error envelope.\n- **JSON `404` / `405`** — unmatched routes and wrong methods return the standard error\n  envelope, not chi's plain text.\n\nPer-action knobs live on the `Action` struct:\n\n```go\nactions.Action[Req, Resp]{\n    // ...\n    Middleware: []actions.Middleware{authOnly}, // wraps just this route\n    Timeout:    2 * time.Second,                // ctx deadline → 504 on overrun\n    Security:   []actions.SecurityRequirement{{\"BearerAuth\": {\"admin\"}}},\n    Deprecated: true,                           // marked deprecated in OpenAPI\n}\n```\n\nWire observability with one hook:\n\n```go\nreg := actions.NewRegistry(actions.WithObserver(func(o actions.Observation) {\n    slog.Info(\"request\",\n        \"action\", o.ActionID, \"status\", o.Status, \"request_id\", o.RequestID,\n        \"ms\", o.Duration.Milliseconds(), \"err\", o.Err)\n}))\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eAuth \u0026 OpenAPI security\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nDeclare security schemes once; reference them registry-wide or per action. They surface in\nthe generated contract under `components.securitySchemes` and `security`:\n\n```go\nreg := actions.NewRegistry(\n    actions.WithSecurityScheme(\"BearerAuth\", actions.BearerAuth(\"JWT\")),\n    actions.WithSecurityScheme(\"ApiKeyAuth\", actions.APIKeyAuth(\"header\", \"X-API-Key\")),\n    actions.WithSecurity(actions.SecurityRequirement{\"ApiKeyAuth\": nil}), // default for all ops\n    actions.WithServers(actions.Server{URL: \"https://api.example.com\", Description: \"production\"}),\n)\n```\n\ngo-actions does not authenticate requests itself — wire your auth as `WithMiddleware` or a\nper-action `Middleware`; the security blocks document *how* clients authenticate so SDK\ngenerators and gateways configure it correctly.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eValidation tags\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nThe `validate` struct tag drives **both** request validation and the generated JSON\nSchema constraints. Supported rules:\n\n`required`, `min=N`, `max=N`, `oneof=a b c`, `uuid`, `email`, `e164`, `rfc3339`.\n\nFor strings and slices, `min`/`max` bound the length; for numbers they bound the value.\nFormat rules (`uuid`, `email`, etc.) are skipped on an empty value — use `required` to\nreject emptiness.\n\nFor rules a tag can't express, a request type may implement `Validatable`\n(`Validate() error`); it runs after the tag rules and its field details merge into the\n`422` response.\n\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n## 📚 Documentation\n\n- **API Reference** – Dive into the godocs at [pkg.go.dev/github.com/mrz1836/go-actions](https://pkg.go.dev/github.com/mrz1836/go-actions)\n- **Benchmarks** – Check the latest numbers in the [benchmarks](#-benchmarks) section\n- **Test Suite** – Review the [unit tests](action_test.go) (powered by [`testify`](https://github.com/stretchr/testify))\n- **Examples** – Browse the runnable pet-store API in [`examples/`](examples)\n\n\u003cbr/\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eRepository Features\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nThis repository includes 25+ built-in features covering CI/CD, security, code quality, developer experience, and community tooling.\n\n**[View the full Repository Features list →](.github/docs/repository-features.md)**\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eLibrary Deployment\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nThis project uses [goreleaser](https://github.com/goreleaser/goreleaser) for streamlined binary and library deployment to GitHub. To get started, install it via:\n\n```bash\nbrew install goreleaser\n```\n\nThe release process is defined in the [.goreleaser.yml](.goreleaser.yml) configuration file.\n\n\nThen create and push a new Git tag using:\n\n```bash\nmagex version:bump push=true bump=patch branch=master\n```\n\nThis process ensures consistent, repeatable releases with properly versioned artifacts and metadata.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003ePre-commit Hooks\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nSet up the Go-Pre-commit System to run the same formatting, linting, and tests defined in [AGENTS.md](.github/AGENTS.md) before every commit:\n\n```bash\ngo install github.com/mrz1836/go-pre-commit/cmd/go-pre-commit@latest\ngo-pre-commit install\n```\n\nThe system is configured via modular env files in [`.github/env/`](.github/env/README.md) and provides 17x faster execution than traditional Python-based pre-commit hooks. See the [complete documentation](http://github.com/mrz1836/go-pre-commit) for details.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eGitHub Workflows\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nAll workflows are driven by modular configuration in [`.github/env/`](.github/env/README.md) — no YAML editing required.\n\n**[View all workflows and the control center →](.github/docs/workflows.md)**\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eUpdating Dependencies\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nTo update all dependencies (Go modules, linters, and related tools), run:\n\n```bash\nmagex deps:update\n```\n\nThis command ensures all dependencies are brought up to date in a single step, including Go modules and any tools managed by [MAGE-X](https://github.com/mrz1836/mage-x). It is the recommended way to keep your development environment and CI in sync with the latest versions.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eBuild Commands\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nView all build commands\n\n```bash script\nmagex help\n```\n\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n## 🧪 Examples \u0026 Tests\n\nAll unit tests run via [GitHub Actions](https://github.com/mrz1836/go-actions/actions) and use [Go version 1.25.x](https://go.dev/doc/go1.25). View the [configuration file](.github/workflows/fortress.yml).\n\nThe [`examples/`](examples) directory contains a runnable pet-store API:\n\n- [`examples/main.go`](examples/main.go) — a server declaring actions, mounting `Handler()`, and serving `/openapi.json`, `/openapi.yaml`, and `/_actions`.\n- [`examples/openapi-snapshot`](examples/openapi-snapshot) — a contract-drift guard that writes or `check`s a committed OpenAPI snapshot (wire `check` into CI to fail the build when the generated contract drifts).\n- [`examples/petstore`](examples/petstore) — the shared registry both examples use.\n\nRun the example server:\n\n```bash script\ncd examples \u0026\u0026 go run .\n# then browse http://localhost:8080/_actions\n```\n\nThe `actiontest` helper exercises actions through the real pipeline or directly:\n\n```go\nimport \"github.com/mrz1836/go-actions/actiontest\"\n\n// Spin up a test server running the full decode/validate/encode pipeline:\nsrv := actiontest.NewServer(t, reg)\n\n// Or invoke a handler directly, bypassing decode/validate/encode:\nresp, err := actiontest.Invoke(t, createUser(), createUserReq{Name: \"Ada\", Email: \"ada@example.com\"})\n```\n\nRun all tests (fast):\n\n```bash script\nmagex test\n```\n\nRun all tests with race detector (slower):\n```bash script\nmagex test:race\n```\n\n\u003cbr/\u003e\n\n## 📊 Benchmarks\n\nRun the Go benchmarks:\n\n```bash script\nmagex bench\n```\n\n\u003e Benchmarks cover the hot path — request decoding, validation, and response encoding through the typed pipeline — plus the one-time OpenAPI contract generation.\n\n\u003cbr/\u003e\n\n## 🛠️ Code Standards\nRead more about this Go project's [code standards](.github/CODE_STANDARDS.md).\n\n\u003cbr/\u003e\n\n## 🤖 AI Usage \u0026 Assistant Guidelines\nRead the [AI Usage \u0026 Assistant Guidelines](.github/tech-conventions/ai-compliance.md) for details on how AI is used in this project and how to interact with the AI assistants.\n\n\u003cbr/\u003e\n\n## 👥 Maintainers\n| [\u003cimg src=\"https://github.com/mrz1836.png\" height=\"50\" width=\"50\" alt=\"MrZ\" /\u003e](https://github.com/mrz1836) |\n|:-----------------------------------------------------------------------------------------------------------:|\n|                                      [MrZ](https://github.com/mrz1836)                                      |\n\n\u003cbr/\u003e\n\n## 🤝 Contributing\nView the [contributing guidelines](.github/CONTRIBUTING.md) and please follow the [code of conduct](.github/CODE_OF_CONDUCT.md).\n\n### How can I help?\nAll kinds of contributions are welcome :raised_hands:!\nThe most basic way to show your support is to star :star2: the project, or to raise issues :speech_balloon:.\nYou can also support this project by [becoming a sponsor on GitHub](https://github.com/sponsors/mrz1836) :clap:\nor by making a [**bitcoin donation**](https://mrz1818.com/?tab=tips\u0026utm_source=github\u0026utm_medium=sponsor-link\u0026utm_campaign=go-actions\u0026utm_term=go-actions\u0026utm_content=go-actions) to ensure this journey continues indefinitely! :rocket:\n\n[![Stars](https://img.shields.io/github/stars/mrz1836/go-actions?label=Please%20like%20us\u0026style=social\u0026v=1)](https://github.com/mrz1836/go-actions/stargazers)\n\n\u003cbr/\u003e\n\n## 📝 License\n\n[![License](https://img.shields.io/github/license/mrz1836/go-actions.svg?style=flat\u0026v=1)](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrz1836%2Fgo-actions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrz1836%2Fgo-actions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrz1836%2Fgo-actions/lists"}