mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
Ten of eleven examples declared their surface with untyped handlers. An untyped route registers no op, so every one of them modelled the single pattern that leaves an endpoint out of the OpenAPI document, out of the MCP tool list, out of the CLI and out of the call plane — while the README two files away calls that registry the one source of truth. The examples are where the default is learned, and they were teaching the wrong one. hello, subsystem-mount, migrate-from-chi, migrate-from-beego, plugin/billing, plugin/host and internal/testplugin now declare typed ops. The migrate-from-* examples show the port as the two steps it is: mechanical first (same handler shape, one edit, and invisible to every projection), typed second, because stopping after the first leaves exactly the surface you were migrating away from. sse-streaming and websocket stay untyped and now say why in the file header: a typed op is one decode, one call, one serialize, and a stream has no single value to return while an upgrade has no response body at all. That is the whole list of reasons — a stream, an upgrade, a proxied byte range, a non-JSON body. express-in-zip registered two routes on app.Fiber(), BELOW zip entirely: no *zip.Ctx, no error handler, no middleware, no Authorizer, and no projection — served by the app but not governed by it, which is a security seam and not a shortcut. Both move onto the zip router. /runtime/:lang becomes a typed op, so "run this source in this language" is now in the document and is a tool an agent can call; the legacy JS wildcard stays a wildcard and says what that costs. testplugin's catch-all stays untyped for the reason it exists: it answers paths that do not exist, so a host mounting it at two prefixes can tell its own 404 from the plugin's. A wildcard is deliberately not one named endpoint with one schema. Every example was run and probed, not just built: 30 assertions across all eleven, including the openapi/mcp routes the typed ops now light up. Co-authored-by: Hanzo Dev <[email protected]>
67 lines
2.2 KiB
Go
67 lines
2.2 KiB
Go
// migrate-from-chi example — chi → zip via stdlib adapter.
|
|
//
|
|
// Use zip.AdaptNetHTTP(chiRouter) to mount an existing chi.Router as a
|
|
// migration step. Replace adapted routes with native zip handlers when
|
|
// feasible; the adapter costs ~5% perf vs native Fiber dispatch.
|
|
//
|
|
// chi is NOT a dep of this example — we adapt any http.Handler, and a
|
|
// chi.Router satisfies that interface natively.
|
|
//
|
|
// The adapted subtree is a wildcard route and registers no operation, so nothing
|
|
// behind /legacy/chi is in the OpenAPI document, the MCP tool list, the CLI or
|
|
// the call plane. That is the price of not rewriting it yet, and it is the
|
|
// reason to: new work goes in as a TYPED op, and a native route added later
|
|
// still wins by specificity, so a legacy path can be replaced one op at a time
|
|
// with no un-mount step.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/zap-proto/zip"
|
|
"github.com/zap-proto/zip/middleware"
|
|
)
|
|
|
|
// legacyHandler stands in for an existing chi.Router. Same shape
|
|
// (http.Handler) so zip.AdaptNetHTTP works without changes.
|
|
type legacyHandler struct{}
|
|
|
|
func (legacyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"legacy":true}`))
|
|
}
|
|
|
|
// GetUserIn addresses one user; `id` is the path segment.
|
|
type GetUserIn struct {
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// User is one user record.
|
|
type User struct {
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// GetUser returns one user by id.
|
|
func getUser(_ context.Context, in *GetUserIn) (*User, error) {
|
|
return &User{ID: in.ID}, nil
|
|
}
|
|
|
|
func main() {
|
|
app := zip.New(zip.Config{AppName: "migrate-from-chi"})
|
|
app.Use(middleware.Recover())
|
|
|
|
// New work goes in typed: one declaration, and the route, the document, the
|
|
// tool list, the CLI and the call plane all follow from it.
|
|
zip.Get(app, "/v1/users/:id", getUser)
|
|
|
|
// Front the legacy chi router under /legacy/chi for incremental
|
|
// migration — one adapted wildcard route. Replace one path at a time
|
|
// with native zip handlers; a native route added later wins by
|
|
// specificity, no un-mount step needed.
|
|
app.Group("/legacy/chi").All("/*", zip.AdaptNetHTTP(legacyHandler{}))
|
|
|
|
log.Fatal(app.Listen("http://:8080"))
|
|
}
|