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 }