GEN4: the seat generator emits the types of bodies, resolves #/bodies/ refs and names an accepted kind by its stream
Builds: design 47 G-9 §452, §454, §455 (§391: no verb is refused for a missing class) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -79,6 +79,12 @@ func declareAll(d seatdef.Definition, fromPart map[string]string) (*typer, error
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// A body of `bodies` yields its type alone: the sending is mesh-sdk's (design 47 G §452, §454).
|
||||
for _, n := range sortedKeys(d.Bodies) {
|
||||
if err := decl(n, "", d.Bodies[n]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, v := range d.Verbs {
|
||||
if err := decl(ident(v.Name)+"Input", "is what "+v.Name+" takes.", v.Input); err != nil {
|
||||
return nil, err
|
||||
@@ -88,7 +94,7 @@ func declareAll(d seatdef.Definition, fromPart map[string]string) (*typer, error
|
||||
}
|
||||
}
|
||||
for _, a := range d.Accepts {
|
||||
if err := decl(ident(a.Name)+"Accept", "is the body of "+a.Name+".", a.Body); err != nil {
|
||||
if err := decl(ident(acceptName(a))+"Accept", "is the body of "+acceptName(a)+".", a.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -171,7 +177,7 @@ func goWhole(m *Module, d seatdef.Definition) ([]byte, []byte, error) {
|
||||
}
|
||||
fmt.Fprintf(&b, "// Seat is the seat's name.\nconst Seat = %q\n\n", d.Seat)
|
||||
writeNames(&b, "Verb", seatdef.VerbNames(d.Verbs))
|
||||
writeNames(&b, "Accept", seatdef.MessageNames(d.Accepts))
|
||||
writeNames(&b, "Accept", acceptNames(d.Accepts))
|
||||
writeNames(&b, "Event", seatdef.MessageNames(d.Events))
|
||||
writeList(&b, "Verbs", "Verb", "every verb the seat serves, in its definition's order", seatdef.VerbNames(d.Verbs))
|
||||
writeList(&b, "Events", "Event", "every event published on the seat's subject", seatdef.MessageNames(d.Events))
|
||||
@@ -195,12 +201,12 @@ func goWhole(m *Module, d seatdef.Definition) ([]byte, []byte, error) {
|
||||
}
|
||||
}
|
||||
for _, a := range d.Accepts {
|
||||
subj := seatdef.AcceptSubject(d.Seat, a.Name)
|
||||
subj := seatdef.AcceptSubject(d.Seat, acceptName(a))
|
||||
if a.ByCaller {
|
||||
fmt.Fprintf(&b, "// AcceptSubject%s is where one caller sends %s.\nfunc AcceptSubject%s(caller string) string { return %q + caller }\n\n",
|
||||
ident(a.Name), a.Name, ident(a.Name), subj+".")
|
||||
ident(acceptName(a)), acceptName(a), ident(acceptName(a)), subj+".")
|
||||
} else {
|
||||
fmt.Fprintf(&b, "// AcceptSubject%s is where %s is sent.\nconst AcceptSubject%s = %q\n\n", ident(a.Name), a.Name, ident(a.Name), subj)
|
||||
fmt.Fprintf(&b, "// AcceptSubject%s is where %s is sent.\nconst AcceptSubject%s = %q\n\n", ident(acceptName(a)), acceptName(a), ident(acceptName(a)), subj)
|
||||
}
|
||||
}
|
||||
if len(d.HolderEvents) > 0 {
|
||||
@@ -430,7 +436,7 @@ func fixtureTarget(d seatdef.Definition, kind, name string) (string, string, err
|
||||
ms, subj, suffix = d.Events, seatdef.EventSubject(d.Seat, name), "Event"
|
||||
}
|
||||
for _, msg := range ms {
|
||||
if msg.Name == name {
|
||||
if (kind == "event" && msg.Name == name) || (kind == "accept" && acceptName(msg) == name) {
|
||||
if kind == "event" && len(msg.Parts()) > 0 {
|
||||
return ident(name) + suffix, partsPattern(d.Seat, msg), nil
|
||||
}
|
||||
@@ -460,6 +466,23 @@ func fixtureTarget(d seatdef.Definition, kind, name string) (string, string, err
|
||||
return "", "", fmt.Errorf("the definition has no %s %q", kind, name)
|
||||
}
|
||||
|
||||
// acceptName is an accepted kind's name as the generated names and its subject carry it: the plain kind, or for a
|
||||
// kind on a stream `<stream>.<kind>` (design 47 G §455), so `step` on `check` and on `delivery` stay two.
|
||||
func acceptName(m seatdef.Message) string {
|
||||
if m.Stream != "" {
|
||||
return m.Stream + "." + m.Name
|
||||
}
|
||||
return m.Name
|
||||
}
|
||||
|
||||
func acceptNames(ms []seatdef.Message) []string {
|
||||
out := make([]string, len(ms))
|
||||
for i, m := range ms {
|
||||
out[i] = acceptName(m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func regexp(s string) string {
|
||||
var b strings.Builder
|
||||
for _, r := range s {
|
||||
|
||||
@@ -337,3 +337,43 @@ func TestADefinitionsActsSubjectsKeepAndCallersAreRead(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBodiesAreTypesAndAcceptedKindsRideStreams is design 47 G §452, §454, §455 and §391's check for the generator: a
|
||||
// definition's `bodies` yield their types in both languages and no sending code, a reference into `bodies` resolves
|
||||
// as one into `defs`, an accepted kind on a stream has the subject `<stream>.<kind>` (`step` on both streams stays
|
||||
// two), and a verb naming no class is refused by nothing.
|
||||
func TestBodiesAreTypesAndAcceptedKindsRideStreams(t *testing.T) {
|
||||
m, err := Load("testdata/contracts", "example.org/contracts/go", "@example/contracts")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
files, err := Generate(m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := string(files["go/seats/aregistry/contract.go"])
|
||||
for _, want := range []string{
|
||||
"type Finding struct", "Related []Finding", "type PeekAnswer = Finding",
|
||||
`"check.step"`, `"delivery.step"`, `"check.run"`,
|
||||
`const AcceptSubjectCheckStep = "mesh.seat.a-registry.accept.check.step"`,
|
||||
`const AcceptSubjectDeliveryStep = "mesh.seat.a-registry.accept.delivery.step"`,
|
||||
"type CheckStepAccept struct", "type DeliveryStepAccept",
|
||||
} {
|
||||
if !strings.Contains(src, want) {
|
||||
t.Errorf("the Go contract lacks %q:\n%s", want, src)
|
||||
}
|
||||
}
|
||||
if strings.Contains(src, "SendFinding") || strings.Contains(src, "SubjectFinding") {
|
||||
t.Error("a body yields more than its type")
|
||||
}
|
||||
dts := string(files["ts/seats/aregistry/index.d.ts"])
|
||||
js := string(files["ts/seats/aregistry/index.js"])
|
||||
for _, want := range []string{"export interface Finding", "checkStepSubject", "deliveryStepSubject"} {
|
||||
if !strings.Contains(dts, want) {
|
||||
t.Errorf("index.d.ts lacks %q:\n%s", want, dts)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(js, `"mesh.seat.a-registry.accept.delivery.step"`) {
|
||||
t.Errorf("index.js lacks the delivery stream's subject:\n%s", js)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,5 +1,11 @@
|
||||
{"format":1,"seat":"a-registry","scope":"mesh","grain":"whole","domain":"Module","decision":"ADR 1","includes":["registry"],
|
||||
"verbs":[{"name":"hold","class":"read","callers":["invokers","persons","sessions","seat:b-registry","module:gitea","operator","healers","holder:node-backup@same-node"],"input":{"type":"object"},"answer":{"type":"object"}}],
|
||||
"verbs":[{"name":"hold","class":"read","callers":["invokers","persons","sessions","seat:b-registry","module:gitea","operator","healers","holder:node-backup@same-node"],"input":{"type":"object"},"answer":{"type":"object"}},
|
||||
{"name":"peek","input":{"type":"object"},"answer":{"$ref":"#/bodies/Finding"}}],
|
||||
"accepts":[
|
||||
{"name":"step","stream":"check","body":{"type":"object","properties":{"id":{"type":"string"}}}},
|
||||
{"name":"step","stream":"delivery","body":{"type":"object"}},
|
||||
{"name":"run","stream":"check","body":{"type":"object"}}],
|
||||
"bodies":{"Finding":{"type":"object","properties":{"id":{"type":"string"},"owner":{"type":"string"},"related":{"type":"array","items":{"$ref":"#/bodies/Finding"}}},"required":["id"]}},
|
||||
"acts":[{"step":"build","on":"kind:image","bound":"60m"},{"step":"check","on":"every"},{"step":"build","on":"go"}],
|
||||
"events":[
|
||||
{"name":"milestone-asked","subject":"milestone-asked.<milestone>","body":{"type":"object"},"keep":{"max-age-days":90,"max-mib":16,"last-per-subject":true}},
|
||||
|
||||
@@ -103,7 +103,7 @@ func tsFiles(m *Module, out map[string][]byte) error {
|
||||
fmt.Fprintf(&o.js, "export const seat = %q;\n\n", d.Seat)
|
||||
fmt.Fprintf(&o.dts, "export declare const seat: %q;\n\n", d.Seat)
|
||||
tsNames(o, "verbs", seatdef.VerbNames(d.Verbs))
|
||||
tsNames(o, "accepts", seatdef.MessageNames(d.Accepts))
|
||||
tsNames(o, "accepts", acceptNames(d.Accepts))
|
||||
tsNames(o, "events", seatdef.MessageNames(d.Events))
|
||||
if f := d.Family; f != nil {
|
||||
fmt.Fprintf(&o.js, "export const family = { name: %q, maxAgeDays: %d };\n\n", f.Name, f.MaxAgeDays)
|
||||
@@ -121,7 +121,7 @@ func tsFiles(m *Module, out map[string][]byte) error {
|
||||
tsSubject(o, tsName(e.Name), seatdef.EventSubject(d.Seat, e.Name), e.ByCaller)
|
||||
}
|
||||
for _, a := range d.Accepts {
|
||||
tsSubject(o, tsName(a.Name), seatdef.AcceptSubject(d.Seat, a.Name), a.ByCaller)
|
||||
tsSubject(o, tsName(acceptName(a)), seatdef.AcceptSubject(d.Seat, acceptName(a)), a.ByCaller)
|
||||
}
|
||||
if len(d.HolderEvents) > 0 {
|
||||
tsNames(o, "holderEvents", holderNames(d.HolderEvents))
|
||||
|
||||
@@ -62,9 +62,13 @@ func (tp *typer) read(where string, s map[string]any, top bool) (*typ, error) {
|
||||
return &typ{kind: "any"}, nil
|
||||
}
|
||||
if r, ok := s["$ref"].(string); ok {
|
||||
// A reference into `bodies` is read as one into `defs` (design 47 G §454).
|
||||
n, found := strings.CutPrefix(r, "#/defs/")
|
||||
if !found {
|
||||
n, found = strings.CutPrefix(r, "#/bodies/")
|
||||
}
|
||||
if !found || n == "" {
|
||||
return nil, fmt.Errorf("$ref %q is not #/defs/<name>", r)
|
||||
return nil, fmt.Errorf("$ref %q is not #/defs/<name> or #/bodies/<name>", r)
|
||||
}
|
||||
return &typ{kind: "ref", ref: n}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user