GDS Data Validation
A rule-based schema definition language and data validation library.
- Introduction
- Schema definition
- Usage with Ruby
- Hash specification
- Array specification
- Basic data type specification
- Literal value
- Disjunction of basic data type specifications and literal values
- Rule reference
- Practical Examples
- Contrived Examples
- Ruby Gem
- Source Code
Introduction
The original idea was to create a library for the validation of data structures which have been created by the GDS (General Data Structure) language.
Synonyms and related terms are data validation, validation checker, schema validation, schema validator, schema checker.
Schema definition
The schema definition is a specification describing which kind of data is expected and should be valid. The purpose is to validate incoming data against a schema.
Data to be validated
Incoming data to be validated can be
- a simple value of a basic data type (e.g. integer, floating-point, string, boolean)
- or a complex value of a composite data type (e.g. hash, array). A composite data type can be recursively nested by other hashes and arrays. A hash and an array can also include simple values of basic data types.
Rule-based schema definition
The validation schema is defined with a set of rules. This approach is borrowed from topics of syntax analysis and parser generation. The schema definition is similar to a set of production rules for the definition of a context free grammar describing valid sentences of a formal language. Usually for the notation of a context free grammar Backus-Naur form (BNF) or one of its variants like extended Backus-Naur form (EBNF) is used. The syntax of rule-based schema definitions is more similar to augmented Backus-Naur form (ABNF).
The first rule you define is important, because this is the starting rule for the overall schema definition. The order of following rules does not matter. As a name for the starting rule, you can basically choose any name. Depending on the context in which you are using the validation, abstract names like ‘main’, ‘top’, ‘start’, ‘base’, ‘spec’, ‘schema’, ‘contract’ or ‘value’ might be suitable, in other cases more specific names like ‘persons’, ‘addresses’ or ‘company’ might be more expressive.
In general, you have to pay attention and avoid collisions of rule names with keywords. Predefined keywords of the schema definition language are described later.
Please note that, even for the validation of a simple value you would need to define a starting rule with a rule name.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int
EOS
dataValidation.check( 4 ) # => true
dataValidation.check( "a" ) # => false
dataValidation.check( 4.0 ) # => false
dataValidation.check( true ) # => false
A more sophisticated schema definition:
dataValidation = GdsDataValidation.create( <<-EOS )
company = :name : @t_string, :address : address, :ceo : person, :employees : person*
person = :firstname : @t_string, :lastname : @t_string, :yearOfBirth : @t_int, :address : address
address = :street : @t_string, :zipcode : @t_int, :city : @t_string
EOS
dataValidation.check( nil ) # => false
dataValidation.check(
{ name: 'My Company', address: { street: 'Broadway 300', zipcode: 22222, city: 'New York' },
ceo: { firstname: 'John', lastname: 'McArthur', yearOfBirth: 1959,
address: { street: 'Rosedale Dr. 40', zipcode: 34003, city: 'Los Angeles' } },
employees: [
{ firstname: 'Berry', lastname: 'Miller', yearOfBirth: 1989,
address: { street: 'South St. 12', zipcode: 48333, city: 'Chicago' } },
{ firstname: 'Jane', lastname: 'Smith', yearOfBirth: 1993,
address: { street: 'Mainstreet 4', zipcode: 62883, city: 'Seattle' } }
]
}
) # => true
Same schema definition just in a different formatting style:
dataValidation = GdsDataValidation.create( <<-EOS )
company = :name : @t_string,
:address : address,
:ceo : person,
:employees : person*
person = :firstname : @t_string,
:lastname : @t_string,
:yearOfBirth : @t_int,
:address : address
address = :street : @t_string,
:zipcode : @t_int,
:city : @t_string
EOS
Each rule starts with a rule name followed by an equal sign ‘=’ and a rule specification.
<rule name> = <rule specification>
The rule specification is used to define a valid structure or valid values. The rule specification also can refer to other rules by referencing other rules by their rule names and express dependencies to these rules. Each rule specification is part of the overall schema definition and is checked for validity. To be more precise, each rule specification which is directly or indirectly referenced by the starting rule is verified by the validation process.
The following rule specifications are available:
- structural specifications for composite data types
- value specifications for basic data types
- rule reference
Each of these rule specifications define some kind of constraint or assertion which needs to be satisfied or fulfilled in order to successfully validate the incoming data.
Rule alternatives
If necessary you can define for one rule several alternative rule specifications. This list of rule specifications needs to be sparated by the slash character ‘/’.
This is corresponding to alternatives for a production rule of a context free grammar definition.
@maybe
Each rule specification can be prefixed with the @maybe specifier.
If the concerned value is nil, then this rule specification, that means this part of the schema definition is valid and it is not futher investigated.
Usage with Ruby
Installation
gem install gds-data-validation
Coding
require 'gds-data-validation'
Hash specification
The hash specification consists of a comma-separated list of key-value specifications.
The syntax of a single key-value specification is the following:
<key> : <value specification>
The key is always a symbol, it has to start with a colon ‘:’.
All specified keys are required, that means for a successful validation the presence of all specified keys is an obligatory requirement.
Value specification
The value specification describes requirements for the value part of a key-value pair.
As a value specification the following rule specifications are allowed:
- array specification
- basic data type specification
- literal value
- disjunction of basic data type specifications and/or literal values
- rule reference
As you see, the major difference compared to a rule specification is, that for a value specification of a hash another hash specification is not allowed. At least it is not allowed yet, maybe in the future this will be changed. Anyway you can express the fact that the value part of a key-value pair of a hash is another hash simply by referencing another rule which is specifying a hash.
dataValidation = GdsDataValidation.create( <<-EOS )
person = :name : @t_string, :address : address
address = :street : @t_string, :zipcode : @t_int, :city : @t_string
EOS
The above example contains two rule definitions. Each of them is specifying a hash. The person rule is specifying a key :address which is referencing for its value the rule named address. The address rule is specifying its own key-value pairs.
Another difference compared to a rule specification is, that you could even reference to the same rule, that means to the one you are currently defining. This self-reference allows to define schemas for recursive data structures.
The following example uses a self-reference
dataValidation = GdsDataValidation.create( <<-EOS )
person = :name : @t_string, :farther : person, :mother : person
/ nil
EOS
it could be written like this
dataValidation = GdsDataValidation.create( <<-EOS )
person = @maybe :name : @t_string, :farther : person, :mother : person
EOS
Each value specification can be prefixed with the @maybe specifier.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = :number : @maybe @t_int
EOS
dataValidation.check( {} ) # => false
dataValidation.check( { number: "a" } ) # => false
dataValidation.check( { number: 1 } ) # => true
dataValidation.check( { number: nil } ) # => true
Optional key-value pairs
Usually you use a colon (‘:’) to separate a key from a value specification in the definition of a key–value-specification pair.
In this case for a successful validation the presence of the key is an obligatory requirement.
In the case the key and its corresponding value should be optional you use the notation ‘:?’ instead of ‘:’.
The syntax for an optional key-value specification is the following:
<key> :? <value specification>
dataValidation = GdsDataValidation.create( <<-EOS )
schema = :number :? @t_int
EOS
dataValidation.check( {} ) # => true
dataValidation.check( { number: "a" } ) # => false
dataValidation.check( { number: nil } ) # => false
dataValidation.check( { number: 1 } ) # => true
@strict
So far we defined required keys and optional keys. But what about unwanted keys?
By default all additional, possibly unwanted keys are accepted.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = :number : @t_int
EOS
dataValidation.check( { number: 1 } ) # => true
dataValidation.check( { number: 1, unwanted: "bang!" } ) # => true
By default the validation logic is only checking for specified keys making sure they are present.
If you want to allow only specified keys then you have to prefix the hash specification with the @strict specifier.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @strict :number : @t_int
EOS
dataValidation.check( { number: 1 } ) # => true
dataValidation.check( { number: 1, unwanted: "bang!" } ) # => false
Array specification
The array specification consists of a rule name followed by a cardinality specification.
<rule name> <cardinality specification>
The cardinality specification can be one of the following:
The parameters size, minSize and maxSize have to be integer literals.
*
Verifies that the value is an array which could be empty or of any size.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = number*
number = @t_int
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 1 ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( [] ) # => true
dataValidation.check( [ 1 ] ) # => true
dataValidation.check( [ 1, 2 ] ) # => true
+
Verifies that the value is an array which has at least one element.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = number+
number = @t_int
EOS
dataValidation.check( [] ) # => false
dataValidation.check( [ 1 ] ) # => true
dataValidation.check( [ 1, 2 ] ) # => true
( minSize .. maxSize )
Verifies that the value is an array which has at least minSize elements and not more than maxSize elements.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = number(2..4)
number = @t_int
EOS
dataValidation.check( [] ) # => false
dataValidation.check( [ 1 ] ) # => false
dataValidation.check( [ 1, 2 ] ) # => true
dataValidation.check( [ 1, 2, 3 ] ) # => true
dataValidation.check( [ 1, 2, 3, 4 ] ) # => true
dataValidation.check( [ 1, 2, 3, 4, 5 ] ) # => false
( minSize .. )
Verifies that the value is an array which has at least minSize elements.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = number(2..)
number = @t_int
EOS
dataValidation.check( [] ) # => false
dataValidation.check( [ 1 ] ) # => false
dataValidation.check( [ 1, 2 ] ) # => true
dataValidation.check( [ 1, 2, 3 ] ) # => true
( .. maxSize )
Verifies that the value is an array which has not more than maxSize elements.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = number(..4)
number = @t_int
EOS
dataValidation.check( [] ) # => true
dataValidation.check( [ 1 ] ) # => true
dataValidation.check( [ 1, 2 ] ) # => true
dataValidation.check( [ 1, 2, 3 ] ) # => true
dataValidation.check( [ 1, 2, 3, 4 ] ) # => true
dataValidation.check( [ 1, 2, 3, 4, 5 ] ) # => false
( size )
Verifies that the value is an array which has exact size elements.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = number(3)
number = @t_int
EOS
dataValidation.check( [] ) # => false
dataValidation.check( [ 1 ] ) # => false
dataValidation.check( [ 1, 2 ] ) # => false
dataValidation.check( [ 1, 2, 3 ] ) # => true
dataValidation.check( [ 1, 2, 3, 4 ] ) # => false
Basic data type specification
The following basic data types are available:
- @t_string
- @t_int
- @t_float
- @t_numeric
- @t_symbol
- @t_true
- @t_false
- @t_bool
- @t_nil
- @t_any
A basic data type is a predicate which will be verified.
For several basic data types there are additional value restricting predicates available which are restricting the set of allowed values of a basic data type. They are used as refinements for the specification.
Each predicate can be prefixed with the logical not operator ‘!’ in order to negate the predicate. That means a basic data type predicate and an additional value restricting predicate can be prefixed with ‘!’.
All predicates only need to be separated by a space, however logically they represent a conjunction and you can think of an imaginary logical and operator ‘&’ being in between each of them.
As you would expect from other programming languages, the logical not operator ‘!’ has a higher precedence than the (imaginary) logical and operator and binds strong to the following predicate.
@t_string
The value is required to be of type String.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 4 ) # => false
dataValidation.check( 4.0 ) # => false
dataValidation.check( "a" ) # => true
dataValidation.check( "hello" ) # => true
Value restricting predicates for @t_string
If one of the following predicates requires a compareValue, then it has to be noted as a double-quoted string literal.
== compareValue
Verifies that the string value is equal to compareValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string == "hello"
EOS
dataValidation.check( "a" ) # => false
dataValidation.check( "hello" ) # => true
!= compareValue
Verifies that the string value is not equal to compareValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string != "hello"
EOS
dataValidation.check( "a" ) # => true
dataValidation.check( "hello" ) # => false
[ <list of comma-separated double-quoted string literals> ]
Verifies that the string value is included in the given list.
The list acts like an allowlist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string [ "a", "b", "c" ]
EOS
dataValidation.check( "a" ) # => true
dataValidation.check( "hello" ) # => false
dataValidation.check( "b" ) # => true
dataValidation.check( "c" ) # => true
If you negate this predicate then it verifies that the value is excluded from the given list.
In this case the list acts like a blocklist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string ! [ "a", "b", "c" ]
EOS
dataValidation.check( "a" ) # => false
dataValidation.check( "hello" ) # => true
dataValidation.check( "b" ) # => false
dataValidation.check( "c" ) # => false
%( <list of space-separated unquoted string values> )
Verifies that the string value is included in the given list.
The list acts like an allowlist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string %( a b c )
EOS
dataValidation.check( "a" ) # => true
dataValidation.check( "hello" ) # => false
dataValidation.check( "b" ) # => true
dataValidation.check( "c" ) # => true
If you negate this predicate then it verifies that the value is excluded from the given list.
In this case the list acts like a blocklist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string ! %( a b c )
EOS
dataValidation.check( "a" ) # => false
dataValidation.check( "hello" ) # => true
dataValidation.check( "b" ) # => false
dataValidation.check( "c" ) # => false
empty
Verifies that the string value is empty.
empty is the opposite of something.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string empty
EOS
dataValidation.check( "a" ) # => false
dataValidation.check( "" ) # => true
something
Verifies that the string value is not empty, it needs to contain at least one character.
something is the opposite of emtpy.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string something
EOS
dataValidation.check( "a" ) # => true
dataValidation.check( "abc" ) # => true
dataValidation.check( " " ) # => true
dataValidation.check( "" ) # => false
blank
Verifies that the string value is blank.
A string is blank if it is empty or contains whitespace characters only.
This is equivalent to the method ‘blank?’ defined in Rail’s ActiveSupport extension for String class.
blank is the opposite of present.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string blank
EOS
dataValidation.check( nil ) # => false
dataValidation.check( "" ) # => true
dataValidation.check( " " ) # => true
dataValidation.check( "\t" ) # => true
dataValidation.check( "\n" ) # => true
dataValidation.check( "\n \t" ) # => true
dataValidation.check( "a" ) # => false
present
Verifies that the string value is present.
A string is present if it is not empty and contains not only whitespace characters.
This is equivalent to the method ‘present?’ defined in Rail’s ActiveSupport extension for String class.
present is the opposite of blank.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string present
EOS
dataValidation.check( nil ) # => false
dataValidation.check( "" ) # => false
dataValidation.check( " " ) # => false
dataValidation.check( "\t" ) # => false
dataValidation.check( "\n" ) # => false
dataValidation.check( "\n \t" ) # => false
dataValidation.check( "a" ) # => true
/regexp/
Verifies that the string value matches the given regular expression.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string /HELLO/
EOS
dataValidation.check( "HELLO" ) # => true
dataValidation.check( "hello" ) # => false
dataValidation.check( "Hello" ) # => false
You can extend the regular expression with option ‘i’ to perform a case insensitive match.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string /HELLO/i
EOS
dataValidation.check( "HELLO" ) # => true
dataValidation.check( "hello" ) # => true
dataValidation.check( "Hello" ) # => true
length <length specification>
Verifies that the length of the string value is in accordance with the stated specification.
The length specification can be one of the following:
The parameters compareValue, minValue and maxValue have to be integer literals.
== compareValue
Verifies that the length is equal to compareValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string length == 3
EOS
dataValidation.check( "a" ) # => false
dataValidation.check( "abc" ) # => true
dataValidation.check( "abcd" ) # => false
!= compareValue
Verifies that the length is not equal to compareValue.
< maxValue
Verifies that the length is less than maxValue.
<= maxValue
Verifies that the length is less than or equal to maxValue.
> minValue
Verifies that the length is greater than minValue.
>= minValue
Verifies that the length is greater than or equal to minValue.
> minValue < maxValue
Verifies that the length is greater than minValue and less than maxValue.
> minValue <= maxValue
Verifies that the length is greater than minValue and less than or equal to maxValue.
>= minValue < maxValue
Verifies that the length is greater than or equal to minValue and less than maxValue.
>= minValue <= maxValue
Verifies that the length is greater than or equal to minValue and less than or equal to maxValue.
< maxValue > minValue
Verifies that the length is less than maxValue and greater than minValue.
<= maxValue > minValue
Verifies that the length is less than or equal maxValue and greater than minValue.
< maxValue >= minValue
Verifies that the length is less than maxValue and greater than or equal minValue.
<= maxValue >= minValue
Verifies that the length is less than or equal to maxValue and greater than or equal to minValue.
( minValue .. maxValue )
Verifies that the length is within the specified range, that means greater than or equal to minValue and less than or equal to maxValue.
( minValue .. )
Verifies that the length is greater than or equal to minValue.
( .. maxValue )
Verifies that the length is less than or equal to maxValue.
A contrived example
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string present ! length (2..3) != "a" ! == "b" ! %( aaaa bbbb ) ! /\\Aab/
EOS
dataValidation.check( nil ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( "b" ) # => false
dataValidation.check( "c" ) # => true
dataValidation.check( "ba" ) # => false
dataValidation.check( "aaaa" ) # => false
dataValidation.check( "bbbb" ) # => false
dataValidation.check( "abbb" ) # => false
dataValidation.check( "babb" ) # => true
dataValidation.check( " " ) # => false
@t_int
The value is required to be of type Integer.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 4.0 ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( 3 ) # => true
dataValidation.check( 4 ) # => true
Value restricting predicates for @t_int
The parameters compareValue, minValue and maxValue have to be integer literals.
== compareValue
Verifies that the integer value is equal to compareValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int == 4
EOS
dataValidation.check( 3 ) # => false
dataValidation.check( 4 ) # => true
!= compareValue
Verifies that the integer value is not equal to compareValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int != 4
EOS
dataValidation.check( 4 ) # => false
dataValidation.check( 5 ) # => true
The result is the same as if you negate the == predicate:
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int ! == 4
EOS
< maxValue
Verifies that the integer value is less than maxValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int < 4
EOS
dataValidation.check( 3 ) # => true
dataValidation.check( 4 ) # => false
<= maxValue
Verifies that the integer value is less than or equal to maxValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int <= 4
EOS
dataValidation.check( 3 ) # => true
dataValidation.check( 4 ) # => true
dataValidation.check( 5 ) # => false
> minValue
Verifies that the integer value is greater than minValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int > 4
EOS
dataValidation.check( 4 ) # => false
dataValidation.check( 5 ) # => true
>= minValue
Verifies that the integer value is greater than or equal to minValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int >= 4
EOS
dataValidation.check( 3 ) # => false
dataValidation.check( 4 ) # => true
dataValidation.check( 5 ) # => true
( minValue .. maxValue )
Verifies that the integer value is within the specified range, that means greater than or equal to minValue and less than or equal to maxValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int (4..7)
EOS
dataValidation.check( 3 ) # => false
dataValidation.check( 4 ) # => true
dataValidation.check( 5 ) # => true
dataValidation.check( 7 ) # => true
dataValidation.check( 8 ) # => false
( minValue .. )
Verifies that the integer value is greater than or equal to minValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int (4..)
EOS
dataValidation.check( 3 ) # => false
dataValidation.check( 4 ) # => true
dataValidation.check( 8 ) # => true
( .. maxValue )
Verifies that the integer value is less than or equal to maxValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int (..7)
EOS
dataValidation.check( 3 ) # => true
dataValidation.check( 7 ) # => true
dataValidation.check( 8 ) # => false
[ <list of comma-separated integer literals> ]
Verifies that the integer value is included in the given list.
The list acts like an allowlist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int [ 5, 7, 11 ]
EOS
dataValidation.check( 4 ) # => false
dataValidation.check( 5 ) # => true
dataValidation.check( 7 ) # => true
dataValidation.check( 11 ) # => true
If you negate this predicate then it verifies that the value is excluded from the given list.
In this case the list acts like a blocklist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int ! [ 5, 7, 11 ]
EOS
dataValidation.check( 4 ) # => true
dataValidation.check( 5 ) # => false
dataValidation.check( 7 ) # => false
dataValidation.check( 11 ) # => false
%( <list of space-separated integer literals> )
Verifies that the integer value is included in the given list.
The list acts like an allowlist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int %( 5 7 11 )
EOS
dataValidation.check( 4 ) # => false
dataValidation.check( 5 ) # => true
dataValidation.check( 7 ) # => true
dataValidation.check( 11 ) # => true
If you negate this predicate then it verifies that the value is excluded from the given list.
In this case the list acts like a blcklist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int ! %( 5 7 11 )
EOS
dataValidation.check( 4 ) # => true
dataValidation.check( 5 ) # => false
dataValidation.check( 7 ) # => false
dataValidation.check( 11 ) # => false
odd
Verifies that the integer value is an odd number.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int odd
EOS
dataValidation.check( 4 ) # => false
dataValidation.check( 5 ) # => true
even
Verifies that the integer value is an even number.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_int even
EOS
dataValidation.check( 4 ) # => true
dataValidation.check( 5 ) # => false
@t_float
The value is required to be of type Float.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 4 ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( 4.0 ) # => true
dataValidation.check( 4.1 ) # => true
Value restricting predicates for @t_float
The parameters compareValue, minValue and maxValue have to be floating-point literals.
== compareValue
Verifies that the floating-point value is equal to compareValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float == 4.0
EOS
dataValidation.check( 4.0 ) # => true
dataValidation.check( 4.1 ) # => false
!= compareValue
Verifies that the floating-point value is not equal to compareValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float != 4.0
EOS
dataValidation.check( 4.0 ) # => false
dataValidation.check( 4.1 ) # => true
The result is the same as if you negate the == predicate:
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float ! == 4.0
EOS
< maxValue
Verifies that the floating-point value is less than maxValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float < 4.0
EOS
dataValidation.check( 3.9 ) # => true
dataValidation.check( 4.0 ) # => false
<= maxValue
Verifies that the floating-point value is less than or equal to maxValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float <= 4.0
EOS
dataValidation.check( 3.9 ) # => true
dataValidation.check( 4.0 ) # => true
dataValidation.check( 4.1 ) # => false
> minValue
Verifies that the floating-point value is greater than minValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float > 4.0
EOS
dataValidation.check( 4.0 ) # => false
dataValidation.check( 4.1 ) # => true
>= minValue
Verifies that the floating-point value is greater than or equal to minValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float >= 4.0
EOS
dataValidation.check( 3.9 ) # => false
dataValidation.check( 4.0 ) # => true
( minValue .. maxValue )
Verifies that the floating-point value is within the specified range, that means greater than or equal to minValue and less than or equal to maxValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float (4.1..7.2)
EOS
dataValidation.check( 4.1 ) # => true
dataValidation.check( 7.2 ) # => true
dataValidation.check( 7.3 ) # => false
( minValue .. )
Verifies that the floating-point value is greater than or equal to minValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float (4.1..)
EOS
dataValidation.check( 4.0 ) # => false
dataValidation.check( 4.1 ) # => true
dataValidation.check( 9.9 ) # => true
( .. maxValue )
Verifies that the floating-point value is less than or equal to maxValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float (..7.2)
EOS
dataValidation.check( 7.2 ) # => true
dataValidation.check( 7.3 ) # => false
[ <list of comma-separated floating-point literals> ]
Verifies that the floating-point value is included in the given list.
The list acts like an allowlist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float [ 5.3, 7.1, 11.8 ]
EOS
dataValidation.check( 4.9 ) # => false
dataValidation.check( 5.3 ) # => true
dataValidation.check( 7.1 ) # => true
dataValidation.check( 11.8 ) # => true
If you negate this predicate then it verifies that the value is excluded from the given list.
In this case the list acts like a blocklist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float ! [ 5.3, 7.1, 11.8 ]
EOS
dataValidation.check( 4.9 ) # => true
dataValidation.check( 5.3 ) # => false
dataValidation.check( 7.1 ) # => false
dataValidation.check( 11.8 ) # => false
%( <list of space-separated integer literals> )
Verifies that the floating-point value is included in the given list.
The list acts like an allowlist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float %( 5.3 7.1 11.8 )
EOS
dataValidation.check( 4.9 ) # => false
dataValidation.check( 5.3 ) # => true
dataValidation.check( 7.1 ) # => true
dataValidation.check( 11.8 ) # => true
If you negate this predicate then it verifies that the value is excluded from the given list.
In this case the list acts like a blocklist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_float ! %( 5.3 7.1 11.8 )
EOS
dataValidation.check( 4.9 ) # => true
dataValidation.check( 5.3 ) # => false
dataValidation.check( 7.1 ) # => false
dataValidation.check( 11.8 ) # => false
@t_numeric
The value is required to be of type Numeric. Type Integer and type Float are both of type Numeric.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_numeric
EOS
dataValidation.check( nil ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( 4 ) # => true
dataValidation.check( 4.0 ) # => true
@t_symbol
The value is required to be of type Symbol.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 4 ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( :a ) # => true
dataValidation.check( :sym ) # => true
Value restricting predicates for @t_symbol
If one of the following predicates requires a compareValue, then it has to be noted as a symbol literal.
== compareValue
Verifies that the symbol value is equal to compareValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol == :sym
EOS
dataValidation.check( :newsym ) # => false
dataValidation.check( :sym ) # => true
!= compareValue
Verifies that the symbol value is not equal to compareValue.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol != :sym
EOS
dataValidation.check( :newsym ) # => true
dataValidation.check( :sym ) # => false
[ <list of comma-separated symbol literals> ]
Verifies that the symbol value is included in the given list.
The list acts like an allowlist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol [ :a, :b, :")" ]
EOS
dataValidation.check( :a ) # => true
dataValidation.check( :sym ) # => false
dataValidation.check( :b ) # => true
dataValidation.check( :")" ) # => true
If you negate this predicate then it verifies that the value is excluded from the given list.
In this case the list acts like a blocklist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol ! [ :a, :b, :")" ]
EOS
dataValidation.check( :a ) # => false
dataValidation.check( :sym ) # => true
dataValidation.check( :b ) # => false
dataValidation.check( :")" ) # => false
%( <list of space-separated symbol values, without leading colon> )
Verifies that the symbol value is included in the given list.
The list acts like an allowlist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol %( a b ")" )
EOS
dataValidation.check( :a ) # => true
dataValidation.check( :sym ) # => false
dataValidation.check( :b ) # => true
dataValidation.check( :")" ) # => true
If you negate this predicate then it verifies that the value is excluded from the given list.
In this case the list acts like a blocklist.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol ! %( a b ")" )
EOS
dataValidation.check( :a ) # => false
dataValidation.check( :sym ) # => true
dataValidation.check( :b ) # => false
dataValidation.check( :")" ) # => false
/regexp/
Verifies that the symbol value matches the given regular expression.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol /MYSYMBOL/
EOS
dataValidation.check( :MYSYMBOL ) # => true
dataValidation.check( :mysymbol ) # => false
dataValidation.check( :Mysymbol ) # => false
You can extend the regular expression with option ‘i’ to perform a case insensitive match.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol /MYSYMBOL/i
EOS
dataValidation.check( :MYSYMBOL ) # => true
dataValidation.check( :mysymbol ) # => true
dataValidation.check( :Mysymbol ) # => true
length <length specification>
Verifies that the length of the symbol value is in accordance with the stated specification.
The length specification is the same as already described for @t_string. Please look it up there.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_symbol length == 3
EOS
dataValidation.check( :a ) # => false
dataValidation.check( :abc ) # => true
dataValidation.check( :abcd ) # => false
@t_true
The value is required to be of type TrueClass.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_true
EOS
dataValidation.check( nil ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( 4 ) # => false
dataValidation.check( 4.0 ) # => false
dataValidation.check( false ) # => false
dataValidation.check( true ) # => true
@t_false
The value is required to be of type FalseClass.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_false
EOS
dataValidation.check( nil ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( 4 ) # => false
dataValidation.check( 4.0 ) # => false
dataValidation.check( false ) # => true
dataValidation.check( true ) # => false
@t_bool
The value is required to be either of type TrueClass or of type FalseClass.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_bool
EOS
dataValidation.check( nil ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( 4 ) # => false
dataValidation.check( 4.0 ) # => false
dataValidation.check( false ) # => true
dataValidation.check( true ) # => true
@t_nil
The value is required to be of type NilClass.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_nil
EOS
dataValidation.check( nil ) # => true
dataValidation.check( "a" ) # => false
dataValidation.check( 4 ) # => false
dataValidation.check( 4.0 ) # => false
dataValidation.check( false ) # => false
dataValidation.check( true ) # => false
@t_any
The value can be of any type. It is required to be of type Object.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_any
EOS
dataValidation.check( nil ) # => true
dataValidation.check( "a" ) # => true
dataValidation.check( 4 ) # => true
dataValidation.check( 4.0 ) # => true
dataValidation.check( false ) # => true
dataValidation.check( true ) # => true
dataValidation.check( {} ) # => true
dataValidation.check( [] ) # => true
Literal value
Literal values can be used as a rule specification or as a value specification for the value part of a hash.
The following literals are available:
- string literals
- integer literals
- floating-point literals
- symbol literals
- keyword literals
String literals
dataValidation = GdsDataValidation.create( <<-EOS )
schema = "hello"
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 4 ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( "hello" ) # => true
dataValidation = GdsDataValidation.create( <<-EOS )
male = :title : "Mr.", :name : @t_string
EOS
dataValidation.check( { title: "Mrs.", name: "John" } ) # => false
dataValidation.check( { title: "Mr.", name: "John" } ) # => true
Integer literals
dataValidation = GdsDataValidation.create( <<-EOS )
schema = 4
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 4 ) # => true
dataValidation.check( 4.0 ) # => true , true because of Ruby's type coercion
dataValidation.check( "a" ) # => false
dataValidation.check( "hello" ) # => false
Floating-point literals
dataValidation = GdsDataValidation.create( <<-EOS )
schema = 4.0
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 4 ) # => true , true because of Ruby's type coercion
dataValidation.check( 4.0 ) # => true
dataValidation.check( "a" ) # => false
dataValidation.check( "hello" ) # => false
Symbol literals
dataValidation = GdsDataValidation.create( <<-EOS )
schema = :on
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 4 ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( :off ) # => false
dataValidation.check( :on ) # => true
Keyword literals
The following keyword literals are available:
- true
- false
- nil
- null
In Ruby nil and null are considered as nil.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = null
EOS
dataValidation.check( nil ) # => true
dataValidation.check( 4 ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( :on ) # => false
Disjunction of basic data type specifications and literal values
You can define disjunctions of basic data type specifications and literal values and use it as a rule specification or as a value specification for the value part of a hash.
You have to use the logical or operator ‘|’ to create the disjunction.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = true | @t_int | @t_string
EOS
dataValidation.check( nil ) # => false
dataValidation.check( 4 ) # => true
dataValidation.check( "a" ) # => true
dataValidation.check( true ) # => true
dataValidation = GdsDataValidation.create( <<-EOS )
schema = :special : true | @t_int | :on | @t_string
EOS
dataValidation.check( { special: nil } ) # => false
dataValidation.check( { special: 4 } ) # => true
dataValidation.check( { special: "a" } ) # => true
dataValidation.check( { special: true } ) # => true
dataValidation.check( { special: :on } ) # => true
You can negate the whole disjunction using the logical not operator ‘!’, but then you have to define the disjunction within parentheses.
dataValidation = GdsDataValidation.create( <<-EOS )
schema = ! ( true | @t_int | @t_string | 4.3 )
EOS
dataValidation.check( nil ) # => true
dataValidation.check( 4 ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( true ) # => false
dataValidation.check( 4.3 ) # => false
dataValidation.check( 4.4 ) # => true
Rule reference
A rule reference makes an indirection to another rule by stating the rule name of the other rule.
<rule name>
This is necessary, when you want to define schemas for nested hashes. If the value part of a key-value pair is another hash (sub-hash), then you have to use a rule reference for the value and you have to define the schema for the sub-hash with this referenced rule.
dataValidation = GdsDataValidation.create( <<-EOS )
person = :name : @t_string, :address : address
address = :street : @t_string, :zip : @t_int, :city : @t_string
EOS
The value for the key :address of the person schema is referencing the rule address which defines the schema for the sub-hash.
Sometimes using a rule reference as a rule definition could be suitable for development and testing. It behaves like an additional layer of indirection and serves as a decision point or single point of change to switch between different (sub) schema definitions.
dataValidation = GdsDataValidation.create( <<-EOS )
company = :name : @t_string, :visitor_address : address, :delivery_address : address, :employees : person
person = :name : @t_string, :address : address
address = address2
address1 = :street : @t_string, :zip : @t_int, :city : @t_string
address2 = :street : @t_string, :zip : @t_int, :city : @t_string, :country : @t_string
address3 = :street : @t_string, :street_number : @t_int, :zip : @t_int, :city : @t_string, :country : @t_string
EOS
Practical Examples
Rule alternatives
Use rule alternatives to let your validation recognize different kinds of forms of data.
In the following validation the attribute :payment_type acts like a discriminator to identify the type of payment.
If the payment is of type ‘cash’ or ‘cheque’, then only the attribute :amount has to be available.
If the payment is of type ‘card’ then in addition to :amount also the attribute :card_number has to be available.
dataValidation = GdsDataValidation.create( <<-EOS )
payment =
:payment_type : @t_string %( cash cheque ),
:amount : @t_int
/
:payment_type : @t_string %( card ),
:card_number : @t_int
:amount : @t_int
EOS
Web services
The following validation is used in production for a small web service which is parsing GDS (General Data Structure)
expressions and pretty printing the resulting hash/array structure for various languages and styles.
Actually this web service is used by the GDS Converter.
The parameters of the REST request have to comply with a certain schema:
There is a top-level hash containing one key-value pair with the key :gdseval4 and a value which is a sub-hash. This sub-hash contains key-value pairs with the keys :gdsexpr, :language and :style. The value for the key :gdsexpr defines the GDS expression, the value for the key :language defines the destination language and the :style defines the style of the pritty print. For the langages ‘ruby’, ‘elixir’, ‘python’ and ‘json’ there are the styles ‘compact’, ‘semiverbose’ and ‘verbose’ available. For the languages ‘xml’ and ‘yaml’ there is only the style ‘default’ available.
The following schema defines this specification for the request parameters.
GdsEvalValidation = GdsDataValidation.create(<<EOS)
schema = :gdseval4 : gdseval4
gdseval4 =
:gdsexpr : @t_string,
:language : @t_string %( ruby elixir python json ),
:style : @t_string %( compact semiverbose verbose )
/
:gdsexpr : @t_string,
:language : @t_string %( xml yaml ),
:style : @t_string %( default )
EOS
GdsEvalValidation.check( { gdseval4: { gdsexpr: 'key value', language: 'ruby', style: 'compact' } } ) # => true
GdsEvalValidation.check( { gdseval4: { gdsexpr: 'key value', language: 'xml', style: 'default' } } ) # => true
GdsEvalValidation.check( { gdseval4: { gdsexpr: 'key value', language: 'python', style: 'default' } } ) # => false
GdsEvalValidation.check( { gdseval4: { gdsexpr: 'key value', language: 'yaml', style: 'verbose' } } ) # => false
The web service expects a JSON object in the body of a REST HTTP POST request. A Rails or Sinatra web application loads this JSON data into the params hash, which is implemented as a hash with indifferent access, where the key as a Symbol (e.g. :gdsexpr) and key as a String (e.g. “gdsexpr”) is considered to be the same.
The validation can check straight this params hash for validity.
result = GdsEvalValidation.check( params ) # => true or false
In Rails usually this validation check is done inside an Action Controller.
Self-referential schema definitions
The following is an example for a self-referential schema definition.
dataValidation = GdsDataValidation.create( <<-EOS )
person = @maybe :name : @t_string, :farther : person, :mother : person
EOS
dataValidation.check( { name: 'John', farther: nil, mother: nil } )
# => true
dataValidation.check( { name: 'John',
farther: { name: 'Berry', farther: nil, mother: nil },
mother: nil } )
# => true
dataValidation.check( { name: 'John',
farther: { name: 'Berry',
farther: { name: "Robert", farther: nil, mother: nil },
mother: nil },
mother: nil } )
# => true
dataValidation.check( { name: 'John',
farther: { name: 'Berry', farther: nil, mother: nil },
mother: { name: 'Mary',
farther: { name: "Martin", farther: nil, mother: nil },
mother: { name: "Olivia", farther: nil, mother: nil } } } )
# => true
dataValidation.check( { name: 'John',
farther: { name: 'Berry', farther: nil, mother: 2 },
mother: nil } )
# => false
Contrived Examples
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @t_string present ! length (2..3) != "a" ! == "b" ! %( aaaa bbbb ) ! /\\Aab/
EOS
dataValidation.check( nil ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( "b" ) # => false
dataValidation.check( "c" ) # => true
dataValidation.check( "ba" ) # => false
dataValidation.check( "aaaa" ) # => false
dataValidation.check( "bbbb" ) # => false
dataValidation.check( "abbb" ) # => false
dataValidation.check( "babb" ) # => true
dataValidation.check( " " ) # => false
dataValidation = GdsDataValidation.create( <<-EOS )
schema = @maybe @strict :number : @t_int, :name : @t_string
EOS
dataValidation.check( nil ) # => true
dataValidation.check( { number: 4, name: "a" } ) # => true
dataValidation.check( { number: 4, name: "a", unwanted: "bang!" } ) # => false
dataValidation = GdsDataValidation.create( <<-EOS )
schema = :special : true | @t_int | :on | @t_string
EOS
dataValidation.check( { special: nil } ) # => false
dataValidation.check( { special: 4 } ) # => true
dataValidation.check( { special: "a" } ) # => true
dataValidation.check( { special: true } ) # => true
dataValidation.check( { special: false } ) # => false
dataValidation.check( { special: :on } ) # => true
dataValidation.check( { special: :off } ) # => false
dataValidation = GdsDataValidation.create( <<-EOS )
schema = ! ( true | @t_int | @t_string | 4.3 )
EOS
dataValidation.check( nil ) # => true
dataValidation.check( 4 ) # => false
dataValidation.check( "a" ) # => false
dataValidation.check( true ) # => false
dataValidation.check( false ) # => true
dataValidation.check( 4.3 ) # => false
dataValidation.check( 4.4 ) # => true
Ruby Gem
You can find it on RubyGems.org:
rubygems.org/gems/gds-data-validation
Source Code
You can find the source code on GitHub: