/groovyschema

Groovy object validation

Primary LanguageGroovyMIT LicenseMIT

groovy-schema

Groovy object validation library that tries to emulate the JSON Schema specification. It is vastly inspired by @tdegrunt's implementation.

It is meant to be used with JsonSlurper when validating incoming JSON content in REST API implementations.

Build Status

Installing

Install groovy-schema to the local maven repository using Gradle 1.9+ :

$ gradle test install

Then use it in other Groovy or Grails projects :

@Grab('ca.code3:groovyschema:0.1-SNAPSHOT')
import groovyschema.Validator
// BuildConfig.groovy
dependencies {
  compile 'ca.code3:groovyschema:0.1-SNAPSHOT'
}

Usage

def schema = [
  type: 'object',
  required: true,
  properties: [
    honorificPrefix: [enum:['Ms.', 'Mr.', 'Dr.']],
    givenName: [type:'string', required:true],
    additionalName: [type:'string'],
    familyName: [type:'string', required:true],
    honorificSuffix: [enum:['Ph.D.', 'Esq.']],
    email: [format:'email', required:true],
  ]
]

def validator = new groovyschema.Validator()
def instance = new groovy.json.JsonSlurper().parseText(req.body)

def validationErrors = validator.validate(instance, schema)

The validationErrors array complies to:

class Validator {

  public static final ERRORS_SCHEMA = [
    type: 'array',
    minItems: 0,
    required: true,
    items: [
      type: 'object',
      required: true,
      additionalProperties: false,
      properties: [
        instance: [type:'any'], // the validated (sub-)instance e.g. "abc"
        schema: [type:'object', required:true], // the associated (sub-)schema e.g. [format:'email']
        message: [
          type: 'string',
          required: true,
          enum: [
            "groovyschema.additionalItems.message",
            "groovyschema.additionalProperties.message",
            "groovyschema.allOf.message",
            "groovyschema.anyOf.message",
            "groovyschema.dependencies.message",
            "groovyschema.divisibleBy.message",
            "groovyschema.enum.message",
            "groovyschema.fixed.message",
            "groovyschema.format.message",
            "groovyschema.maxItems.message",
            "groovyschema.maxLength.message",
            "groovyschema.maximum.message",
            "groovyschema.minItems.message",
            "groovyschema.minLength.message",
            "groovyschema.minimum.message",
            "groovyschema.not.message",
            "groovyschema.oneOf.message",
            "groovyschema.pattern.message",
            "groovyschema.required.message",
            "groovyschema.type.message",
            "groovyschema.uniqueItems.message"
          ]
        ]
      ]
    ]
  ]

All schema objects must comply to:

class Validator {

