mesh/delivery-group group feat/sdk1 rejected: composed together: the checker's verdict at the members' heads (428d048dd50a): unit: git.novox.be/novox/mesh-sdk/…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: git.novox.be/novox/mesh-sdk/go/process.TestStatePutWritesAKeyWhole: a Put through a short blackout: 0, mesh-mail's state send…
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
173 lines
5.0 KiB
Go
173 lines
5.0 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
)
|
|
|
|
// UnknownFieldError is a key a manifest has that this controller does not know, wherever it is: at the
|
|
// top of the manifest or inside a block (a state, an offer, a data item's backup, an own secret, a verb).
|
|
// Typed, because the blocks' decoders wrap the decoder's words in their own.
|
|
type UnknownFieldError struct {
|
|
Field string
|
|
err error
|
|
}
|
|
|
|
func (e *UnknownFieldError) Error() string { return e.err.Error() }
|
|
func (e *UnknownFieldError) Unwrap() error { return e.err }
|
|
|
|
// unknownFieldText is how the JSON decoder words a key its target does not have.
|
|
var unknownFieldText = regexp.MustCompile(`^json: unknown field "([^"]*)"$`)
|
|
|
|
// typedUnknown is err as an UnknownFieldError when it is the decoder refusing an unknown key, else err.
|
|
func typedUnknown(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if m := unknownFieldText.FindStringSubmatch(err.Error()); m != nil {
|
|
return &UnknownFieldError{Field: m[1], err: err}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// asUnknownField is the unknown key err is about, at any depth, or nil.
|
|
func asUnknownField(err error) *UnknownFieldError {
|
|
var u *UnknownFieldError
|
|
if errors.As(typedUnknown(err), &u) {
|
|
return u
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// prunedFields is a stored manifest's fields with every key this controller does not know taken out,
|
|
// and the keys taken out (novox/hq ADR 0262). A second pass, after the strict one refused: each
|
|
// top-level field is decoded alone, and where an entry inside it has an unknown key, the one
|
|
// occurrence whose removal moves the decoder past it is removed — never a key of the same name that
|
|
// the entry around it knows. So a left-out module still provides what it provides, and its data and
|
|
// directories are still read, which its backup lines are made from.
|
|
func prunedFields(keys map[string]json.RawMessage) (manifestFields, []string, error) {
|
|
var removed []string
|
|
tree := map[string]any{}
|
|
for k, raw := range keys {
|
|
var v any
|
|
dec := json.NewDecoder(bytes.NewReader(raw))
|
|
dec.UseNumber()
|
|
if err := dec.Decode(&v); err != nil {
|
|
return manifestFields{}, nil, err
|
|
}
|
|
tree[k] = v
|
|
}
|
|
for _, k := range sortedAnyKeys(tree) {
|
|
for tries := 0; ; tries++ {
|
|
err := decodesAlone(k, tree[k])
|
|
if err == nil {
|
|
break
|
|
}
|
|
unknown := asUnknownField(err)
|
|
if unknown == nil || tries > 64 {
|
|
return manifestFields{}, nil, err
|
|
}
|
|
if unknown.Field == k {
|
|
delete(tree, k)
|
|
removed = append(removed, k)
|
|
break
|
|
}
|
|
path, ok := removalThatHelps(k, tree[k], unknown.Field, err.Error())
|
|
if !ok {
|
|
// The same key unknown in two entries alike: no one removal changes the words.
|
|
// Every occurrence goes, and the field is judged again.
|
|
if removeEvery(tree[k], unknown.Field) == 0 {
|
|
return manifestFields{}, nil, err
|
|
}
|
|
path = "…." + unknown.Field
|
|
}
|
|
removed = append(removed, k+path)
|
|
}
|
|
}
|
|
raw, err := json.Marshal(tree)
|
|
if err != nil {
|
|
return manifestFields{}, nil, err
|
|
}
|
|
var fields manifestFields
|
|
dec := json.NewDecoder(bytes.NewReader(raw))
|
|
dec.DisallowUnknownFields()
|
|
if err := dec.Decode(&fields); err != nil {
|
|
return manifestFields{}, nil, err
|
|
}
|
|
return fields, removed, nil
|
|
}
|
|
|
|
// decodesAlone is whether one top-level field decodes strictly on its own.
|
|
func decodesAlone(k string, v any) error {
|
|
raw, err := json.Marshal(map[string]any{k: v})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var fields manifestFields
|
|
dec := json.NewDecoder(bytes.NewReader(raw))
|
|
dec.DisallowUnknownFields()
|
|
return dec.Decode(&fields)
|
|
}
|
|
|
|
// removalThatHelps removes, from v, the one occurrence of key whose removal changes what the strict
|
|
// decoder says of field k, and says where it was. Every other occurrence is left as it was.
|
|
func removalThatHelps(k string, v any, key, said string) (string, bool) {
|
|
var found bool
|
|
var where string
|
|
var walk func(node any, at string) bool
|
|
walk = func(node any, at string) bool {
|
|
switch n := node.(type) {
|
|
case map[string]any:
|
|
if value, has := n[key]; has {
|
|
delete(n, key)
|
|
// Accepted only when the decoder is past it: nothing left, or an unknown key said
|
|
// elsewhere. A different kind of error means the removal broke the entry; the same
|
|
// words mean this was not the occurrence it refused.
|
|
err := decodesAlone(k, v)
|
|
if err == nil || (asUnknownField(err) != nil && err.Error() != said) {
|
|
found, where = true, at+"."+key
|
|
return true
|
|
}
|
|
n[key] = value
|
|
}
|
|
for _, sub := range sortedAnyKeys(n) {
|
|
if walk(n[sub], at+"."+sub) {
|
|
return true
|
|
}
|
|
}
|
|
case []any:
|
|
for i, item := range n {
|
|
if walk(item, fmt.Sprintf("%s[%d]", at, i)) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
walk(v, "")
|
|
return where, found
|
|
}
|
|
|
|
// removeEvery removes key from every object in v, and says how many it removed.
|
|
func removeEvery(v any, key string) int {
|
|
n := 0
|
|
switch node := v.(type) {
|
|
case map[string]any:
|
|
if _, has := node[key]; has {
|
|
delete(node, key)
|
|
n++
|
|
}
|
|
for _, sub := range node {
|
|
n += removeEvery(sub, key)
|
|
}
|
|
case []any:
|
|
for _, item := range node {
|
|
n += removeEvery(item, key)
|
|
}
|
|
}
|
|
return n
|
|
}
|