github.com/kaptinlin/jsonschema@v0.4.6/ref.go (about) 1 package jsonschema 2 3 import ( 4 "net/url" 5 "strconv" 6 "strings" 7 ) 8 9 // resolveRef resolves a reference to another schema, either locally or globally, supporting both $ref and $dynamicRef. 10 func (s *Schema) resolveRef(ref string) (*Schema, error) { 11 if ref == "#" { 12 return s.getRootSchema(), nil 13 } 14 15 if strings.HasPrefix(ref, "#") { 16 return s.resolveAnchor(ref[1:]) 17 } 18 19 // Resolve the full URL if ref is a relative URL 20 if !isAbsoluteURI(ref) && s.baseURI != "" { 21 ref = resolveRelativeURI(s.baseURI, ref) 22 } 23 24 // Handle full URL references 25 return s.resolveRefWithFullURL(ref) 26 } 27 28 func (s *Schema) resolveAnchor(anchorName string) (*Schema, error) { 29 var schema *Schema 30 var err error 31 32 if strings.HasPrefix(anchorName, "/") { 33 schema, err = s.resolveJSONPointer(anchorName) 34 } else { 35 if schema, ok := s.anchors[anchorName]; ok { 36 return schema, nil 37 } 38 39 if schema, ok := s.dynamicAnchors[anchorName]; ok { 40 return schema, nil 41 } 42 } 43 44 if schema == nil && s.parent != nil { 45 return s.parent.resolveAnchor(anchorName) 46 } 47 48 return schema, err 49 } 50 51 // resolveRefWithFullURL resolves a full URL reference to another schema. 52 func (s *Schema) resolveRefWithFullURL(ref string) (*Schema, error) { 53 root := s.getRootSchema() 54 if resolved, err := root.getSchema(ref); err == nil { 55 return resolved, nil 56 } 57 58 // If not found in the current schema or its parents, look for the reference in the compiler 59 if resolved, err := s.GetCompiler().GetSchema(ref); err != nil { 60 return nil, ErrFailedToResolveGlobalReference 61 } else { 62 return resolved, nil 63 } 64 } 65 66 // resolveJSONPointer resolves a JSON Pointer within the schema based on JSON Schema structure. 67 func (s *Schema) resolveJSONPointer(pointer string) (*Schema, error) { 68 if pointer == "/" { 69 return s, nil 70 } 71 72 segments := strings.Split(strings.TrimPrefix(pointer, "/"), "/") 73 currentSchema := s 74 previousSegment := "" 75 76 for i, segment := range segments { 77 decodedSegment, err := url.PathUnescape(strings.ReplaceAll(strings.ReplaceAll(segment, "~1", "/"), "~0", "~")) 78 if err != nil { 79 return nil, ErrFailedToDecodeSegmentWithJSONPointer 80 } 81 82 nextSchema, found := findSchemaInSegment(currentSchema, decodedSegment, previousSegment) 83 if found { 84 currentSchema = nextSchema 85 previousSegment = decodedSegment // Update the context for the next iteration 86 continue 87 } 88 89 if !found && i == len(segments)-1 { 90 // If no schema is found and it's the last segment, throw error 91 return nil, ErrSegmentNotFoundForJSONPointer 92 } 93 94 previousSegment = decodedSegment // Update the context for the next iteration 95 } 96 97 return currentSchema, nil 98 } 99 100 // Helper function to find a schema within a given segment 101 func findSchemaInSegment(currentSchema *Schema, segment string, previousSegment string) (*Schema, bool) { 102 switch previousSegment { 103 case "properties": 104 if currentSchema.Properties != nil { 105 if schema, exists := (*currentSchema.Properties)[segment]; exists { 106 return schema, true 107 } 108 } 109 case "prefixItems": 110 index, err := strconv.Atoi(segment) 111 112 if err == nil && currentSchema.PrefixItems != nil && index < len(currentSchema.PrefixItems) { 113 return currentSchema.PrefixItems[index], true 114 } 115 case "$defs": 116 if defSchema, exists := currentSchema.Defs[segment]; exists { 117 return defSchema, true 118 } 119 case "items": 120 if currentSchema.Items != nil { 121 return currentSchema.Items, true 122 } 123 } 124 return nil, false 125 } 126 127 // ResolveUnresolvedReferences tries to resolve any previously unresolved references 128 // This is called after new schemas are added to the compiler 129 func (s *Schema) ResolveUnresolvedReferences() { 130 // Try to resolve unresolved $ref 131 if s.Ref != "" && s.ResolvedRef == nil { 132 if resolved, err := s.resolveRef(s.Ref); err == nil { 133 s.ResolvedRef = resolved 134 } 135 } 136 137 // Try to resolve unresolved $dynamicRef 138 if s.DynamicRef != "" && s.ResolvedDynamicRef == nil { 139 if resolved, err := s.resolveRef(s.DynamicRef); err == nil { 140 s.ResolvedDynamicRef = resolved 141 } 142 } 143 144 // Recursively resolve references within definitions 145 if s.Defs != nil { 146 for _, defSchema := range s.Defs { 147 defSchema.ResolveUnresolvedReferences() 148 } 149 } 150 151 // Recursively resolve references in properties 152 if s.Properties != nil { 153 for _, schema := range *s.Properties { 154 if schema != nil { 155 schema.ResolveUnresolvedReferences() 156 } 157 } 158 } 159 160 // Additional fields that can have subschemas 161 resolveUnresolvedInList(s.AllOf) 162 resolveUnresolvedInList(s.AnyOf) 163 resolveUnresolvedInList(s.OneOf) 164 if s.Not != nil { 165 s.Not.ResolveUnresolvedReferences() 166 } 167 if s.Items != nil { 168 s.Items.ResolveUnresolvedReferences() 169 } 170 if s.PrefixItems != nil { 171 for _, schema := range s.PrefixItems { 172 schema.ResolveUnresolvedReferences() 173 } 174 } 175 176 if s.AdditionalProperties != nil { 177 s.AdditionalProperties.ResolveUnresolvedReferences() 178 } 179 if s.Contains != nil { 180 s.Contains.ResolveUnresolvedReferences() 181 } 182 if s.PatternProperties != nil { 183 for _, schema := range *s.PatternProperties { 184 schema.ResolveUnresolvedReferences() 185 } 186 } 187 } 188 189 func (s *Schema) resolveReferences() { 190 // Resolve the root reference if this schema itself is a reference 191 if s.Ref != "" { 192 resolved, _ := s.resolveRef(s.Ref) // Resolve against root schema 193 s.ResolvedRef = resolved 194 } 195 196 if s.DynamicRef != "" { 197 resolved, _ := s.resolveRef(s.DynamicRef) // Resolve dynamic references against root schema 198 s.ResolvedDynamicRef = resolved 199 } 200 201 // Recursively resolve references within definitions 202 if s.Defs != nil { 203 for _, defSchema := range s.Defs { 204 defSchema.resolveReferences() 205 } 206 } 207 208 // Recursively resolve references in properties 209 if s.Properties != nil { 210 for _, schema := range *s.Properties { 211 if schema != nil { 212 schema.resolveReferences() 213 } 214 } 215 } 216 217 // Additional fields that can have subschemas 218 resolveSubschemaList(s.AllOf) 219 resolveSubschemaList(s.AnyOf) 220 resolveSubschemaList(s.OneOf) 221 if s.Not != nil { 222 s.Not.resolveReferences() 223 } 224 if s.Items != nil { 225 s.Items.resolveReferences() 226 } 227 if s.PrefixItems != nil { 228 for _, schema := range s.PrefixItems { 229 schema.resolveReferences() 230 } 231 } 232 233 if s.AdditionalProperties != nil { 234 s.AdditionalProperties.resolveReferences() 235 } 236 if s.Contains != nil { 237 s.Contains.resolveReferences() 238 } 239 if s.PatternProperties != nil { 240 for _, schema := range *s.PatternProperties { 241 schema.resolveReferences() 242 } 243 } 244 } 245 246 // Helper function to resolve references in a list of schemas 247 func resolveSubschemaList(schemas []*Schema) { 248 for _, schema := range schemas { 249 if schema != nil { 250 schema.resolveReferences() 251 } 252 } 253 } 254 255 // Helper function to resolve unresolved references in a list of schemas 256 func resolveUnresolvedInList(schemas []*Schema) { 257 for _, schema := range schemas { 258 if schema != nil { 259 schema.ResolveUnresolvedReferences() 260 } 261 } 262 } 263 264 // GetUnresolvedReferenceURIs returns a list of URIs that this schema references but are not yet resolved 265 func (s *Schema) GetUnresolvedReferenceURIs() []string { 266 var unresolvedURIs []string 267 268 // Check direct references 269 if s.Ref != "" && s.ResolvedRef == nil { 270 unresolvedURIs = append(unresolvedURIs, s.Ref) 271 } 272 273 if s.DynamicRef != "" && s.ResolvedDynamicRef == nil { 274 unresolvedURIs = append(unresolvedURIs, s.DynamicRef) 275 } 276 277 // Recursively check nested schemas 278 if s.Defs != nil { 279 for _, defSchema := range s.Defs { 280 unresolvedURIs = append(unresolvedURIs, defSchema.GetUnresolvedReferenceURIs()...) 281 } 282 } 283 284 if s.Properties != nil { 285 for _, propSchema := range *s.Properties { 286 if propSchema != nil { 287 unresolvedURIs = append(unresolvedURIs, propSchema.GetUnresolvedReferenceURIs()...) 288 } 289 } 290 } 291 292 // Check other schema fields 293 unresolvedURIs = append(unresolvedURIs, getUnresolvedFromList(s.AllOf)...) 294 unresolvedURIs = append(unresolvedURIs, getUnresolvedFromList(s.AnyOf)...) 295 unresolvedURIs = append(unresolvedURIs, getUnresolvedFromList(s.OneOf)...) 296 297 if s.Not != nil { 298 unresolvedURIs = append(unresolvedURIs, s.Not.GetUnresolvedReferenceURIs()...) 299 } 300 301 if s.Items != nil { 302 unresolvedURIs = append(unresolvedURIs, s.Items.GetUnresolvedReferenceURIs()...) 303 } 304 305 if s.PrefixItems != nil { 306 for _, schema := range s.PrefixItems { 307 unresolvedURIs = append(unresolvedURIs, schema.GetUnresolvedReferenceURIs()...) 308 } 309 } 310 311 if s.AdditionalProperties != nil { 312 unresolvedURIs = append(unresolvedURIs, s.AdditionalProperties.GetUnresolvedReferenceURIs()...) 313 } 314 315 if s.Contains != nil { 316 unresolvedURIs = append(unresolvedURIs, s.Contains.GetUnresolvedReferenceURIs()...) 317 } 318 319 if s.PatternProperties != nil { 320 for _, schema := range *s.PatternProperties { 321 unresolvedURIs = append(unresolvedURIs, schema.GetUnresolvedReferenceURIs()...) 322 } 323 } 324 325 return unresolvedURIs 326 } 327 328 // Helper function to get unresolved references from a list of schemas 329 func getUnresolvedFromList(schemas []*Schema) []string { 330 var unresolvedURIs []string 331 for _, schema := range schemas { 332 if schema != nil { 333 unresolvedURIs = append(unresolvedURIs, schema.GetUnresolvedReferenceURIs()...) 334 } 335 } 336 return unresolvedURIs 337 }