mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
App, Group, Operation. A group is a prefix and what holds under it, an operation
is one typed route declared there, and the verb methods are generic methods —
which Go 1.27 allows on a concrete type and still refuses on an interface. So In
and Out come from the handler and the caller writes no type arguments:
users := app.Group("/v1/users")
users.Post("/", api.Create)
mint := users.Group("/mint").With(middleware.Mint(gate))
mint.Post("/deposit", api.Deposit)
A Group is a CLOSED HANDLE: two unexported fields, and no method that hands back
the app, the parent or the fiber underneath. A subsystem given the group at its
own prefix cannot reach above it, so confinement is a property of the type
instead of a rule something has to police — which is what a host needs in order
to hand out a bounded surface and know what comes back.
That is bought by giving up the router interface, deliberately. Router and
OpTarget are gone, and with them the ability to implement a route table outside
this package: a decorator is no longer a router, a gate is middleware on a group,
and a bound is a path. The five package-level verb functions go the same way —
a registration is a method on the thing it registers into.
Raw carries what is not a typed operation: a stream, an upgrade, a probe, a
foreign subtree. It names its method, and it is the longer spelling because
opting out of the typed model is the exception and should read like one.
Co-authored-by: Hanzo Dev <[email protected]>
5.0 KiB
5.0 KiB
HANZO_CHANGES.md — diff from upstream zeekay/zip
zeekay/zip was a tiny net/http wrapper (~250 LOC) experimenting with
Sinatra-style routing in Go circa 2014. Hanzo adopted the name and
rebuilt the framework from scratch on Fiber v3 / fasthttp for the
canonical Hanzo Go web framework.
What's gone
Every file from upstream has been deleted. We kept the name.
handlers.go— old net/http handler registry. Replaced byzip.App.Get/Post/...plus internalrouterAdapterover fiber.Router.request.go,response.go— oldReq/Restypes. Replaced by*zip.Ctxwrappingfiber.Ctx.websocket.go— old gorilla/websocket adapter. Replaced by packagewsxoverfasthttp/websocket.examples/{hello,json,websocket}— old examples. Replaced by 9 new examples covering Sinatra-style, typed handlers, OpenAPI, module routes, websocket, SSE, and three migration paths.- Module path:
zeekay.io/zip→github.com/hanzoai/zip.
What's new
| Area | Implementation |
|---|---|
| Core | *fiber.App under the hood; zip.App is the public type. |
| Ctx | fiber.Ctx interface wrapped + Org/User/UserEmail/IsAdmin/RequestID/Host accessors. |
| Typed handlers | Generic zip.Get[In, Out](on, path, fn) etc. with reflection-built OpenAPI 3.1. on is the App, any Group of it, or app.With(mw…). |
| Validation | ~120 LOC reflection: required, min/max, minlen/maxlen struct tags. |
| OpenAPI | Auto-generated at /.well-known/openapi.json; Swagger UI at /docs. |
| Extension routes | app.Module(method+path, runtime, dir) — HIP-0105 surface. Loader is duck-typed (no hanzoai/base dep). |
| Middleware | Recover, Logger, RequestID, Timeout, MaxBody, CORS, RateLimit, Telemetry, ProductionHeaders. Auth + StripIdentityHeaders moved to hanzoai/gateway/middleware (HIP-0106). |
| Production headers | ProductionHeaders(cfg) stamps the Stripe/CF/GitHub-grade posture on every response (success/error/404): Server = white-label brand by Host (injected Brand func(host) string, never the framework name), X-Api-Version (brand-neutral), X-Content-Type-Options: nosniff, and Strict-Transport-Security when HSTS. X-Request-Id stays owned by RequestID. Never emits X-Powered-By or any framework/version string. |
| Adapters | AdaptNetHTTP / AdaptNetHTTPMiddleware; front a foreign subtree via app.Raw(zip.MethodAll, prefix+"/*", zip.AdaptNetHTTP(h)). A bare func adapts as AdaptNetHTTP(http.HandlerFunc(fn)) — http.HandlerFunc IS an http.Handler, so there is no separate func adapter. |
| WebSocket | wsx.Upgrade(fn) over fasthttp/websocket. |
| Streaming | c.SendStream(reader) + c.SendStreamWriter(fn). |
| Transport | ONE verb app.Listen(addrs...); the address scheme selects the transport (:9653=ZAP default, http://:8080=HTTP, any RegisterTransport'd proto). app.Listen(":9653", "http://:8080") serves both from one call. Routes ARE the surface over every transport. |
| Free MCP | Typed handlers auto-project to a Model Context Protocol tool surface at /mcp (JSON-RPC 2.0: initialize/tools/list/tools/call). Same op registry as OpenAPI — one schema, five projections (REST·OpenAPI·MCP·CLI·call plane) — served over every transport, so ZAP-native MCP is free. Config.MCP.Disabled to suppress. |
Dependencies
github.com/gofiber/fiber/v3 v3.2.0— Fiber v3.github.com/valyala/fasthttp v1.70.0— transport.github.com/fasthttp/websocket v1.5.12— WebSocket.github.com/luxfi/log v1.4.3— canonical Hanzo logging. Forces Go 1.26.3 floor.
No uber-go/zap, no log/slog, no chi/gin/echo deps. Adapters use
Fiber's own middleware/adaptor package which is part of the v3 core.
Policy adherence
- luxfi/log everywhere — zero
zaporslogimports. - No
.capnpanywhere — ZAP only on the RPC plane. - No major version bumps — all deps pinned to specific patches on existing major lines.
- luxfi canonical packages preferred — log was already on luxfi; no other Hanzo-canonical alternative existed for the Fiber surface.
- Go 1.26.3 floor — bumped from the brief's 1.23.6 only because luxfi/log canonical (the mandatory dep) requires it. Every other dep is on its current patch.
- HIP-0026 identity headers —
c.Org/User/UserEmail/IsAdminmap directly to gateway-mintedX-Org-Id / X-User-Id / X-User-Email / X-User-IsAdmin. - HIP-0105 extension contract — duck-typed via
zip.Loaderat the root, so the framework stays decoupled fromhanzoai/baseAND from a JS toolchain: the goja/esbuild implementation is the separate opt-in importzip/js. - HIP-0106 Mount() contract —
examples/subsystem-mountis the reference; the binary composes N subsystems via the sameMount(app, deps) errorsignature commerce/checkout uses today.
Backwards compatibility
None. zeekay/zip and hanzoai/zip share only the name. The module path
changed (zeekay.io/zip → github.com/hanzoai/zip) so go-modules
treats them as distinct packages — no transitive collision is possible.