97 lines
3.4 KiB
Go
97 lines
3.4 KiB
Go
package assignment
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
client "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
|
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
|
)
|
|
|
|
// Assignments candidates (novox/hq ADR 0567 §3): for each node a proposal must hear from, the holder writes one key
|
|
// `<proposal>.<node>` in its state `candidates`: the node's assignments with the proposal laid over it for a node whose key
|
|
// it changes, or the node's current key for one whose composition it changes only through another node (Reached). The
|
|
// layouts are the seat's contract's (CandidatesByProposalNodeValue); what refuses a candidate is here.
|
|
|
|
// CandidateKey is the key of one node's candidate in one proposal: `<proposal>.<node>`, so a node watches `*.<node>` and a
|
|
// proposal's keys are `<proposal>.*`.
|
|
func CandidateKey(proposal, node string) (string, error) {
|
|
if err := client.CheckProposalID(proposal); err != nil {
|
|
return "", err
|
|
}
|
|
if err := keys.CheckName("node", node); err != nil {
|
|
return "", err
|
|
}
|
|
return seat.CandidatesByProposalNodeKey(proposal, node), nil
|
|
}
|
|
|
|
// ParseCandidateKey is the inverse of CandidateKey; it refuses any other key, naming it.
|
|
func ParseCandidateKey(key string) (proposal, node string, err error) {
|
|
proposal, node, ok := strings.Cut(key, ".")
|
|
if !ok || keys.CheckName("node", node) != nil || client.CheckProposalID(proposal) != nil {
|
|
return "", "", fmt.Errorf("assignments: %q is not an assignments candidate key, <proposal>.<node>", key)
|
|
}
|
|
return proposal, node, nil
|
|
}
|
|
|
|
func checkCandidate(c seat.CandidatesByProposalNodeValue) error {
|
|
if err := client.CheckProposalID(c.Proposal); err != nil {
|
|
return err
|
|
}
|
|
if c.Assignments != nil {
|
|
return client.CheckAssignments(*c.Assignments)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EncodeCandidate is c as JSON at the keys' Version; it refuses what DecodeCandidate refuses.
|
|
func EncodeCandidate(c seat.CandidatesByProposalNodeValue) ([]byte, error) {
|
|
if err := checkCandidate(c); err != nil {
|
|
return nil, err
|
|
}
|
|
c.V = keys.Version
|
|
if c.Assignments != nil {
|
|
p := *c.Assignments
|
|
p.V = keys.Version
|
|
c.Assignments = &p
|
|
}
|
|
return json.Marshal(c)
|
|
}
|
|
|
|
// DecodeCandidate reads a candidate; it refuses an unknown version, a proposal id that is not one, and assignments the
|
|
// client refuses.
|
|
func DecodeCandidate(data []byte) (seat.CandidatesByProposalNodeValue, error) {
|
|
if err := keys.CheckVersion(data); err != nil {
|
|
return seat.CandidatesByProposalNodeValue{}, err
|
|
}
|
|
c, err := seat.DecodeCandidatesByProposalNodeValue(data)
|
|
if err != nil {
|
|
return seat.CandidatesByProposalNodeValue{}, fmt.Errorf("assignments: a candidate does not fit its shape: %w", err)
|
|
}
|
|
if err := checkCandidate(c); err != nil {
|
|
return seat.CandidatesByProposalNodeValue{}, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// PulseAnswerKey is the key of the bound on one node's answer to a proposal: `answer.<proposal>.<node>`.
|
|
func PulseAnswerKey(proposal, node string) (string, error) {
|
|
if err := client.CheckProposalID(proposal); err != nil {
|
|
return "", err
|
|
}
|
|
if err := keys.CheckName("node", node); err != nil {
|
|
return "", err
|
|
}
|
|
return seat.PulsesByAnswerProposalNodeKey(proposal, node), nil
|
|
}
|
|
|
|
// PulseAskKey is the key of the bound on a proposal's raised ask: `ask.<proposal>`.
|
|
func PulseAskKey(proposal string) (string, error) {
|
|
if err := client.CheckProposalID(proposal); err != nil {
|
|
return "", err
|
|
}
|
|
return seat.PulsesByAskProposalKey(proposal), nil
|
|
}
|