Skip to content

Commit 5350ef0

Browse files
committed
remove 3 fields
1 parent 9b6327b commit 5350ef0

File tree

4 files changed

+59
-35
lines changed

4 files changed

+59
-35
lines changed

jsonschema/resolve.go

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ type Resolved struct {
2525
root *Schema
2626
// map from $ids to their schemas
2727
resolvedURIs map[string]*Schema
28+
// map from schemas to additional info computed during resolution
29+
resolvedInfo map[*Schema]*resolvedInfo
30+
}
31+
32+
// resolvedInfo holds information specific to a schema that is computed by [Schema.Resolve].
33+
type resolvedInfo struct {
34+
s *Schema
35+
// The schema to which Ref refers.
36+
resolvedRef *Schema
37+
38+
// If the schema has a dynamic ref, exactly one of the next two fields
39+
// will be non-zero after successful resolution.
40+
// The schema to which the dynamic ref refers when it acts lexically.
41+
resolvedDynamicRef *Schema
42+
// The anchor to look up on the stack when the dynamic ref acts dynamically.
43+
dynamicRefAnchor string
2844
}
2945

3046
// Schema returns the schema that was resolved.
@@ -59,6 +75,8 @@ type ResolveOptions struct {
5975
// Resolve resolves all references within the schema and performs other tasks that
6076
// prepare the schema for validation.
6177
// If opts is nil, the default values are used.
78+
// The schema must not be changed after Resolve is called.
79+
// The same schema may be resolved multiple times.
6280
func (root *Schema) Resolve(opts *ResolveOptions) (*Resolved, error) {
6381
// There are up to five steps required to prepare a schema to validate.
6482
// 1. Load: read the schema from somewhere and unmarshal it.
@@ -71,9 +89,6 @@ func (root *Schema) Resolve(opts *ResolveOptions) (*Resolved, error) {
7189
// in a map from URIs to schemas within root.
7290
// 4. Resolve references: all refs in the schemas are replaced with the schema they refer to.
7391
// 5. (Optional.) If opts.ValidateDefaults is true, validate the defaults.
74-
if root.path != "" {
75-
return nil, fmt.Errorf("jsonschema: Resolve: %s already resolved", root)
76-
}
7792
r := &resolver{loaded: map[string]*Resolved{}}
7893
if opts != nil {
7994
r.opts = *opts
@@ -129,7 +144,8 @@ func (r *resolver) resolve(s *Schema, baseURI *url.URL) (*Resolved, error) {
129144
if err != nil {
130145
return nil, err
131146
}
132-
rs := &Resolved{root: s, resolvedURIs: m}
147+
rs := &Resolved{root: s, resolvedURIs: m, resolvedInfo: map[*Schema]*resolvedInfo{}}
148+
133149
// Remember the schema by both the URI we loaded it from and its canonical name,
134150
// which may differ if the schema has an $id.
135151
// We must set the map before calling resolveRefs, or ref cycles will cause unbounded recursion.
@@ -161,6 +177,10 @@ func (root *Schema) check() error {
161177
// checkStructure verifies that root and its subschemas form a tree.
162178
// It also assigns each schema a unique path, to improve error messages.
163179
func (root *Schema) checkStructure() error {
180+
if root.path != "" {
181+
// We have done this before, and it will always produce the same result.
182+
return nil
183+
}
164184
var check func(reflect.Value, []byte) error
165185
check = func(v reflect.Value, path []byte) error {
166186
// For the purpose of error messages, the root schema has path "root"
@@ -382,14 +402,17 @@ func resolveURIs(root *Schema, baseURI *url.URL) (map[string]*Schema, error) {
382402
// that needs to be loaded.
383403
func (r *resolver) resolveRefs(rs *Resolved) error {
384404
for s := range rs.root.all() {
405+
assert(rs.resolvedInfo[s] == nil, "schema resolved info already set")
406+
info := &resolvedInfo{s: s}
407+
rs.resolvedInfo[s] = info
385408
if s.Ref != "" {
386409
refSchema, _, err := r.resolveRef(rs, s, s.Ref)
387410
if err != nil {
388411
return err
389412
}
390413
// Whether or not the anchor referred to by $ref fragment is dynamic,
391414
// the ref still treats it lexically.
392-
s.resolvedRef = refSchema
415+
info.resolvedRef = refSchema
393416
}
394417
if s.DynamicRef != "" {
395418
refSchema, frag, err := r.resolveRef(rs, s, s.DynamicRef)
@@ -399,11 +422,11 @@ func (r *resolver) resolveRefs(rs *Resolved) error {
399422
if frag != "" {
400423
// The dynamic ref's fragment points to a dynamic anchor.
401424
// We must resolve the fragment at validation time.
402-
s.dynamicRefAnchor = frag
425+
info.dynamicRefAnchor = frag
403426
} else {
404427
// There is no dynamic anchor in the lexically referenced schema,
405428
// so the dynamic ref behaves like a lexical ref.
406-
s.resolvedDynamicRef = refSchema
429+
info.resolvedDynamicRef = refSchema
407430
}
408431
}
409432
}
@@ -447,6 +470,13 @@ func (r *resolver) resolveRef(rs *Resolved, s *Schema, ref string) (_ *Schema, d
447470
}
448471
referencedSchema = lrs.root
449472
assert(referencedSchema != nil, "nil referenced schema")
473+
// Copy the resolvedInfos from lrs into rs, without overwriting
474+
// (hence we can't use maps.Insert).
475+
for s, i := range lrs.resolvedInfo {
476+
if rs.resolvedInfo[s] == nil {
477+
rs.resolvedInfo[s] = i
478+
}
479+
}
450480
}
451481
}
452482

jsonschema/resolve_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,14 @@ func TestRefCycle(t *testing.T) {
193193
}
194194

195195
rs, err := schemas["root"].Resolve(&ResolveOptions{Loader: loader})
196+
t.Logf("%#v", rs.resolvedInfo)
196197
if err != nil {
197198
t.Fatal(err)
198199
}
199200

200201
check := func(s *Schema, key string) {
201202
t.Helper()
202-
if s.resolvedRef != schemas[key] {
203+
if rs.resolvedInfo[s].resolvedRef != schemas[key] {
203204
t.Errorf("%s resolvedRef != schemas[%q]", s.json(), key)
204205
}
205206
}

jsonschema/schema.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -151,16 +151,6 @@ type Schema struct {
151151
// Used in errors.
152152
path string
153153

154-
// The schema to which Ref refers.
155-
resolvedRef *Schema
156-
157-
// If the schema has a dynamic ref, exactly one of the next two fields
158-
// will be non-zero after successful resolution.
159-
// The schema to which the dynamic ref refers when it acts lexically.
160-
resolvedDynamicRef *Schema
161-
// The anchor to look up on the stack when the dynamic ref acts dynamically.
162-
dynamicRefAnchor string
163-
164154
// Map from anchors to subschemas.
165155
anchors map[string]anchorInfo
166156

@@ -200,13 +190,13 @@ func (s *Schema) String() string {
200190
return "<anonymous schema>"
201191
}
202192

203-
// ResolvedRef returns the Schema to which this schema's $ref keyword
204-
// refers, or nil if it doesn't have a $ref.
205-
// It returns nil if this schema has not been resolved, meaning that
206-
// [Schema.Resolve] was called on it or one of its ancestors.
207-
func (s *Schema) ResolvedRef() *Schema {
208-
return s.resolvedRef
209-
}
193+
// // ResolvedRef returns the Schema to which this schema's $ref keyword
194+
// // refers, or nil if it doesn't have a $ref.
195+
// // It returns nil if this schema has not been resolved, meaning that
196+
// // [Schema.Resolve] was called on it or one of its ancestors.
197+
// func (s *Schema) ResolvedRef() *Schema {
198+
// return s.resolvedRef
199+
// }
210200

211201
func (s *Schema) basicChecks() error {
212202
if s.Type != "" && s.Types != nil {

jsonschema/validate.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
9090
instance = instance.Elem()
9191
}
9292

93+
info := st.rs.resolvedInfo[schema]
94+
9395
// type: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.1
9496
if schema.Type != "" || schema.Types != nil {
9597
gotType, ok := jsonType(instance)
@@ -185,19 +187,19 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
185187

186188
// $ref: https://json-schema.org/draft/2020-12/json-schema-core#section-8.2.3.1
187189
if schema.Ref != "" {
188-
if err := st.validate(instance, schema.resolvedRef, &anns); err != nil {
190+
if err := st.validate(instance, info.resolvedRef, &anns); err != nil {
189191
return err
190192
}
191193
}
192194

193195
// $dynamicRef: https://json-schema.org/draft/2020-12/json-schema-core#section-8.2.3.2
194196
if schema.DynamicRef != "" {
195197
// The ref behaves lexically or dynamically, but not both.
196-
assert((schema.resolvedDynamicRef == nil) != (schema.dynamicRefAnchor == ""),
198+
assert((info.resolvedDynamicRef == nil) != (info.dynamicRefAnchor == ""),
197199
"DynamicRef not resolved properly")
198-
if schema.resolvedDynamicRef != nil {
200+
if info.resolvedDynamicRef != nil {
199201
// Same as $ref.
200-
if err := st.validate(instance, schema.resolvedDynamicRef, &anns); err != nil {
202+
if err := st.validate(instance, info.resolvedDynamicRef, &anns); err != nil {
201203
return err
202204
}
203205
} else {
@@ -212,14 +214,14 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
212214
// For an example, search for "detached" in testdata/draft2020-12/dynamicRef.json.
213215
var dynamicSchema *Schema
214216
for _, s := range st.stack {
215-
info, ok := s.base.anchors[schema.dynamicRefAnchor]
217+
info, ok := s.base.anchors[info.dynamicRefAnchor]
216218
if ok && info.dynamic {
217219
dynamicSchema = info.schema
218220
break
219221
}
220222
}
221223
if dynamicSchema == nil {
222-
return fmt.Errorf("missing dynamic anchor %q", schema.dynamicRefAnchor)
224+
return fmt.Errorf("missing dynamic anchor %q", info.dynamicRefAnchor)
223225
}
224226
if err := st.validate(instance, dynamicSchema, &anns); err != nil {
225227
return err
@@ -554,10 +556,11 @@ func (st *state) resolveDynamicRef(schema *Schema) (*Schema, error) {
554556
if schema.DynamicRef == "" {
555557
return nil, nil
556558
}
559+
info := st.rs.resolvedInfo[schema]
557560
// The ref behaves lexically or dynamically, but not both.
558-
assert((schema.resolvedDynamicRef == nil) != (schema.dynamicRefAnchor == ""),
561+
assert((info.resolvedDynamicRef == nil) != (info.dynamicRefAnchor == ""),
559562
"DynamicRef not statically resolved properly")
560-
if r := schema.resolvedDynamicRef; r != nil {
563+
if r := info.resolvedDynamicRef; r != nil {
561564
// Same as $ref.
562565
return r, nil
563566
}
@@ -571,12 +574,12 @@ func (st *state) resolveDynamicRef(schema *Schema) (*Schema, error) {
571574
// on the stack.
572575
// For an example, search for "detached" in testdata/draft2020-12/dynamicRef.json.
573576
for _, s := range st.stack {
574-
info, ok := s.base.anchors[schema.dynamicRefAnchor]
577+
info, ok := s.base.anchors[info.dynamicRefAnchor]
575578
if ok && info.dynamic {
576579
return info.schema, nil
577580
}
578581
}
579-
return nil, fmt.Errorf("missing dynamic anchor %q", schema.dynamicRefAnchor)
582+
return nil, fmt.Errorf("missing dynamic anchor %q", info.dynamicRefAnchor)
580583
}
581584

582585
// ApplyDefaults modifies an instance by applying the schema's defaults to it. If

0 commit comments

Comments
 (0)