@jsonql/validator
v0.11.2
Published
@jsonql/validator general purpose validation library that test type and value with a plugin system
Downloads
12
Readme
@jsonql/validator
This was call jsonql-param-validator
now move into the @jsonql
scope with many additional features
We are now making it a general purpose validation library to use with Javascript / Typescript project
Usage
// breaking change 0.7.0 change to Validator
import { Validator } from '@jsonql/validator'
// @TODO how to get the ast
const validator = new Validator(ast: Array<JsonqlPropertyParamMap>)
validator.validate(values)
.then(result => {
// The same that comes in - if there is default value then nit will be apply
})
.catch(error => {
console.log(error.details)
// contains position where your validation failed
})
Type JsonqlPropertyParamMap
This is what the ast
looks like, we use our other plugin @jsonql/ast
to generate this automatically, but you can write your own
// @TODO should we have two different types map 1 for external use?
declare type JsonqlPropertyParamMap = {
name: string // the argument name
required: boolean
type: string | Array<string> // Array<string> is union type
tstype?: string // generated by swc
types?: any // this is internal generated
validate?: (value: unknown) => boolean
validateAsync?: (value: unknown) => Promise<boolean>
// rules get create the moment we init the object
rules?: Array<JsonqlValidateCbFn>
}
Here is an example of a normal function using our customized AST extractor
export default function baselineFn(value1: string, value2: number, value3 = false): string {
return `${value1} with number ${value2} ${value3 ? ' fine' : ' or not'}`
}
please note our extractor only support default export - not export
this is to do with how we organize code within a jsonql
project
but fear not if this doesn't work for you. The way we build our @jsonql/ast
is
like many small parts, and you can easily mix and match to build one for your need
Then it will transform into this for the validator
:
{
"baselineFn": [
{
"name": "value1",
"required": true,
"type": "string"
},
{
"name": "value2",
"required": true,
"type": "number"
},
{
"name": "value3",
"required": true,
"type": "boolean",
"defaultvalue": false
}
]
}
To use with the @jsonql/validator
const validator = new Validator(ast.baselineFn)
Note that we ONLY use the ast.baselineFn
part which is an array,
because the validator
only cares about the argument parameter
and nothing else
When use with Javascript, you can create your own AST map like the above example, and it will able to understand your parameter types.
Register your plugin
import { Validator } from '@jsonql/validator'
// See above example how to get the ast
const validator = new Validator(ast)
validator.registerPlugin('myStandardPlugin', {
main: (value: any): boolean => {
// must return boolean
}
})
validator.registerPlugin('myPluginRequireArg', {
main: (arg1: number, arg2: number, value: any): boolean => {
// do things with it
},
params: ['arg1', 'arg2']
})
There are required fields when you build a plugin with extra argument:
name: string
- the name of the plugin, and we will check if its collide with one of our own (see the complete list below)main: Function: boolean
- the actual function to perform the validation. It expect a boolean return for validator to know if the validation is success or not.params: Array<string>
- the parameters name used in yourmain
function, see thevalue
which is the value that gets validate MUST be the last of the argument. The reason is during run time, we curry your function first and pass into a queue system to run the value. See example below
Curry main function example:
// your function
function main(arg1: number, arg2: number, value: any) {
return value >= arg2 && value < arg2
}
const queueFn = curry(main)(arg1, arg2) // see how we get that value from example below
queueFn(value): boolean
Then when you need to execute your validator
validator.addValidationRules([
{plugin: 'myPlugin', arg1: 100, arg2: 200}
])
validator.validate([101])
.then((result: any) => {
// result will be an object
// {arg: 101}
})
.catch((err: ValidationError) => {
// look for err.detail
// if this fail you will get an array contain two number
// [1,0]
// what that means is it failed the second rule (zero based index) on the first position
// the built in type checking rule always comes first
})
At first it might sound weird why the Error return [argNameIndex, validationRuleIndex]
.
The reason behind this is because we need to have a super tiny format that travel between server side / client side. Instead of adding loads of code just to deal with an Error message. We just tell you the position which rule failed. And you can map it out yourself. And the up side is - you can map it in multiple languages, in different code, your imagination is the limitation :)
The built-in rule supported types
The build in rules only support primitive types: string
, number
, boolean
, array
checking (without value check, just Array.isArray
) and object
(typeof
) check. If you need complex array or object key/value check, please DIY that's what the plugin is for!
Built-in plugins
All the built-in plugins provide by another package, please see @jsonql/validator-core for more info.
Writing plugin vs writing function
Apart from creating a plugin, you could just pass a function or Async function directly.
// continue from previous example
// using name parameter
validator.addValidationRules({
value2: {
name: 'myExtraRule1',
validate: function(value: number) {
return value > 1000
}
}
})
The above method will automatically insert into the validation sequence right after the built-in rule (in our example, which is checking if your input value is a number)
If this fail then you will get [1,1]
as an error result.
You could also add async
method (all rules convert to async
internally):
// let say we want to validate an email address against the database
validator.addValidatorRules({
email: {
name: 'MyServerSideCheck',
server: true, // for our contract system this will only get call on the server side @TODO
validateAsync: async function(value: string) {
return db.checkIfExists(value)
.then(result => {
return true
})
.catch(error => {
return false
})
}
}
})
All the function(s) expect a true / false boolean
return result and nothing else.
Important unlike using registerPlugin
, you can not pass extra parameters
(of course you can just put the required value inside the function itself). Also
this validate method will always be one side only (server:true
if you use the jsonql / velocejs) system
because we can not pass your inline function over the wire.
If you need share this rule between client / server. You MUST create it in an external file,
and use the loadPlugin
method.
Server side only validation rule
@TODO will be available in next release
By default, all your inline plugin, validate
, validateAsync
function will treat as server: true
what that means is, when using our jsonql / velocejs system, the contract will not contain (@TODO in the next release) those validation info.
The reason is, very often on a SPA system, you validate user input on your UI once then the same rule will run again on the server side. Which is a lot of duplication; and in our example about validate an email address, you might want to do some server side only check (i.e. check against the database). We provide this mapping to separate rules for front end / back end.
More to come.
Standalone ValidatorBase
You can use a standalone (without plugin system) Jsonql Validator by import it like this:
import { ValidatorBase } from '@jsonql/validator/base'
const vb = new ValidatorBase(astMap)
vb.validate(values)
.then(result=> {
// the result object is different
})
This module will only validate against the type you define plus the validate
or validateAsync
method you add via
the addValidationRules
method. If you try to use the plugin then it will throw in that particular rule you add with
ValidationError
object, and the ValidationError.message
will be NO_PLUGIN_DUMMY_FUNCTION
.
We add this mainly for us to create a more modular system, in our @jsonql/validators
(@NOTE with an s
)
it will create one plugin system and share among all the API interfaces.
NEWBRAN LTD / TO1SOURCE CN (c) 2022