112 lines
4.3 KiB
Go
112 lines
4.3 KiB
Go
// Package operatorask is a module's asking of a person through the seat that routes asks to the operator (design 47 G
|
|
// A-101, §700, A-74): the seat's holder is found on the seat list, an ask is published under the module's own name, the
|
|
// router's record of it is read for the warrant, and the router's `decided` is heard. It is shared code, so no module
|
|
// keeps its own copy; it names no seat, verb or module: the generated contracts' names and types are its parameters.
|
|
package operatorask
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// Caller calls a seat verb or module tool as `seat:<seat>.<verb>` or `<module>.<tool>` and returns its answer
|
|
// (stdio.Ask is one; so is a process's calls).
|
|
type Caller func(key string, body any) (json.RawMessage, error)
|
|
|
|
// Contract is what the asker takes from the generated contracts: the verb that answers the seat list with its seats
|
|
// (`Lists` is its call key, `ListBody` its input) and the asking seat and its state of the router's records.
|
|
type Contract struct {
|
|
Lists string
|
|
ListBody any
|
|
Seat string
|
|
// StateAsks is the seat's state holding the router's record of each ask.
|
|
StateAsks string
|
|
}
|
|
|
|
// Bucket is the bucket of the router's records: the seat's state in the bucket of the seat's holder on the seat list.
|
|
func Bucket(call Caller, c Contract) (string, error) {
|
|
raw, err := call(c.Lists, c.ListBody)
|
|
if err != nil {
|
|
return "", fmt.Errorf("who holds the seat %s is not asked of %s: %w", c.Seat, c.Lists, err)
|
|
}
|
|
var held struct {
|
|
Seats []struct {
|
|
Seat string `json:"seat"`
|
|
Holders []struct {
|
|
Module string `json:"module"`
|
|
} `json:"holders"`
|
|
} `json:"seats"`
|
|
}
|
|
if err := json.Unmarshal(raw, &held); err != nil {
|
|
return "", fmt.Errorf("the answer of %s is not the seat list's: %w", c.Lists, err)
|
|
}
|
|
for _, s := range held.Seats {
|
|
if s.Seat == c.Seat && len(s.Holders) > 0 {
|
|
return s.Holders[0].Module + "_" + c.StateAsks, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("no module holds the seat %s on the seat list, so the router's record of an ask is not read", c.Seat)
|
|
}
|
|
|
|
// Asker asks as one module. W is the generated contract's warrant type.
|
|
type Asker[W any] struct {
|
|
Module string
|
|
Contract Contract
|
|
// Call reaches the seat list's verb.
|
|
Call Caller
|
|
// Record reads one record of a bucket under the module's own name (process.Conn.Ask).
|
|
Record func(bucket, id string) (*stdio.StateEntry, error)
|
|
// Publish publishes a body de-duplicated by id: the same id twice is one message (stdio.SeatPublish).
|
|
Publish func(subject string, body any, id string) error
|
|
// Subscribe hears an event (stdio.Subscribe).
|
|
Subscribe func(pattern string, handle func(stdio.Envelope) error) error
|
|
// AskSubject and DecidedSubject are the generated contract's subjects for Module.
|
|
AskSubject, DecidedSubject func(module string) string
|
|
}
|
|
|
|
// Ask publishes the ask (the generated contract's Ask) under the module's own name, de-duplicated by its id.
|
|
func (a *Asker[W]) Ask(id string, ask any) error {
|
|
if a.Publish == nil || a.AskSubject == nil {
|
|
return errors.New("the asker has no way to publish")
|
|
}
|
|
return a.Publish(a.AskSubject(a.Module), ask, id)
|
|
}
|
|
|
|
// Where is an ask's state as the router keeps it, and its warrant once there is one; a record not yet there is an empty
|
|
// state. It is an error while the seat's holder or the record cannot be read.
|
|
func (a *Asker[W]) Where(id string) (state string, warrant *W, err error) {
|
|
bucket, err := Bucket(a.Call, a.Contract)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
e, err := a.Record(bucket, id)
|
|
if err != nil || e == nil {
|
|
return "", nil, err
|
|
}
|
|
var rec struct {
|
|
State string `json:"state"`
|
|
Warrant *W `json:"warrant"`
|
|
}
|
|
if err := json.Unmarshal(e.Value, &rec); err != nil {
|
|
return "", nil, fmt.Errorf("the router's record of %s cannot be read: %w", id, err)
|
|
}
|
|
return rec.State, rec.Warrant, nil
|
|
}
|
|
|
|
// Hear hands handle each warrant the router says to this module; a handler's error leaves it for the bus to give again.
|
|
func (a *Asker[W]) Hear(handle func(W) error) error {
|
|
if a.Subscribe == nil || a.DecidedSubject == nil {
|
|
return errors.New("the asker has no way to hear")
|
|
}
|
|
return a.Subscribe(a.DecidedSubject(a.Module), func(env stdio.Envelope) error {
|
|
var w W
|
|
if err := json.Unmarshal(env.Body, &w); err != nil {
|
|
return fmt.Errorf("%s holds what is no warrant: %w", env.Key, err)
|
|
}
|
|
return handle(w)
|
|
})
|
|
}
|