Quick and easy validation framework for Json inputs

Validators can be used in Popcorn apps to valid your json inputs before data processing and persistence.

Here an example with a Book management app. We use an ObjectValidator to validate the books passed to the API in the POST /books handler.

import popcorn
import popcorn::pop_json
import serialization

# Serializable book representation.
class Book
    super Serializable

    # Book ISBN
    var isbn: String

    # Book title
    var title: String

    # Book image (optional)
    var img: nullable String

    # Book price
    var price: Float
end

class BookValidator
    super ObjectValidator

    redef init do
        add new ISBNField("isbn")
        add new StringField("title", min_size=1, max_size=255)
        add new StringField("img", required=false)
        add new FloatField("price", min=0.0, max=999.0)
    end
end

class BookHandler
    super Handler

    # Insert a new Book
    redef fun post(req, res) do
        var validator = new BookValidator
        if not validator.validate(req.body) then
            res.json(validator.validation, 400)
            return
        end
        # TODO data persistence
    end
end

Introduced classes

class ArrayField

popcorn :: ArrayField

Check that a field is a JsonArray
class ArrayValidator

popcorn :: ArrayValidator

Check a JsonArray
class BoolField

popcorn :: BoolField

Check if a field is a Bool
abstract class DocumentValidator

popcorn :: DocumentValidator

The base class of all validators
class EmailField

popcorn :: EmailField

Check if a field is a valid email
abstract class FieldValidator

popcorn :: FieldValidator

Something that can validate a JsonObject field
class FieldsMatch

popcorn :: FieldsMatch

Check if two fields values match
class FloatField

popcorn :: FloatField

Check if a field is a Float
class ISBNField

popcorn :: ISBNField

Check if a field is a valid ISBN
class IntField

popcorn :: IntField

Check if a field is an Int
class ObjectField

popcorn :: ObjectField

Check that a field is a JsonObject
class ObjectValidator

popcorn :: ObjectValidator

Check a JsonObject
class RegexField

popcorn :: RegexField

Check if a field match a regular expression
class RequiredField

popcorn :: RequiredField

Check if a field exists
class StringField

popcorn :: StringField

Check if a field is a String
class URLField

popcorn :: URLField

Check if a field is a valid URL
class UniqueField

popcorn :: UniqueField

Check if a field value is already used
class ValidationResult

popcorn :: ValidationResult

Validation Result representation

All class definitions

class ArrayField

popcorn $ ArrayField

Check that a field is a JsonArray
class ArrayValidator

popcorn $ ArrayValidator

Check a JsonArray
class BoolField

popcorn $ BoolField

Check if a field is a Bool
abstract class DocumentValidator

popcorn $ DocumentValidator

The base class of all validators
class EmailField

popcorn $ EmailField

Check if a field is a valid email
abstract class FieldValidator

popcorn $ FieldValidator

Something that can validate a JsonObject field
class FieldsMatch

popcorn $ FieldsMatch

Check if two fields values match
class FloatField

popcorn $ FloatField

Check if a field is a Float
class ISBNField

popcorn $ ISBNField

Check if a field is a valid ISBN
class IntField

popcorn $ IntField

Check if a field is an Int
class ObjectField

popcorn $ ObjectField

Check that a field is a JsonObject
class ObjectValidator

popcorn $ ObjectValidator

Check a JsonObject
class RegexField

popcorn $ RegexField

Check if a field match a regular expression
class RequiredField

popcorn $ RequiredField

Check if a field exists
class StringField

popcorn $ StringField

Check if a field is a String
class URLField

popcorn $ URLField

Check if a field is a valid URL
class UniqueField

popcorn $ UniqueField

Check if a field value is already used
class ValidationResult

popcorn $ ValidationResult

