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]>
75 lines
2.0 KiB
Go
75 lines
2.0 KiB
Go
package zip_test
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/zap-proto/zip"
|
|
)
|
|
|
|
type localIn struct{}
|
|
type localOut struct {
|
|
Local bool `json:"local"`
|
|
Peer bool `json:"peer"`
|
|
}
|
|
|
|
// A call the process makes to itself has no peer and is Local; a call over the
|
|
// socket has a peer the kernel attested and is not.
|
|
func TestLocalSeparatesHereFromTheSocket(t *testing.T) {
|
|
dir := sockDir(t)
|
|
t.Setenv(zip.RuntimeDirEnv, dir)
|
|
app := zip.New(zip.Config{AppName: "who"})
|
|
app.Post("/who", func(ctx context.Context, _ *localIn) (*localOut, error) {
|
|
return &localOut{Local: zip.Local(ctx), Peer: zip.PeerOf(ctx) != nil}, nil
|
|
}, zip.WithOperationID("who"))
|
|
sock := zip.SocketPath("who")
|
|
go func() { _ = app.Listen(sock) }()
|
|
waitFor(t, sock)
|
|
|
|
here, err := zip.Here[localIn, localOut](context.Background(), app, "who", &localIn{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !here.Local || here.Peer {
|
|
t.Fatalf("Here: local=%v peer=%v, want local and no peer", here.Local, here.Peer)
|
|
}
|
|
conn, err := zip.DialApp("who")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
over, err := zip.Call[localIn, localOut](context.Background(), conn, "who", &localIn{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if over.Local || !over.Peer {
|
|
t.Fatalf("socket: local=%v peer=%v, want a peer and not local", over.Local, over.Peer)
|
|
}
|
|
}
|
|
|
|
// The socket is bound with the mode the config names, and 0600 when it names
|
|
// none.
|
|
func TestSocketModeIsTheConfigs(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
cfg os.FileMode
|
|
want os.FileMode
|
|
}{{"shut", 0, 0o600}, {"open", 0o666, 0o666}} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
dir := sockDir(t)
|
|
t.Setenv(zip.RuntimeDirEnv, dir)
|
|
app := zip.New(zip.Config{AppName: "mode-" + tc.name, SocketMode: tc.cfg})
|
|
sock := zip.SocketPath("mode-" + tc.name)
|
|
go func() { _ = app.Listen(sock) }()
|
|
waitFor(t, sock)
|
|
st, err := os.Stat(sock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := st.Mode().Perm(); got != tc.want {
|
|
t.Fatalf("socket mode %o, want %o", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|