1package gojsonschema
2
3import (
4	"bytes"
5	"sync"
6	"text/template"
7)
8
9var errorTemplates errorTemplate = errorTemplate{template.New("errors-new"), sync.RWMutex{}}
10
11// template.Template is not thread-safe for writing, so some locking is done
12// sync.RWMutex is used for efficiently locking when new templates are created
13type errorTemplate struct {
14	*template.Template
15	sync.RWMutex
16}
17
18type (
19	// RequiredError. ErrorDetails: property string
20	RequiredError struct {
21		ResultErrorFields
22	}
23
24	// InvalidTypeError. ErrorDetails: expected, given
25	InvalidTypeError struct {
26		ResultErrorFields
27	}
28
29	// NumberAnyOfError. ErrorDetails: -
30	NumberAnyOfError struct {
31		ResultErrorFields
32	}
33
34	// NumberOneOfError. ErrorDetails: -
35	NumberOneOfError struct {
36		ResultErrorFields
37	}
38
39	// NumberAllOfError. ErrorDetails: -
40	NumberAllOfError struct {
41		ResultErrorFields
42	}
43
44	// NumberNotError. ErrorDetails: -
45	NumberNotError struct {
46		ResultErrorFields
47	}
48
49	// MissingDependencyError. ErrorDetails: dependency
50	MissingDependencyError struct {
51		ResultErrorFields
52	}
53
54	// InternalError. ErrorDetails: error
55	InternalError struct {
56		ResultErrorFields
57	}
58
59	// ConstError. ErrorDetails: allowed
60	ConstError struct {
61		ResultErrorFields
62	}
63
64	// EnumError. ErrorDetails: allowed
65	EnumError struct {
66		ResultErrorFields
67	}
68
69	// ArrayNoAdditionalItemsError. ErrorDetails: -
70	ArrayNoAdditionalItemsError struct {
71		ResultErrorFields
72	}
73
74	// ArrayMinItemsError. ErrorDetails: min
75	ArrayMinItemsError struct {
76		ResultErrorFields
77	}
78
79	// ArrayMaxItemsError. ErrorDetails: max
80	ArrayMaxItemsError struct {
81		ResultErrorFields
82	}
83
84	// ItemsMustBeUniqueError. ErrorDetails: type, i, j
85	ItemsMustBeUniqueError struct {
86		ResultErrorFields
87	}
88
89	// ArrayContainsError. ErrorDetails:
90	ArrayContainsError struct {
91		ResultErrorFields
92	}
93
94	// ArrayMinPropertiesError. ErrorDetails: min
95	ArrayMinPropertiesError struct {
96		ResultErrorFields
97	}
98
99	// ArrayMaxPropertiesError. ErrorDetails: max
100	ArrayMaxPropertiesError struct {
101		ResultErrorFields
102	}
103
104	// AdditionalPropertyNotAllowedError. ErrorDetails: property
105	AdditionalPropertyNotAllowedError struct {
106		ResultErrorFields
107	}
108
109	// InvalidPropertyPatternError. ErrorDetails: property, pattern
110	InvalidPropertyPatternError struct {
111		ResultErrorFields
112	}
113
114	// InvalidPopertyNameError. ErrorDetails: property
115	InvalidPropertyNameError struct {
116		ResultErrorFields
117	}
118
119	// StringLengthGTEError. ErrorDetails: min
120	StringLengthGTEError struct {
121		ResultErrorFields
122	}
123
124	// StringLengthLTEError. ErrorDetails: max
125	StringLengthLTEError struct {
126		ResultErrorFields
127	}
128
129	// DoesNotMatchPatternError. ErrorDetails: pattern
130	DoesNotMatchPatternError struct {
131		ResultErrorFields
132	}
133
134	// DoesNotMatchFormatError. ErrorDetails: format
135	DoesNotMatchFormatError struct {
136		ResultErrorFields
137	}
138
139	// MultipleOfError. ErrorDetails: multiple
140	MultipleOfError struct {
141		ResultErrorFields
142	}
143
144	// NumberGTEError. ErrorDetails: min
145	NumberGTEError struct {
146		ResultErrorFields
147	}
148
149	// NumberGTError. ErrorDetails: min
150	NumberGTError struct {
151		ResultErrorFields
152	}
153
154	// NumberLTEError. ErrorDetails: max
155	NumberLTEError struct {
156		ResultErrorFields
157	}
158
159	// NumberLTError. ErrorDetails: max
160	NumberLTError struct {
161		ResultErrorFields
162	}
163
164	// ConditionThenError. ErrorDetails: -
165	ConditionThenError struct {
166		ResultErrorFields
167	}
168
169	// ConditionElseError. ErrorDetails: -
170	ConditionElseError struct {
171		ResultErrorFields
172	}
173)
174
175// newError takes a ResultError type and sets the type, context, description, details, value, and field
176func newError(err ResultError, context *JsonContext, value interface{}, locale locale, details ErrorDetails) {
177	var t string
178	var d string
179	switch err.(type) {
180	case *RequiredError:
181		t = "required"
182		d = locale.Required()
183	case *InvalidTypeError:
184		t = "invalid_type"
185		d = locale.InvalidType()
186	case *NumberAnyOfError:
187		t = "number_any_of"
188		d = locale.NumberAnyOf()
189	case *NumberOneOfError:
190		t = "number_one_of"
191		d = locale.NumberOneOf()
192	case *NumberAllOfError:
193		t = "number_all_of"
194		d = locale.NumberAllOf()
195	case *NumberNotError:
196		t = "number_not"
197		d = locale.NumberNot()
198	case *MissingDependencyError:
199		t = "missing_dependency"
200		d = locale.MissingDependency()
201	case *InternalError:
202		t = "internal"
203		d = locale.Internal()
204	case *ConstError:
205		t = "const"
206		d = locale.Const()
207	case *EnumError:
208		t = "enum"
209		d = locale.Enum()
210	case *ArrayNoAdditionalItemsError:
211		t = "array_no_additional_items"
212		d = locale.ArrayNoAdditionalItems()
213	case *ArrayMinItemsError:
214		t = "array_min_items"
215		d = locale.ArrayMinItems()
216	case *ArrayMaxItemsError:
217		t = "array_max_items"
218		d = locale.ArrayMaxItems()
219	case *ItemsMustBeUniqueError:
220		t = "unique"
221		d = locale.Unique()
222	case *ArrayContainsError:
223		t = "contains"
224		d = locale.ArrayContains()
225	case *ArrayMinPropertiesError:
226		t = "array_min_properties"
227		d = locale.ArrayMinProperties()
228	case *ArrayMaxPropertiesError:
229		t = "array_max_properties"
230		d = locale.ArrayMaxProperties()
231	case *AdditionalPropertyNotAllowedError:
232		t = "additional_property_not_allowed"
233		d = locale.AdditionalPropertyNotAllowed()
234	case *InvalidPropertyPatternError:
235		t = "invalid_property_pattern"
236		d = locale.InvalidPropertyPattern()
237	case *InvalidPropertyNameError:
238		t = "invalid_property_name"
239		d = locale.InvalidPropertyName()
240	case *StringLengthGTEError:
241		t = "string_gte"
242		d = locale.StringGTE()
243	case *StringLengthLTEError:
244		t = "string_lte"
245		d = locale.StringLTE()
246	case *DoesNotMatchPatternError:
247		t = "pattern"
248		d = locale.DoesNotMatchPattern()
249	case *DoesNotMatchFormatError:
250		t = "format"
251		d = locale.DoesNotMatchFormat()
252	case *MultipleOfError:
253		t = "multiple_of"
254		d = locale.MultipleOf()
255	case *NumberGTEError:
256		t = "number_gte"
257		d = locale.NumberGTE()
258	case *NumberGTError:
259		t = "number_gt"
260		d = locale.NumberGT()
261	case *NumberLTEError:
262		t = "number_lte"
263		d = locale.NumberLTE()
264	case *NumberLTError:
265		t = "number_lt"
266		d = locale.NumberLT()
267	case *ConditionThenError:
268		t = "condition_then"
269		d = locale.ConditionThen()
270	case *ConditionElseError:
271		t = "condition_else"
272		d = locale.ConditionElse()
273	}
274
275	err.SetType(t)
276	err.SetContext(context)
277	err.SetValue(value)
278	err.SetDetails(details)
279	err.SetDescriptionFormat(d)
280	details["field"] = err.Field()
281
282	if _, exists := details["context"]; !exists && context != nil {
283		details["context"] = context.String()
284	}
285
286	err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details))
287}
288
289// formatErrorDescription takes a string in the default text/template
290// format and converts it to a string with replacements. The fields come
291// from the ErrorDetails struct and vary for each type of error.
292func formatErrorDescription(s string, details ErrorDetails) string {
293
294	var tpl *template.Template
295	var descrAsBuffer bytes.Buffer
296	var err error
297
298	errorTemplates.RLock()
299	tpl = errorTemplates.Lookup(s)
300	errorTemplates.RUnlock()
301
302	if tpl == nil {
303		errorTemplates.Lock()
304		tpl = errorTemplates.New(s)
305
306		if ErrorTemplateFuncs != nil {
307			tpl.Funcs(ErrorTemplateFuncs)
308		}
309
310		tpl, err = tpl.Parse(s)
311		errorTemplates.Unlock()
312
313		if err != nil {
314			return err.Error()
315		}
316	}
317
318	err = tpl.Execute(&descrAsBuffer, details)
319	if err != nil {
320		return err.Error()
321	}
322
323	return descrAsBuffer.String()
324}
325