jsonschema: resolve anchors
Change-Id: Ibe7d8df0a46d3e3a8b5d0651b1113a54e756c7df Reviewed-on: https://go-review.googlesource.com/c/tools/+/669998 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
0a79e67c62
commit
5cc4e6ea54
3 changed files with 34 additions and 8 deletions
|
|
@ -122,8 +122,7 @@ func (s *Schema) checkLocal(report func(error)) {
|
|||
// See https://json-schema.org/draft/2020-12/json-schema-core#section-8.2, section
|
||||
// 8.2.1.
|
||||
|
||||
// TODO(jba): anchors (section 8.2.2)
|
||||
// TODO(jba): dynamicAnchors (ditto)
|
||||
// TODO(jba): dynamicAnchors (§8.2.2)
|
||||
//
|
||||
// Every schema has a base URI and a parent base URI.
|
||||
//
|
||||
|
|
@ -180,6 +179,18 @@ func resolveURIs(root *Schema, baseURI *url.URL) (map[string]*Schema, error) {
|
|||
base = s // needed for anchors
|
||||
}
|
||||
|
||||
// Anchors are URI fragments that are scoped to their base.
|
||||
// We treat them as keys in a map stored within the schema.
|
||||
if s.Anchor != "" {
|
||||
if base.anchors[s.Anchor] != nil {
|
||||
return fmt.Errorf("duplicate anchor %q in %s", s.Anchor, base.baseURI)
|
||||
}
|
||||
if base.anchors == nil {
|
||||
base.anchors = map[string]*Schema{}
|
||||
}
|
||||
base.anchors[s.Anchor] = s
|
||||
}
|
||||
|
||||
for c := range s.children() {
|
||||
if err := resolve(c, base); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -70,23 +70,36 @@ func TestResolveURIs(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := map[string]*Schema{
|
||||
wantIDs := map[string]*Schema{
|
||||
baseURI: root,
|
||||
"http://b.com/foo.json": root.Items,
|
||||
"http://b.com/bar.json": root.Contains,
|
||||
"http://b.com/bar.json?items": root.Contains.Items.Items,
|
||||
}
|
||||
if baseURI != root.ID {
|
||||
want[root.ID] = root
|
||||
wantIDs[root.ID] = root
|
||||
}
|
||||
wantAnchors := map[*Schema]map[string]*Schema{
|
||||
root.Contains: {"a": root.Contains, "b": root.Contains.Items},
|
||||
root.Contains.Items.Items: {"c": root.Contains.Items.Items},
|
||||
}
|
||||
|
||||
gotKeys := slices.Sorted(maps.Keys(got))
|
||||
wantKeys := slices.Sorted(maps.Keys(want))
|
||||
wantKeys := slices.Sorted(maps.Keys(wantIDs))
|
||||
if !slices.Equal(gotKeys, wantKeys) {
|
||||
t.Errorf("ID keys:\ngot %q\nwant %q", gotKeys, wantKeys)
|
||||
}
|
||||
if !maps.Equal(got, want) {
|
||||
t.Errorf("IDs:\ngot %+v\n\nwant %+v", got, want)
|
||||
if !maps.Equal(got, wantIDs) {
|
||||
t.Errorf("IDs:\ngot %+v\n\nwant %+v", got, wantIDs)
|
||||
}
|
||||
for s := range root.all() {
|
||||
if want := wantAnchors[s]; want != nil {
|
||||
if got := s.anchors; !maps.Equal(got, want) {
|
||||
t.Errorf("anchors:\ngot %+v\n\nwant %+v", got, want)
|
||||
}
|
||||
} else if s.anchors != nil {
|
||||
t.Errorf("non-nil anchors for %s", s)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,9 @@ type Schema struct {
|
|||
// Otherwise, the base URI is the ID resolved against the parent's baseURI.
|
||||
// The parent base URI at top level is where the schema was loaded from, or
|
||||
// if not loaded, then it should be provided to Schema.Resolve.
|
||||
baseURI *url.URL
|
||||
baseURI *url.URL
|
||||
// map from anchors to subschemas
|
||||
anchors map[string]*Schema
|
||||
pattern *regexp.Regexp
|
||||
patternProperties map[*regexp.Regexp]*Schema
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue