...

Source file src/github.com/xeipuuv/gojsonschema/schemaPool.go

Documentation: github.com/xeipuuv/gojsonschema

     1  // Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //   http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // author           xeipuuv
    16  // author-github    https://github.com/xeipuuv
    17  // author-mail      xeipuuv@gmail.com
    18  //
    19  // repository-name  gojsonschema
    20  // repository-desc  An implementation of JSON Schema, based on IETF's draft v4 - Go language.
    21  //
    22  // description		Defines resources pooling.
    23  //                  Eases referencing and avoids downloading the same resource twice.
    24  //
    25  // created          26-02-2013
    26  
    27  package gojsonschema
    28  
    29  import (
    30  	"errors"
    31  	"fmt"
    32  	"reflect"
    33  
    34  	"github.com/xeipuuv/gojsonreference"
    35  )
    36  
    37  type schemaPoolDocument struct {
    38  	Document interface{}
    39  	Draft    *Draft
    40  }
    41  
    42  type schemaPool struct {
    43  	schemaPoolDocuments map[string]*schemaPoolDocument
    44  	jsonLoaderFactory   JSONLoaderFactory
    45  	autoDetect          *bool
    46  }
    47  
    48  func (p *schemaPool) parseReferences(document interface{}, ref gojsonreference.JsonReference, pooled bool) error {
    49  
    50  	var (
    51  		draft     *Draft
    52  		err       error
    53  		reference = ref.String()
    54  	)
    55  	// Only the root document should be added to the schema pool if pooled is true
    56  	if _, ok := p.schemaPoolDocuments[reference]; pooled && ok {
    57  		return fmt.Errorf("Reference already exists: \"%s\"", reference)
    58  	}
    59  
    60  	if *p.autoDetect {
    61  		_, draft, err = parseSchemaURL(document)
    62  		if err != nil {
    63  			return err
    64  		}
    65  	}
    66  
    67  	err = p.parseReferencesRecursive(document, ref, draft)
    68  
    69  	if pooled {
    70  		p.schemaPoolDocuments[reference] = &schemaPoolDocument{Document: document, Draft: draft}
    71  	}
    72  
    73  	return err
    74  }
    75  
    76  func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonreference.JsonReference, draft *Draft) error {
    77  	// parseReferencesRecursive parses a JSON document and resolves all $id and $ref references.
    78  	// For $ref references it takes into account the $id scope it is in and replaces
    79  	// the reference by the absolute resolved reference
    80  
    81  	// When encountering errors it fails silently. Error handling is done when the schema
    82  	// is syntactically parsed and any error encountered here should also come up there.
    83  	switch m := document.(type) {
    84  	case []interface{}:
    85  		for _, v := range m {
    86  			p.parseReferencesRecursive(v, ref, draft)
    87  		}
    88  	case map[string]interface{}:
    89  		localRef := &ref
    90  
    91  		keyID := KEY_ID_NEW
    92  		if existsMapKey(m, KEY_ID) {
    93  			keyID = KEY_ID
    94  		}
    95  		if existsMapKey(m, keyID) && isKind(m[keyID], reflect.String) {
    96  			jsonReference, err := gojsonreference.NewJsonReference(m[keyID].(string))
    97  			if err == nil {
    98  				localRef, err = ref.Inherits(jsonReference)
    99  				if err == nil {
   100  					if _, ok := p.schemaPoolDocuments[localRef.String()]; ok {
   101  						return fmt.Errorf("Reference already exists: \"%s\"", localRef.String())
   102  					}
   103  					p.schemaPoolDocuments[localRef.String()] = &schemaPoolDocument{Document: document, Draft: draft}
   104  				}
   105  			}
   106  		}
   107  
   108  		if existsMapKey(m, KEY_REF) && isKind(m[KEY_REF], reflect.String) {
   109  			jsonReference, err := gojsonreference.NewJsonReference(m[KEY_REF].(string))
   110  			if err == nil {
   111  				absoluteRef, err := localRef.Inherits(jsonReference)
   112  				if err == nil {
   113  					m[KEY_REF] = absoluteRef.String()
   114  				}
   115  			}
   116  		}
   117  
   118  		for k, v := range m {
   119  			// const and enums should be interpreted literally, so ignore them
   120  			if k == KEY_CONST || k == KEY_ENUM {
   121  				continue
   122  			}
   123  			// Something like a property or a dependency is not a valid schema, as it might describe properties named "$ref", "$id" or "const", etc
   124  			// Therefore don't treat it like a schema.
   125  			if k == KEY_PROPERTIES || k == KEY_DEPENDENCIES || k == KEY_PATTERN_PROPERTIES {
   126  				if child, ok := v.(map[string]interface{}); ok {
   127  					for _, v := range child {
   128  						p.parseReferencesRecursive(v, *localRef, draft)
   129  					}
   130  				}
   131  			} else {
   132  				p.parseReferencesRecursive(v, *localRef, draft)
   133  			}
   134  		}
   135  	}
   136  	return nil
   137  }
   138  
   139  func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) {
   140  
   141  	var (
   142  		spd   *schemaPoolDocument
   143  		draft *Draft
   144  		ok    bool
   145  		err   error
   146  	)
   147  
   148  	if internalLogEnabled {
   149  		internalLog("Get Document ( %s )", reference.String())
   150  	}
   151  
   152  	// Create a deep copy, so we can remove the fragment part later on without altering the original
   153  	refToURL, _ := gojsonreference.NewJsonReference(reference.String())
   154  
   155  	// First check if the given fragment is a location independent identifier
   156  	// http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.3
   157  
   158  	if spd, ok = p.schemaPoolDocuments[refToURL.String()]; ok {
   159  		if internalLogEnabled {
   160  			internalLog(" From pool")
   161  		}
   162  		return spd, nil
   163  	}
   164  
   165  	// If the given reference is not a location independent identifier,
   166  	// strip the fragment and look for a document with it's base URI
   167  
   168  	refToURL.GetUrl().Fragment = ""
   169  
   170  	if cachedSpd, ok := p.schemaPoolDocuments[refToURL.String()]; ok {
   171  		document, _, err := reference.GetPointer().Get(cachedSpd.Document)
   172  
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  
   177  		if internalLogEnabled {
   178  			internalLog(" From pool")
   179  		}
   180  
   181  		spd = &schemaPoolDocument{Document: document, Draft: cachedSpd.Draft}
   182  		p.schemaPoolDocuments[reference.String()] = spd
   183  
   184  		return spd, nil
   185  	}
   186  
   187  	// It is not possible to load anything remotely that is not canonical...
   188  	if !reference.IsCanonical() {
   189  		return nil, errors.New(formatErrorDescription(
   190  			Locale.ReferenceMustBeCanonical(),
   191  			ErrorDetails{"reference": reference.String()},
   192  		))
   193  	}
   194  
   195  	jsonReferenceLoader := p.jsonLoaderFactory.New(reference.String())
   196  	document, err := jsonReferenceLoader.LoadJSON()
   197  
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	// add the whole document to the pool for potential re-use
   203  	p.parseReferences(document, refToURL, true)
   204  
   205  	_, draft, _ = parseSchemaURL(document)
   206  
   207  	// resolve the potential fragment and also cache it
   208  	document, _, err = reference.GetPointer().Get(document)
   209  
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	return &schemaPoolDocument{Document: document, Draft: draft}, nil
   215  }
   216  

View as plain text