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