Validation Result representation
package_diagram popcorn::pop_validation pop_validation json::static static popcorn::pop_validation->json::static json::error error json::static->json::error ...json::error ... ...json::error->json::error popcorn::pop_json pop_json popcorn::pop_json->popcorn::pop_validation popcorn::pop_auth pop_auth popcorn::pop_auth->popcorn::pop_json popcorn::pop_templates pop_templates popcorn::pop_templates->popcorn::pop_json popcorn::pop_tracker pop_tracker popcorn::pop_tracker->popcorn::pop_json popcorn::example_angular example_angular popcorn::example_angular->popcorn::pop_json popcorn::pop_auth... ... popcorn::pop_auth...->popcorn::pop_auth popcorn::pop_templates... ... popcorn::pop_templates...->popcorn::pop_templates popcorn::pop_tracker... ... popcorn::pop_tracker...->popcorn::pop_tracker popcorn::example_angular... ... popcorn::example_angular...->popcorn::example_angular

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module array

core :: array

This module introduces the standard array structure.
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

json :: error

Intro JsonParseError which is exposed by all JSON reading APIs
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module parser_base

parser_base :: parser_base

Simple base for hand-made parsers of all kinds
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module static

json :: static

Static interface to read Nit objects from JSON strings

Children

module pop_json

popcorn :: pop_json

Introduce useful services for JSON REST API handlers.

Descendants

module a_star-m

a_star-m

module example_angular

popcorn :: example_angular

This is an example of how to use angular.js with popcorn
module pop_auth

popcorn :: pop_auth

Authentification handlers.
module pop_templates

popcorn :: pop_templates

Template rendering for popcorn
# Quick and easy validation framework for Json inputs
#
# Validators can be used in Popcorn apps to valid your json inputs before
# data processing and persistence.
#
# Here an example with a Book management app. We use an ObjectValidator to validate
# the books passed to the API in the `POST /books` handler.
#
# ~~~
# import popcorn
# import popcorn::pop_json
# import serialization
#
# # Serializable book representation.
# class Book
#	super Serializable
#
#	# Book ISBN
#	var isbn: String
#
#	# Book title
#	var title: String
#
#	# Book image (optional)
#	var img: nullable String
#
#	# Book price
#	var price: Float
# end
#
# class BookValidator
#	super ObjectValidator
#
#	redef init do
#		add new ISBNField("isbn")
#		add new StringField("title", min_size=1, max_size=255)
#		add new StringField("img", required=false)
#		add new FloatField("price", min=0.0, max=999.0)
#	end
# end
#
# class BookHandler
#	super Handler
#
#	# Insert a new Book
#	redef fun post(req, res) do
#		var validator = new BookValidator
#		if not validator.validate(req.body) then
#			res.json(validator.validation, 400)
#			return
#		end
#		# TODO data persistence
#	end
# end
# ~~~
module pop_validation

import json::static

# The base class of all validators
abstract class DocumentValidator

	# Validation result
	#
	# Accessible to the client after the `validate` method has been called.
	var validation: ValidationResult is noinit

	# Validate the `document` input
	#
	# Result of the validation can be found in the `validation` attribute.
	fun validate(document: String): Bool do
		validation = new ValidationResult
		return true
	end
end

