@@ -29,6 +29,14 @@ type Resolved struct {
2929 resolvedInfo map [* Schema ]* resolvedInfo
3030}
3131
32+ func newResolved (s * Schema ) * Resolved {
33+ return & Resolved {
34+ root : s ,
35+ resolvedURIs : map [string ]* Schema {},
36+ resolvedInfo : map [* Schema ]* resolvedInfo {},
37+ }
38+ }
39+
3240// resolvedInfo holds information specific to a schema that is computed by [Schema.Resolve].
3341type resolvedInfo struct {
3442 s * Schema
@@ -57,12 +65,37 @@ type resolvedInfo struct {
5765 resolvedDynamicRef * Schema
5866 // The anchor to look up on the stack when the dynamic ref acts dynamically.
5967 dynamicRefAnchor string
68+
69+ // The following fields are independent of arguments to Schema.Resolved,
70+ // so they could live on the Schema. We put them here for simplicity.
71+
72+ // The set of required properties.
73+ isRequired map [string ]bool
74+
75+ // Compiled regexps.
76+ pattern * regexp.Regexp
77+ patternProperties map [* regexp.Regexp ]* Schema
78+
79+ // Map from anchors to subschemas.
80+ anchors map [string ]anchorInfo
6081}
6182
6283// Schema returns the schema that was resolved.
6384// It must not be modified.
6485func (r * Resolved ) Schema () * Schema { return r .root }
6586
87+ // schemaString returns a short string describing the schema.
88+ func (r * Resolved ) schemaString (s * Schema ) string {
89+ if s .ID != "" {
90+ return s .ID
91+ }
92+ info := r .resolvedInfo [s ]
93+ if info .path != "" {
94+ return info .path
95+ }
96+ return "<anonymous schema>"
97+ }
98+
6699// A Loader reads and unmarshals the schema at uri, if any.
67100type Loader func (uri * url.URL ) (* Schema , error )
68101
@@ -152,12 +185,9 @@ func (r *resolver) resolve(s *Schema, baseURI *url.URL) (*Resolved, error) {
152185 if baseURI .Fragment != "" {
153186 return nil , fmt .Errorf ("base URI %s must not have a fragment" , baseURI )
154187 }
155- rs := & Resolved {root : s , resolvedInfo :map [* Schema ]* resolvedInfo {}}
156- for s := rs .root .all () {
157- rs .resolvedInfo [s ] = & resolvedInfo {s :s }
158- }
188+ rs := newResolved (s )
159189
160- if err := s .check (); err != nil {
190+ if err := s .check (rs . resolvedInfo ); err != nil {
161191 return nil , err
162192 }
163193
@@ -177,29 +207,27 @@ func (r *resolver) resolve(s *Schema, baseURI *url.URL) (*Resolved, error) {
177207 return rs , nil
178208}
179209
180- func (root * Schema ) check () error {
210+ func (root * Schema ) check (infos map [ * Schema ] * resolvedInfo ) error {
181211 // Check for structural validity. Do this first and fail fast:
182212 // bad structure will cause other code to panic.
183- if err := root .checkStructure (); err != nil {
213+ if err := root .checkStructure (infos ); err != nil {
184214 return err
185215 }
186216
187217 var errs []error
188218 report := func (err error ) { errs = append (errs , err ) }
189219
190220 for ss := range root .all () {
191- ss .checkLocal (report )
221+ ss .checkLocal (report , infos )
192222 }
193223 return errors .Join (errs ... )
194224}
195225
196226// checkStructure verifies that root and its subschemas form a tree.
197227// It also assigns each schema a unique path, to improve error messages.
198- func (root * Schema ) checkStructure () error {
199- if root .path != "" {
200- // We have done this before, and it will always produce the same result.
201- return nil
202- }
228+ func (root * Schema ) checkStructure (infos map [* Schema ]* resolvedInfo ) error {
229+ assert (len (infos ) == 0 , "non-empty infos" )
230+
203231 var check func (reflect.Value , []byte ) error
204232 check = func (v reflect.Value , path []byte ) error {
205233 // For the purpose of error messages, the root schema has path "root"
@@ -212,16 +240,15 @@ func (root *Schema) checkStructure() error {
212240 if s == nil {
213241 return fmt .Errorf ("jsonschema: schema at %s is nil" , p )
214242 }
215- if s . path != "" {
243+ if info , ok := infos [ s ]; ok {
216244 // We've seen s before.
217245 // The schema graph at root is not a tree, but it needs to
218246 // be because a schema's base must be unique.
219- // A cycle would also put Schema.all into an infinite
220- // recursion.
247+ // A cycle would also put Schema.all into an infinite recursion.
221248 return fmt .Errorf ("jsonschema: schemas at %s do not form a tree; %s appears more than once (also at %s)" ,
222- root , s .path , p )
249+ root , info .path , p )
223250 }
224- s . path = p
251+ infos [ s ] = & resolvedInfo { s : s , path : p }
225252
226253 for _ , info := range schemaFieldInfos {
227254 fv := v .Elem ().FieldByIndex (info .sf .Index )
@@ -263,7 +290,7 @@ func (root *Schema) checkStructure() error {
263290// Since checking a regexp involves compiling it, checkLocal saves those compiled regexps
264291// in the schema for later use.
265292// It appends the errors it finds to errs.
266- func (s * Schema ) checkLocal (report func (error )) {
293+ func (s * Schema ) checkLocal (report func (error ), infos map [ * Schema ] * resolvedInfo ) {
267294 addf := func (format string , args ... any ) {
268295 msg := fmt .Sprintf (format , args ... )
269296 report (fmt .Errorf ("jsonschema.Schema: %s: %s" , s , msg ))
@@ -289,33 +316,35 @@ func (s *Schema) checkLocal(report func(error)) {
289316 addf ("cannot validate a schema with $vocabulary" )
290317 }
291318
319+ info := infos [s ]
320+
292321 // Check and compile regexps.
293322 if s .Pattern != "" {
294323 re , err := regexp .Compile (s .Pattern )
295324 if err != nil {
296325 addf ("pattern: %v" , err )
297326 } else {
298- s .pattern = re
327+ info .pattern = re
299328 }
300329 }
301330 if len (s .PatternProperties ) > 0 {
302- s .patternProperties = map [* regexp.Regexp ]* Schema {}
331+ info .patternProperties = map [* regexp.Regexp ]* Schema {}
303332 for reString , subschema := range s .PatternProperties {
304333 re , err := regexp .Compile (reString )
305334 if err != nil {
306335 addf ("patternProperties[%q]: %v" , reString , err )
307336 continue
308337 }
309- s .patternProperties [re ] = subschema
338+ info .patternProperties [re ] = subschema
310339 }
311340 }
312341
313342 // Build a set of required properties, to avoid quadratic behavior when validating
314343 // a struct.
315344 if len (s .Required ) > 0 {
316- s .isRequired = map [string ]bool {}
345+ info .isRequired = map [string ]bool {}
317346 for _ , r := range s .Required {
318- s .isRequired [r ] = true
347+ info .isRequired [r ] = true
319348 }
320349 }
321350}
@@ -356,13 +385,8 @@ func (s *Schema) checkLocal(report func(error)) {
356385func resolveURIs (rs * Resolved , baseURI * url.URL ) error {
357386 var resolve func (s , base * Schema ) error
358387 resolve = func (s , base * Schema ) error {
359- assert (rs .resolvedInfo [base ] != nil , "base resolved info not set" )
360388 info := rs .resolvedInfo [s ]
361- if info == nil {
362- info = & resolvedInfo {s : s }
363- rs .resolvedInfo [s ] = info
364- }
365- baseURI := rs .resolvedInfo [base ].uri
389+ baseInfo := rs .resolvedInfo [base ]
366390
367391 // ids are scoped to the root.
368392 if s .ID != "" {
@@ -375,26 +399,27 @@ func resolveURIs(rs *Resolved, baseURI *url.URL) error {
375399 return fmt .Errorf ("$id %s must not have a fragment" , s .ID )
376400 }
377401 // The base URI for this schema is its $id resolved against the parent base.
378- info .uri = baseURI .ResolveReference (idURI )
402+ info .uri = baseInfo . uri .ResolveReference (idURI )
379403 if ! info .uri .IsAbs () {
380- return fmt .Errorf ("$id %s does not resolve to an absolute URI (base is %s )" , s .ID , baseURI )
404+ return fmt .Errorf ("$id %s does not resolve to an absolute URI (base is %q )" , s .ID , baseInfo . uri )
381405 }
382406 rs .resolvedURIs [info .uri .String ()] = s
383407 base = s // needed for anchors
408+ baseInfo = rs .resolvedInfo [base ]
384409 }
385410 info .base = base
386411
387412 // Anchors and dynamic anchors are URI fragments that are scoped to their base.
388413 // We treat them as keys in a map stored within the schema.
389414 setAnchor := func (anchor string , dynamic bool ) error {
390415 if anchor != "" {
391- if _ , ok := base .anchors [anchor ]; ok {
392- return fmt .Errorf ("duplicate anchor %q in %s" , anchor , baseURI )
416+ if _ , ok := baseInfo .anchors [anchor ]; ok {
417+ return fmt .Errorf ("duplicate anchor %q in %s" , anchor , baseInfo . uri )
393418 }
394- if base .anchors == nil {
395- base .anchors = map [string ]anchorInfo {}
419+ if baseInfo .anchors == nil {
420+ baseInfo .anchors = map [string ]anchorInfo {}
396421 }
397- base .anchors [anchor ] = anchorInfo {s , dynamic }
422+ baseInfo .anchors [anchor ] = anchorInfo {s , dynamic }
398423 }
399424 return nil
400425 }
@@ -411,11 +436,9 @@ func resolveURIs(rs *Resolved, baseURI *url.URL) error {
411436 }
412437
413438 // Set the root URI to the base for now. If the root has an $id, this will change.
414- rs .resolvedInfo = map [* Schema ]* resolvedInfo {
415- rs .root : {s : rs .root , uri : baseURI },
416- }
439+ rs .resolvedInfo [rs .root ].uri = baseURI
417440 // The original base, even if changed, is still a valid way to refer to the root.
418- rs .resolvedURIs = map [ string ] * Schema { baseURI .String (): rs .root }
441+ rs .resolvedURIs [ baseURI .String ()] = rs .root
419442
420443 return resolve (rs .root , rs .root )
421444}
@@ -508,7 +531,9 @@ func (r *resolver) resolveRef(rs *Resolved, s *Schema, ref string) (_ *Schema, d
508531 // A JSON Pointer is either the empty string or begins with a '/',
509532 // whereas anchors are always non-empty strings that don't contain slashes.
510533 if frag != "" && ! strings .HasPrefix (frag , "/" ) {
511- info , found := referencedSchema .anchors [frag ]
534+ resInfo := rs .resolvedInfo [referencedSchema ]
535+ info , found := resInfo .anchors [frag ]
536+
512537 if ! found {
513538 return nil , "" , fmt .Errorf ("no anchor %q in %s" , frag , s )
514539 }
0 commit comments