schema.go RawHistoryFolder
1// SPDX-License-Identifier: MIT
2
3package template
4
5import (
6 "fmt"
7 "strings"
8)
9
10// DefinitionType is the type of a schema [Definition].
11type DefinitionType int
12
13// The definition types.
14const (
15 DefNull DefinitionType = iota
16 DefBoolean
17 DefNumber
18 DefString
19 DefSequence
20 DefMapping
21 DefOneOf
22)
23
24// Property is a well-known key of a mapping definition.
25type Property struct {
26 // Type is the name of the definition of the value.
27 Type string
28 Required bool
29 Description string
30}
31
32// Definition is a named type of the schema. Which fields are meaningful
33// depends on Type.
34type Definition struct {
35 Name string
36 Type DefinitionType
37 Description string
38 // Context lists the named contexts and functions ("github",
39 // "success(0,MAX)") expressions in values of this type may use.
40 Context []string
41
42 // String definitions.
43 Constant string
44 IgnoreCase bool
45 RequireNonEmpty bool
46 IsExpression bool
47
48 // Sequence definitions.
49 ItemType string
50
51 // Mapping definitions: Properties in schema order.
52 PropertyNames []string
53 Properties map[string]Property
54 LooseKeyType string
55 LooseValueType string
56
57 // One-of definitions: the names of the alternatives.
58 OneOf []string
59}
60
61// Schema is a set of named definitions.
62type Schema struct {
63 Version string
64 definitions map[string]*Definition
65}
66
67// Definition returns the definition called name, or nil.
68func (s *Schema) Definition(name string) *Definition { return s.definitions[name] }
69
70// get is the reference's getDefinition, which throws when the name is unknown.
71func (s *Schema) get(name string) *Definition {
72 d := s.definitions[name]
73 if d == nil {
74 fatalf("Schema definition '%s' not found", name)
75 }
76 return d
77}
78
79// scalarDefinitions expands one-of definitions and returns all scalar ones.
80func (s *Schema) scalarDefinitions(d *Definition) []*Definition {
81 switch d.Type {
82 case DefNull, DefBoolean, DefNumber, DefString:
83 return []*Definition{d}
84 case DefOneOf:
85 var out []*Definition
86 for _, n := range d.OneOf {
87 out = append(out, s.scalarDefinitions(s.get(n))...)
88 }
89 return out
90 }
91 return nil
92}
93
94// definitionsOfType expands a one-of definition one level and returns the
95// alternatives of type t.
96func (s *Schema) definitionsOfType(d *Definition, t DefinitionType) []*Definition {
97 if d.Type == t {
98 return []*Definition{d}
99 }
100 var out []*Definition
101 if d.Type == DefOneOf {
102 for _, n := range d.OneOf {
103 if nd := s.get(n); nd.Type == t {
104 out = append(out, nd)
105 }
106 }
107 }
108 return out
109}
110
111// isMatch reports whether a literal token is accepted by a scalar definition.
112func (d *Definition) isMatch(t Token) bool {
113 switch d.Type {
114 case DefNull:
115 return t.Kind() == KindNull
116 case DefBoolean:
117 return t.Kind() == KindBoolean
118 case DefNumber:
119 return t.Kind() == KindNumber
120 case DefString:
121 s, ok := t.(*String)
122 if !ok {
123 return false
124 }
125 switch {
126 case d.Constant != "":
127 if d.IgnoreCase {
128 return strings.EqualFold(d.Constant, s.Value)
129 }
130 return d.Constant == s.Value
131 case d.RequireNonEmpty:
132 return s.Value != ""
133 }
134 return true
135 }
136 return false
137}
138
139// matchPropertyAndFilter looks for a property called name in any of defs; when
140// found in some only, defs is narrowed to those. It returns the property, if
141// any, and the possibly narrowed list.
142func matchPropertyAndFilter(defs []*Definition, name string) (*Property, []*Definition) {
143 var result *Property
144 notFoundInSome := false
145 for _, d := range defs {
146 if p, ok := d.Properties[name]; ok {
147 p := p
148 result = &p
149 } else {
150 notFoundInSome = true
151 }
152 }
153 if result != nil && notFoundInSome {
154 kept := defs[:0:0]
155 for _, d := range defs {
156 if _, ok := d.Properties[name]; ok {
157 kept = append(kept, d)
158 }
159 }
160 defs = kept
161 }
162 return result, defs
163}
164
165// fatal is the panic value used for errors that abort a read, which the
166// reference throws as exceptions.
167type fatal struct{ msg string }
168
169func fatalf(format string, a ...any) { panic(fatal{fmt.Sprintf(format, a...)}) }