An important part of defining a type is defining its validation rules. These are used by DNAL to validate all values of that type. Rules are placed between the base type and the end keyword. For struct and enum, they appear after the closing brace }. Rules are a sequence of conditions that are combined using AND logic
type StudentGrade int
 >= 0,
 <= 100
end

This read “StudentGrade is an int whose values are >= 0 AND ⇐ 100”

It is recommended to put each rule on a separate line.

7.1. Built-in Rules

The built-in rules are

  • > greater than
  • < less than
  • >= greater than or equals
  • less than or equals
  • == equals
  • ieq is case-insensitive version of ==
  • ilt,igt is case-insensitive version of < and >
  • ile,ige is case-insensitive version of <= and >=
  • in the value is one of the values specified in the in expression
  • range the value is within the range of values specified. An exclusive range.
  • irange case-insensitive version of range, for string values. An exclusive range.
  • contains the value contains within itself the value in the expression
  • empty the value is empty, such as “”.
  • hasText the value is not empty and contains at least one non-whitespace character
  • or
  • and
  • ! not
  • regex
  • startsWith and endsWith
  • isa

Rules are shape-specific. The following table describes the shapes that can be used with each rule.

Rule

int

long

number

boolean

string

date

list

struct

enum

>,<,>=,⇐

yes

yes

yes

no

yes

yes

no

no

no

!=,==

yes

yes

yes

no

yes

yes

no

no

yes

ieq,ilt,ile,igt,ige

no

no

no

no

yes

no

no

no

no

in

yes

yes

yes

no

yes

no

no

no

yes

range

yes

yes

yes

no

yes

yes

no

no

yes

contains

no

no

no

no

yes

no

yes

no

yes

empty

no

no

no

no

yes

no

yes

no

no

regex

no

no

no

no

yes

no

no

no

no

isa

yes

yes

no

no

yes

no

no

no

no

startsWith,endsWith

no

no

no

no

yes

no

no

no

no

hasText

no

no

no

no

yes

no

no

no

no

7.1.1. Comparison Operators

The six comparison operators can be used to specify a condition about the type’s value. The type’s value is implied but not stated

type PositiveInt int
 >= 0
end

This can be read as “PositiveInt is an int whose value is >= 0”

Rules for struct members specify the member by name. Here we validate that price is greater than or equals zero:

type Product struct {
  name string,
  price number
}
  price >= 0.0
end
The len pseudo-value

For string and list values, a special pseudo-value len represents the length of the value.

type NonEmptyList list
  len > 0
end

Note. A struct using the len pseudo-value must not have a member named “len”.

type Student struct {
  firstName string,
  lastName string
 }
  len(firstName) > 0
end

7.1.1.1. ieq, ilt, igt, ile, ige

For string values, there are case-insensitive versions

  ieq('tcp')  //equals
  ilt('z')    //less-than
  ile('z')    //less-than-or-equals
  igt('a')    //greater-than
  ige('a')    //greater-than-or-equals

For struct members, use the fieldName + “.” and the rule.

type StudentsNamedBob struct {
  firstName string,
  lastName string
 }
  firstName.iseq('Bob')
end

7.1.2. in

The in rule contains a list of allowed values. It can be specified as a list or range

  in(3,5,7)
  in(0..100)

The first rule requires that the type’s value is 3, 5, or 7.
The second rule requires that the type’s value is between 0 and 100 (inclusive).

Note
the range of values syntax “..” is not yet supported.

For struct members, use the fieldName + “.” and the in rule.

  someField.in(3,5,7)

7.1.3. range

The range rule contains a range of allowed values. It is an exclusive range.

  range(0,100)
  range(0..100) //same as above

This rule says that values 0,1,2,..99 are allowed.

Range of dates are supported, but only the comma form.

  range(1481482266000, 1481482266089) //using longs
  range('2016-12-11T13:51:06.079-0500','2016-12-11T13:51:06.099-0500')
  range('2016','2017')

Range of strings are supported, but only the comma form.

  range('a', 'z') 
  range('aardvark', 'zither')

For struct members, use the fieldName + “.” and the range rule.

  someField.range(0..100)

7.1.3.2 irange

The irange rule contains a range of allowed string values. It is an exclusive range, and a case-insensitive compare is used.

  irange('aa','zz')
  

For struct members, use the fieldName + “.” and the irange rule.

  someField.irange('aa','zz')

7.1.4. contains

Used with strings or lists. It means the the string or list contains, as one of its elements, the given value

 contains("able")

It can be used to ensure that a value does not contain certain characters

 !contains(" ", "\t", "\r", "\n")

For struct members, use the fieldName + “.” and the contains rule.

  someField.contains("able")

7.1.5. empty

The empty rule can be used with strings and lists. It is equivalent to len == 0.

7.1.6. regex

The regex rule applies a java regex

7.1.7. startsWith

This rule can be used with string values. It is equivalent to the Java String method startsWith.

7.1.8. endsWith

This rule can be used with string values. It is equivalent to the Java String method endsWith.

7.1.9. !

This reverses the logic and can be used with these rules

  • !in
  • !range
  • !contains
  • !empty
  • !regex, !startsWith, or !endsWith

7.1.10. isa

The isa rules defines a value as being a foreign key to another type through the specified field. The specified field is usually a natural key for that type (and it ususally unique).

 type Address struct {
   customerId string
 }
   customerId isa Customer.email
   street string
 end

 let someAddr Address = { 'rudy@home.com', '150 Main St.' }

The rule “customerId isa Customer.email” says that customerId will always hold an email value from Customer. When this rule is executed, DNAL searches
all Customer values
(in the current DataSet) until one is found with the value rudy@home.com. The rule fails if no Customer value has that email. An isa statement is equivalent to a foreign key statement in SQL.

The isa rule is used with the Via statement (see below)

7.1.11. or and and

Rules can be combined using or and and. Expressions can be grouped using parenthesis

  (len == 4 and contains("A")) or (len == 5 and contains("B"))

7.1.12. hasText

Used with strings.  This rule validates that the string is not-empty and contains at least one non-whitespace character.

For struct members, use the fieldName + “.” and the contains rule.

  someField.hasText()

7.2. Custom Rules

The DNAL rule system is extensible. You can define your own rules. There are two steps required. First, declare a custom rule using the rule statement.
This declares the name of the rule and which base type (eg. shape) it can be used on. Declare the rule once for each shape

 rule myspecialrule int
 rule myspecialrule long

Rules are type-specific. The above rule could be used on int or long values or any types derived from int or long.

Custom rules have the syntax

   rulename()
   rulename(arg0,arg1,...)

A custom rule can have zero or more arguments. They can be a mix of explicit values, or a reference to a value.

 let maxScore = 50

 type Score int
   myspecialrule(0,maxScore)
 end

The second step is to provide DNAL with the Java class that implements your custom rule. dnalc has command-line arguments for loading JAR files
containing custom rules. DNALCompiler has the method registryRuleFactory for registering custom rule classes.

For struct members, use the fieldName + “.” and the custom rule.

  someField.myspecialrule(0, maxScore)