  public static final META_SCHEMA = [
    type: 'object',
    required: true,
    additionalProperties: false,
    properties: [

      required: [type:'boolean'],

      type: [type:'string', enum:['string', 'number', 'integer', 'boolean', 'array', 'null', 'any', 'object']],

      enum: [type:'array', minItems:1, items:[type:'any']],

      fixed: [type:'any'],

      pattern: [type:'string'],

      format: [type:'string', enum:['date-time', 'email', 'hostname', 'ipv4', 'ipv6', 'uri']],

      minLength: [type:'number', minimum:0],

      maxLength: [type:'number', minimum:0],

      minimum: [type:'number'],

      maximum: [type:'number'],

      divisibleBy: [type:'number', minimum:0, exclusiveMinimum:true],

      properties: [
        type: 'object',
        patternProperties: [
          /.+/: [type:'object'] // in fact, all values of the `property` object should comply to this metaschema.
        ]
      ],

      additionalProperties: [
        anyOf: [
          [type:'boolean'],
          [type:'null'],
          [type:'string'],
          [type:'array', items:[type:'string']],
          [type:'object'] // in fact, the schema for all additional properties
        ]
      ],

      patternProperties: [
        type: 'object',
        patternProperties: [
          /.+/: [type:'object'] // in fact, all values of the `property` object should comply to this metaschema.
        ]
      ],

      dependencies: [
        type: 'object',
        patternProperties: [
          /.+/: [
            anyOf: [
              [type:'string'],
              [type:'array', minItems:1, items:[type:'string']],
              [type:'object'] // in fact, the schema for the dependency
            ]
          ]
        ]
      ],

      items: [
        anyOf: [
          [type:'object'], // in fact, the schema for all items
          [type:'array', items:[type:'object']], // in fact, schemas for each item in the list
        ]
      ],

      additionalItems: [type:'boolean'],

      exclusiveMinimum: [type:'boolean'],

      exclusiveMaximum: [type:'boolean'],

      minItems: [type:'number', minimum:0],

      maxItems: [type:'number', minimum:0],

      uniqueItems: [type:'boolean'],

      allOf: [type:'array', items:[type:'object']], // in fact, an array of schemas

      anyOf: [type:'array', items:[type:'object']], // in fact, an array of schemas

      oneOf: [type:'array', items:[type:'object']], // in fact, an array of schemas

      not: [type:'array', items:[type:'object']], // in fact, an array of schemas
    ]
  ]

Non type-specific validations attributes

required

Validates that the passed-in instance is not null.

type

Validates that the instance is of the specified type. Possible values are string, number, integer, boolean, array (valid for instances of java.util.List), object (valid for instances of java.util.Map), null and any.

null instances that do not specify null as their type are always valid.

enum

Validates that the instance is in one of the given values. Enum values can be of any type.

fixed

Validates that the instance is equal to the given value. fixed value can be of any type.

String-specific validations attributes

pattern

Validates that the instance matches the specified regular expression:

def schema = [
  type: 'string',
  required: true,
  pattern: /^fo+$/,
]

format

Same as pattern but references predefined regular expressions. Possible values are date-time, email, hostname, ipv4, ipv6, and uri.

minLength, maxLength

Validates the length of the string. By default, minimum and maximum lengths are inclusive. Add the exclusiveMinimum or exclusiveMaximum attributes to alter this behaviour.

// Valid if string length in (0..30]
def schema = [
  type: 'string',
  minLength: 0,
  maxLength: 30,
  exclusiveMinimum: true,
]

Number-specific validations attributes

minimum, maximum

By default, minimum and maximum values are inclusive. Add the exclusiveMinimum or exclusiveMaximum attributes to alter this behaviour.

// Valid if number in (0..100]
def schema = [
  type: 'number',
  minimum: 0,
  maximum: 100,
  exclusiveMinimum: true,
]

divisibleBy

Validates that the instance is divisible by the attribute value.

Map-specific validations attributes

properties

Validates a schema for each of the instance's entries. For each entry in the properties map, the key specifies a certain attribute found in the instance; the value specifies a schema for that attribute.

The following schema indicates that its instances must be maps with two entries. One of those must have a key called name and its value must be non-null and a string. The other key is called email and its value must be a non-null string that matches the predefined email regular expression.

def schema = [
  type: 'object',
  properties: [
    name: [type:'string', required:true],
    email: [type:'string', format:'email', required:true],
  ]
]

additionalProperties

Validates what additional properties can be part of the instance. Possible values can be a boolean (true by default) a string or list of strings representing the allowed, optional, additional properties or the schema for all additional properties.

def schema = [
  type: 'object',
  properties: [
    name: [type:'string', required:true],
    email: [type:'string', format:'email', required:true],
  ],
  additionalProperties: ['givenName', 'familyName']
]

patternProperties

Similar to the properties attribute, patternProperties specifies regular expressions instead of exact strings for property names. Values for properties that match more than one regular expression must conform to all matching schemas.

def schema = [
  type: 'object',
  patternProperties: [
    /^(given|family)Name$/: [type:'string', required:true],
  ]
]

dependencies

Defines dependencies between properties. Dependencies can either be property-dependencies or schema-dependencies.

Property-dependency (e.g. if property a is present, so also must be properties b and c):

def schema = [
  type: 'object',
  dependencies: [
    a: ['b', 'c']
  ]
]

Schema-dependency (e.g. if property a is present, the instance must also conform to an additional schema):

def schema = [
  type: 'object',
  dependency: [
    a: [
      properties: [
        b: [type:'integer', minimum:0, divisibleBy:2, required:true]
      ]
    ]
  ]
]

In this example, valid instances would include [a:1, b:2] and [c:3]. Invalid instances could be [a:1] or [a:1, b:1].

List-specific validations attributes

items

Validates that the items in the instance all conform to a specified schema. Possible values for the items attribute are a single, common, schema for all items in the list or an ordered list of schemas (one for each item in the list).

All items must be non-null positive integers:

def schema = [
  type: 'array',
  items: [type:'integer', minimum:0, required:true]
]

The first item must be an email; the second must be an integer. Additional items are not allowed:

def schema = [
  type: 'array',
  items: [
    [type:'string', format:'email'],
    [type:'integer']
  ]
]

The first item must be an email. Additional items are allowed:

def schema = [
  type: 'array',
  additionalItems: true,
  items: [
    [type:'string', format:'email']
  ]
]

minItems, maxItems

By default, minItems and maxItems values are inclusive. Add the exclusiveMinimum or exclusiveMaximum attributes to alter this behaviour.

uniqueItems

Validates that all items are unique. Makes a deep comparison for collections and uses == for simple types.

Multi-schema validations attributes (allOf, anyOf, oneOf, not)

Validates an instance against an array of schemas. allOf indicates that the instance must conform to all given schemas; not indicates that the instance must not conform to any schemas.

Match either strings or integers between 0 and 5:

def schema = [
  anyOf: [
    [type:'string', required:true],
    [type:'integer', minimum:0, maximum:5, required:true],
  ]
]

License

MIT License.