# Validation Result representation
#
# Can be convertted to a JsonObject so it can be reterned in a Json HttpResponse.
#
# Errors messages are grouped into *scopes*. A scope is a string that specify wich
# field or document the error message is related to.
class ValidationResult
	super Serializable

	# Object parsed during validation
	#
	# Can be used as a quick way to access the parsed JsonObject instead of
	# reparsing it during the answer.
	#
	# See `ObjectValidator`.
	var object: nullable JsonObject = null is writable

	# Array parsed during validation
	#
	# Can be used as a quick way to access the parsed JsonArray instead of
	# reparsing it during the answer.
	#
	# See `ArrayValidator`.
	var array: nullable JsonArray = null is writable

	# Errors found during validation
	#
	# Errors are grouped by scope.
	var errors = new HashMap[String, Array[String]]

	# Generate a new error `message` into `scope`
	fun add_error(scope, message: String) do
		if not errors.has_key(scope) then
			errors[scope] = new Array[String]
		end
		errors[scope].add message
	end

	# Get the errors for `scope`
	fun error(scope: String): Array[String] do
		if not errors.has_key(scope) then
			return new Array[String]
		end
		return errors[scope]
	end

	# Does `self` contains `errors`?
	fun has_error: Bool do return errors.not_empty

	redef fun core_serialize_to(v) do
		var errors = new JsonObject
		for k, e in self.errors do
			errors[k] = new JsonArray.from(e)
		end
		v.serialize_attribute("has_error", has_error)
		v.serialize_attribute("errors", errors)
	end

	# Returns the validation result as a pretty formated string
	fun to_pretty_string: String do
		var b = new Buffer
		if not has_error then
			b.append "Everything is correct\n"
		else
			b.append "There is errors\n\n"
			for k, v in errors do
				b.append "{k}:\n"
				for vv in v do
					b.append "\t{vv}\n"
				end
				b.append "\n"
			end
		end
		return b.write_to_string
	end
end

# Check a JsonObject
# ~~~
# var validator = new ObjectValidator
# validator.add new RequiredField("id", required = true)
# validator.add new StringField("login", min_size=4)
# validator.add new IntField("age", min=0, max=100)
# assert not validator.validate("""{}""")
# assert not validator.validate("""[]""")
# assert validator.validate("""{ "id": "", "login": "Alex", "age": 10 }""")
# ~~~
class ObjectValidator
	super DocumentValidator

	# Validators to apply on the object
	var validators = new Array[FieldValidator]

	redef fun validate(document) do
		super
		var json = document.parse_json
		if json == null then
			validation.add_error("document", "Expected JsonObject got `null`")
			return false
		end
		return validate_json(json)
	end

	# Validate a Serializable input
	fun validate_json(json: Serializable): Bool do
		if not json isa JsonObject then
			validation.add_error("document", "Expected JsonObject got `{json.class_name}`")
			return false
		end
		validation.object = json
		for validator in validators do
			var res = validator.validate_field(self, json)
			if not res then return false
		end
		return true
	end

	# Add a validator
	fun add(validator: FieldValidator) do validators.add validator
end

# Check a JsonArray
# ~~~
# var validator = new ArrayValidator
# assert not validator.validate("""{}""")
# assert validator.validate("""[]""")
# assert validator.validate("""[ "id", 10, {} ]""")
#
# validator = new ArrayValidator(allow_empty=false)
# assert not validator.validate("""[]""")
# assert validator.validate("""[ "id", 10, {} ]""")
#
# validator = new ArrayValidator(length=3)
# assert not validator.validate("""[]""")
# assert validator.validate("""[ "id", 10, {} ]""")
# ~~~
class ArrayValidator
	super DocumentValidator

	# Allow empty arrays (default: true)
	var allow_empty: nullable Bool

	# Check array length (default: no check)
	var length: nullable Int

	redef fun validate(document) do
		super
		var json = document.parse_json
		if json == null then
			validation.add_error("document", "Expected JsonArray got `null`")
			return false
		end
		return validate_json(json)
	end

	# Validate a Serializable input
	fun validate_json(json: Serializable): Bool do
		if not json isa JsonArray then
			validation.add_error("document", "Expected JsonArray got `{json.class_name}`")
			return false
		end
		validation.array = json
		var allow_empty = self.allow_empty
		if json.is_empty and (allow_empty != null and not allow_empty) then
			validation.add_error("document", "Cannot be empty")
			return false
		end
		var length = self.length
		if length != null and json.length != length then
			validation.add_error("document", "Array length must be exactly `{length}`")
			return false
		end

		return true
	end
end

# Something that can validate a JsonObject field
abstract class FieldValidator

	# Field to validate
	var field: String

	# Validate `field` in `obj`
	fun validate_field(v: ObjectValidator, obj: JsonObject): Bool is abstract
end

