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