1// Copyright 2018 johandorland ( https://github.com/johandorland ) 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 15package gojsonschema 16 17import ( 18 "bytes" 19 "errors" 20 21 "github.com/xeipuuv/gojsonreference" 22) 23 24// SchemaLoader is used to load schemas 25type SchemaLoader struct { 26 pool *schemaPool 27 AutoDetect bool 28 Validate bool 29 Draft Draft 30} 31 32// NewSchemaLoader creates a new NewSchemaLoader 33func NewSchemaLoader() *SchemaLoader { 34 35 ps := &SchemaLoader{ 36 pool: &schemaPool{ 37 schemaPoolDocuments: make(map[string]*schemaPoolDocument), 38 }, 39 AutoDetect: true, 40 Validate: false, 41 Draft: Hybrid, 42 } 43 ps.pool.autoDetect = &ps.AutoDetect 44 45 return ps 46} 47 48func (sl *SchemaLoader) validateMetaschema(documentNode interface{}) error { 49 50 var ( 51 schema string 52 err error 53 ) 54 if sl.AutoDetect { 55 schema, _, err = parseSchemaURL(documentNode) 56 if err != nil { 57 return err 58 } 59 } 60 61 // If no explicit "$schema" is used, use the default metaschema associated with the draft used 62 if schema == "" { 63 if sl.Draft == Hybrid { 64 return nil 65 } 66 schema = drafts.GetSchemaURL(sl.Draft) 67 } 68 69 //Disable validation when loading the metaschema to prevent an infinite recursive loop 70 sl.Validate = false 71 72 metaSchema, err := sl.Compile(NewReferenceLoader(schema)) 73 74 if err != nil { 75 return err 76 } 77 78 sl.Validate = true 79 80 result := metaSchema.validateDocument(documentNode) 81 82 if !result.Valid() { 83 var res bytes.Buffer 84 for _, err := range result.Errors() { 85 res.WriteString(err.String()) 86 res.WriteString("\n") 87 } 88 return errors.New(res.String()) 89 } 90 91 return nil 92} 93 94// AddSchemas adds an arbritrary amount of schemas to the schema cache. As this function does not require 95// an explicit URL, every schema should contain an $id, so that it can be referenced by the main schema 96func (sl *SchemaLoader) AddSchemas(loaders ...JSONLoader) error { 97 emptyRef, _ := gojsonreference.NewJsonReference("") 98 99 for _, loader := range loaders { 100 doc, err := loader.LoadJSON() 101 102 if err != nil { 103 return err 104 } 105 106 if sl.Validate { 107 if err := sl.validateMetaschema(doc); err != nil { 108 return err 109 } 110 } 111 112 // Directly use the Recursive function, so that it get only added to the schema pool by $id 113 // and not by the ref of the document as it's empty 114 if err = sl.pool.parseReferences(doc, emptyRef, false); err != nil { 115 return err 116 } 117 } 118 119 return nil 120} 121 122//AddSchema adds a schema under the provided URL to the schema cache 123func (sl *SchemaLoader) AddSchema(url string, loader JSONLoader) error { 124 125 ref, err := gojsonreference.NewJsonReference(url) 126 127 if err != nil { 128 return err 129 } 130 131 doc, err := loader.LoadJSON() 132 133 if err != nil { 134 return err 135 } 136 137 if sl.Validate { 138 if err := sl.validateMetaschema(doc); err != nil { 139 return err 140 } 141 } 142 143 return sl.pool.parseReferences(doc, ref, true) 144} 145 146// Compile loads and compiles a schema 147func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) { 148 149 ref, err := rootSchema.JsonReference() 150 151 if err != nil { 152 return nil, err 153 } 154 155 d := Schema{} 156 d.pool = sl.pool 157 d.pool.jsonLoaderFactory = rootSchema.LoaderFactory() 158 d.documentReference = ref 159 d.referencePool = newSchemaReferencePool() 160 161 var doc interface{} 162 if ref.String() != "" { 163 // Get document from schema pool 164 spd, err := d.pool.GetDocument(d.documentReference) 165 if err != nil { 166 return nil, err 167 } 168 doc = spd.Document 169 } else { 170 // Load JSON directly 171 doc, err = rootSchema.LoadJSON() 172 if err != nil { 173 return nil, err 174 } 175 // References need only be parsed if loading JSON directly 176 // as pool.GetDocument already does this for us if loading by reference 177 err = sl.pool.parseReferences(doc, ref, true) 178 if err != nil { 179 return nil, err 180 } 181 } 182 183 if sl.Validate { 184 if err := sl.validateMetaschema(doc); err != nil { 185 return nil, err 186 } 187 } 188 189 draft := sl.Draft 190 if sl.AutoDetect { 191 _, detectedDraft, err := parseSchemaURL(doc) 192 if err != nil { 193 return nil, err 194 } 195 if detectedDraft != nil { 196 draft = *detectedDraft 197 } 198 } 199 200 err = d.parse(doc, draft) 201 if err != nil { 202 return nil, err 203 } 204 205 return &d, nil 206} 207