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
127 lines
3.7 KiB
Go
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
|
|
}
|