2 Star 0 Fork 0

TO1SOURCE / jsonql

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 8.86 KB
一键复制 编辑 原始数据 按行查看 历史

@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 your main function, see the value 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.


Joel Chu

NEWBRAN LTD / TO1SOURCE CN (c) 2022

TypeScript
1
https://gitee.com/to1source/jsonql.git
git@gitee.com:to1source/jsonql.git
to1source
jsonql
jsonql
main

搜索帮助

53164aa7 5694891 3bd8fe86 5694891