# Check if a field exists
#
# ~~~
# var json1 = """{ "field1": "", "field2": "foo", "field3": 10, "field4": [] }"""
# var json2 = """{ "field1": "", "field2": "foo", "field3": 10 }"""
# var json3 = """{ "field1": "", "field2": "foo" }"""
#
# var validator = new ObjectValidator
# validator.add new RequiredField("field1")
# validator.add new RequiredField("field2")
# validator.add new RequiredField("field3")
# validator.add new RequiredField("field4", required=false)
#
# assert validator.validate(json1)
# assert validator.validate(json2)
# assert not validator.validate(json3)
# assert validator.validation.error("field3") == ["Required field"]
# ~~~
class RequiredField
	super FieldValidator

	# Is this field required?
	var required: nullable Bool

	redef fun validate_field(v, obj) do
		var required = self.required
		if (required != null and required or required == null) and not obj.has_key(field) then
			v.validation.add_error(field, "Required field")
			return false
		end
		return true
	end
end

# Check if a field is a String
#
# `min_size` and `max_size` are optional
#
# ~~~
# var validator = new ObjectValidator
# validator.add new StringField("field", required=false)
# assert validator.validate("""{}""")
#
# validator = new ObjectValidator
# validator.add new StringField("field")
# assert not validator.validate("""{}""")
# assert not validator.validate("""{ "field": 10 }""")
#
# validator = new ObjectValidator
# validator.add new StringField("field", min_size=3)
# assert validator.validate("""{ "field": "foo" }""")
# assert not validator.validate("""{ "field": "fo" }""")
# assert not validator.validate("""{ "field": "" }""")
#
# validator = new ObjectValidator
# validator.add new StringField("field", max_size=3)
# assert validator.validate("""{ "field": "foo" }""")
# assert not validator.validate("""{ "field": "fooo" }""")
#
# validator = new ObjectValidator
# validator.add new StringField("field", min_size=3, max_size=5)
# assert not validator.validate("""{ "field": "fo" }""")
# assert validator.validate("""{ "field": "foo" }""")
# assert validator.validate("""{ "field": "foooo" }""")
# assert not validator.validate("""{ "field": "fooooo" }""")
# ~~~
class StringField
	super RequiredField

	# String min size (default: not checked)
	var min_size: nullable Int

	# String max size (default: not checked)
	var max_size: nullable Int

	redef fun validate_field(v, obj) do
		if not super then return false
		var val = obj.get_or_null(field)
		if val == null then
			if required == null or required == true then
				v.validation.add_error(field, "Expected String got `null`")
				return false
			else
				return true
			end
		end
		if not val isa String then
			v.validation.add_error(field, "Expected String got `{val.class_name}`")
			return false
		end
		var min_size = self.min_size
		if min_size != null and val.length < min_size then
			v.validation.add_error(field, "Must be at least `{min_size} characters long`")
			return false
		end
		var max_size = self.max_size
		if max_size != null and val.length > max_size then
			v.validation.add_error(field, "Must be at max `{max_size} characters long`")
			return false
		end
		return true
	end
end

