Merge: gamnit: customize writing with BMFont
[nit.git] / lib / popcorn / pop_validation.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Quick and easy validation framework for Json inputs
18 #
19 # Validators can be used in Popcorn apps to valid your json inputs before
20 # data processing and persistence.
21 #
22 # Here an example with a Book management app. We use an ObjectValidator to validate
23 # the books passed to the API in the `POST /books` handler.
24 #
25 # ~~~
26 # import popcorn
27 # import popcorn::pop_json
28 # import serialization
29 #
30 # # Serializable book representation.
31 # class Book
32 # super Serializable
33 #
34 # # Book ISBN
35 # var isbn: String
36 #
37 # # Book title
38 # var title: String
39 #
40 # # Book image (optional)
41 # var img: nullable String
42 #
43 # # Book price
44 # var price: Float
45 # end
46 #
47 # class BookValidator
48 # super ObjectValidator
49 #
50 # redef init do
51 # add new ISBNField("isbn")
52 # add new StringField("title", min_size=1, max_size=255)
53 # add new StringField("img", required=false)
54 # add new FloatField("price", min=0.0, max=999.0)
55 # end
56 # end
57 #
58 # class BookHandler
59 # super Handler
60 #
61 # # Insert a new Book
62 # redef fun post(req, res) do
63 # var validator = new BookValidator
64 # if not validator.validate(req.body) then
65 # res.json(validator.validation, 400)
66 # return
67 # end
68 # # TODO data persistence
69 # end
70 # end
71 # ~~~
72 module pop_validation
73
74 import json::static
75
76 # The base class of all validators
77 abstract class DocumentValidator
78
79 # Validation result
80 #
81 # Accessible to the client after the `validate` method has been called.
82 var validation: ValidationResult is noinit
83
84 # Validate the `document` input
85 #
86 # Result of the validation can be found in the `validation` attribute.
87 fun validate(document: String): Bool do
88 validation = new ValidationResult
89 return true
90 end
91 end
92
93 # Validation Result representation
94 #
95 # Can be convertted to a JsonObject so it can be reterned in a Json HttpResponse.
96 #
97 # Errors messages are grouped into *scopes*. A scope is a string that specify wich
98 # field or document the error message is related to.
99 class ValidationResult
100 super Serializable
101
102 # Object parsed during validation
103 #
104 # Can be used as a quick way to access the parsed JsonObject instead of
105 # reparsing it during the answer.
106 #
107 # See `ObjectValidator`.
108 var object: nullable JsonObject = null is writable
109
110 # Array parsed during validation
111 #
112 # Can be used as a quick way to access the parsed JsonArray instead of
113 # reparsing it during the answer.
114 #
115 # See `ArrayValidator`.
116 var array: nullable JsonArray = null is writable
117
118 # Errors found during validation
119 #
120 # Errors are grouped by scope.
121 var errors = new HashMap[String, Array[String]]
122
123 # Generate a new error `message` into `scope`
124 fun add_error(scope, message: String) do
125 if not errors.has_key(scope) then
126 errors[scope] = new Array[String]
127 end
128 errors[scope].add message
129 end
130
131 # Get the errors for `scope`
132 fun error(scope: String): Array[String] do
133 if not errors.has_key(scope) then
134 return new Array[String]
135 end
136 return errors[scope]
137 end
138
139 # Does `self` contains `errors`?
140 fun has_error: Bool do return errors.not_empty
141
142 redef fun core_serialize_to(v) do
143 var errors = new JsonObject
144 for k, e in self.errors do
145 errors[k] = new JsonArray.from(e)
146 end
147 v.serialize_attribute("has_error", has_error)
148 v.serialize_attribute("errors", errors)
149 end
150
151 # Returns the validation result as a pretty formated string
152 fun to_pretty_string: String do
153 var b = new Buffer
154 if not has_error then
155 b.append "Everything is correct\n"
156 else
157 b.append "There is errors\n\n"
158 for k, v in errors do
159 b.append "{k}:\n"
160 for vv in v do
161 b.append "\t{vv}\n"
162 end
163 b.append "\n"
164 end
165 end
166 return b.write_to_string
167 end
168 end
169
170 # Check a JsonObject
171 # ~~~
172 # var validator = new ObjectValidator
173 # validator.add new RequiredField("id", required = true)
174 # validator.add new StringField("login", min_size=4)
175 # validator.add new IntField("age", min=0, max=100)
176 # assert not validator.validate("""{}""")
177 # assert not validator.validate("""[]""")
178 # assert validator.validate("""{ "id": "", "login": "Alex", "age": 10 }""")
179 # ~~~
180 class ObjectValidator
181 super DocumentValidator
182
183 # Validators to apply on the object
184 var validators = new Array[FieldValidator]
185
186 redef fun validate(document) do
187 super
188 var json = document.parse_json
189 if json == null then
190 validation.add_error("document", "Expected JsonObject got `null`")
191 return false
192 end
193 return validate_json(json)
194 end
195
196 # Validate a Serializable input
197 fun validate_json(json: Serializable): Bool do
198 if not json isa JsonObject then
199 validation.add_error("document", "Expected JsonObject got `{json.class_name}`")
200 return false
201 end
202 validation.object = json
203 for validator in validators do
204 var res = validator.validate_field(self, json)
205 if not res then return false
206 end
207 return true
208 end
209
210 # Add a validator
211 fun add(validator: FieldValidator) do validators.add validator
212 end
213
214 # Check a JsonArray
215 # ~~~
216 # var validator = new ArrayValidator
217 # assert not validator.validate("""{}""")
218 # assert validator.validate("""[]""")
219 # assert validator.validate("""[ "id", 10, {} ]""")
220 #
221 # validator = new ArrayValidator(allow_empty=false)
222 # assert not validator.validate("""[]""")
223 # assert validator.validate("""[ "id", 10, {} ]""")
224 #
225 # validator = new ArrayValidator(length=3)
226 # assert not validator.validate("""[]""")
227 # assert validator.validate("""[ "id", 10, {} ]""")
228 # ~~~
229 class ArrayValidator
230 super DocumentValidator
231
232 # Allow empty arrays (default: true)
233 var allow_empty: nullable Bool
234
235 # Check array length (default: no check)
236 var length: nullable Int
237
238 redef fun validate(document) do
239 super
240 var json = document.parse_json
241 if json == null then
242 validation.add_error("document", "Expected JsonArray got `null`")
243 return false
244 end
245 return validate_json(json)
246 end
247
248 # Validate a Serializable input
249 fun validate_json(json: Serializable): Bool do
250 if not json isa JsonArray then
251 validation.add_error("document", "Expected JsonArray got `{json.class_name}`")
252 return false
253 end
254 validation.array = json
255 var allow_empty = self.allow_empty
256 if json.is_empty and (allow_empty != null and not allow_empty) then
257 validation.add_error("document", "Cannot be empty")
258 return false
259 end
260 var length = self.length
261 if length != null and json.length != length then
262 validation.add_error("document", "Array length must be exactly `{length}`")
263 return false
264 end
265
266 return true
267 end
268 end
269
270 # Something that can validate a JsonObject field
271 abstract class FieldValidator
272
273 # Field to validate
274 var field: String
275
276 # Validate `field` in `obj`
277 fun validate_field(v: ObjectValidator, obj: JsonObject): Bool is abstract
278 end
279
280 # Check if a field exists
281 #
282 # ~~~
283 # var json1 = """{ "field1": "", "field2": "foo", "field3": 10, "field4": [] }"""
284 # var json2 = """{ "field1": "", "field2": "foo", "field3": 10 }"""
285 # var json3 = """{ "field1": "", "field2": "foo" }"""
286 #
287 # var validator = new ObjectValidator
288 # validator.add new RequiredField("field1")
289 # validator.add new RequiredField("field2")
290 # validator.add new RequiredField("field3")
291 # validator.add new RequiredField("field4", required=false)
292 #
293 # assert validator.validate(json1)
294 # assert validator.validate(json2)
295 # assert not validator.validate(json3)
296 # assert validator.validation.error("field3") == ["Required field"]
297 # ~~~
298 class RequiredField
299 super FieldValidator
300
301 # Is this field required?
302 var required: nullable Bool
303
304 redef fun validate_field(v, obj) do
305 var required = self.required
306 if (required != null and required or required == null) and not obj.has_key(field) then
307 v.validation.add_error(field, "Required field")
308 return false
309 end
310 return true
311 end
312 end
313
314 # Check if a field is a String
315 #
316 # `min_size` and `max_size` are optional
317 #
318 # ~~~
319 # var validator = new ObjectValidator
320 # validator.add new StringField("field", required=false)
321 # assert validator.validate("""{}""")
322 #
323 # validator = new ObjectValidator
324 # validator.add new StringField("field")
325 # assert not validator.validate("""{}""")
326 # assert not validator.validate("""{ "field": 10 }""")
327 #
328 # validator = new ObjectValidator
329 # validator.add new StringField("field", min_size=3)
330 # assert validator.validate("""{ "field": "foo" }""")
331 # assert not validator.validate("""{ "field": "fo" }""")
332 # assert not validator.validate("""{ "field": "" }""")
333 #
334 # validator = new ObjectValidator
335 # validator.add new StringField("field", max_size=3)
336 # assert validator.validate("""{ "field": "foo" }""")
337 # assert not validator.validate("""{ "field": "fooo" }""")
338 #
339 # validator = new ObjectValidator
340 # validator.add new StringField("field", min_size=3, max_size=5)
341 # assert not validator.validate("""{ "field": "fo" }""")
342 # assert validator.validate("""{ "field": "foo" }""")
343 # assert validator.validate("""{ "field": "foooo" }""")
344 # assert not validator.validate("""{ "field": "fooooo" }""")
345 # ~~~
346 class StringField
347 super RequiredField
348
349 # String min size (default: not checked)
350 var min_size: nullable Int
351
352 # String max size (default: not checked)
353 var max_size: nullable Int
354
355 redef fun validate_field(v, obj) do
356 if not super then return false
357 var val = obj.get_or_null(field)
358 if val == null then
359 if required == null or required == true then
360 v.validation.add_error(field, "Expected String got `null`")
361 return false
362 else
363 return true
364 end
365 end
366 if not val isa String then
367 v.validation.add_error(field, "Expected String got `{val.class_name}`")
368 return false
369 end
370 var min_size = self.min_size
371 if min_size != null and val.length < min_size then
372 v.validation.add_error(field, "Must be at least `{min_size} characters long`")
373 return false
374 end
375 var max_size = self.max_size
376 if max_size != null and val.length > max_size then
377 v.validation.add_error(field, "Must be at max `{max_size} characters long`")
378 return false
379 end
380 return true
381 end
382 end
383
384 # Check if a field is an Int
385 #
386 # ~~~
387 # var validator = new ObjectValidator
388 # validator.add new IntField("field", required=false)
389 # assert validator.validate("""{}""")
390 #
391 # validator = new ObjectValidator
392 # validator.add new IntField("field")
393 # assert not validator.validate("""{}""")
394 # assert not validator.validate("""{ "field": "foo" }""")
395 # assert validator.validate("""{ "field": 10 }""")
396 #
397 # validator = new ObjectValidator
398 # validator.add new IntField("field", min=3)
399 # assert validator.validate("""{ "field": 3 }""")
400 # assert not validator.validate("""{ "field": 2 }""")
401 #
402 # validator = new ObjectValidator
403 # validator.add new IntField("field", max=3)
404 # assert validator.validate("""{ "field": 3 }""")
405 # assert not validator.validate("""{ "field": 4 }""")
406 #
407 # validator = new ObjectValidator
408 # validator.add new IntField("field", min=3, max=5)
409 # assert not validator.validate("""{ "field": 2 }""")
410 # assert validator.validate("""{ "field": 3 }""")
411 # assert validator.validate("""{ "field": 5 }""")
412 # assert not validator.validate("""{ "field": 6 }""")
413 # ~~~
414 class IntField
415 super RequiredField
416
417 # Min value (default: not checked)
418 var min: nullable Int
419
420 # Max value (default: not checked)
421 var max: nullable Int
422
423 redef fun validate_field(v, obj) do
424 if not super then return false
425 var val = obj.get_or_null(field)
426 if val == null then
427 if required == null or required == true then
428 v.validation.add_error(field, "Expected Int got `null`")
429 return false
430 else
431 return true
432 end
433 end
434 if not val isa Int then
435 v.validation.add_error(field, "Expected Int got `{val.class_name}`")
436 return false
437 end
438 var min = self.min
439 if min != null and val < min then
440 v.validation.add_error(field, "Must be greater or equal to `{min}`")
441 return false
442 end
443 var max = self.max
444 if max != null and val > max then
445 v.validation.add_error(field, "Must be smaller or equal to `{max}`")
446 return false
447 end
448 return true
449 end
450 end
451
452 # Check if a field is a Float
453 #
454 # ~~~
455 # var validator = new ObjectValidator
456 # validator.add new FloatField("field", required=false)
457 # assert validator.validate("""{}""")
458 #
459 # validator = new ObjectValidator
460 # validator.add new FloatField("field")
461 # assert not validator.validate("""{}""")
462 # assert not validator.validate("""{ "field": "foo" }""")
463 # assert validator.validate("""{ "field": 10.5 }""")
464 #
465 # validator = new ObjectValidator
466 # validator.add new FloatField("field", min=3.0)
467 # assert validator.validate("""{ "field": 3.0 }""")
468 # assert not validator.validate("""{ "field": 2.0 }""")
469 #
470 # validator = new ObjectValidator
471 # validator.add new FloatField("field", max=3.0)
472 # assert validator.validate("""{ "field": 3.0 }""")
473 # assert not validator.validate("""{ "field": 4.0 }""")
474 #
475 # validator = new ObjectValidator
476 # validator.add new FloatField("field", min=3.0, max=5.0)
477 # assert not validator.validate("""{ "field": 2.0 }""")
478 # assert validator.validate("""{ "field": 3.0 }""")
479 # assert validator.validate("""{ "field": 5.0 }""")
480 # assert not validator.validate("""{ "field": 6.0 }""")
481 # ~~~
482 class FloatField
483 super RequiredField
484
485 # Min value (default: not checked)
486 var min: nullable Float
487
488 # Max value (default: not checked)
489 var max: nullable Float
490
491 redef fun validate_field(v, obj) do
492 if not super then return false
493 var val = obj.get_or_null(field)
494 if val == null then
495 if required == null or required == true then
496 v.validation.add_error(field, "Expected Float got `null`")
497 return false
498 else
499 return true
500 end
501 end
502 if not val isa Float then
503 v.validation.add_error(field, "Expected Float got `{val.class_name}`")
504 return false
505 end
506 var min = self.min
507 if min != null and val < min then
508 v.validation.add_error(field, "Must be smaller or equal to `{min}`")
509 return false
510 end
511 var max = self.max
512 if max != null and val > max then
513 v.validation.add_error(field, "Must be greater or equal to `{max}`")
514 return false
515 end
516 return true
517 end
518 end
519
520 # Check if a field is a Bool
521 #
522 # ~~~
523 # var validator = new ObjectValidator
524 # validator.add new BoolField("field", required=false)
525 # assert validator.validate("""{}""")
526 # assert validator.validate("""{ "field": true }""")
527 # assert validator.validate("""{ "field": false }""")
528 # assert not validator.validate("""{ "field": "foo" }""")
529 #
530 # validator = new ObjectValidator
531 # validator.add new BoolField("field")
532 # assert not validator.validate("""{}""")
533 # assert validator.validate("""{ "field": true }""")
534 # assert validator.validate("""{ "field": false }""")
535 # assert not validator.validate("""{ "field": "foo" }""")
536 # ~~~
537 #
538 # No type conversion is applied on the input value:
539 # ~~~
540 # assert not validator.validate("""{ "field": "true" }""")
541 # assert not validator.validate("""{ "field": 1 }""")
542 # assert not validator.validate("""{ "field": [true] }""")
543 # ~~~
544 class BoolField
545 super RequiredField
546
547 redef fun validate_field(v, obj) do
548 if not super then return false
549 var val = obj.get_or_null(field)
550 if val == null then
551 if required == null or required == true then
552 v.validation.add_error(field, "Expected Bool got `null`")
553 return false
554 else
555 return true
556 end
557 end
558 if not val isa Bool then
559 v.validation.add_error(field, "Expected Bool got `{val.class_name}`")
560 return false
561 end
562 return true
563 end
564 end
565
566 # Check that a field is a JsonObject
567 #
568 # ~~~
569 # var validator = new ObjectValidator
570 # validator.add new RequiredField("id", required = true)
571 # var user_val = new ObjectField("user")
572 # user_val.add new RequiredField("id", required = true)
573 # user_val.add new StringField("login", min_size=4)
574 # validator.add user_val
575 # assert not validator.validate("""{ "id": "", "user": { "login": "Alex" } }""")
576 # assert validator.validate("""{ "id": "", "user": { "id": "foo", "login": "Alex" } }""")
577 # ~~~
578 class ObjectField
579 super RequiredField
580 super ObjectValidator
581
582 redef var validation = new ValidationResult
583
584 redef fun validate_field(v, obj) do
585 if not super then return false
586 var val = obj.get_or_null(field)
587 if val == null then
588 if required == null or required == true then
589 v.validation.add_error(field, "Expected Object got `null`")
590 return false
591 else
592 return true
593 end
594 end
595 var res = validate_json(val)
596 for field, messages in validation.errors do
597 for message in messages do v.validation.add_error("{self.field}.{field}", message)
598 end
599 return res
600 end
601 end
602
603 # Check that a field is a JsonArray
604 #
605 # ~~~
606 # var validator = new ObjectValidator
607 # validator.add new RequiredField("id", required = true)
608 # validator.add new ArrayField("orders", allow_empty=false)
609 # assert not validator.validate("""{ "id": "", "orders": [] }""")
610 # assert validator.validate("""{ "id": "", "orders": [ 1 ] }""")
611 # ~~~
612 class ArrayField
613 super RequiredField
614 super ArrayValidator
615
616 autoinit field=, required=, allow_empty=, length=
617
618 redef var validation = new ValidationResult
619
620 redef fun validate_field(v, obj) do
621 if not super then return false
622 var val = obj.get_or_null(field)
623 if val == null then
624 if required == null or required == true then
625 v.validation.add_error(field, "Expected Array got `null`")
626 return false
627 else
628 return true
629 end
630 end
631 var res = validate_json(val)
632 for field, messages in validation.errors do
633 for message in messages do v.validation.add_error("{self.field}.{field}", message)
634 end
635 return res
636 end
637 end
638
639 # Check if two fields values match
640 #
641 # ~~~
642 # var validator = new ObjectValidator
643 # validator.add new FieldsMatch("field1", "field2")
644 #
645 # assert validator.validate("""{ "field1": {}, "field2": {} }""")
646 # assert validator.validate("""{ "field1": "foo", "field2": "foo" }""")
647 # assert validator.validate("""{ "field1": null, "field2": null }""")
648 # assert validator.validate("""{}""")
649 #
650 # assert not validator.validate("""{ "field1": {}, "field2": [] }""")
651 # assert not validator.validate("""{ "field1": "foo", "field2": "bar" }""")
652 # assert not validator.validate("""{ "field1": null, "field2": "" }""")
653 # assert not validator.validate("""{ "field1": "foo" }""")
654 # ~~~
655 class FieldsMatch
656 super FieldValidator
657
658 # Other field to compare with
659 var other: String
660
661 redef fun validate_field(v, obj) do
662 var val1 = obj.get_or_null(field)
663 var val2 = obj.get_or_null(other)
664 if val1 != val2 then
665 v.validation.add_error(field, "Values mismatch: `{val1 or else "null"}` against `{val2 or else "null"}`")
666 return false
667 end
668 return true
669 end
670 end
671
672 # Check if a field match a regular expression
673 #
674 # ~~~
675 # var validator = new ObjectValidator
676 # validator.add new RegexField("title", "[A-Z][a-z]+".to_re)
677 # assert not validator.validate("""{ "title": "foo" }""")
678 # assert validator.validate("""{ "title": "Foo" }""")
679 # ~~~
680 class RegexField
681 super RequiredField
682
683 autoinit field, re, required
684
685 # Regular expression to match
686 var re: Regex
687
688 redef fun validate_field(v, obj) do
689 if not super then return false
690 var val = obj.get_or_null(field)
691 if val == null then
692 if required == null or required == true then
693 v.validation.add_error(field, "Expected String got `null`")
694 return false
695 else
696 return true
697 end
698 end
699 if not val isa String then
700 v.validation.add_error(field, "Expected String got `{val.class_name}`")
701 return false
702 end
703 if not val.has(re) then
704 v.validation.add_error(field, "Does not match `{re.string}`")
705 return false
706 end
707 return true
708 end
709 end
710
711 # Check if a field is a valid email
712 #
713 # ~~~
714 # var validator = new ObjectValidator
715 # validator.add new EmailField("email")
716 # assert not validator.validate("""{ "email": "" }""")
717 # assert not validator.validate("""{ "email": "foo" }""")
718 # assert validator.validate("""{ "email": "alexandre@moz-code.org" }""")
719 # assert validator.validate("""{ "email": "a+b@c.d" }""")
720 # ~~~
721 class EmailField
722 super RegexField
723
724 autoinit field, required
725
726 redef var re = "(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9.-]+$)".to_re
727 end
728
729 # Check if a field is a valid ISBN
730 #
731 # ~~~
732 # var validator = new ObjectValidator
733 # validator.add new ISBNField("isbn")
734 # assert not validator.validate("""{ "isbn": "foo" }""")
735 # assert validator.validate("""{ "isbn": "ISBN 0-596-00681-0" }""")
736 # ~~~
737 class ISBNField
738 super RegexField
739
740 autoinit field, required
741
742 redef var re = "(^ISBN [0-9]-[0-9]\{3\}-[0-9]\{5\}-[0-9]?$)".to_re
743 end
744
745 # Check if a field is a valid URL
746 #
747 # Matched against the following regular expression:
748 # ~~~raw
749 # ^(http|https):\/\/[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)+([a-zA-Z0-9\-\.,@?^=%&amp;:/~\+#]*[a-zA-Z0-9\-\@?^=%&amp;/~\+#])?
750 # ~~~
751 # You should redefine the base regular expression `re` with your own.
752 #
753 # ~~~
754 # var validator = new ObjectValidator
755 # validator.add new URLField("url")
756 # assert not validator.validate("""{ "url": "" }""")
757 # assert not validator.validate("""{ "url": "foo" }""")
758 # assert not validator.validate("""{ "url": "http://foo" }""")
759 # assert validator.validate("""{ "url": "http://nitlanguage.org" }""")
760 # assert validator.validate("""{ "url": "http://nitlanguage.org/foo" }""")
761 # assert validator.validate("""{ "url": "http://nitlanguage.org/foo?q" }""")
762 # assert validator.validate("""{ "url": "http://nitlanguage.org/foo?q&a" }""")
763 # assert validator.validate("""{ "url": "http://nitlanguage.org/foo?q&a=1" }""")
764 # ~~~
765 class URLField
766 super RegexField
767
768 autoinit field, required
769
770 redef var re = "^(http|https):\\/\\/[a-zA-Z0-9\\-_]+(\\.[a-zA-Z0-9\\-_]+)+([a-zA-Z0-9\\-\\.,@?^=%&:/~\\+#]*[a-zA-Z0-9\\-\\@?^=%&/~\\+#])?".to_re
771 end
772
773 # Check if a field value is already used
774 #
775 # This class provides a stub validator for fields that should contain a unique value along an
776 # application (typically logins or ids).
777 #
778 # Here an example that uses a `Repository` if an email is unique:
779 # ~~~nitish
780 # class UniqueEmailField
781 # super UniqueField
782 #
783 # var users: UsersRepository
784 #
785 # redef fun check_unicity(v, field, val) do
786 # var user = users.find_by_email(val)
787 # if user != null then
788 # v.validation.add_error(field, "Email `{val}` already used")
789 # return false
790 # end
791 # return true
792 # end
793 # end
794 # ~~~
795 class UniqueField
796 super StringField
797
798 # Check if `val` is already used somewhere
799 #
800 # You must redefine this method to handle your own validation.
801 fun check_unicity(v: ObjectValidator, field, val: String): Bool is abstract
802
803 redef fun validate_field(v, obj) do
804 if not super then return false
805 var val = obj.get_or_null(field)
806 if not val isa String then return false
807 if not check_unicity(v, field, val) then return false
808 return true
809 end
810 end