@@ -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.
6280func (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.
163179func (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.
383403func (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
0 commit comments