# Check if a field is an Int
#
# ~~~
# var validator = new ObjectValidator
# validator.add new IntField("field", required=false)
# assert validator.validate("""{}""")
#
# validator = new ObjectValidator
# validator.add new IntField("field")
# assert not validator.validate("""{}""")
# assert not validator.validate("""{ "field": "foo" }""")
# assert validator.validate("""{ "field": 10 }""")
#
# validator = new ObjectValidator
# validator.add new IntField("field", min=3)
# assert validator.validate("""{ "field": 3 }""")
# assert not validator.validate("""{ "field": 2 }""")
#
# validator = new ObjectValidator
# validator.add new IntField("field", max=3)
# assert validator.validate("""{ "field": 3 }""")
# assert not validator.validate("""{ "field": 4 }""")
#
# validator = new ObjectValidator
# validator.add new IntField("field", min=3, max=5)
# assert not validator.validate("""{ "field": 2 }""")
# assert validator.validate("""{ "field": 3 }""")
# assert validator.validate("""{ "field": 5 }""")
# assert not validator.validate("""{ "field": 6 }""")
# ~~~
class IntField
	super RequiredField

	# Min value (default: not checked)
	var min: nullable Int

	# Max value (default: not checked)
	var max: nullable Int

	redef fun validate_field(v, obj) do
		if not super then return false
		var val = obj.get_or_null(field)
		if val == null then
			if required == null or required == true then
				v.validation.add_error(field, "Expected Int got `null`")
				return false
			else
				return true
			end
		end
		if not val isa Int then
			v.validation.add_error(field, "Expected Int got `{val.class_name}`")
			return false
		end
		var min = self.min
		if min != null and val < min then
			v.validation.add_error(field, "Must be greater or equal to `{min}`")
			return false
		end
		var max = self.max
		if max != null and val > max then
			v.validation.add_error(field, "Must be smaller or equal to `{max}`")
			return false
		end
		return true
	end
end

# Check if a field is a Float
#
# ~~~
# var validator = new ObjectValidator
# validator.add new FloatField("field", required=false)
# assert validator.validate("""{}""")
#
# validator = new ObjectValidator
# validator.add new FloatField("field")
# assert not validator.validate("""{}""")
# assert not validator.validate("""{ "field": "foo" }""")
# assert validator.validate("""{ "field": 10.5 }""")
#
# validator = new ObjectValidator
# validator.add new FloatField("field", min=3.0)
# assert validator.validate("""{ "field": 3.0 }""")
# assert not validator.validate("""{ "field": 2.0 }""")
#
# validator = new ObjectValidator
# validator.add new FloatField("field", max=3.0)
# assert validator.validate("""{ "field": 3.0 }""")
# assert not validator.validate("""{ "field": 4.0 }""")
#
# validator = new ObjectValidator
# validator.add new FloatField("field", min=3.0, max=5.0)
# assert not validator.validate("""{ "field": 2.0 }""")
# assert validator.validate("""{ "field": 3.0 }""")
# assert validator.validate("""{ "field": 5.0 }""")
# assert not validator.validate("""{ "field": 6.0 }""")
# ~~~
class FloatField
	super RequiredField

	# Min value (default: not checked)
	var min: nullable Float

	# Max value (default: not checked)
	var max: nullable Float

	redef fun validate_field(v, obj) do
		if not super then return false
		var val = obj.get_or_null(field)
		if val == null then
			if required == null or required == true then
				v.validation.add_error(field, "Expected Float got `null`")
				return false
			else
				return true
			end
		end
		if not val isa Float then
			v.validation.add_error(field, "Expected Float got `{val.class_name}`")
			return false
		end
		var min = self.min
		if min != null and val < min then
			v.validation.add_error(field, "Must be smaller or equal to `{min}`")
			return false
		end
		var max = self.max
		if max != null and val > max then
			v.validation.add_error(field, "Must be greater or equal to `{max}`")
			return false
		end
		return true
	end
end

# Check if a field is a Bool
#
# ~~~
# var validator = new ObjectValidator
# validator.add new BoolField("field", required=false)
# assert validator.validate("""{}""")
# assert validator.validate("""{ "field": true }""")
# assert validator.validate("""{ "field": false }""")
# assert not validator.validate("""{ "field": "foo" }""")
#
# validator = new ObjectValidator
# validator.add new BoolField("field")
# assert not validator.validate("""{}""")
# assert validator.validate("""{ "field": true }""")
# assert validator.validate("""{ "field": false }""")
# assert not validator.validate("""{ "field": "foo" }""")
# ~~~
#
# No type conversion is applied on the input value:
# ~~~
# assert not validator.validate("""{ "field": "true" }""")
# assert not validator.validate("""{ "field": 1 }""")
# assert not validator.validate("""{ "field": [true] }""")
# ~~~
class BoolField
	super RequiredField

	redef fun validate_field(v, obj) do
		if not super then return false
		var val = obj.get_or_null(field)
		if val == null then
			if required == null or required == true then
				v.validation.add_error(field, "Expected Bool got `null`")
				return false
			else
				return true
			end
		end
		if not val isa Bool then
			v.validation.add_error(field, "Expected Bool got `{val.class_name}`")
			return false
		end
		return true
	end
