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]>
114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
package zip_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestProseReachesAServiceInPackageMain runs cmd/zipdoc over a service whose
|
|
// handler is in package main, then asks the built program for its OpenAPI
|
|
// document. A test binary names package main by its import path, so only a real
|
|
// program shows whether the prose zipdoc filed is found at run time.
|
|
func TestProseReachesAServiceInPackageMain(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("compiles a program with the go toolchain")
|
|
}
|
|
zipDir, err := filepath.Abs(".")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sum, err := os.ReadFile("go.sum")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mod, err := os.ReadFile("go.mod")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
directive := ""
|
|
for _, line := range strings.Split(string(mod), "\n") {
|
|
if strings.HasPrefix(line, "go ") {
|
|
directive = line
|
|
break
|
|
}
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
write := func(name, body string) {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
write("go.mod", "module example.com/prose\n\n"+directive+
|
|
"\n\nrequire github.com/zap-proto/zip v1.36.47\n\nreplace github.com/zap-proto/zip => "+zipDir+"\n")
|
|
write("go.sum", string(sum))
|
|
write("main.go", proseMain)
|
|
|
|
run := func(args ...string) {
|
|
t.Helper()
|
|
cmd := exec.Command("go", args...)
|
|
cmd.Dir = dir
|
|
cmd.Env = append(os.Environ(), "GOFLAGS=-mod=mod", "GOPROXY=off")
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
if strings.Contains(string(out), "missing go.sum entry") || strings.Contains(string(out), "cannot find module") {
|
|
t.Skipf("module cache does not hold zip's dependencies: %s", out)
|
|
}
|
|
t.Fatalf("go %s: %v\n%s", strings.Join(args, " "), err, out)
|
|
}
|
|
}
|
|
run("run", "github.com/zap-proto/zip/cmd/zipdoc")
|
|
run("run", ".", "openapi", "openapi.json")
|
|
|
|
raw, err := os.ReadFile(filepath.Join(dir, "openapi.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var doc struct {
|
|
Paths map[string]map[string]struct {
|
|
Description string `json:"description"`
|
|
} `json:"paths"`
|
|
}
|
|
if err := json.Unmarshal(raw, &doc); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := doc.Paths["/v1/hello"]["get"].Description; got != "Greets whoever asks." {
|
|
t.Errorf("description = %q, want the doc comment zipdoc read", got)
|
|
}
|
|
}
|
|
|
|
const proseMain = `package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
|
|
"github.com/zap-proto/zip"
|
|
)
|
|
|
|
// Greeting is what Hello answers.
|
|
type Greeting struct {
|
|
Message string ` + "`json:\"message\"`" + `
|
|
}
|
|
|
|
// Empty takes nothing.
|
|
type Empty struct{}
|
|
|
|
// Hello greets whoever asks.
|
|
func Hello(context.Context, *Empty) (*Greeting, error) {
|
|
return &Greeting{Message: "hello"}, nil
|
|
}
|
|
|
|
func main() {
|
|
app := zip.New(zip.Config{AppName: "prose", DisableStartupMessage: true})
|
|
app.Get("/v1/hello", Hello)
|
|
if done, err := app.Described(); done && err != nil {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
`
|