Issue 1585, S19: the strict parser, every manifest problem function and the judge's tables (192 declarations, about 5,200 lines) and their 244 tests leave; composition keeps its own refusals and the helpers it uses. The catalogue's tests decode the manifests they build. ParseManifest, ParseManifestWith, ParseManifestForInputs, CatalogueProblems and the rest are gone; mesh-delivery-indexer/judge holds them. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
85 lines
3.5 KiB
Go
85 lines
3.5 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// What a module may call its state, and whose state it may ask to read (novox/hq ADR 0201).
|
|
//
|
|
// A module names its state **locally** — `servers`, never a bucket or a subject — and another
|
|
// module's as `<module>.<name>`, the way a consumed event names its emitter (design 32 §1). The
|
|
// mesh derives the bucket from the two names, so the module and the local name must each be one
|
|
// token: the bucket joins them with an underscore, which neither may contain, so two modules can
|
|
// never derive one bucket.
|
|
|
|
// stateName is one local name of a module's state: lower-case, no dot, no underscore.
|
|
var stateName = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
|
|
|
// The mesh's caps on what a module may ask of a bucket's history.
|
|
const (
|
|
// StateMostHistory is the most past values a key may keep. The server's own limit.
|
|
StateMostHistory = 64
|
|
)
|
|
|
|
// StateDeclaration is one bucket a module owns: its local name, and the options that are the
|
|
// owner's to choose, as a seat chooses how long its backlog survives (design 32 §3).
|
|
type StateDeclaration struct {
|
|
Name string `json:"name"`
|
|
// History is how many values a key keeps, the current one included; zero is one.
|
|
History int `json:"history,omitempty"`
|
|
// TTLSeconds is how long a value lives once written; zero is until it is replaced or deleted.
|
|
TTLSeconds int `json:"ttl-seconds,omitempty"`
|
|
// PerMachine says the state is keyed by machine: each machine's instance writes the key named for
|
|
// its machine and no other, and the bus grants it that key alone (novox/hq ADR 0260).
|
|
PerMachine bool `json:"per-machine,omitempty"`
|
|
// KeyTTL says each key of the bucket lives the time its writer gives it, and the server writes a delete marker
|
|
// when it ends (novox/hq ADR 0433 §1): the bucket is made with the server's time to live per message.
|
|
KeyTTL bool `json:"key-ttl,omitempty"`
|
|
}
|
|
|
|
// UnmarshalJSON reads a bucket as its bare name, or as {name, history, ttl-seconds}.
|
|
func (s *StateDeclaration) UnmarshalJSON(raw []byte) error {
|
|
trimmed := bytes.TrimSpace(raw)
|
|
if len(trimmed) > 0 && trimmed[0] == '"' {
|
|
return json.Unmarshal(trimmed, &s.Name)
|
|
}
|
|
type plain StateDeclaration
|
|
var full plain
|
|
dec := json.NewDecoder(bytes.NewReader(trimmed))
|
|
dec.DisallowUnknownFields()
|
|
if err := dec.Decode(&full); err != nil {
|
|
return fmt.Errorf("a state is either a name or {name, history, ttl-seconds, per-machine, key-ttl}: %w", typedUnknown(err))
|
|
}
|
|
*s = StateDeclaration(full)
|
|
return nil
|
|
}
|
|
|
|
// MarshalJSON writes back the short form when there is nothing else to say.
|
|
func (s StateDeclaration) MarshalJSON() ([]byte, error) {
|
|
if s.History == 0 && s.TTLSeconds == 0 && !s.PerMachine && !s.KeyTTL {
|
|
return json.Marshal(s.Name)
|
|
}
|
|
type plain StateDeclaration
|
|
return json.Marshal(plain(s))
|
|
}
|
|
|
|
// ReadState splits a read into the owning module and the local name, or says why it is not one.
|
|
func ReadState(read string) (module, local string, err error) {
|
|
at := strings.LastIndex(read, ".")
|
|
if at <= 0 || at == len(read)-1 {
|
|
return "", "", fmt.Errorf("%q does not name a module and its state: a read is <module>.<name>", read)
|
|
}
|
|
module, local = read[:at], read[at+1:]
|
|
if !stateName.MatchString(module) {
|
|
return "", "", fmt.Errorf("%q cannot own state: a module whose state is read is one plain name", module)
|
|
}
|
|
if !stateName.MatchString(local) {
|
|
return "", "", fmt.Errorf("%q is not a state name: lower-case letters, digits and hyphens", local)
|
|
}
|
|
return module, local, nil
|
|
}
|