end

# Check that a field is a JsonObject
#
# ~~~
# var validator = new ObjectValidator
# validator.add new RequiredField("id", required = true)
# var user_val = new ObjectField("user")
# user_val.add new RequiredField("id", required = true)
# user_val.add new StringField("login", min_size=4)
# validator.add user_val
# assert not validator.validate("""{ "id": "", "user": { "login": "Alex" } }""")
# assert validator.validate("""{ "id": "", "user": { "id": "foo", "login": "Alex" } }""")
# ~~~
class ObjectField
	super RequiredField
	super ObjectValidator

	redef var validation = new ValidationResult

	redef fun validate_field(v, obj) do
		if not super then return false
		var val = obj.get_or_null(field)
		if val == null then
			if required == null or required == true then
				v.validation.add_error(field, "Expected Object got `null`")
				return false
			else
				return true
			end
		end
		var res = validate_json(val)
		for field, messages in validation.errors do
			for message in messages do v.validation.add_error("{self.field}.{field}", message)
		end
		return res
	end
end

# Check that a field is a JsonArray
#
# ~~~
# var validator = new ObjectValidator
# validator.add new RequiredField("id", required = true)
# validator.add new ArrayField("orders", allow_empty=false)
# assert not validator.validate("""{ "id": "", "orders": [] }""")
# assert validator.validate("""{ "id": "", "orders": [ 1 ] }""")
# ~~~
class ArrayField
	super RequiredField
	super ArrayValidator

	autoinit field=, required=, allow_empty=, length=

	redef var validation = new ValidationResult

	redef fun validate_field(v, obj) do
		if not super then return false
		var val = obj.get_or_null(field)
		if val == null then
			if required == null or required == true then
				v.validation.add_error(field, "Expected Array got `null`")
				return false
			else
				return true
			end
		end
		var res = validate_json(val)
		for field, messages in validation.errors do
			for message in messages do v.validation.add_error("{self.field}.{field}", message)
		end
		return res
	end
end

# Check if two fields values match
#
# ~~~
# var validator = new ObjectValidator
# validator.add new FieldsMatch("field1", "field2")
#
# assert validator.validate("""{ "field1": {}, "field2": {} }""")
# assert validator.validate("""{ "field1": "foo", "field2": "foo" }""")
# assert validator.validate("""{ "field1": null, "field2": null }""")
# assert validator.validate("""{}""")
#
# assert not validator.validate("""{ "field1": {}, "field2": [] }""")
# assert not validator.validate("""{ "field1": "foo", "field2": "bar" }""")
# assert not validator.validate("""{ "field1": null, "field2": "" }""")
# assert not validator.validate("""{ "field1": "foo" }""")
# ~~~
class FieldsMatch
	super FieldValidator

	# Other field to compare with
	var other: String

	redef fun validate_field(v, obj) do
		var val1 = obj.get_or_null(field)
		var val2 = obj.get_or_null(other)
		if val1 != val2 then
			v.validation.add_error(field, "Values mismatch: `{val1 or else "null"}` against `{val2 or else "null"}`")
			return false
		end
		return true
	end
end

# Check if a field match a regular expression
#
# ~~~
# var validator = new ObjectValidator
# validator.add new RegexField("title", "[A-Z][a-z]+".to_re)
# assert not validator.validate("""{ "title": "foo" }""")
# assert validator.validate("""{ "title": "Foo" }""")
# ~~~
class RegexField
	super RequiredField

	autoinit field, re, required

	# Regular expression to match
	var re: Regex

	redef fun validate_field(v, obj) do
		if not super then return false
		var val = obj.get_or_null(field)
		if val == null then
			if required == null or required == true then
				v.validation.add_error(field, "Expected String got `null`")
				return false
			else
				return true
			end
		end
		if not val isa String then
			v.validation.add_error(field, "Expected String got `{val.class_name}`")
			return false
		end
		if not val.has(re) then
			v.validation.add_error(field, "Does not match `{re.string}`")
			return false
		end
		return true
	end
