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 Result and ResultError implementations. 23 // 24 // created 01-01-2015 25 26 package gojsonschema 27 28 import ( 29 "fmt" 30 "strings" 31 ) 32 33 type ( 34 // ErrorDetails is a map of details specific to each error. 35 // While the values will vary, every error will contain a "field" value 36 ErrorDetails map[string]interface{} 37 38 // ResultError is the interface that library errors must implement 39 ResultError interface { 40 // Field returns the field name without the root context 41 // i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName 42 Field() string 43 // SetType sets the error-type 44 SetType(string) 45 // Type returns the error-type 46 Type() string 47 // SetContext sets the JSON-context for the error 48 SetContext(*JsonContext) 49 // Context returns the JSON-context of the error 50 Context() *JsonContext 51 // SetDescription sets a description for the error 52 SetDescription(string) 53 // Description returns the description of the error 54 Description() string 55 // SetDescriptionFormat sets the format for the description in the default text/template format 56 SetDescriptionFormat(string) 57 // DescriptionFormat returns the format for the description in the default text/template format 58 DescriptionFormat() string 59 // SetValue sets the value related to the error 60 SetValue(interface{}) 61 // Value returns the value related to the error 62 Value() interface{} 63 // SetDetails sets the details specific to the error 64 SetDetails(ErrorDetails) 65 // Details returns details about the error 66 Details() ErrorDetails 67 // String returns a string representation of the error 68 String() string 69 } 70 71 // ResultErrorFields holds the fields for each ResultError implementation. 72 // ResultErrorFields implements the ResultError interface, so custom errors 73 // can be defined by just embedding this type 74 ResultErrorFields struct { 75 errorType string // A string with the type of error (i.e. invalid_type) 76 context *JsonContext // Tree like notation of the part that failed the validation. ex (root).a.b ... 77 description string // A human readable error message 78 descriptionFormat string // A format for human readable error message 79 value interface{} // Value given by the JSON file that is the source of the error 80 details ErrorDetails 81 } 82 83 // Result holds the result of a validation 84 Result struct { 85 errors []ResultError 86 // Scores how well the validation matched. Useful in generating 87 // better error messages for anyOf and oneOf. 88 score int 89 } 90 ) 91 92 // Field returns the field name without the root context 93 // i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName 94 func (v *ResultErrorFields) Field() string { 95 return strings.TrimPrefix(v.context.String(), STRING_ROOT_SCHEMA_PROPERTY+".") 96 } 97 98 // SetType sets the error-type 99 func (v *ResultErrorFields) SetType(errorType string) { 100 v.errorType = errorType 101 } 102 103 // Type returns the error-type 104 func (v *ResultErrorFields) Type() string { 105 return v.errorType 106 } 107 108 // SetContext sets the JSON-context for the error 109 func (v *ResultErrorFields) SetContext(context *JsonContext) { 110 v.context = context 111 } 112 113 // Context returns the JSON-context of the error 114 func (v *ResultErrorFields) Context() *JsonContext { 115 return v.context 116 } 117 118 // SetDescription sets a description for the error 119 func (v *ResultErrorFields) SetDescription(description string) { 120 v.description = description 121 } 122 123 // Description returns the description of the error 124 func (v *ResultErrorFields) Description() string { 125 return v.description 126 } 127 128 // SetDescriptionFormat sets the format for the description in the default text/template format 129 func (v *ResultErrorFields) SetDescriptionFormat(descriptionFormat string) { 130 v.descriptionFormat = descriptionFormat 131 } 132 133 // DescriptionFormat returns the format for the description in the default text/template format 134 func (v *ResultErrorFields) DescriptionFormat() string { 135 return v.descriptionFormat 136 } 137 138 // SetValue sets the value related to the error 139 func (v *ResultErrorFields) SetValue(value interface{}) { 140 v.value = value 141 } 142 143 // Value returns the value related to the error 144 func (v *ResultErrorFields) Value() interface{} { 145 return v.value 146 } 147 148 // SetDetails sets the details specific to the error 149 func (v *ResultErrorFields) SetDetails(details ErrorDetails) { 150 v.details = details 151 } 152 153 // Details returns details about the error 154 func (v *ResultErrorFields) Details() ErrorDetails { 155 return v.details 156 } 157 158 // String returns a string representation of the error 159 func (v ResultErrorFields) String() string { 160 // as a fallback, the value is displayed go style 161 valueString := fmt.Sprintf("%v", v.value) 162 163 // marshal the go value value to json 164 if v.value == nil { 165 valueString = TYPE_NULL 166 } else { 167 if vs, err := marshalToJSONString(v.value); err == nil { 168 if vs == nil { 169 valueString = TYPE_NULL 170 } else { 171 valueString = *vs 172 } 173 } 174 } 175 176 return formatErrorDescription(Locale.ErrorFormat(), ErrorDetails{ 177 "context": v.context.String(), 178 "description": v.description, 179 "value": valueString, 180 "field": v.Field(), 181 }) 182 } 183 184 // Valid indicates if no errors were found 185 func (v *Result) Valid() bool { 186 return len(v.errors) == 0 187 } 188 189 // Errors returns the errors that were found 190 func (v *Result) Errors() []ResultError { 191 return v.errors 192 } 193 194 // AddError appends a fully filled error to the error set 195 // SetDescription() will be called with the result of the parsed err.DescriptionFormat() 196 func (v *Result) AddError(err ResultError, details ErrorDetails) { 197 if _, exists := details["context"]; !exists && err.Context() != nil { 198 details["context"] = err.Context().String() 199 } 200 201 err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details)) 202 203 v.errors = append(v.errors, err) 204 } 205 206 func (v *Result) addInternalError(err ResultError, context *JsonContext, value interface{}, details ErrorDetails) { 207 newError(err, context, value, Locale, details) 208 v.errors = append(v.errors, err) 209 v.score -= 2 // results in a net -1 when added to the +1 we get at the end of the validation function 210 } 211 212 // Used to copy errors from a sub-schema to the main one 213 func (v *Result) mergeErrors(otherResult *Result) { 214 v.errors = append(v.errors, otherResult.Errors()...) 215 v.score += otherResult.score 216 } 217 218 func (v *Result) incrementScore() { 219 v.score++ 220 } 221