mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
zip is shared protocol infra (the fiber-over-ZAP web framework used by every
Lux/Hanzo/Zoo Go binary — nodes, gateway, cloud, iam, kms, o11y, ai, …), so its
home is the ZAP protocol family (zap-proto/{go,http,zapd,zip}), NOT any one org's
brand namespace. This consolidates the rich framework (formerly hanzoai/zip v0.5.0
— Ctx/Handler/Errorf/Err*/HTTPError, typed handlers, middleware, runtime/extension
loading, openapi, validate, zaprpc) with the real ZAP transport (ListenZAP/
ListenHTTP/Serve over zap-proto/http) into ONE canonical module.
Module path github.com/hanzoai/zip -> github.com/zap-proto/zip. Code identical to
hanzoai/zip v0.5.0; promoted to v1.0.0 as the stable shared-infra framework. The
prior hanzoai/zip lineage is retired — one home, one import path, one way.
Verified: go build/vet/test ./... green; the ZAP round-trip integration test serves
/v1/health over the real ZAP transport (200 + handler JSON).
108 lines
3.1 KiB
Go
108 lines
3.1 KiB
Go
package zip
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
|
|
"github.com/zap-proto/zip/internal/jsonenc"
|
|
)
|
|
|
|
// TypedHandler is the generic handler signature: takes an *In, returns
|
|
// (*Out, error). zip generates OpenAPI 3.1 spec from the In/Out types
|
|
// and registers a Fiber route that unmarshals body → In, runs the
|
|
// handler, and marshals Out → JSON response.
|
|
type TypedHandler[In, Out any] func(ctx context.Context, in *In) (*Out, error)
|
|
|
|
// registeredOp is the bookkeeping zip keeps for OpenAPI generation.
|
|
// One op per typed route.
|
|
type registeredOp struct {
|
|
Method string
|
|
Path string
|
|
OperationID string
|
|
Summary string
|
|
Tags []string
|
|
InType reflect.Type
|
|
OutType reflect.Type
|
|
}
|
|
|
|
// Get registers a GET typed handler at path.
|
|
func Get[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption) {
|
|
registerTyped(app, "GET", path, fn, opts...)
|
|
}
|
|
|
|
// Post registers a POST typed handler at path.
|
|
func Post[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption) {
|
|
registerTyped(app, "POST", path, fn, opts...)
|
|
}
|
|
|
|
// Put registers a PUT typed handler at path.
|
|
func Put[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption) {
|
|
registerTyped(app, "PUT", path, fn, opts...)
|
|
}
|
|
|
|
// Patch registers a PATCH typed handler at path.
|
|
func Patch[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption) {
|
|
registerTyped(app, "PATCH", path, fn, opts...)
|
|
}
|
|
|
|
// Delete registers a DELETE typed handler at path.
|
|
func Delete[In, Out any](app *App, path string, fn TypedHandler[In, Out], opts ...OpOption) {
|
|
registerTyped(app, "DELETE", path, fn, opts...)
|
|
}
|
|
|
|
// OpOption configures a typed handler registration (OpenAPI metadata).
|
|
type OpOption func(*registeredOp)
|
|
|
|
// WithSummary sets the operation summary in OpenAPI.
|
|
func WithSummary(s string) OpOption { return func(op *registeredOp) { op.Summary = s } }
|
|
|
|
// WithTags sets the operation tags in OpenAPI.
|
|
func WithTags(tags ...string) OpOption { return func(op *registeredOp) { op.Tags = tags } }
|
|
|
|
// WithOperationID sets the operation ID in OpenAPI.
|
|
func WithOperationID(id string) OpOption {
|
|
return func(op *registeredOp) { op.OperationID = id }
|
|
}
|
|
|
|
func registerTyped[In, Out any](app *App, method, path string, fn TypedHandler[In, Out], opts ...OpOption) {
|
|
var inZero In
|
|
var outZero Out
|
|
op := ®isteredOp{
|
|
Method: method,
|
|
Path: path,
|
|
InType: reflect.TypeOf(inZero),
|
|
OutType: reflect.TypeOf(outZero),
|
|
}
|
|
for _, o := range opts {
|
|
o(op)
|
|
}
|
|
app.ops = append(app.ops, op)
|
|
|
|
handler := func(c fiber.Ctx) error {
|
|
var in In
|
|
if method != "GET" && method != "HEAD" {
|
|
if len(c.Body()) > 0 {
|
|
if err := jsonenc.Unmarshal(c.Body(), &in); err != nil {
|
|
return ErrBadRequest("invalid json body: " + err.Error())
|
|
}
|
|
}
|
|
}
|
|
// Validate via struct tags.
|
|
if err := validate(&in); err != nil {
|
|
return ErrBadRequest(err.Error())
|
|
}
|
|
out, err := fn(c.Context(), &in)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if out == nil {
|
|
c.Status(204)
|
|
return nil
|
|
}
|
|
return c.JSON(out)
|
|
}
|
|
app.fiber.Add([]string{method}, path, handler)
|
|
}
|