Skip to content

Validates an entire Javascript object against a set of nested matchers

License

Notifications You must be signed in to change notification settings

Tabcorp/js-match

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

81 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

js-match

Warning

This project is now deprecated, in favour of strummer. Strummer has a similiar syntax (porting shouldn't be too hard), but is less ambiguous / more flexible.


NPM License

Build Status Dependencies Dev dependencies

Validates an entire Javascript object against a set of nested matchers. This can be useful to quickly validate:

  • a JSON config file
  • the structure of an HTTP request payload

Note: js-match will always ignore extra fields, as long as the set of matchers passes. This conforms with the robustness principle of "be conservative in what you do, be liberal in what you accept".

Basic usage

npm install js-match --save
jsm = require 'js-match'

person =
  name: { match: 'string' }
  age:  { match: 'number' }

# success
errors = jsm.validate {name: 'Bob', age: 30}, person
errors.should.eql []

# failure
errors = jsm.validate {age: 'foo'}, person
errors.should.eql [
  {path: 'name', error: 'required'},
  {path: 'age',  value: 'foo', error: 'should be a number'}
]

Matchers

Values can be tested against a default set of matchers, for ex:

{ match: 'string'  }
{ match: 'number'  }
{ match: 'boolean' }
{ match: 'ip'      }
{ match: 'host'    }
{ match: 'url'     }
{ match: 'uri'     }
{ match: 'file'    }
{ match: 'dollars' }
{ match: 'uuid-v4' }
{ match: 'enum', values: ['foo', 'bar'] }

You can also register custom matchers for advanced logic:

jsm.matchers['custom-id'] = (value) ->
  if not value.match /[0-9a-f]{10}/i
    return "should be an ID of the form 8d9f0ab3c5"

jsm.validate object,
  name:     { match: 'string'    }
  uniqueId: { match: 'custom-id' }

Matchers can also take optional parameters:

jsm.matchers['age'] = (value, options) ->
  if value < options.min or value > options.max
    return "should be an age between #{options.min} and #{options.max}"

jsm.validate object,
  name:  { match: 'string' }
  age:   { match: 'age', min: 1, max: 100 }

Matchers can also return custom messages:

person =
  name: { match: 'string', message: 'this is a custom message' }
  age:  { match: 'number' }

# success
errors = jsm.validate {name: 'Bob', age: 30}, person
errors.should.eql []

# failure
errors = jsm.validate {name: 12, age: 30}, person
errors.should.eql [
  {path: 'name', value: 12, error: 'this is a custom message'}
]

Nested configs

js-match supports nested objects, arrays, and primitives:

jsm.validate object,
  credentials:
    user:  { match: 'string' }
    pass:  { match: 'string' }
  machines: [
    host:  { match: 'string' }
    port:  { match: 'number' }
  ]
  values: [
    { match: 'number' }
  ]

which matches the following object:

{
  "credentials": {
    "user": "bob",
    "pass": "p@ssw0rd"
  },
  "machines": [
    { "host": "serverX", "port": 3000 },
    { "host": "serverY", "port": 3000 }
  ],
  "values": [1, 2, 3, 4, 5]
}

Any errors will be returned with fully qualified paths, for ex:

[
  {path: 'credentials.user', error: 'required'},
  {path: 'machines[1].host', value: 1234, error: 'should be a string'},
  {path: 'values[3]', value: true, error: 'should be a number'}
]

Custom schemas

You can either nest the matchers directly, like above, or extract them into separate "schemas":

auth =
  accountNumber: { match: 'integer' }
  password:      { match: 'string' }

jsm.validate object,
  name: { match: 'string' }
  auth: { schema: auth }

You can also pass a function that dynamically returns a schema:

dynamic = (parent, value) ->
  if parent.type is 'A'
    { match: 'number' }
  else
    { match: 'string' }

jsm.validate object,
  type:  { match: 'string' }
  value: { schema: dynamic }

Required and optional fields

By default, all fields are assumed to be required. Fields can be manually marked as optional:

jsm.validate object,
  username: { match: 'string' }
  password: { match: 'string', optional: true }

Validation will ignore missing optional fields, but will run the matcher if an optional field is present. E.g. it won't complain if password isn't there, but will if password is a number.

You could also use optional on a "schema" matcher:

account =
  number:  { match 'integer' }
  suburb:  { match 'string'  }

jsm.validate object,
  username: { match: 'string' }
  password: { match: 'string' }
  account:  { schema: account, optional: true }

Validation will pass if account is missing, but will run the matcher if account node is present. E.g. it will complain if you have the account node without presenting the number or suburb leaf under the account node.

Arrays

You can also do more complex matching on arrays

You could also use optional on an array of simple matchers:

jsm.validate object,
  username: { match: 'string' }
  password: { match: 'string' }
  accountIds:  [match: 'number', optional: true ]

Validation will pass if accountIds is missing, but will run the matcher if accountIds node is present. E.g it will complain if accountIds is present but is not an array of numbers

You could also use optional on an array of schema:

account =
  number:  { match 'integer' }
  suburb:  { match 'string'  }

jsm.validate object,
  username: { match: 'string' }
  password: { match: 'string' }
  accounts: [ schema: account, optional: true ]

Validation will pass if accounts is missing, but will run the matcher if accounts node is present. E.g it will complain if accounts is present but is not an array of accounts

You could specify the min and max length of an array of schema:

account =
  number:  { match 'integer' }
  suburb:  { match 'string'  }

jsm.validate object,
  username: { match: 'string' }
  password: { match: 'string' }
  accounts: [ schema: account, min: 2, max: 5 ]

Validation will pass if accounts is an array of accounts. With length 2-5 E.g it will complain if accounts is an array of 6 accounts.

About

Validates an entire Javascript object against a set of nested matchers

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •