{"id":15732293,"url":"https://github.com/stopsopa/validator","last_synced_at":"2025-03-13T04:31:13.489Z","repository":{"id":57163025,"uuid":"158882964","full_name":"stopsopa/validator","owner":"stopsopa","description":"JSR-303 Bean Validation for javascript","archived":false,"fork":false,"pushed_at":"2024-04-20T19:07:31.000Z","size":1296,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-26T04:48:08.831Z","etag":null,"topics":["data-validation","data-validator","jsr-303","jsr-303-bean","jsr-303-bean-validation","jsr303","jsr303-bean","jsr303-bean-validation","validate","validate-js","validation","validation-engine","validation-library","validations","validator","validatorjs","validators"],"latest_commit_sha":null,"homepage":"https://stopsopa.github.io/validator/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stopsopa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2018-11-23T22:26:02.000Z","updated_at":"2023-08-23T14:39:02.000Z","dependencies_parsed_at":"2024-04-20T20:23:50.604Z","dependency_job_id":"691e12b2-457b-4d64-92ee-737534eeb8dd","html_url":"https://github.com/stopsopa/validator","commit_stats":{"total_commits":324,"total_committers":5,"mean_commits":64.8,"dds":0.3858024691358025,"last_synced_commit":"b8bf125f1101102cf092d01a25fdd07107d6e624"},"previous_names":[],"tags_count":115,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stopsopa%2Fvalidator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stopsopa%2Fvalidator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stopsopa%2Fvalidator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stopsopa%2Fvalidator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stopsopa","download_url":"https://codeload.github.com/stopsopa/validator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243341406,"owners_count":20275866,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-validation","data-validator","jsr-303","jsr-303-bean","jsr-303-bean-validation","jsr303","jsr303-bean","jsr303-bean-validation","validate","validate-js","validation","validation-engine","validation-library","validations","validator","validatorjs","validators"],"created_at":"2024-10-04T00:08:52.972Z","updated_at":"2025-03-13T04:31:13.152Z","avatar_url":"https://github.com/stopsopa.png","language":"JavaScript","readme":"![example workflow](https://github.com/stopsopa/validator/actions/workflows/playwright.yml/badge.svg)\n[![npm version](https://badge.fury.io/js/%40stopsopa%2Fvalidator.svg)](https://badge.fury.io/js/%40stopsopa%2Fvalidator)\n[![npm version](https://shields.io/npm/v/%40stopsopa%2Fvalidator)](https://www.npmjs.com/package/%40stopsopa%2Fvalidator)\n[![NpmLicense](https://img.shields.io/npm/l/@stopsopa/validator.svg)](https://github.com/stopsopa/validator/blob/master/LICENSE)\n![jest coverage](https://stopsopa.github.io/validator/coverage/coverage-badge.svg)\n\n\n## Table of Contents\n\n\u003c!-- toc --\u003e\n\n- [Motivation](#motivation)\n- [Loosely inspired by:](#loosely-inspired-by)\n- [Live example:](#live-example)\n- [Simple example:](#simple-example)\n- [Some basic facts about functioning of the validator](#some-basic-facts-about-functioning-of-the-validator)\n- [Example](#example)\n  * [Entity manager](#entity-manager)\n  * [Controller](#controller)\n- [Validators references](#validators-references)\n  * [Blank](#blank)\n  * [Callback](#callback)\n  * [Choice](#choice)\n  * [Collection](#collection)\n  * [Count](#count)\n  * [Email](#email)\n  * [IsFalse](#isfalse)\n  * [IsTrue](#istrue)\n  * [IsNull](#isnull)\n  * [Length](#length)\n  * [NotBlank](#notblank)\n  * [NotNull](#notnull)\n  * [Regex](#regex)\n  * [Type](#type)\n- [Addidional tools](#addidional-tools)\n- [Other similar libraries:](#other-similar-libraries)\n- [next generation](#next-generation)\n- [Conclusions:](#conclusions)\n\n\u003c!-- tocstop --\u003e\n\n_(TOC generated using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_\n\n# Motivation\n\nI haven't found good enough implementation of JSR-303 Bean Validation for javascript, so here we go:\n\nMain goals during implementation of this library was:\n\n- simple and robust architecture\n- asynchronous behaviour (due to asynchronous nature of javascript)\n- extendability (custom asynchronous validator)\n- validation of any data structure and easyness in use (guaranteed by following JSR-303)\n- well tested (different node versions and browsers - done with \"jest\" and \"karma\") for polymorphic use on server and in the browser\n\nFeel free to contribute.\n\n---\n\n---\n\n# Loosely inspired by:\n\n- https://symfony.com/doc/current/components/validator.html\n- https://beanvalidation.org/1.0/spec/\n\n# Live example:\n\n[https://codesandbox.io/s/ymwky9603j](https://codesandbox.io/s/ymwky9603j)\n\n# Simple example:\n\n```javascript\nimport validator, {\n  Required,\n  Optional,\n  Collection,\n  All,\n  Blank,\n  Callback,\n  Choice,\n  Count,\n  Email,\n  IsFalse,\n  IsNull,\n  IsTrue,\n  Length,\n  NotBlank,\n  NotNull,\n  Regex,\n  Type,\n  ValidatorLogicError,\n} from \"@stopsopa/validator\";\n\n(async () =\u003e {\n  const errors = await validator(\n    {\n      name: \"\",\n      surname: \"doe\",\n      email: \"\",\n      terms: false,\n      comments: [\n        {\n          comment: \"What an ugly library\",\n        },\n        {\n          comment: \"empty\",\n        },\n      ],\n    },\n    new Collection({\n      name: new Required([new NotBlank(), new Length({ min: 3, max: 255 })]),\n      surname: new Required([new NotBlank(), new Length({ min: 10, max: 255 })]),\n      email: new Required(new Email()),\n      terms: new Optional(new IsTrue()),\n      comments: new All(\n        new Collection({\n          comment: new Required(new Length({ min: 10 })),\n        })\n      ),\n    })\n  );\n\n  if (errors.count()) {\n    // ... handle errors\n\n    console.log(JSON.stringify(errors.getFlat(), null, 4));\n    // {\n    //     \"name\": \"This value should not be blank.\",\n    //     \"surname\": \"This value is too short. It should have 10 characters or more.\",\n    //     \"email\": \"This value is not a valid email address.\",\n    //     \"terms\": \"This value should be true.\",\n    //     \"comments.1.comment\": \"This value is too short. It should have 10 characters or more.\"\n    // }\n\n    console.log(JSON.stringify(errors.getTree(), null, 4));\n    // {\n    //     \"name\": \"This value should not be blank.\",\n    //     \"surname\": \"This value is too short. It should have 10 characters or more.\",\n    //     \"email\": \"This value is not a valid email address.\",\n    //     \"terms\": \"This value should be true.\",\n    //     \"comments\": {\n    //         \"1\": {\n    //             \"comment\": \"This value is too short. It should have 10 characters or more.\"\n    //         }\n    //     }\n    // }\n  }\n})();\n```\n\n# Some basic facts about functioning of the validator\n\n- validator() don't care if some validation errors will occur or not, it will just count them and return two methods to extract them in different formats (as it is visible in above example)\n- validator() always return a promise. Rejected promise returned when special ValidatorLogicError() is thrown in Callback type validator only. Only this kind of error is different because it's not \"validation error\" but actual error in the process of validation - that's a different thing. Usually it's not something user can \"fix\" in his form or in his UI -\u003e this is rather system error that should be logged and addressed by developers.\n- normally all validators are executed in single [Promise.allSettled()](https://github.com/stopsopa/validator/blob/fa760a89089cfe5e5b10770ace849ebf7adb08e5/validator/index.js#L57) but there is a way to group sets of validators into separate Promise.allSettled() (using integer \"[async](https://github.com/stopsopa/validator/blob/fa760a89089cfe5e5b10770ace849ebf7adb08e5/test/validator.test.js#L141)\" extra flag) and execute those groups one by one. This is where another \"extra\" flag called \"stop\" of individual validators comes handy because turning it ON on particular validator will result in not executing next Promise.allSettled() in case when error was detected by that single validator -\u003e so returning resolved or rejected promise from individual validators together with stearing it through flag \"stop\" serves rather as an flow control mechanizm.\n- read [Conclusions](https://github.com/stopsopa/validator/blob/master/README.md#conclusions) section of this readme\n\n# Example\n\n## Entity manager\n\n```javascript\nconst abstract = require(\"@stopsopa/knex-abstract\");\n\nconst extend = abstract.extend;\n\nconst prototype = abstract.prototype;\n\nconst log = require(\"inspc\");\n\nconst a = prototype.a;\n\nconst {\n  Collection,\n  All,\n  Required,\n  Optional,\n  NotBlank,\n  Length,\n  Email,\n  Type,\n  IsTrue,\n  Callback,\n  Regex,\n} = require(\"@stopsopa/validator\");\n\nconst ext = {\n  initial: async function () {\n    return {\n      updated: this.now(),\n      created: this.now(),\n      port: 80,\n    };\n  },\n  toDb: (row) =\u003e {\n    return row;\n  },\n  update: function (...args) {\n    let [debug, trx, entity, id] = a(args);\n\n    delete entity.created;\n\n    entity.updated = this.now();\n\n    return prototype.prototype.update.call(this, debug, trx, entity, id);\n  },\n  insert: async function (...args) {\n    let [debug, trx, entity] = a(args);\n\n    entity.created = this.now();\n\n    delete entity.updated;\n\n    const id = await prototype.prototype.insert.call(this, debug, trx, entity);\n\n    return id;\n  },\n  prepareToValidate: function (data = {}, mode) {\n    delete data.created;\n\n    delete data.updated;\n\n    return data;\n  },\n  getValidators: function (mode = null, id, entity) {\n    const validators = {\n      id: new Optional(),\n      cluster: new Required([\n        new NotBlank(),\n        new Length({ max: 50 }),\n        new Callback(\n          (value, context, path, extra) =\u003e\n            new Promise(async (resolve, reject) =\u003e {\n              const { cluster, node, id } = context.rootData;\n\n              const condition = node === null ? \"is\" : \"=\";\n\n              let c;\n\n              log(mode);\n\n              if (mode === \"create\") {\n                c = await this.queryColumn(\n                  true,\n                  `select count(*) c from :table: where cluster = :cluster and node ${condition} :node`,\n                  {\n                    cluster,\n                    node,\n                  }\n                );\n              } else {\n                c = await this.queryColumn(\n                  true,\n                  `select count(*) c from :table: where cluster = :cluster and node ${condition} :node and id != :id`,\n                  {\n                    cluster,\n                    node,\n                    id,\n                  }\n                );\n              }\n\n              log.dump(c);\n\n              const code = \"CALLBACK-NOTUNIQUE\";\n\n              if (c \u003e 0) {\n                context\n                  .buildViolation(\"Not unique\")\n                  .atPath(path)\n                  .setParameter(\"{{ callback }}\", \"not equal\")\n                  .setCode(code)\n                  .setInvalidValue(`cluster: '${cluster}' and node: '${node}'`)\n                  .addViolation();\n\n                if (extra \u0026\u0026 extra.stop) {\n                  return reject(\"reject \" + code);\n                }\n              }\n\n              resolve(\"resolve \" + code);\n            })\n        ),\n      ]),\n      domain: new Required([new NotBlank(), new Length({ max: 50 })]),\n      port: new Required([new NotBlank(), new Length({ max: 8 }), new Regex(/^\\d+$/)]),\n    };\n\n    if (typeof entity.node !== \"undefined\") {\n      if (entity.node === null) {\n        validators.node = new Optional();\n      } else {\n        validators.node = new Required([new NotBlank(), new Length({ max: 50 })]);\n      }\n    }\n\n    return new Collection(validators);\n  },\n};\n\nmodule.exports = (knex) =\u003e extend(knex, prototype, Object.assign({}, require(\"./abstract\"), ext), \"clusters\", \"id\");\n```\n\n## Controller\n\n```javascript\n\nconst knex          = require('@stopsopa/knex-abstract');\n\nconst log           = require('inspc');\n\nconst validator     = require('@stopsopa/validator');\n    ...\n    app.all('/register', async (req, res) =\u003e {\n\n        let entity              = req.body;\n\n        let id                  = entity.id;\n\n        const mode              = id ? 'edit' : 'create';\n\n        const man               = knex().model.clusters;\n\n        const validators        = man.getValidators(mode, id);\n\n        if (mode === 'create') {\n\n            entity = {\n                ...man.initial(),\n                ...entity,\n            };\n        }\n\n        const entityPrepared    = man.prepareToValidate(entity, mode);\n\n        const errors            = await validator(entityPrepared, validators);\n\n        if ( ! errors.count() ) {\n\n            try {\n\n                if (mode === 'edit') {\n\n                    await man.update(entityPrepared, id);\n                }\n                else {\n\n                    id = await man.insert(entityPrepared);\n                }\n\n                entity = await man.find(id);\n\n                if ( ! entity ) {\n\n                    return res.jsonError(\"Database state conflict: updated/created entity doesn't exist\");\n                }\n            }\n            catch (e) {\n\n                log.dump(e);\n\n                return res.jsonError(`Can't register: ` + JSON.stringify(req.body));\n            }\n        }\n\n        return res.jsonNoCache({\n            entity: entity,\n            errors: errors.getTree(),\n        });\n\n    });\n    ...\n```\n\nFor further examples please follow [test cases](https://github.com/stopsopa/validator/tree/master/test/constraints)\n\n# Validators references\n\n## Blank\n\nSource code [Blank.js](validator/constraints/Blank.js)\n\n```javascript\nnew Blank({\n  message: \"This value should be blank.\",\n});\n```\n\n## Callback\n\nSource code [Callback.js](validator/constraints/Callback.js)\n\nSee test example [Callback.test.js](test/constraints/Callback.test.js)\n\n```javascript\nnew Callback((value, context, path, extra) =\u003e {...}); // function required\n```\n\n## Choice\n\nSource code [Choice.js](validator/constraints/Choice.js)\n\n```javascript\nnew Choice({\n  choices: [\"...\"], // required\n\n  multiple: false,\n  min: 0, // only if multiple=true\n  max: 0, // only if multiple=true\n\n  message: \"The value you selected is not a valid choice.\",\n  multipleMessage: \"One or more of the given values is invalid.\",\n  minMessage: \"You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.\",\n  maxMessage: \"You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.\",\n});\n\n// or shorter syntax if ony choices are given:\n\nnew Choice([\"...\"]); // just choices\n```\n\n## Collection\n\nSource code [Collection.js](validator/constraints/Collection.js)\n\n```javascript\nnew Collection({\n  fields: {\n    // required type: non empty object\n    a: new Require(),\n    b: new Optional(),\n  },\n  allowExtraFields: false,\n  allowMissingFields: false,\n  extraFieldsMessage: \"This field was not expected.\",\n  missingFieldsMessage: \"This field is missing.\",\n});\n\n// or shorter syntax if only fields are given:\n\nnew Collection({\n  // required type: non empty object\n  a: new Require(),\n  b: new Optional(),\n});\n```\n\n## Count\n\nSource code [Count.js](validator/constraints/Count.js)\n\n```javascript\nnew Count({\n  // min; // min or max required (or both) - if min given then have to be \u003e 0\n  // max, // min or max required (or both) - if max given then have to be \u003e 0\n\n  minMessage:\n    \"This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.\",\n  maxMessage:\n    \"This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.\",\n  exactMessage:\n    \"This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.\",\n});\n\n// or shorter syntax if ony min and max given and min = max:\n\nnew Count(5);\n```\n\n## Email\n\nSource code [Email.js](validator/constraints/Email.js)\n\n```javascript\nnew Email({\n  message: \"This value is not a valid email address.\",\n});\n```\n\n## IsFalse\n\nSource code [IsFalse.js](validator/constraints/IsFalse.js)\n\n```javascript\nnew IsFalse({\n  message: \"This value should be false.\",\n});\n```\n\n## IsTrue\n\nSource code [IsTrue.js](validator/constraints/IsTrue.js)\n\n```javascript\nnew IsTrue({\n  message: \"This value should be true.\",\n});\n```\n\n## IsNull\n\nSource code [IsNull.js](validator/constraints/IsNull.js)\n\n```javascript\nnew IsNull({\n  message: \"This value should be null.\",\n});\n```\n\n## Length\n\nSource code [Length.js](validator/constraints/Length.js)\n\n```javascript\nnew Length({\n  // min; // min or max required (or both)\n  // max, // min or max required (or both)\n\n  maxMessage:\n    \"This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.\",\n  minMessage:\n    \"This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.\",\n  exactMessage:\n    \"This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.\",\n});\n```\n\n## NotBlank\n\nSource code [NotBlank.js](validator/constraints/NotBlank.js)\n\n```javascript\nnew NotBlank({\n  message: \"This value should not be blank.\",\n});\n```\n\n## NotNull\n\nSource code [NotNull.js](validator/constraints/NotNull.js)\n\n```javascript\nnew NotNull({\n  message: \"This value should not be blank.\",\n});\n```\n\n## Regex\n\nSource code [Regex.js](validator/constraints/Regex.js)\n\n```javascript\nnew Regex({\n  pattern: /abc/gi, // required, type regex\n  message: \"This value is not valid.\",\n  match: true, // true     - if value match regex then validation passed\n  // false    - if value NOT match regex then validation passed\n});\n```\n\n## Type\n\nSource code [Type.js](validator/constraints/Type.js)\n\n```javascript\n// available values for field 'type' are:\n// 'undefined', 'object', 'boolean', 'bool', 'number', 'str', 'string',\n// 'symbol', 'function', 'integer', 'int', 'array'\nnew Type({\n  type: \"...\", // required\n  message: `This value should be of type '{{ type }}'.`,\n});\n\n// or shorter syntax if ony type is given:\n\nnew Type(\"str\");\n```\n\n# Addidional tools\n\n    require('@stopsopa/validator/set')\n    require('@stopsopa/validator/get')\n    require('@stopsopa/validator/delay')\n    require('@stopsopa/validator/each')\n    require('@stopsopa/validator/size')\n\n# Other similar libraries:\n\n- [express-validator](https://express-validator.github.io/docs/)\n\n# next generation\n\n- or validator\n- condition validator\n- respecting order of validators - executing in the same order as declared\n\n# Conclusions:\n\n1.\n\nAlways use types for primitives and collections:\n\nexample cases:\n\n- Length validator fires only if given data type is string (use Type('str') to avoid issues)\n- Collection validator validates only if given data is object (use Type('object') to avoid issues)\n\n```js\n\n(async function () {\n    const errors = await validator(6, new Collection({\n    // collection fires only if given data is object\n    // here it is integer\n        a: new Type('str'),\n        b: new Length({\n             min: 1,\n             max: 2,\n          })\n        ])\n    }));\n\n    const raw = errors.getRaw();\n\n    expect(raw).toEqual([]);\n    //\n\n    done();\n})();\n\n```\n\nfixed:\n\n```js\n(async function () {\n  const errors = await validator(\n    undefined, // will generate error: \"This value should be of type 'object'.\"\n    // {a: '', b: 7}, // will generate error: \"This value should be of type 'str'.\" on field \"b\"\n    new Required([\n      new Type(\"object\"), // this solves the problem on that level\n      new Collection({\n        a: new Type(\"str\"),\n        b: new Required([\n          new Type(\"str\"), // this solves the problem on that level\n          new Length({\n            min: 1,\n            max: 2,\n          }),\n        ]),\n      }),\n    ])\n  );\n\n  const raw = errors.getRaw();\n\n  expect(raw).toEqual([[undefined, \"This value should be of type 'object'.\", \"INVALID_TYPE_ERROR\", undefined]]);\n\n  done();\n})();\n```\n\nwith above the question might come \"why Collection itself don't validate if field is an object?\". The thing is that Collection can be used to check object but also array element by element, so it is better to deal with checking the type and validation of that structure separately.\n\n2.\n\nDon't relay on new Optional on the root level, more about: [option_require_case.test.js](test/edge/option_require_case.test.js)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstopsopa%2Fvalidator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstopsopa%2Fvalidator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstopsopa%2Fvalidator/lists"}