| // Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // author xeipuuv |
| // author-github https://github.com/xeipuuv |
| // author-mail xeipuuv@gmail.com |
| // |
| // repository-name gojsonschema |
| // repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. |
| // |
| // description Defines resources pooling. |
| // Eases referencing and avoids downloading the same resource twice. |
| // |
| // created 26-02-2013 |
| |
| package gojsonschema |
| |
| import ( |
| "errors" |
| "fmt" |
| "reflect" |
| |
| "github.com/xeipuuv/gojsonreference" |
| ) |
| |
| type schemaPoolDocument struct { |
| Document interface{} |
| Draft *Draft |
| } |
| |
| type schemaPool struct { |
| schemaPoolDocuments map[string]*schemaPoolDocument |
| jsonLoaderFactory JSONLoaderFactory |
| autoDetect *bool |
| } |
| |
| func (p *schemaPool) parseReferences(document interface{}, ref gojsonreference.JsonReference, pooled bool) error { |
| |
| var ( |
| draft *Draft |
| err error |
| reference = ref.String() |
| ) |
| // Only the root document should be added to the schema pool if pooled is true |
| if _, ok := p.schemaPoolDocuments[reference]; pooled && ok { |
| return fmt.Errorf("Reference already exists: \"%s\"", reference) |
| } |
| |
| if *p.autoDetect { |
| _, draft, err = parseSchemaURL(document) |
| if err != nil { |
| return err |
| } |
| } |
| |
| err = p.parseReferencesRecursive(document, ref, draft) |
| |
| if pooled { |
| p.schemaPoolDocuments[reference] = &schemaPoolDocument{Document: document, Draft: draft} |
| } |
| |
| return err |
| } |
| |
| func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonreference.JsonReference, draft *Draft) error { |
| // parseReferencesRecursive parses a JSON document and resolves all $id and $ref references. |
| // For $ref references it takes into account the $id scope it is in and replaces |
| // the reference by the absolute resolved reference |
| |
| // When encountering errors it fails silently. Error handling is done when the schema |
| // is syntactically parsed and any error encountered here should also come up there. |
| switch m := document.(type) { |
| case []interface{}: |
| for _, v := range m { |
| p.parseReferencesRecursive(v, ref, draft) |
| } |
| case map[string]interface{}: |
| localRef := &ref |
| |
| keyID := KEY_ID_NEW |
| if existsMapKey(m, KEY_ID) { |
| keyID = KEY_ID |
| } |
| if existsMapKey(m, keyID) && isKind(m[keyID], reflect.String) { |
| jsonReference, err := gojsonreference.NewJsonReference(m[keyID].(string)) |
| if err == nil { |
| localRef, err = ref.Inherits(jsonReference) |
| if err == nil { |
| if _, ok := p.schemaPoolDocuments[localRef.String()]; ok { |
| return fmt.Errorf("Reference already exists: \"%s\"", localRef.String()) |
| } |
| p.schemaPoolDocuments[localRef.String()] = &schemaPoolDocument{Document: document, Draft: draft} |
| } |
| } |
| } |
| |
| if existsMapKey(m, KEY_REF) && isKind(m[KEY_REF], reflect.String) { |
| jsonReference, err := gojsonreference.NewJsonReference(m[KEY_REF].(string)) |
| if err == nil { |
| absoluteRef, err := localRef.Inherits(jsonReference) |
| if err == nil { |
| m[KEY_REF] = absoluteRef.String() |
| } |
| } |
| } |
| |
| for k, v := range m { |
| // const and enums should be interpreted literally, so ignore them |
| if k == KEY_CONST || k == KEY_ENUM { |
| continue |
| } |
| // Something like a property or a dependency is not a valid schema, as it might describe properties named "$ref", "$id" or "const", etc |
| // Therefore don't treat it like a schema. |
| if k == KEY_PROPERTIES || k == KEY_DEPENDENCIES || k == KEY_PATTERN_PROPERTIES { |
| if child, ok := v.(map[string]interface{}); ok { |
| for _, v := range child { |
| p.parseReferencesRecursive(v, *localRef, draft) |
| } |
| } |
| } else { |
| p.parseReferencesRecursive(v, *localRef, draft) |
| } |
| } |
| } |
| return nil |
| } |
| |
| func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) { |
| |
| var ( |
| spd *schemaPoolDocument |
| draft *Draft |
| ok bool |
| err error |
| ) |
| |
| if internalLogEnabled { |
| internalLog("Get Document ( %s )", reference.String()) |
| } |
| |
| // Create a deep copy, so we can remove the fragment part later on without altering the original |
| refToUrl, _ := gojsonreference.NewJsonReference(reference.String()) |
| |
| // First check if the given fragment is a location independent identifier |
| // http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.3 |
| |
| if spd, ok = p.schemaPoolDocuments[refToUrl.String()]; ok { |
| if internalLogEnabled { |
| internalLog(" From pool") |
| } |
| return spd, nil |
| } |
| |
| // If the given reference is not a location independent identifier, |
| // strip the fragment and look for a document with it's base URI |
| |
| refToUrl.GetUrl().Fragment = "" |
| |
| if cachedSpd, ok := p.schemaPoolDocuments[refToUrl.String()]; ok { |
| document, _, err := reference.GetPointer().Get(cachedSpd.Document) |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| if internalLogEnabled { |
| internalLog(" From pool") |
| } |
| |
| spd = &schemaPoolDocument{Document: document, Draft: cachedSpd.Draft} |
| p.schemaPoolDocuments[reference.String()] = spd |
| |
| return spd, nil |
| } |
| |
| // It is not possible to load anything remotely that is not canonical... |
| if !reference.IsCanonical() { |
| return nil, errors.New(formatErrorDescription( |
| Locale.ReferenceMustBeCanonical(), |
| ErrorDetails{"reference": reference.String()}, |
| )) |
| } |
| |
| jsonReferenceLoader := p.jsonLoaderFactory.New(reference.String()) |
| document, err := jsonReferenceLoader.LoadJSON() |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| // add the whole document to the pool for potential re-use |
| p.parseReferences(document, refToUrl, true) |
| |
| _, draft, _ = parseSchemaURL(document) |
| |
| // resolve the potential fragment and also cache it |
| document, _, err = reference.GetPointer().Get(document) |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| return &schemaPoolDocument{Document: document, Draft: draft}, nil |
| } |