package views import ( "encoding/json" "fmt" "slices" "strings" "time" stdio "git.novox.be/novox/mesh-sdk/go" ) // KeptSource is a state as a tools bundle reads it through its runtime (stdio.KeptState), as a Source. type KeptSource struct{ State stdio.KeptState } // Keys is every key with a value. func (k KeptSource) Keys() ([]string, error) { return k.State.Keys() } // Get is one key's value and revision. func (k KeptSource) Get(key string) (*Entry, bool, error) { e, err := k.State.Get(key) if err != nil || e == nil { return nil, false, err } return &Entry{Value: e.Value, Revision: e.Revision, Written: e.At}, true, nil } // History is every value the state still keeps for key, oldest first (stdio.KeptState.History): as many as the // state declares it keeps, deletes included. func (k KeptSource) History(key string) ([]Revision, error) { kept, err := k.State.History(key) if err != nil { return nil, err } out := make([]Revision, 0, len(kept)) for _, r := range kept { out = append(out, Revision{Revision: r.Revision, At: r.At, Op: r.Op, Value: r.Value}) } return out, nil } // ParseCallers is a callers list as a manifest writes it into an env value: comma-separated, each a module (on any // machine), . or person. (stdio.Callers). func ParseCallers(list string) stdio.Callers { var out stdio.Callers for _, c := range strings.Split(list, ",") { if c = strings.TrimSpace(c); c != "" { out = append(out, c) } } return out } // CallersEnv is the env value of a first-view tool's callers list, in the owner's tools bundle: MESH_VIEW_CALLERS_, // the tool's name in capitals, a hyphen an underscore. func CallersEnv(tool string) string { return "MESH_VIEW_CALLERS_" + strings.ToUpper(strings.ReplaceAll(tool, "-", "_")) } // Refusal is why caller may not ask tool, or nil: the caller's proof, never an argument, against the tool's callers // list holding exactly its readers (ADR 0621 §2, ADR 0622 §1); the owner itself is on no list, so it is refused too. func Refusal(tool string, callers stdio.Callers, caller string) error { if callers.Allows(caller) { return nil } return fmt.Errorf("%s is refused to %q: the tool answers only %s (novox/hq ADR 0621 §2)", tool, caller, strings.Join(callers, ", ")) } // FirstViewTool is an owner's first-view tool of one of its states (ADR 0621 §1): each call answers one page of what the // state holds now, at the revision its mark says, to the callers its list names and nobody else. It reads only the // owner's own state, so it answers as soon as the runtime is connected, before the owner has asked anyone anything // (ADR 0621 §3). func FirstViewTool(owner, name, state, words string, callers stdio.Callers, values, marks Source, now func() time.Time) stdio.Tool { return stdio.Tool{ Name: name, Description: fmt.Sprintf("The first view of %s's state %s (novox/hq ADR 0615, ADR 0621): %s Each value whole by key, at the revision "+ "the owner said its views family up to, so a reader then follows the events on that family and asks again only on a gap in "+ "revisions. One page at a time: next is the key to ask after for the rest. Answered only to %s; any other caller is refused.", owner, state, words, strings.Join(callers, ", ")), Input: map[string]any{"type": "object", "properties": map[string]any{ "after": map[string]any{"type": "string", "description": "the key the page before ended at (its next); none starts at the first"}}}, Run: func(args map[string]any) (any, error) { if err := Refusal(name, callers, stdio.CallerOf(args)); err != nil { return nil, err } after, _ := args["after"].(string) return FirstView(owner, state, values, marks, strings.TrimSpace(after), now()) }, } } // ReadTool is an owner's ordinary read tool of one of its states (design 25 § 12.2): the keys, one key's value and // revision, one key's kept history (values a Historian), or a page of values in the first view's form, and the state's // fill against its cap as its mark last said it. Granted, as any tool is, to every reader that invokes it, and callable // by a session. func ReadTool(owner, name, state, words string, values, marks Source, now func() time.Time) stdio.Tool { return stdio.Tool{ Name: name, Description: fmt.Sprintf("Read %s's state %s: %s With no argument, its keys and how full it is against its cap (as the "+ "owner last marked it); with key, that key's value and revision; with key and history, every value the state still "+ "keeps for that key, oldest first, each with its revision, time and op; with values, a page of values whole by key, "+ "at the revision the owner said its views family up to, continued with after (the page's next).", owner, state, words), Input: map[string]any{"type": "object", "properties": map[string]any{ "key": map[string]any{"type": "string", "description": "one key: its value and revision"}, "history": map[string]any{"type": "boolean", "description": "with key: every value the state keeps for it, oldest first"}, "values": map[string]any{"type": "boolean", "description": "a page of values whole by key"}, "after": map[string]any{"type": "string", "description": "with values: the key the page before ended at"}}}, Run: func(args map[string]any) (any, error) { key, _ := args["key"].(string) after, _ := args["after"].(string) all, _ := args["values"].(bool) if history, _ := args["history"].(bool); history { return ReadHistory(owner, state, values, strings.TrimSpace(key)) } return Read(owner, state, values, marks, strings.TrimSpace(key), all, strings.TrimSpace(after), now()) }, } } // ReadAnswer is a read tool's answer without values: the state's keys and fill. type ReadAnswer struct { Owner string `json:"owner"` State string `json:"state"` Keys []string `json:"keys"` Fill *Fill `json:"fill,omitempty"` Revision uint64 `json:"revision"` Marked time.Time `json:"marked,omitzero"` // Words say what the fill means where the owner has not marked the state yet. Words string `json:"words,omitempty"` } // KeyAnswer is one key of a read tool's answer. type KeyAnswer struct { Owner string `json:"owner"` State string `json:"state"` Key string `json:"key"` Found bool `json:"found"` Value json.RawMessage `json:"value,omitempty"` Revision uint64 `json:"revision,omitempty"` } // Revision is one value a state keeps for a key: its revision, when the bus wrote it, its op (put, or delete) and // its value, null for a delete. type Revision struct { Revision uint64 `json:"revision"` At time.Time `json:"at"` Op string `json:"op"` Value json.RawMessage `json:"value"` } // Historian is a Source that also keeps each key's history (KeptSource: the state's own kept values). type Historian interface { History(key string) ([]Revision, error) } // HistoryAnswer is a read tool's answer of one key's history: every value kept, oldest first; none kept is empty. type HistoryAnswer struct { Owner string `json:"owner"` State string `json:"state"` Key string `json:"key"` History []Revision `json:"history"` } // ReadHistory is a read tool's answer of key's history from values, refused when no key is named or values keeps none. func ReadHistory(owner, state string, values Source, key string) (HistoryAnswer, error) { if key == "" { return HistoryAnswer{}, fmt.Errorf("a history of %s is of one key: name it", state) } h, ok := values.(Historian) if !ok { return HistoryAnswer{}, fmt.Errorf("%s's state %s keeps no history of its keys here", owner, state) } kept, err := h.History(key) if err != nil { return HistoryAnswer{}, fmt.Errorf("the history of %s.%s cannot be read: %w", state, key, err) } if kept == nil { kept = []Revision{} } return HistoryAnswer{Owner: owner, State: state, Key: key, History: kept}, nil } // Read is a read tool's answer: one key, a page of values, or the keys and fill. func Read(owner, state string, values, marks Source, key string, all bool, after string, now time.Time) (any, error) { switch { case key != "": e, ok, err := values.Get(key) if err != nil { return nil, fmt.Errorf("%s.%s cannot be read: %w", state, key, err) } a := KeyAnswer{Owner: owner, State: state, Key: key, Found: ok} if ok { a.Value, a.Revision = e.Value, e.Revision } return a, nil case all: return FirstView(owner, state, values, marks, after, now) } keys, err := values.Keys() if err != nil { return nil, fmt.Errorf("the state %s cannot be listed: %w", state, err) } slices.Sort(keys) a := ReadAnswer{Owner: owner, State: state, Keys: keys} if marks != nil { if e, ok, err := marks.Get(state); err == nil && ok { if m, err := DecodeMark(e.Value); err == nil { fill := m.Fill a.Fill, a.Revision, a.Marked = &fill, m.Revision, m.At } } } if a.Fill == nil { a.Words = "the owner has not marked this state yet: its fill is said once its views family says a change of it" } if a.Keys == nil { a.Keys = []string{} } return a, nil }