Files
mesh-sdk/go/seatdef/callers.go
T
jschoubben 078d3f53bf RR-1, RR-2: the seat-definition reader reads callers as text or list, class, keep as an object, step pipeline and on kind:image
Text callers stay read in the release (G's own reader refuses them); both old and new keep forms are read; check
stays beside pipeline.

Builds: design 47 G §340, §409, §336, §338, §391, §396, §397, §203, §266, §324

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 10:35:22 +01:00

127 lines
3.7 KiB
Go

package seatdef
import (
"bytes"
"encoding/json"
"errors"
"fmt"
)
// Callers says who may call a verb or send an accepted kind. It is read as text (the form every definition on main
// has) or as a list (the form design 47 G §336 writes); the reader release carries both so that a definition in
// either form is read (design 47 G §340), and G's own reader refuses the text.
type Callers struct {
// Text is the callers in words, when the definition gave them so.
Text string
// List is the callers as a list of entries, when the definition gave them so.
List []string
}
// IsText says whether the definition gave its callers in words.
func (c Callers) IsText() bool { return c.List == nil }
// UnmarshalJSON reads a string or a list of strings.
func (c *Callers) UnmarshalJSON(raw []byte) error {
raw = bytes.TrimSpace(raw)
switch {
case len(raw) > 0 && raw[0] == '"':
return json.Unmarshal(raw, &c.Text)
case len(raw) > 0 && raw[0] == '[':
c.List = []string{}
return json.Unmarshal(raw, &c.List)
}
return errors.New("callers is a text or a list of entries")
}
// MarshalJSON writes the form the definition was read in.
func (c Callers) MarshalJSON() ([]byte, error) {
if c.IsText() {
return json.Marshal(c.Text)
}
return json.Marshal(c.List)
}
func checkCallers(where string, c *Callers) Problems {
if c == nil {
return nil
}
if c.IsText() {
if c.Text == "" {
return Problems{{where, "is empty"}}
}
return nil
}
var ps Problems
for i, e := range c.List {
if e == "" {
ps = append(ps, Problem{fmt.Sprintf("%s[%d]", where, i), "is empty"})
}
}
return ps
}
// The classes of a verb (design 47 G §391).
const (
ClassRead = "read"
ClassAct = "act"
ClassWarrant = "warrant"
)
// Classes are the classes a verb's `class` may name.
var Classes = []string{ClassRead, ClassAct, ClassWarrant}
// Keep is an event's `keep`: the string of today's definitions (KeepLastPerSubject or KeepStream, with the message's
// max-age-days and max-mib beside it) or the object of design 47 G §396 (`max-age-days`, `max-mib`,
// `last-per-subject`). The reader release reads both (design 47 G §340, §409).
type Keep struct {
// Mode is the string form; empty when the object form was given.
Mode string
// MaxAgeDays, MaxMiB and LastPerSubject are the object form's fields; MaxAgeDays and MaxMiB are nil when unsaid.
MaxAgeDays *int
MaxMiB *int
LastPerSubject bool
// Object says the object form was given.
Object bool
}
type keepObject struct {
MaxAgeDays *int `json:"max-age-days"`
MaxMiB *int `json:"max-mib"`
LastPerSubject bool `json:"last-per-subject"`
}
// UnmarshalJSON reads the string or the object, the object strictly.
func (k *Keep) UnmarshalJSON(raw []byte) error {
raw = bytes.TrimSpace(raw)
if len(raw) > 0 && raw[0] == '"' {
return json.Unmarshal(raw, &k.Mode)
}
var o keepObject
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&o); err != nil {
return fmt.Errorf("keep is a string or an object of max-age-days, max-mib and last-per-subject: %w", err)
}
*k = Keep{MaxAgeDays: o.MaxAgeDays, MaxMiB: o.MaxMiB, LastPerSubject: o.LastPerSubject, Object: true}
return nil
}
// MarshalJSON writes the form the definition was read in.
func (k Keep) MarshalJSON() ([]byte, error) {
if !k.Object {
return json.Marshal(k.Mode)
}
return json.Marshal(keepObject{k.MaxAgeDays, k.MaxMiB, k.LastPerSubject})
}
func checkKeepObject(where string, k Keep) Problems {
var ps Problems
if k.MaxAgeDays != nil && *k.MaxAgeDays < 0 {
ps = append(ps, Problem{where + ".max-age-days", "is negative (0 is no age limit)"})
}
if k.MaxMiB != nil && *k.MaxMiB <= 0 {
ps = append(ps, Problem{where + ".max-mib", "is not above zero"})
}
return ps
}