end

# Check if a field is a valid email
#
# ~~~
# var validator = new ObjectValidator
# validator.add new EmailField("email")
# assert not validator.validate("""{ "email": "" }""")
# assert not validator.validate("""{ "email": "foo" }""")
# assert validator.validate("""{ "email": "alexandre@moz-code.org" }""")
# assert validator.validate("""{ "email": "a+b@c.d" }""")
# ~~~
class EmailField
	super RegexField

	autoinit field, required

	redef var re = "(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9.-]+$)".to_re
end

# Check if a field is a valid ISBN
#
# ~~~
# var validator = new ObjectValidator
# validator.add new ISBNField("isbn")
# assert not validator.validate("""{ "isbn": "foo" }""")
# assert validator.validate("""{ "isbn": "ISBN 0-596-00681-0" }""")
# ~~~
class ISBNField
	super RegexField

	autoinit field, required

	redef var re = "(^ISBN [0-9]-[0-9]\{3\}-[0-9]\{5\}-[0-9]?$)".to_re
end

# Check if a field is a valid URL
#
# Matched against the following regular expression:
# ~~~raw
# ^(http|https):\/\/[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)+([a-zA-Z0-9\-\.,@?^=%&amp;:/~\+#]*[a-zA-Z0-9\-\@?^=%&amp;/~\+#])?
# ~~~
# You should redefine the base regular expression `re` with your own.
#
# ~~~
# var validator = new ObjectValidator
# validator.add new URLField("url")
# assert not validator.validate("""{ "url": "" }""")
# assert not validator.validate("""{ "url": "foo" }""")
# assert not validator.validate("""{ "url": "http://foo" }""")
# assert validator.validate("""{ "url": "http://nitlanguage.org" }""")
# assert validator.validate("""{ "url": "http://nitlanguage.org/foo" }""")
# assert validator.validate("""{ "url": "http://nitlanguage.org/foo?q" }""")
# assert validator.validate("""{ "url": "http://nitlanguage.org/foo?q&a" }""")
# assert validator.validate("""{ "url": "http://nitlanguage.org/foo?q&a=1" }""")
# ~~~
class URLField
	super RegexField

	autoinit field, required

	redef var re = "^(http|https):\\/\\/[a-zA-Z0-9\\-_]+(\\.[a-zA-Z0-9\\-_]+)+([a-zA-Z0-9\\-\\.,@?^=%&:/~\\+#]*[a-zA-Z0-9\\-\\@?^=%&/~\\+#])?".to_re
end

# Check if a field value is already used
#
# This class provides a stub validator for fields that should contain a unique value along an
# application (typically logins or ids).
#
# Here an example that uses a `Repository` if an email is unique:
# ~~~nitish
# class UniqueEmailField
#	super UniqueField
#
#	var users: UsersRepository
#
#	redef fun check_unicity(v, field, val) do
#		var user = users.find_by_email(val)
#		if user != null then
#			v.validation.add_error(field, "Email `{val}` already used")
#			return false
#		end
#		return true
#	end
# end
# ~~~
class UniqueField
	super StringField

	# Check if `val` is already used somewhere
	#
	# You must redefine this method to handle your own validation.
	fun check_unicity(v: ObjectValidator, field, val: String): Bool is abstract

	redef fun validate_field(v, obj) do
		if not super then return false
		var val = obj.get_or_null(field)
		if not val isa String then return false
		if not check_unicity(v, field, val) then return false
		return true
	end
end
lib/popcorn/pop_validation.nit:17,1--810,3