69 lines
2.7 KiB
Go
69 lines
2.7 KiB
Go
package process
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// Ask reads the router's record of one of this process's module's own asks, under its own module and no
|
|
// other's (novox/hq ADR 0436 clause 4, ADR 0259 §3): by direct get on bucket, the same subject the tool
|
|
// runner reads (mesh-tools node-tools/internal/bus/seat.go ~300-345):
|
|
//
|
|
// $JS.API.DIRECT.GET.KV_<bucket>.$KV.<bucket>.<module>.<id>
|
|
//
|
|
// bucket is the router's records bucket as the caller's own manifest names it (operator-channel's is
|
|
// messenger_asks in the controller's own test fixtures) — never hard-coded to one bucket here, since a
|
|
// process may read records of more than one seat's router. A missing, deleted or purged key is nil, not
|
|
// an error.
|
|
func (c *Conn) Ask(bucket, id string) (*stdio.StateEntry, error) {
|
|
if err := checkAskID(id); err != nil {
|
|
return nil, err
|
|
}
|
|
subject := "$JS.API.DIRECT.GET.KV_" + bucket + ".$KV." + bucket + "." + c.account.Module + "." + id
|
|
ctx, cancel := context.WithTimeout(context.Background(), StateTimeout)
|
|
defer cancel()
|
|
reply, err := c.nc.RequestWithContext(ctx, subject, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading %s's ask %s from %s: %w", c.account.Module, id, bucket, err)
|
|
}
|
|
if status := reply.Header.Get("Status"); status == "404" {
|
|
return nil, nil
|
|
} else if status != "" {
|
|
return nil, fmt.Errorf("the record %s.%s could not be read: %s %s", c.account.Module, id, status,
|
|
reply.Header.Get("Description"))
|
|
}
|
|
if op := reply.Header.Get("KV-Operation"); op == "DEL" || op == "PURGE" {
|
|
return nil, nil
|
|
}
|
|
var revision uint64
|
|
if _, err := fmt.Sscan(reply.Header.Get("Nats-Sequence"), &revision); err != nil {
|
|
return nil, fmt.Errorf("the record %s.%s carries no readable revision: %w", c.account.Module, id, err)
|
|
}
|
|
return &stdio.StateEntry{Key: c.account.Module + "." + id, Value: askValueOf(reply.Data), Revision: revision}, nil
|
|
}
|
|
|
|
// checkAskID refuses an id holding a dot or a wildcard before anything is sent: either would name a
|
|
// different key under the direct-get subject's own dot-joined path, never this one (novox/hq issue 1189
|
|
// AC2).
|
|
func checkAskID(id string) error {
|
|
if id == "" || strings.ContainsAny(id, ".*>") {
|
|
return fmt.Errorf("%q is not an ask id: an ask id holds no dot and no wildcard, naming one key and "+
|
|
"no other", id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// askValueOf is a record's raw bytes as JSON, quoting them when they are not already valid JSON
|
|
// (mesh-tools node-tools/internal/bus/state.go:351-357, valueOf).
|
|
func askValueOf(raw []byte) json.RawMessage {
|
|
if len(raw) == 0 || !json.Valid(raw) {
|
|
b, _ := json.Marshal(string(raw))
|
|
return b
|
|
}
|
|
return json.RawMessage(append([]byte(nil), raw...))
|
|
}
|