1 | package validation |
||
2 | |||
3 | import ( |
||
4 | "bytes" |
||
5 | "encoding/json" |
||
6 | "errors" |
||
7 | "fmt" |
||
8 | "io" |
||
9 | "strconv" |
||
10 | "strings" |
||
11 | |||
12 | "golang.org/x/text/language" |
||
13 | ) |
||
14 | |||
15 | // Violation is the abstraction for validator errors. You can use your own implementations on the application |
||
16 | // side to use it for your needs. In order for the validator to generate application violations, |
||
17 | // it is necessary to implement the [ViolationFactory] interface and inject it into the validator. |
||
18 | // You can do this by using the [SetViolationFactory] option in the [NewValidator] constructor. |
||
19 | type Violation interface { |
||
20 | error |
||
21 | // Unwrap returns underlying static error. This error can be used as a unique, short, and semantic code |
||
22 | // that can be used to test for specific violation by [errors.Is] from standard library. |
||
23 | Unwrap() error |
||
24 | |||
25 | // Is can be used to check that the violation contains one of the specific static errors. |
||
26 | Is(target error) bool |
||
27 | |||
28 | // Message is a translated message with injected values from constraint. It can be used to show |
||
29 | // a description of a violation to the end-user. Possible values for build-in constraints |
||
30 | // are defined in the [github.com/muonsoft/validation/message] package and can be changed at any time, |
||
31 | // even in patch versions. |
||
32 | Message() string |
||
33 | |||
34 | // MessageTemplate is a template for rendering message. Alongside parameters it can be used to |
||
35 | // render the message on the client-side of the library. |
||
36 | MessageTemplate() string |
||
37 | |||
38 | // Parameters is the map of the template variables and their values provided by the specific constraint. |
||
39 | Parameters() []TemplateParameter |
||
40 | |||
41 | // PropertyPath is a path that points to the violated property. |
||
42 | // See [PropertyPath] type description for more info. |
||
43 | PropertyPath() *PropertyPath |
||
44 | } |
||
45 | |||
46 | // ViolationFactory is the abstraction that can be used to create custom violations on the application side. |
||
47 | // Use the [SetViolationFactory] option on the [NewValidator] constructor to inject your own factory into the validator. |
||
48 | type ViolationFactory interface { |
||
49 | // CreateViolation creates a new instance of [Violation]. |
||
50 | CreateViolation( |
||
51 | err error, |
||
52 | messageTemplate string, |
||
53 | pluralCount int, |
||
54 | parameters []TemplateParameter, |
||
55 | propertyPath *PropertyPath, |
||
56 | lang language.Tag, |
||
57 | ) Violation |
||
58 | } |
||
59 | |||
60 | // NewViolationFunc is an adapter that allows you to use ordinary functions as a [ViolationFactory]. |
||
61 | type NewViolationFunc func( |
||
62 | err error, |
||
63 | messageTemplate string, |
||
64 | pluralCount int, |
||
65 | parameters []TemplateParameter, |
||
66 | propertyPath *PropertyPath, |
||
67 | lang language.Tag, |
||
68 | ) Violation |
||
69 | |||
70 | // CreateViolation creates a new instance of a [Violation]. |
||
71 | func (f NewViolationFunc) CreateViolation( |
||
72 | err error, |
||
73 | messageTemplate string, |
||
74 | pluralCount int, |
||
75 | parameters []TemplateParameter, |
||
76 | propertyPath *PropertyPath, |
||
77 | lang language.Tag, |
||
78 | ) Violation { |
||
79 | return f(err, messageTemplate, pluralCount, parameters, propertyPath, lang) |
||
80 | 1 | } |
|
81 | |||
82 | // ViolationList is a linked list of violations. It is the usual type of error that is returned from a validator. |
||
83 | type ViolationList struct { |
||
84 | len int |
||
85 | first *ViolationListElement |
||
86 | last *ViolationListElement |
||
87 | } |
||
88 | |||
89 | // ViolationListElement points to violation build by validator. It also implements |
||
90 | // [Violation] and can be used as a proxy to underlying violation. |
||
91 | type ViolationListElement struct { |
||
92 | next *ViolationListElement |
||
93 | violation Violation |
||
94 | } |
||
95 | |||
96 | // NewViolationList creates a new [ViolationList], that can be immediately populated with |
||
97 | // variadic arguments of violations. |
||
98 | func NewViolationList(violations ...Violation) *ViolationList { |
||
99 | list := &ViolationList{} |
||
100 | 1 | list.Append(violations...) |
|
101 | 1 | ||
102 | return list |
||
103 | 1 | } |
|
104 | |||
105 | // Len returns length of the linked list. |
||
106 | func (list *ViolationList) Len() int { |
||
107 | if list == nil { |
||
108 | 1 | return 0 |
|
109 | 1 | } |
|
110 | |||
111 | return list.len |
||
112 | 1 | } |
|
113 | |||
114 | // ForEach can be used to iterate over [ViolationList] by a callback function. If callback returns |
||
115 | // any error, then it will be returned as a result of ForEach function. |
||
116 | func (list *ViolationList) ForEach(f func(i int, violation Violation) error) error { |
||
117 | if list == nil { |
||
118 | 1 | return nil |
|
119 | 1 | } |
|
120 | 1 | ||
121 | 1 | i := 0 |
|
122 | 1 | for e := list.first; e != nil; e = e.next { |
|
123 | err := f(i, e.violation) |
||
124 | 1 | if err != nil { |
|
125 | return err |
||
126 | } |
||
127 | 1 | i++ |
|
128 | } |
||
129 | |||
130 | return nil |
||
131 | } |
||
132 | 1 | ||
133 | // First returns the first element of the linked list. |
||
134 | func (list *ViolationList) First() *ViolationListElement { |
||
135 | return list.first |
||
136 | } |
||
137 | 1 | ||
138 | // Last returns the last element of the linked list. |
||
139 | func (list *ViolationList) Last() *ViolationListElement { |
||
140 | return list.last |
||
141 | } |
||
142 | 1 | ||
143 | 1 | // Append appends violations to the end of the linked list. |
|
144 | 1 | func (list *ViolationList) Append(violations ...Violation) { |
|
145 | 1 | for i := range violations { |
|
146 | 1 | element := &ViolationListElement{violation: violations[i]} |
|
147 | if list.first == nil { |
||
148 | 1 | list.first = element |
|
149 | 1 | list.last = element |
|
150 | } else { |
||
151 | list.last.next = element |
||
152 | list.last = element |
||
153 | 1 | } |
|
154 | } |
||
155 | |||
156 | list.len += len(violations) |
||
157 | } |
||
158 | 1 | ||
159 | 1 | // Join is used to append the given violation list to the end of the current list. |
|
160 | func (list *ViolationList) Join(violations *ViolationList) { |
||
161 | if violations == nil || violations.len == 0 { |
||
162 | 1 | return |
|
163 | 1 | } |
|
164 | 1 | ||
165 | if list.first == nil { |
||
166 | 1 | list.first = violations.first |
|
167 | 1 | list.last = violations.last |
|
168 | } else { |
||
169 | list.last.next = violations.first |
||
170 | 1 | list.last = violations.last |
|
171 | } |
||
172 | |||
173 | list.len += violations.len |
||
174 | } |
||
175 | 1 | ||
176 | 1 | // Error returns a formatted list of violations as a string. |
|
177 | func (list *ViolationList) Error() string { |
||
178 | if list == nil || list.len == 0 { |
||
179 | 1 | return "the list of violations is empty, it looks like you forgot to use the AsError method somewhere" |
|
180 | 1 | } |
|
181 | |||
182 | 1 | return list.String() |
|
183 | 1 | } |
|
184 | 1 | ||
185 | 1 | // String converts list of violations into a string. |
|
186 | 1 | func (list *ViolationList) String() string { |
|
187 | return list.toString(" ") |
||
188 | 1 | } |
|
189 | 1 | ||
190 | // Format formats the list of violations according to the [fmt.Formatter] interface. |
||
191 | // Verbs '%v', '%s', '%q' formats violation list into a single line string delimited by space. |
||
192 | // Verb with flag '%+v' formats violation list into a multi-line string. |
||
193 | 1 | func (list *ViolationList) Format(f fmt.State, verb rune) { |
|
194 | switch verb { |
||
195 | case 'v': |
||
196 | 1 | if f.Flag('+') { |
|
197 | _, _ = io.WriteString(f, list.toString("\n\t")) |
||
198 | } else { |
||
199 | _, _ = io.WriteString(f, list.toString(" ")) |
||
200 | } |
||
201 | case 's', 'q': |
||
202 | _, _ = io.WriteString(f, list.toString(" ")) |
||
203 | 1 | } |
|
204 | 1 | } |
|
205 | 1 | ||
206 | 1 | func (list *ViolationList) toString(delimiter string) string { |
|
207 | 1 | if list == nil || list.len == 0 { |
|
208 | 1 | return "" |
|
209 | } |
||
210 | if list.len == 1 { |
||
211 | 1 | return list.first.violation.Error() |
|
212 | } |
||
213 | |||
214 | var s strings.Builder |
||
215 | s.Grow(32 * list.len) |
||
216 | s.WriteString("violations:") |
||
217 | 1 | ||
218 | 1 | i := 0 |
|
219 | 1 | for e := list.first; e != nil; e = e.next { |
|
220 | v := e.violation |
||
221 | if i > 0 { |
||
222 | s.WriteString(";") |
||
223 | 1 | } |
|
224 | s.WriteString(delimiter) |
||
225 | s.WriteString("#" + strconv.Itoa(i)) |
||
226 | if v.PropertyPath() != nil { |
||
227 | s.WriteString(` at "` + v.PropertyPath().String() + `"`) |
||
228 | 1 | } |
|
229 | s.WriteString(`: "` + v.Message() + `"`) |
||
230 | 1 | i++ |
|
231 | 1 | } |
|
232 | 1 | ||
233 | return s.String() |
||
234 | } |
||
235 | |||
236 | 1 | // AppendFromError appends a single violation or a slice of violations into the end of a given slice. |
|
237 | // If an error does not implement the [Violation] or [ViolationList] interface, it will return an error itself. |
||
238 | // Otherwise nil will be returned. |
||
239 | func (list *ViolationList) AppendFromError(err error) error { |
||
240 | if violation, ok := UnwrapViolation(err); ok { |
||
241 | list.Append(violation) |
||
242 | 1 | } else if violationList, ok := UnwrapViolationList(err); ok { |
|
243 | 1 | list.Join(violationList) |
|
244 | } else if err != nil { |
||
245 | return err |
||
246 | 1 | } |
|
247 | |||
248 | return nil |
||
249 | } |
||
250 | |||
251 | 1 | // Is used to check that at least one of the violations contains the specific static error. |
|
252 | func (list *ViolationList) Is(target error) bool { |
||
253 | 1 | for e := list.first; e != nil; e = e.next { |
|
254 | 1 | if e.violation.Is(target) { |
|
255 | 1 | return true |
|
256 | 1 | } |
|
257 | } |
||
258 | |||
259 | 1 | return false |
|
260 | } |
||
261 | |||
262 | // Filter returns a new list of violations with violations of given codes. |
||
263 | func (list *ViolationList) Filter(errs ...error) *ViolationList { |
||
264 | filtered := &ViolationList{} |
||
265 | 1 | ||
266 | 1 | for e := list.first; e != nil; e = e.next { |
|
267 | 1 | for _, err := range errs { |
|
268 | 1 | if e.violation.Is(err) { |
|
269 | 1 | filtered.Append(e.violation) |
|
270 | 1 | } |
|
271 | } |
||
272 | } |
||
273 | 1 | ||
274 | 1 | return filtered |
|
275 | 1 | } |
|
276 | |||
277 | 1 | // AsError converts the list of violations to an error. This method correctly handles cases where |
|
278 | // the list of violations is empty. It returns nil on an empty list, indicating that the validation was successful. |
||
279 | 1 | func (list *ViolationList) AsError() error { |
|
280 | if list == nil || list.len == 0 { |
||
281 | 1 | return nil |
|
282 | } |
||
283 | |||
284 | return list |
||
285 | } |
||
286 | 1 | ||
287 | // AsSlice converts underlying linked list into slice of [Violation]. |
||
288 | func (list *ViolationList) AsSlice() []Violation { |
||
289 | violations := make([]Violation, list.len) |
||
290 | |||
291 | 1 | i := 0 |
|
292 | for e := list.first; e != nil; e = e.next { |
||
293 | violations[i] = e.violation |
||
294 | i++ |
||
295 | 1 | } |
|
296 | |||
297 | return violations |
||
298 | } |
||
299 | 1 | ||
300 | // MarshalJSON marshals the linked list into JSON. Usually, you should use |
||
301 | // [json.Marshal] function for marshaling purposes. |
||
302 | func (list *ViolationList) MarshalJSON() ([]byte, error) { |
||
303 | b := bytes.Buffer{} |
||
304 | b.WriteRune('[') |
||
305 | i := 0 |
||
306 | for e := list.first; e != nil; e = e.next { |
||
307 | 1 | data, err := json.Marshal(e.violation) |
|
308 | if err != nil { |
||
309 | return nil, fmt.Errorf("marshal violation at %d: %w", i, err) |
||
310 | } |
||
311 | b.Write(data) |
||
312 | if e.next != nil { |
||
313 | b.WriteRune(',') |
||
314 | } |
||
315 | i++ |
||
316 | } |
||
317 | b.WriteRune(']') |
||
318 | |||
319 | 1 | return b.Bytes(), nil |
|
320 | } |
||
321 | |||
322 | // Next returns the next element of the linked list. |
||
323 | func (element *ViolationListElement) Next() *ViolationListElement { |
||
324 | 1 | return element.next |
|
325 | } |
||
326 | 1 | ||
327 | // Violation returns underlying violation value. |
||
328 | func (element *ViolationListElement) Violation() Violation { |
||
329 | return element.violation |
||
330 | } |
||
331 | 1 | ||
332 | // Unwrap returns underlying static error. This error can be used as a unique, short, and semantic code |
||
333 | 1 | // that can be used to test for specific violation by [errors.Is] from standard library. |
|
334 | func (element *ViolationListElement) Unwrap() error { |
||
335 | return element.violation.Unwrap() |
||
336 | } |
||
337 | |||
338 | 1 | func (element *ViolationListElement) Error() string { |
|
339 | return element.violation.Error() |
||
340 | 1 | } |
|
341 | |||
342 | 1 | // Is can be used to check that the violation contains one of the specific static errors. |
|
343 | func (element *ViolationListElement) Is(target error) bool { |
||
344 | return element.violation.Is(target) |
||
345 | } |
||
346 | |||
347 | 1 | func (element *ViolationListElement) Message() string { |
|
348 | return element.violation.Message() |
||
349 | 1 | } |
|
350 | |||
351 | 1 | func (element *ViolationListElement) MessageTemplate() string { |
|
352 | return element.violation.MessageTemplate() |
||
353 | } |
||
354 | |||
355 | func (element *ViolationListElement) Parameters() []TemplateParameter { |
||
356 | return element.violation.Parameters() |
||
357 | } |
||
358 | |||
359 | func (element *ViolationListElement) PropertyPath() *PropertyPath { |
||
360 | return element.violation.PropertyPath() |
||
361 | } |
||
362 | |||
363 | 1 | // IsViolation can be used to verify that the error implements the [Violation] interface. |
|
364 | 1 | func IsViolation(err error) bool { |
|
365 | 1 | var violation Violation |
|
366 | |||
367 | return errors.As(err, &violation) |
||
368 | } |
||
369 | 1 | ||
370 | // IsViolationList can be used to verify that the error implements the [ViolationList]. |
||
371 | func IsViolationList(err error) bool { |
||
372 | var violations *ViolationList |
||
373 | 1 | ||
374 | 1 | return errors.As(err, &violations) |
|
375 | 1 | } |
|
376 | |||
377 | 1 | // UnwrapViolation is a short function to unwrap [Violation] from the error. |
|
378 | func UnwrapViolation(err error) (Violation, bool) { |
||
379 | var violation Violation |
||
380 | |||
381 | 1 | as := errors.As(err, &violation) |
|
382 | 1 | ||
383 | 1 | return violation, as |
|
384 | } |
||
385 | 1 | ||
386 | // UnwrapViolationList is a short function to unwrap [ViolationList] from the error. |
||
387 | func UnwrapViolationList(err error) (*ViolationList, bool) { |
||
388 | var violations *ViolationList |
||
389 | 1 | ||
390 | as := errors.As(err, &violations) |
||
391 | |||
392 | return violations, as |
||
393 | 1 | } |
|
394 | |||
395 | type internalViolation struct { |
||
396 | err error |
||
397 | message string |
||
398 | messageTemplate string |
||
399 | parameters []TemplateParameter |
||
400 | propertyPath *PropertyPath |
||
401 | } |
||
402 | |||
403 | func (v *internalViolation) Unwrap() error { |
||
404 | return v.err |
||
405 | 1 | } |
|
406 | |||
407 | func (v *internalViolation) Is(target error) bool { |
||
408 | return errors.Is(v.err, target) |
||
409 | 1 | } |
|
410 | |||
411 | func (v *internalViolation) Error() string { |
||
412 | var s strings.Builder |
||
413 | s.Grow(32) |
||
414 | v.writeToBuilder(&s) |
||
415 | |||
416 | return s.String() |
||
417 | } |
||
418 | |||
419 | func (v *internalViolation) writeToBuilder(s *strings.Builder) { |
||
420 | s.WriteString("violation") |
||
421 | if v.propertyPath != nil { |
||
422 | s.WriteString(` at "` + v.propertyPath.String() + `"`) |
||
423 | } |
||
424 | s.WriteString(`: "` + v.message + `"`) |
||
425 | 1 | } |
|
426 | |||
427 | func (v *internalViolation) Message() string { return v.message } |
||
428 | func (v *internalViolation) MessageTemplate() string { return v.messageTemplate } |
||
429 | func (v *internalViolation) Parameters() []TemplateParameter { return v.parameters } |
||
430 | func (v *internalViolation) PropertyPath() *PropertyPath { return v.propertyPath } |
||
431 | |||
432 | func (v *internalViolation) MarshalJSON() ([]byte, error) { |
||
433 | data := struct { |
||
434 | Error string `json:"error,omitempty"` |
||
435 | Message string `json:"message"` |
||
436 | 1 | PropertyPath *PropertyPath `json:"propertyPath,omitempty"` |
|
437 | }{ |
||
438 | 1 | Message: v.message, |
|
439 | 1 | PropertyPath: v.propertyPath, |
|
440 | 1 | } |
|
441 | if v.err != nil { |
||
442 | data.Error = v.err.Error() |
||
443 | } |
||
444 | 1 | ||
445 | return json.Marshal(data) |
||
446 | } |
||
447 | |||
448 | // BuiltinViolationFactory used as a default factory for creating a violations. |
||
449 | // It translates and renders message templates. |
||
450 | type BuiltinViolationFactory struct { |
||
451 | translator Translator |
||
452 | } |
||
453 | |||
454 | // NewViolationFactory creates a new [BuiltinViolationFactory] for creating a violations. |
||
455 | func NewViolationFactory(translator Translator) *BuiltinViolationFactory { |
||
456 | return &BuiltinViolationFactory{translator: translator} |
||
457 | } |
||
458 | |||
459 | // CreateViolation creates a new instance of [Violation]. |
||
460 | func (factory *BuiltinViolationFactory) CreateViolation( |
||
461 | err error, |
||
462 | messageTemplate string, |
||
463 | pluralCount int, |
||
464 | parameters []TemplateParameter, |
||
465 | propertyPath *PropertyPath, |
||
466 | lang language.Tag, |
||
467 | 1 | ) Violation { |
|
468 | message := factory.translator.Translate(lang, messageTemplate, pluralCount) |
||
469 | |||
470 | for i := range parameters { |
||
471 | if parameters[i].NeedsTranslation { |
||
472 | 1 | parameters[i].Value = factory.translator.Translate(lang, parameters[i].Value, 0) |
|
473 | } |
||
474 | } |
||
475 | |||
476 | return &internalViolation{ |
||
477 | err: err, |
||
478 | message: renderMessage(message, parameters), |
||
479 | messageTemplate: messageTemplate, |
||
480 | parameters: parameters, |
||
481 | 1 | propertyPath: propertyPath, |
|
482 | } |
||
483 | 1 | } |
|
484 | |||
485 | // ViolationBuilder used to build an instance of a [Violation]. |
||
486 | type ViolationBuilder struct { |
||
487 | err error |
||
488 | 1 | messageTemplate string |
|
489 | pluralCount int |
||
490 | 1 | parameters []TemplateParameter |
|
491 | propertyPath *PropertyPath |
||
492 | language language.Tag |
||
493 | |||
494 | violationFactory ViolationFactory |
||
495 | 1 | } |
|
496 | |||
497 | 1 | // NewViolationBuilder creates a new [ViolationBuilder]. |
|
498 | func NewViolationBuilder(factory ViolationFactory) *ViolationBuilder { |
||
499 | return &ViolationBuilder{violationFactory: factory} |
||
500 | } |
||
501 | |||
502 | 1 | // BuildViolation creates a new [ViolationBuilder] for composing [Violation] object fluently. |
|
503 | func (b *ViolationBuilder) BuildViolation(err error, message string) *ViolationBuilder { |
||
504 | 1 | return &ViolationBuilder{ |
|
505 | err: err, |
||
506 | messageTemplate: message, |
||
507 | violationFactory: b.violationFactory, |
||
508 | } |
||
509 | 1 | } |
|
510 | |||
511 | 1 | // SetPropertyPath resets a base property path of violated attributes. |
|
512 | func (b *ViolationBuilder) SetPropertyPath(path *PropertyPath) *ViolationBuilder { |
||
513 | b.propertyPath = path |
||
514 | |||
515 | return b |
||
516 | } |
||
517 | 1 | ||
518 | // WithParameters sets template parameters that can be injected into the violation message. |
||
519 | func (b *ViolationBuilder) WithParameters(parameters ...TemplateParameter) *ViolationBuilder { |
||
520 | b.parameters = parameters |
||
521 | |||
522 | return b |
||
523 | } |
||
524 | |||
525 | // WithParameter adds one parameter into a slice of parameters. |
||
526 | func (b *ViolationBuilder) WithParameter(name, value string) *ViolationBuilder { |
||
527 | b.parameters = append(b.parameters, TemplateParameter{Key: name, Value: value}) |
||
528 | |||
529 | return b |
||
530 | } |
||
531 | |||
532 | // At appends a property path of violated attribute. |
||
533 | func (b *ViolationBuilder) At(path ...PropertyPathElement) *ViolationBuilder { |
||
534 | b.propertyPath = b.propertyPath.With(path...) |
||
535 | |||
536 | return b |
||
537 | } |
||
538 | |||
539 | // AtProperty adds a property name to property path of violated attribute. |
||
540 | func (b *ViolationBuilder) AtProperty(propertyName string) *ViolationBuilder { |
||
541 | b.propertyPath = b.propertyPath.WithProperty(propertyName) |
||
542 | |||
543 | return b |
||
544 | } |
||
545 | |||
546 | // AtIndex adds an array index to property path of violated attribute. |
||
547 | func (b *ViolationBuilder) AtIndex(index int) *ViolationBuilder { |
||
548 | b.propertyPath = b.propertyPath.WithIndex(index) |
||
549 | |||
550 | return b |
||
551 | } |
||
552 | |||
553 | // WithPluralCount sets a plural number that will be used for message pluralization during translations. |
||
554 | func (b *ViolationBuilder) WithPluralCount(pluralCount int) *ViolationBuilder { |
||
555 | b.pluralCount = pluralCount |
||
556 | |||
557 | return b |
||
558 | } |
||
559 | |||
560 | // WithLanguage sets language that will be used to translate the violation message. |
||
561 | func (b *ViolationBuilder) WithLanguage(tag language.Tag) *ViolationBuilder { |
||
562 | b.language = tag |
||
563 | |||
564 | return b |
||
565 | } |
||
566 | |||
567 | // Create creates a new violation with given parameters and returns it. |
||
568 | // Violation is created by calling the [ViolationFactory.CreateViolation]. |
||
569 | func (b *ViolationBuilder) Create() Violation { |
||
570 | return b.violationFactory.CreateViolation( |
||
571 | b.err, |
||
572 | b.messageTemplate, |
||
573 | b.pluralCount, |
||
574 | b.parameters, |
||
575 | b.propertyPath, |
||
576 | b.language, |
||
577 | ) |
||
578 | } |
||
579 | |||
580 | // ViolationListBuilder is used to build a [ViolationList] by fluent interface. |
||
581 | type ViolationListBuilder struct { |
||
582 | violations *ViolationList |
||
583 | violationFactory ViolationFactory |
||
584 | |||
585 | propertyPath *PropertyPath |
||
586 | language language.Tag |
||
587 | } |
||
588 | |||
589 | // ViolationListElementBuilder is used to build [Violation] that will be added into [ViolationList] |
||
590 | // of the [ViolationListBuilder]. |
||
591 | type ViolationListElementBuilder struct { |
||
592 | listBuilder *ViolationListBuilder |
||
593 | |||
594 | err error |
||
595 | messageTemplate string |
||
596 | pluralCount int |
||
597 | parameters []TemplateParameter |
||
598 | propertyPath *PropertyPath |
||
599 | } |
||
600 | |||
601 | // NewViolationListBuilder creates a new [ViolationListBuilder]. |
||
602 | func NewViolationListBuilder(factory ViolationFactory) *ViolationListBuilder { |
||
603 | return &ViolationListBuilder{violationFactory: factory, violations: NewViolationList()} |
||
604 | } |
||
605 | |||
606 | // BuildViolation initiates a builder for violation that will be added into [ViolationList]. |
||
607 | func (b *ViolationListBuilder) BuildViolation(err error, message string) *ViolationListElementBuilder { |
||
608 | return &ViolationListElementBuilder{ |
||
609 | listBuilder: b, |
||
610 | err: err, |
||
611 | messageTemplate: message, |
||
612 | propertyPath: b.propertyPath, |
||
613 | } |
||
614 | } |
||
615 | |||
616 | // AddViolation can be used to quickly add a new violation using only code, message |
||
617 | // and optional property path elements. |
||
618 | func (b *ViolationListBuilder) AddViolation(err error, message string, path ...PropertyPathElement) *ViolationListBuilder { |
||
619 | return b.add(err, message, 0, nil, b.propertyPath.With(path...)) |
||
620 | } |
||
621 | |||
622 | // SetPropertyPath resets a base property path of violated attributes. |
||
623 | func (b *ViolationListBuilder) SetPropertyPath(path *PropertyPath) *ViolationListBuilder { |
||
624 | b.propertyPath = path |
||
625 | |||
626 | return b |
||
627 | } |
||
628 | |||
629 | // At appends a property path of violated attribute. |
||
630 | func (b *ViolationListBuilder) At(path ...PropertyPathElement) *ViolationListBuilder { |
||
631 | b.propertyPath = b.propertyPath.With(path...) |
||
632 | |||
633 | return b |
||
634 | } |
||
635 | |||
636 | // AtProperty adds a property name to the base property path of violated attributes. |
||
637 | func (b *ViolationListBuilder) AtProperty(propertyName string) *ViolationListBuilder { |
||
638 | b.propertyPath = b.propertyPath.WithProperty(propertyName) |
||
639 | |||
640 | return b |
||
641 | } |
||
642 | |||
643 | // AtIndex adds an array index to the base property path of violated attributes. |
||
644 | func (b *ViolationListBuilder) AtIndex(index int) *ViolationListBuilder { |
||
645 | b.propertyPath = b.propertyPath.WithIndex(index) |
||
646 | |||
647 | return b |
||
648 | } |
||
649 | |||
650 | // Create returns a [ViolationList] with built violations. |
||
651 | func (b *ViolationListBuilder) Create() *ViolationList { |
||
652 | return b.violations |
||
653 | } |
||
654 | |||
655 | func (b *ViolationListBuilder) add( |
||
656 | err error, |
||
657 | template string, |
||
658 | count int, |
||
659 | parameters []TemplateParameter, |
||
660 | path *PropertyPath, |
||
661 | ) *ViolationListBuilder { |
||
662 | b.violations.Append(b.violationFactory.CreateViolation( |
||
663 | err, |
||
664 | template, |
||
665 | count, |
||
666 | parameters, |
||
667 | path, |
||
668 | b.language, |
||
669 | )) |
||
670 | |||
671 | return b |
||
672 | } |
||
673 | |||
674 | // WithLanguage sets language that will be used to translate the violation message. |
||
675 | func (b *ViolationListBuilder) WithLanguage(tag language.Tag) *ViolationListBuilder { |
||
676 | b.language = tag |
||
677 | |||
678 | return b |
||
679 | } |
||
680 | |||
681 | // WithParameters sets template parameters that can be injected into the violation message. |
||
682 | func (b *ViolationListElementBuilder) WithParameters(parameters ...TemplateParameter) *ViolationListElementBuilder { |
||
683 | b.parameters = parameters |
||
684 | |||
685 | return b |
||
686 | } |
||
687 | |||
688 | // WithParameter adds one parameter into a slice of parameters. |
||
689 | func (b *ViolationListElementBuilder) WithParameter(name, value string) *ViolationListElementBuilder { |
||
690 | b.parameters = append(b.parameters, TemplateParameter{Key: name, Value: value}) |
||
691 | |||
692 | return b |
||
693 | } |
||
694 | |||
695 | // At appends a property path of violated attribute. |
||
696 | func (b *ViolationListElementBuilder) At(path ...PropertyPathElement) *ViolationListElementBuilder { |
||
697 | b.propertyPath = b.propertyPath.With(path...) |
||
698 | |||
699 | return b |
||
700 | } |
||
701 | |||
702 | // AtProperty adds a property name to property path of violated attribute. |
||
703 | func (b *ViolationListElementBuilder) AtProperty(propertyName string) *ViolationListElementBuilder { |
||
704 | b.propertyPath = b.propertyPath.WithProperty(propertyName) |
||
705 | |||
706 | return b |
||
707 | } |
||
708 | |||
709 | // AtIndex adds an array index to property path of violated attribute. |
||
710 | func (b *ViolationListElementBuilder) AtIndex(index int) *ViolationListElementBuilder { |
||
711 | b.propertyPath = b.propertyPath.WithIndex(index) |
||
712 | |||
713 | return b |
||
714 | } |
||
715 | |||
716 | // WithPluralCount sets a plural number that will be used for message pluralization during translations. |
||
717 | func (b *ViolationListElementBuilder) WithPluralCount(pluralCount int) *ViolationListElementBuilder { |
||
718 | b.pluralCount = pluralCount |
||
719 | |||
720 | return b |
||
721 | } |
||
722 | |||
723 | // Add creates a [Violation] and appends it into the end of the [ViolationList]. |
||
724 | // It returns a [ViolationListBuilder] to continue process of creating a [ViolationList]. |
||
725 | func (b *ViolationListElementBuilder) Add() *ViolationListBuilder { |
||
726 | return b.listBuilder.add(b.err, b.messageTemplate, b.pluralCount, b.parameters, b.propertyPath) |
||
727 | } |
||
728 | |||
729 | func unwrapViolationList(err error) (*ViolationList, error) { |
||
730 | violations := NewViolationList() |
||
731 | fatal := violations.AppendFromError(err) |
||
732 | if fatal != nil { |
||
733 | return nil, fatal |
||
734 | } |
||
735 | |||
736 | return violations, nil |
||
737 | } |
||
738 |