A decoder keeps the last of two equal keys without a word, so the indexer's judge read a manifest a merge of two branches had given one field twice as if one side never was. mesh-catalog's checks/manifests refuses it already; Decode now refuses it the same way, at any depth. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
95 lines
3.7 KiB
Go
95 lines
3.7 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Decode reads a manifest leniently, as every part but the judge reads one (novox/hq ADR 0578 §3): every field the
|
|
// contract knows as it declares it, and a field it does not know set aside and named (SetAside), never refused. It
|
|
// judges nothing: no name, seat, bus or catalogue rule is applied, since the indexer judged the manifest once and its
|
|
// verdict is in the indexer's state (delivery.VerdictsBucket); a reader takes the verdict beside it
|
|
// (delivery.TakeVerdict). An error is only bytes that are no manifest: not JSON, a field of the wrong shape, or one field
|
|
// given twice in one object at any depth, named by its path: a decoder keeps the last of two equal keys without a word,
|
|
// so a merge of two branches that each added the field would lose one side silently (as mesh-catalog's checks/manifests
|
|
// refuses it).
|
|
func Decode(raw []byte) (Manifest, error) {
|
|
var m Manifest
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
return Manifest{}, err
|
|
}
|
|
if twice := givenTwice(raw); twice != "" {
|
|
return Manifest{}, fmt.Errorf("the field %s is given twice; a manifest gives each field once", twice)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// givenTwice is the first field an object of raw gives twice, as its path (`invokes`, `build.artifacts[0].env`), or empty.
|
|
func givenTwice(raw []byte) string {
|
|
d := json.NewDecoder(bytes.NewReader(raw))
|
|
var walk func(path string) string
|
|
walk = func(path string) string {
|
|
t, err := d.Token()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
switch t {
|
|
case json.Delim('{'):
|
|
seen := map[string]bool{}
|
|
for d.More() {
|
|
k, err := d.Token()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
key, _ := k.(string)
|
|
at := key
|
|
if path != "" {
|
|
at = path + "." + key
|
|
}
|
|
if seen[key] {
|
|
return at
|
|
}
|
|
seen[key] = true
|
|
if twice := walk(at); twice != "" {
|
|
return twice
|
|
}
|
|
}
|
|
_, _ = d.Token()
|
|
case json.Delim('['):
|
|
for i := 0; d.More(); i++ {
|
|
if twice := walk(fmt.Sprintf("%s[%d]", path, i)); twice != "" {
|
|
return twice
|
|
}
|
|
}
|
|
_, _ = d.Token()
|
|
}
|
|
return ""
|
|
}
|
|
return walk("")
|
|
}
|
|
|
|
// SetAside names what Decode set aside of this manifest: each field it does not know, as the decoder words the first
|
|
// and with the path of every one it read without, or "" when it knew every field. A reader says it in its own words
|
|
// (its journal, its finding) and reads the rest; the part that needs the field takes the newer package (ADR 0578 §6).
|
|
func (m Manifest) SetAside() string { return m.unknown }
|
|
|
|
// ReadNoFurther is whether Decode could not read past a field it does not know, so the manifest holds its name and
|
|
// version alone: a reader holds such a module, as it holds a refused one, and names why (SetAside).
|
|
func (m Manifest) ReadNoFurther() bool { return strings.Contains(m.unknown, readNoFurther) }
|
|
|
|
// readNoFurther marks, in what a manifest set aside, that it was not read past it.
|
|
const readNoFurther = "(read no further: "
|
|
|
|
// Predefined is the list of predefined keys the manifest was read with (ReadTreeManifestWritten, or the judge's
|
|
// SetPredefined), nil when none: data of the contract a judge reads, never a rule.
|
|
func (m Manifest) Predefined() PredefinedList { return m.predefined }
|
|
|
|
// SetPredefined keeps the list of predefined keys a manifest is read with, for what reads it after (the judge).
|
|
func (m *Manifest) SetPredefined(list PredefinedList) { m.predefined = list }
|
|
|
|
// FormsWritten is the fields the manifest's config was written with, in the config form (`config`, `takes-config`,
|
|
// `${config:…}`) and in the settings form (novox/hq ADR 0348 §1): data a judge reads to say a manifest mixes them.
|
|
func (m Manifest) FormsWritten() (config, settings []string) { return m.forms.config, m.forms.settings }
|