├── .circleci └── config.yml ├── .eslintrc.json ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── sonarlint │ └── issuestore │ │ └── index.pb ├── validated-types.iml └── vcs.xml ├── .prettierrc ├── .sonarcloud.properties ├── LICENSE ├── README.MD ├── package-lock.json ├── package.json ├── src ├── array │ ├── VArray.ts │ └── Varray.spec.ts ├── base │ ├── VBase.spec.ts │ ├── VBase.ts │ └── VSpecOf.ts ├── error │ ├── ValidationError.ts │ └── ValidationSpecError.ts ├── float │ ├── VFloat.spec.ts │ └── VFloat.ts ├── index.ts ├── integer │ ├── VInt.spec.ts │ └── VInt.ts ├── semfloat │ ├── SemVFloat.spec.ts │ └── SemVFloat.ts ├── seminteger │ ├── SemVInt.spec.ts │ └── SemVInt.ts ├── semstring │ ├── SemVString.spec.ts │ └── SemVString.ts ├── semtype │ ├── SemType.spec.ts │ └── SemType.ts ├── semvar │ ├── SemVar.spec.ts │ └── SemVar.ts └── string │ ├── KnownLengthStringValidatorNames.ts │ ├── ParameterizedStringValidatorNames.ts │ ├── UnknownLengthStringValidatorNames.ts │ ├── VString.spec.ts │ ├── VString.ts │ ├── parseJSONArrayParameter.ts │ ├── stringValidators.spec.ts │ └── stringValidators.ts └── tsconfig.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:16 6 | environment: 7 | TZ: 'Europe/Helsinki' 8 | steps: 9 | - checkout 10 | - restore_cache: 11 | key: dependency-cache-{{ checksum "package.json" }} 12 | - run: 13 | name: Install dependencies 14 | command: npm install 15 | - save_cache: 16 | key: dependency-cache-{{ checksum "package.json" }} 17 | paths: 18 | - node_modules 19 | - run: 20 | name: Build 21 | command: npm run build 22 | - run: 23 | name: Test 24 | command: npm run test:coverage 25 | - run: 26 | name: Report coverage 27 | command: bash <(curl -s https://codecov.io/bash) 28 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "project": "tsconfig.json", 5 | "sourceType": "module" 6 | }, 7 | "plugins": ["@typescript-eslint/eslint-plugin"], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier" 13 | ], 14 | "root": true, 15 | "env": { 16 | "node": true, 17 | "jest": true 18 | }, 19 | "rules": { 20 | "@typescript-eslint/interface-name-prefix": "off", 21 | "@typescript-eslint/explicit-function-return-type": "off", 22 | "@typescript-eslint/no-explicit-any": "off", 23 | "@typescript-eslint/no-inferrable-types": "off" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # parcel-bundler cache (https://parceljs.org/) 72 | .cache 73 | 74 | # Next.js build output 75 | .next 76 | 77 | # Nuxt.js build / generate output 78 | .nuxt 79 | dist 80 | 81 | # Gatsby files 82 | .cache/ 83 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 84 | # https://nextjs.org/blog/next-9-1#public-directory-support 85 | # public 86 | 87 | # vuepress build output 88 | .vuepress/dist 89 | 90 | # Serverless directories 91 | .serverless/ 92 | 93 | # FuseBox cache 94 | .fusebox/ 95 | 96 | # DynamoDB Local files 97 | .dynamodb/ 98 | 99 | # TernJS port file 100 | .tern-port 101 | 102 | lib/ 103 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 117 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/index.pb: -------------------------------------------------------------------------------- 1 | 2 | L 3 | src/error/ValidationError.ts,6/e/6eebe10c033ac60ac0495182f2a31f8f656fcc76 4 | P 5 | src/error/ValidationSpecError.ts,6/e/6ee0925f05e95928febe47a288a8b23aa92bd776 6 | F 7 | .sonarcloud.properties,8/c/8c2ee9288b17817bf601b83cde380a0086a67745 8 | _ 9 | /src/string/UnknownLengthStringValidatorNames.ts,9/8/983ec1d683eb5d9943c4fba7e7090b87012478e6 10 | : 11 | 12 | .gitignore,a/5/a5cc2925ca8258af241be7e5b0381edf30266302 13 | A 14 | package-lock.json,f/a/fa288d1472d29beccb489a676f68739ad365fc47 15 | F 16 | src/base/VBase.spec.ts,c/6/c6d191484d3c442faed1a425396433cf6e211d95 17 | H 18 | src/array/Varray.spec.ts,b/e/be555d3566c7108b5a8206b2040ede93bb52e16c 19 | = 20 | tsconfig.json,6/1/61ebb9fd6e8cf9082658121d5d81e297791dacd0 21 | C 22 | src/base/VSpecOf.ts,4/6/462c00f097c8637655ff8b4c3cf313d26e2b011e 23 | C 24 | src/float/VFloat.ts,a/6/a6cba3511f144a9c1da6e1babbdc2ffdef9f86ba 25 | ] 26 | -src/string/KnownLengthStringValidatorNames.ts,6/a/6a10bbf7ebc1090931b66747f440db0d46d155e9 27 | _ 28 | /src/string/ParameterizedStringValidatorNames.ts,1/e/1e519958cab4df884590a6bdd8d5b512e67af9df 29 | U 30 | %src/string/parseJSONArrayParameter.ts,3/f/3f60b9b013ba94a412df0266a59f951c4f894f86 31 | J 32 | src/string/VString.spec.ts,5/4/54763685a16c7e1af36df511ed4bc71edf627765 33 | S 34 | #src/string/stringValidators.spec.ts,4/3/43e10db965981b670ced3047185e8d4f609830ae 35 | N 36 | src/string/stringValidators.ts,d/2/d20fb0edf0f777a1af9d1743af9fc2018fd4aac8 37 | A 38 | src/base/VBase.ts,8/d/8d27b0f95faaf7ff0a5fb42808e16b86f905ff9c 39 | I 40 | src/semvar/SemVar.spec.ts,b/0/b06a14b9d1130a111a2848a581e5c879b644edf9 -------------------------------------------------------------------------------- /.idea/validated-types.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "printWidth": 110, 4 | "tabWidth": 2, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pksilen/validated-types/50b76e4d3cc8bee1afa18b301cb8c0c6693d2c3b/.sonarcloud.properties -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Petri Silén 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # validated-types 2 | 3 | `validated-types` is a library for Typescript to create types for validated integer, float, string and array values. 4 | It also allows of creating semantic types and declare variables with semantic types. 5 | 6 | [![version][version-badge]][package] 7 | [![build][build]][circleci] 8 | [![coverage][coverage]][codecov] 9 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=pksilen_validated-types&metric=alert_status)](https://sonarcloud.io/dashboard?id=pksilen_validated-types) 10 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=pksilen_validated-types&metric=bugs)](https://sonarcloud.io/dashboard?id=pksilen_validated-types) 11 | [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=pksilen_validated-types&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=pksilen_validated-types) 12 | [![Downloads][downloads]][package] 13 | [![MIT License][license-badge]][license] 14 | 15 | ## Table of Contents 16 | 17 | - [Prerequisites](#prerequisites) 18 | - [Installation](#installation) 19 | - [Usage](#usage) 20 | - [Introduction](#introduction) 21 | - [Validate Integers](#validated-integers) 22 | - [Validate Floats](#validated-floats) 23 | - [Validate Strings](#validated-strings) 24 | - [Validate Arrays](#validated-arrays) 25 | - [Validate Objects](#validated-objects) 26 | - [Create Semantic Types/Variables](#create-semantic-types) 27 | - [Create Semantic Validated Integers](#create-semantic-validated-integers) 28 | - [Create Semantic Validated Floats](#create-semantic-validated-floats) 29 | - [Create Semantic Validated Strings](#create-semantic-validated-strings) 30 | - [API Documentation](#api-documentation) 31 | - [VFloat](#vfloat) 32 | - [registerCustomValidator](#vfloat-registerCustomValidator) 33 | - [createOrThrow](#vfloat-createOrThrow) 34 | - [create](#vfloat-create) 35 | - [createOrError](#vfloat-createOrError) 36 | - [value](#vfloat-value) 37 | - [VInt](#vint) 38 | - [registerCustomValidator](#vint-registerCustomValidator) 39 | - [createOrThrow](#vint-createOrThrow) 40 | - [create](#vint-create) 41 | - [createOrError](#vint-createOrError) 42 | - [value](#vint-value) 43 | - [VString](#vstring) 44 | - [registerCustomValidator](#vstring-registerCustomValidator) 45 | - [createOrThrow](#vstring-createOrThrow) 46 | - [create](#vstring-create) 47 | - [createOrError](#vstring-createOrError) 48 | - [value](#vstring-value) 49 | - [VArray](#varray) 50 | - [registerCustomValidator](#varray-registerCustomValidator) 51 | - [createOrThrow](#varray-createOrThrow) 52 | - [create](#varray-create) 53 | - [createOrError](#varray-createOrError) 54 | - [value](#varray-value) 55 | - [SemType](#SemType) 56 | - [constructor](#SemType-constructor) 57 | - [value](#SemType-value) 58 | - [SemVInt](#SemVInt) 59 | - [SemVFloat](#SemVFloat) 60 | - [SemVString](#SemVString) 61 | - [VSpecOf](#vspecof) 62 | - [Feedback](#feedback) 63 | - [License](#license) 64 | 65 | ## Prerequisites 66 | * Typescript >= 4.1.0 67 | 68 | ## Installation 69 | 70 | ```bash 71 | npm install --save validated-types 72 | ``` 73 | 74 | ## Usage 75 | 76 | ### Introduction 77 | 78 | Validated-types is a type value validation library for Typescript. You can validate values of integers, floats, strings and arrays. 79 | For numbers, you can for example validate the minimum and maximum value. For strings, you can validate the length and also perform semantic validation 80 | to validate if a string should be, for example, a valid URL, IP address or email address. 81 | 82 | By using validated types for the parameters of your application's functions, you can rest assured that only proper values are ever passed to your function. 83 | You don't have to do any validation work inside your functions, just use the value of already validated parameter. The validation of the value is done only once upon the construction of validated objects, 84 | so there is no performance penalty in using the validated values as many times as you need. 85 | 86 | You should create the validated integer, float, string and array objects in functions that receive unvalidated input data and then pass the validated values to the rest of the functions in your application. 87 | 88 | Your application typically receives unvalidated input data from external sources in following scenarios: 89 | 90 | - Reading command line arguments 91 | - Reading environment variables 92 | - Reading standard input 93 | - Reading file(s) from file system 94 | - Reading data from socket (network input) 95 | - End-user input from user interface 96 | 97 | You can also create semantic types and semantically typed variables using `SemType`. Using semantic types, you 98 | can differentiate between multiple types of the same basic type. For example, If a function accepts two boolean arguments, it is possible 99 | that the calling function gives those two arguments in wrong order, but you never notice it, because it does not generate a compilation error. 100 | Using semantic types, you can differentiate between those two boolean types by giving them two different semantic names. 101 | Semantic name is just any string describing the purpose of type/variable. 102 | 103 | The library contains static factory methods in the classes forr creating a new instance of the specific class. 104 | There are different factory methods available. You can choose which factory method to use depending on your 105 | application need. The factory methods are following: 106 | 107 | - `tryCreate(...)` creates a validated value object or throws an exception 108 | - `createOrThrow(...)` is same as above, i.e. creates a validated value object or throws an exception 109 | - `create(...)` creates a validated value object or returns `null`. Use this method if you don't need the error reason 110 | - `createOrError(...)` creates a validated value object or returns an error object, always returns a pair (is similar to Golang returning an error) 111 | 112 | ### Validate Integers 113 | 114 | You can validate your integer, float and string type variables with `validated-types`. 115 | For example, to create a validated integer which allows values between 1 and 10, you declare: 116 | 117 | ```ts 118 | import { VInt } from 'validated-types'; 119 | 120 | function useInt(int: VInt<'1,10'>) { 121 | // use int here 122 | console.log(int.value); 123 | } 124 | 125 | const int = VInt.tryCreate<'1,10'>('1,10', 5); 126 | const maybeInt = VInt.create<'1,10'>('1,10', 12); // Returns null 127 | 128 | useInt(int); // prints to console: 5 129 | useInt(maybeInt ?? VInt.tryCreate('1,10', 10)); // prints to console: 10 130 | ``` 131 | 132 | You can also register a custom validator: 133 | 134 | ```ts 135 | import { VInt } from 'validated-types'; 136 | 137 | VInt.registerCustomValidator('isEven', (value) => value % 2 === 0); 138 | 139 | const evenNumber = VInt.tryCreate<'custom:isEven'>('custom:isEven', 2); 140 | const maybeEvenNumber = VInt<'custom:isEven'>.create('custom:isEven', 1); 141 | ``` 142 | 143 | Custom validator must be registered before it is used by any validated types `create`, `createOrThrow` or `createOrError`method. 144 | This means that you should register your custom validators as early as possible in your application code. 145 | If custom validator is not registered, and it is used, an exception will be thrown. 146 | 147 | ### Validate Floats 148 | 149 | To create a validated float which allows positive values only, you declare: 150 | 151 | ```ts 152 | import { VFloat } from 'validated-types'; 153 | 154 | function useFloat(float: VFloat<'positive'>) { 155 | // use float here 156 | console.log(float.value); 157 | } 158 | 159 | const float = VFloat.tryCreate<'positive'>('positive', 5.25); 160 | const maybeFloat = VFloat<'positive'>.create('positive', -5.25); // returns null 161 | 162 | useFloat(float); // prints to console: 5.25 163 | useFloat(maybeFloat ?? VFloat.tryCreate('positive', 1)); // prints to console: 1 164 | ``` 165 | 166 | ### Validate Strings 167 | 168 | To create a validated string which allows URLs with minimum length of one and maximum length of 1024 characters, you declare: 169 | 170 | ```ts 171 | import { VString } from 'validated-types'; 172 | 173 | function useUrl(url: VString<'1,1024,url'>) { 174 | // use URL here 175 | console.log(url.value); 176 | } 177 | 178 | const url = VString.tryCreate<'1,1024,url'>('1,1024,url', 'https://www.mydomain.com'); 179 | const maybeUrl = VString<'1,1024,url'>.create('1,1024,url', 'invalid URL'); // Returns null 180 | 181 | useUrl(url); // prints to console: https://www.mydomain.com 182 | useUrl(maybeUrl ?? VString.tryCreate('1,1024,url', 'https://google.com')); // prints to console: https://google.com 183 | ``` 184 | 185 | You can combine up to 5 different string validators together. Multiple validators are given as a tuple which can have 2-5 elements. The first element of the tuple should validate the length of the string, if needed. Rest of the elements in tuple should not validate the length of string anymore. 186 | Below example contains 4 validators that validate following string: 187 | 188 | - is at least 1 characters long 189 | - is at most 1024 characters long 190 | - is lower case string 191 | - is valid URL 192 | - URL starts with _https_ 193 | - URL ends with _.html_ 194 | 195 | ```ts 196 | import { SpecOf, VString } from 'validated-types'; 197 | 198 | type Url = VString<['1,1024,lowercase', 'url', 'startsWith,https', 'endsWith,.html']>; 199 | const urlVSpec: VSpecOf = ['1,1024,lowercase', 'url', 'startsWith,https', 'endsWith,.html']; 200 | 201 | function useUrl(url: Url) { 202 | // use URL here 203 | console.log(url.value); 204 | } 205 | 206 | const url: Url = VString.tryCreate(urlVSpec, 'https://server.domain.com:8080/index.html'); 207 | const maybeUrl: Url | null = VString.create(urlVSpec, 'invalid URL'); // Returns null 208 | 209 | useUrl(url); // prints to console: https://server.domain.com:8080/index.html 210 | useUrl(maybeUrl ?? VString.tryCreate(urlVSpec, 'https://server.domain.com:8080/index.html')); // prints to console: https://server.domain.com:8080/index.html 211 | ``` 212 | 213 | ### Validate Arrays 214 | To create a validated array of numbers which allows array length to be from 0 to 10 and requires all array elements to be unique, you declare: 215 | 216 | ```ts 217 | import { VArray } from 'validated-types'; 218 | 219 | function useArray(array: VArray<'0,10,unique', number>) { 220 | // use array here 221 | console.log(array.value); 222 | } 223 | 224 | const array = VArray.tryCreate<'0,10,unique', number>('0,10,unique', [1, 2, 3]); 225 | const maybeArray = VArray<'0,10,unique', number>.create('0,10,unique', [1, 2, 2]); 226 | 227 | useArray(array); // prints to console: [1, 2, 3] 228 | useArray(maybeArray ?? VArray.tryCreate('0,10,unique', [3, 4, 5])); // prints to console: [3, 4, 5] 229 | ``` 230 | 231 | You can also create an array of validated objects, for example below example validates an array of from 1 to max 10 unique email addresses: 232 | ```ts 233 | import { VArray } from 'validated-types'; 234 | 235 | type EmailAddresses = VArray<'1,10,unique', VString<'email'>>; 236 | const emailAddressesVSpec: VSpecOf = '1,10,unique'; 237 | 238 | function sendEmails(emailAddresses: EmailAddresses, subject: string, body: string) { 239 | console.log(emailAddresses.forEach(emailAddress=> emailAddress.value)); 240 | } 241 | 242 | const emailAddress = VString.tryCreate<'email'>('email', 'test@example.com'); 243 | const emailAddress2 = VString.tryCreate<'email'>('email', 'test2@example.com'); 244 | const emailAddresses: EmailAddresses = VArray.tryCreate(emailAddressesVSpec, [emailAddress, emailAddress2]); 245 | 246 | sendEmails(emailAddresses, 'subj', 'body'); // prints to console: 'test@example.com' and 'test2@example.com' 247 | ``` 248 | 249 | ### Validate Objects 250 | 251 | You can also create validated objects, for example: 252 | 253 | ```ts 254 | type Person = { 255 | firstName: VString<1, 64>; 256 | lastName: VString<1, 64>; 257 | nickNames: VString<1, 64>[]; 258 | email: VString<'email'>; 259 | password: VString<'8,1024,strongPassword'>; 260 | age: VInt<0, 255>; 261 | interestPercent: VFloat<'0,100'>; 262 | }; 263 | ``` 264 | 265 | You can assign type aliases to your validated types: 266 | 267 | types.ts 268 | 269 | ```ts 270 | import { VFloat, VInt, VString } from 'validated-types'; 271 | 272 | export type Name = VString<1, 64>; 273 | export type Email = VString<'email'>; 274 | export type Password = VString<'8,1024,strongPassword'>; 275 | export type Age = VInt<0, 255>; 276 | export type Percent = VFloat<'0,100'>; 277 | ``` 278 | 279 | person.ts 280 | 281 | ```ts 282 | import { Age, Email, Name, Password, Percent } from 'types'; 283 | 284 | type Person = { 285 | firstName: Name; 286 | lastName: Name; 287 | nickNames: Name[]; 288 | email: Email; 289 | password: Password; 290 | age: Age; 291 | interestPercent: Percent; 292 | }; 293 | ``` 294 | 295 | ### Create Semantic Types/Variables 296 | 297 | Semantic types and variables let you differentiate between variables of same type. The differentiation is done 298 | by assigning a semantic name to the type. Following example declares two semantic variables of `boolean` type with two different semantic names. 299 | 300 | ```ts 301 | import { SemType } from 'validated-types'; 302 | 303 | type IsRecursiveCall = SemType 304 | type IsInternalCall = SemType; 305 | 306 | function myFunc(isRecursiveCall: IsRecursiveCall, isInternalCall: IsInternalCall) { 307 | console.log(isRecursiveCall.value); 308 | console.log(isInternalCall.value); 309 | } 310 | 311 | const isRecursiveCall = false; 312 | const isInternalCall = true; 313 | 314 | // Only this will succeed 315 | myFunc(new SemType({ isRecursiveCall }), new SemType({ isInternalCall })); 316 | 317 | // These will fail during compilation 318 | myFunc(new SemType({ isInternalCall }), new SemType({ isRecursiveCall })); 319 | myFunc(true, true); 320 | myFunc(new SemType('isSomethingElse', true), new SemType('isInternalCall', true)); 321 | myFunc(new SemType('isRecursiveCall', false), new SemType('isSomethingElse', true)); 322 | myFunc(new SemType('isSomethingElse', true), new SemType('isSomethingElse', true)); 323 | ``` 324 | 325 | You can use a validated type as a semantic type also. In the below example, a function takes two semantic parameters 326 | which both have the same base type `VString<'1,64'>`. 327 | 328 | ```ts 329 | import { SemType, VString } from 'validated-types'; 330 | 331 | type Name = VString<'1,64'>; 332 | const nameVSpec: VSpecOf = '1,64'; 333 | type FirstName = SemType; 334 | type LastName = SemType; 335 | 336 | function myFunc(firstName: FirstName, lastName: LastName) { 337 | console.log(firstName.value); 338 | console.log(lastName.value); 339 | } 340 | 341 | const firstName: Name = VString.tryCreate(nameVSpec, 'John'); 342 | const lastName: Name = VString.tryCreate(nameVSpec, 'Doe'); 343 | 344 | type Name2 = VString<'1,65'> 345 | const name2VSpec: VSpecOf = '1,64'; 346 | const firstName2: Name2 = VString.tryCreate(name2VSpec, 'John'); 347 | const lastName2: Name2 = VString.tryCreate(name2VSpec, 'Doe'); 348 | 349 | // Only this will succeed where 'firstName' and 'lastName' are given in correct order with correct validated type 350 | myFunc(new SemType({ firstName }), new SemType({ lastName })); 351 | 352 | // These below will fail during compilation 353 | myFunc(new SemType({ lastName }), new SemType({ firstName })); 354 | myFunc(new SemType({ firstName: firstName2 }), new SemType({ lastName: lastName2 })); 355 | myFunc('John', 'Doe'); 356 | myFunc(firstName, lastName); 357 | ``` 358 | 359 | ### Create Semantic Validated Integers 360 | 361 | ```ts 362 | import { SemVInt } from 'validated-types'; 363 | 364 | type HttpPort = SemVInt<'httpPort', '1,65535'>; 365 | const httpPort: HttpPort = SemVInt.tryCreate('httpPort', '1,65535', 8080); 366 | console.log(httpPort.value); // Prints 8080 367 | ``` 368 | 369 | ### Create Semantic Validated Floats 370 | 371 | ```ts 372 | import { SemVFloat } from 'validated-types'; 373 | 374 | type LoanInterest = SemVFloat<'loanInterest', '0,100'>; 375 | const loanInterest: LoanInterest = SemVFloat.tryCreate('loanInterest', '0,100', 4.99); 376 | console.log(loanInterest.value); // Prints 4.99 377 | ``` 378 | 379 | ### Create Semantic Validated Strings 380 | 381 | ```ts 382 | import { SemVString } from 'validated-types'; 383 | 384 | type LoginUrl = SemVString<'loginUrl', '1,8192,url'>; 385 | const loginUrl: LoginUrl = SemVString.tryCreate('loginUrl', '1,8192,url', "https://server.com"); 386 | console.log(loginUrl.value) // Prints https://server.com 387 | ``` 388 | 389 | ## API documentation 390 | 391 | ### VFloat 392 | 393 | ```ts 394 | class VFloat { 395 | static registerCustomValidator(validatorName: string, validateFunc: (value: number) => boolean): void; 396 | 397 | static createOrThrow(validationSpec: string, value: number, varName?: string): VFloat | never; 398 | static tryCreate(validationSpec: string, value: number, varName?: string): VFloat | never; 399 | static create(validationSpec: string, value: number, varName?: string): VFloat | null; 400 | static createOrError( 401 | validationSpec: string, 402 | value: number, 403 | varName?: string 404 | ): [VFloat, null] | [null, Error]; 405 | get value(): number; 406 | } 407 | ``` 408 | 409 | #### VFloat.registerCustomValidator 410 | `static registerCustomValidator(validatorName: string, validateFunc: (value: number) => boolean): void` 411 | 412 | Registers a custom validator with given name. `validateFunc` receives a float value as parameter and returns boolean based on success of validation. 413 | You must register your custom validator before using it in any of the `create` functions. 414 | 415 | #### VFloat.createOrThrow and VFloat.tryCreate 416 | `static createOrThrow(validationSpec: string, value: number, varName?: string): VFloat | never` 417 | `static tryCreate(validationSpec: string, value: number, varName?: string): VFloat | never` 418 | 419 | Creates a new validated float value object or throws a `ValidationError` exception if supplied value is invalid. 420 | `validationSpec` is a string of following form `'[],[] | negative | positive | custom:'`. 421 | 422 | `validationSpec` examples: 423 | 424 | - '0,100' 425 | - '100,' 426 | - ',0' 427 | - 'positive' 428 | - 'negative' 429 | - 'custom:isUsShoeSize' 430 | 431 | If `minValue` is missing, `Number.MIN_VALUE` is used. 432 | If `maxValue` is missing, `Number.MAX_VALUE` is used. 433 | If `varName` is supplied, it is mentioned in possible `ValidationError` thrown. 434 | 435 | #### VFloat.create 436 | `static create(validationSpec: string, value: number, varName?: string): VFloat | null` 437 | 438 | Same as `VFloat.createOrThrow`, but instead of throwing on validation failure, it returns `null`. 439 | This method is useful if you don't care about the error message, but just want to know if validation succeeded or not. 440 | 441 | #### VFloat.createOrError 442 | `static createOrError(validationSpec: string, value: number, varName?: string): [VFloat, null] | [null, Error]` 443 | 444 | Same as `VFloat.createOrThrow`, but instead of throwing on validation failure, it returns a tuple `[VFloat, null]` on success and tuple `[null, Error]` on validation failure. 445 | This method is useful for Go language style of programming where you get a 2-tuple return value where the last element in tuple contains the possible error. 446 | For example: 447 | 448 | ```ts 449 | const [float, err] = VFloat.createOrError<'1,10'>('1,10', 1); 450 | if (err) { 451 | // handle error here 452 | } 453 | ``` 454 | 455 | #### value 456 | `get value(): number` 457 | 458 | Returns the valid float value. 459 | 460 | ### VInt 461 | 462 | ```ts 463 | class VInt { 464 | static registerCustomValidator(validatorName: string, validateFunc: (value: number) => boolean): void; 465 | static createOrThrow(validationSpec: string, value: number, varName?: string): VInt | never; 466 | static tryCreate(validationSpec: string, value: number, varName?: string): VInt | never; 467 | static create(validationSpec: string, value: number, varName?: string): VInt | null; 468 | static createOrError( 469 | validationSpec: string, 470 | value: number, 471 | varName?: string 472 | ): [VInt, null] | [null, Error]; 473 | get value(): number; 474 | } 475 | ``` 476 | 477 | #### VInt.registerCustomValidator 478 | `static registerCustomValidator(validatorName: string, validateFunc: (value: number) => boolean): void` 479 | 480 | Registers a custom validator with given name. `validateFunc` receives a number as parameter and returns boolean based on success of validation. 481 | You must register your custom validator before using it in any of the `create` functions. 482 | 483 | #### VInt.createOrThrow and VInt.tryCreate 484 | `static createOrThrow(validationSpec: string, value: number, varName?: string): VInt | never` 485 | `static tryCreate(validationSpec: string, value: number, varName?: string): VInt | never` 486 | 487 | Creates a new validated integer value object or throws a `ValidationError` exception if supplied value is invalid. 488 | `validationSpec` is a string of following form `'[],[][,] | negative | positive | custom:'`. 489 | 490 | `validationSpec` examples: 491 | 492 | - '0,100' 493 | - '0,100,2' 494 | - '100,' 495 | - ',0' 496 | - ',' 497 | - 'positive' 498 | - 'negative' 499 | - 'custom:isEven' 500 | 501 | If `minValue` is missing, `Number.MIN_SAFE_INTEGER` is used. 502 | If `maxValue` is missing, `Number.MAX_SAFE_INTEGER` is used. 503 | If `varName` is supplied, it is mentioned in possible `ValidationError` thrown. 504 | 505 | #### VInt.create 506 | `static create(validationSpec: string, value: number, varName?: string): VInt | null` 507 | 508 | Same as `VInt.createOrThrow`, but instead of throwing on validation failure, it returns `null`. 509 | This method is useful if you don't care about the error message, but just want to know if validation succeeded or not. 510 | 511 | #### VInt.createOrError 512 | `static createOrError(validationSpec: string, value: number, varName?: string): [VInt, null] | [null, Error]` 513 | 514 | Same as `VInt.createOrThrow`, but instead of throwing on validation failure, it returns a tuple `[VInt, null]` on success and tuple `[null, Error]` on validation failure 515 | This method is useful for Go language style of programming where you get a 2-tuple return value where the last element in tuple contains the possible error. 516 | For example: 517 | 518 | ```ts 519 | const [int, err] = VInt.createOrError<'1,10'>('1,10', 1); 520 | if (err) { 521 | // handle error here 522 | } 523 | ``` 524 | 525 | #### value 526 | `get value(): number` 527 | 528 | Returns the valid integer value. 529 | 530 | ### VString 531 | 532 | ```ts 533 | class VString { 534 | static registerCustomValidator(validatorName: string | string[], validateFunc: (value: string) => boolean): void; 535 | static createOrThrow(validationSpec: string | string[], value: number, varName?: string): VString | never; 536 | static tryCreate(validationSpec: string | string[], value: number, varName?: string): VString | never; 537 | static create(validationSpec: string | string[], value: number, varName?: string): VString | null; 538 | static createOrError( 539 | validationSpec: string, 540 | value: number, 541 | varName?: string 542 | ): [VString, null] | [null, Error]; 543 | get value(): string; 544 | } 545 | ``` 546 | 547 | #### VString.registerCustomValidator 548 | `static registerCustomValidator(validatorName: string, validateFunc: (value: string) => boolean): void` 549 | 550 | Registers a custom validator with given name. `validateFunc` receives a string value as parameter and returns boolean based on success of validation. 551 | You must register your custom validator before using it in any of the `create` functions. 552 | 553 | #### VString.createOrThrow and VString.tryCreate 554 | `static createOrThrow(validationSpec: string, value: number, varName?: string): VString | never` 555 | `static tryCreate(validationSpec: string, value: number, varName?: string): VString | never` 556 | 557 | Creates a new validated integer value object or throws a `ValidationError` exception if supplied value is invalid. 558 | `validationSpec` is a string of following form `'[],[,[,]] | | custom:'`. 559 | 560 | Possible value for ``: 561 | 562 | - alpha 563 | - alphanumeric 564 | - ascii 565 | - base32 566 | - base58 567 | - base64 568 | - dataUri 569 | - decimal 570 | - fqdn 571 | - md4 572 | - md5 573 | - sha1 574 | - sha256 575 | - sha384 576 | - sha512 577 | - crc32 578 | - crc32b 579 | - hex 580 | - ipv4Range 581 | - ipv6Range 582 | - json 583 | - lowercase 584 | - magnetUri 585 | - mongoId 586 | - numeric 587 | - octal 588 | - uppercase 589 | - strongPassword 590 | - url 591 | - includes (requires `parameter`) 592 | - match (requires a RegExp string `parameter`) 593 | - isOneOf (requires JSON string array `parameter`) 594 | - isNoneOf (requires JSON string array `parameter`) 595 | - startsWith (requires `parameter`) 596 | - endsWith (requires `parameter`) 597 | - numericRange (requires `parameter`in format: `-`, e.g. 1-65535) 598 | 599 | Possible values for ``: 600 | 601 | - boolean 602 | - bic 603 | - btcAddress 604 | - creditCard 605 | - ean 606 | - email 607 | - ethereumAddress 608 | - hsl 609 | - hexColor 610 | - isin 611 | - iban 612 | - ipv4 613 | - ipv6 614 | - iso31661Alpha2 615 | - iso31661Alpha3 616 | - iso8601 617 | - isrc 618 | - issn 619 | - jwt 620 | - latLong 621 | - macAddress 622 | - mimeType 623 | - port 624 | - rgbColor 625 | - semVer 626 | - uuid 627 | - postalCode 628 | - creditCardExpiration 629 | - cvc 630 | - mobileNumber 631 | 632 | More information about validators can be found in [validator.js documentation](https://github.com/validatorjs/validator.js/) 633 | If you need a new built-in validator, please [open a new issue](https://github.com/pksilen/validated-types/issues) about that. 634 | 635 | `validationSpec` examples: 636 | 637 | - '0,100' 638 | - ',100' 639 | - '1,1024,url' 640 | - '1,1024,startsWith,https' 641 | - 'email' 642 | - 'custom:isSupplierName' 643 | 644 | If `minLength` is missing, 0 is used. 645 | If `varName` is supplied, it is mentioned in possible `ValidationError` thrown. 646 | 647 | #### VString.create 648 | `static create(validationSpec: string, value: number, varName?: string): VString | null` 649 | 650 | Same as `VString.createOrThrow`, but instead of throwing on validation failure, it returns `null`. 651 | This method is useful if you don't care about the error message, but just want to know if validation succeeded or not. 652 | 653 | #### VString.createOrError 654 | `static createOrError(validationSpec: string, value: number, varName?: string): [VString, null] | [null, Error]` 655 | 656 | Same as `VString.createOrThrow`, but instead of throwing on validation failure, it returns a tuple `[VString, null]` on success and tuple `[null, Error]` on validation failure 657 | This method is useful for Go language style of programming where you get a 2-tuple return value where the last element in tuple contains the possible error. 658 | For example: 659 | 660 | ```ts 661 | const [str, err] = VString.createOrError<'1,10'>('1,10', 'abc'); 662 | if (err) { 663 | // handle error here 664 | } 665 | ``` 666 | 667 | #### value 668 | `get value(): string` 669 | 670 | Returns the valid string value. 671 | 672 | ### VArray 673 | 674 | `T` is the type of elements in the array. 675 | 676 | ```ts 677 | class VArray { 678 | static registerCustomValidator(validatorName: string, validateFunc: (value: T[]) => boolean): void; 679 | static createOrThrow(validationSpec: string, value: T[], varName?: string): VArray | never; 680 | static tryCreate(validationSpec: string, value: T[], varName?: string): VArray | never; 681 | static create(validationSpec: string, value: T[], varName?: string): VArray | null; 682 | static createOrError( 683 | validationSpec: string, 684 | value: T[], 685 | varName?: string 686 | ): [VArray, null] | [null, Error]; 687 | get value(): T[]; 688 | } 689 | ``` 690 | 691 | #### VArray.registerCustomValidator 692 | `static registerCustomValidator(validatorName: string, validateFunc: (value: T[]) => boolean): void` 693 | 694 | Registers a custom validator with given name. `validateFunc` receives an array as parameter and returns boolean based on success of validation. 695 | You must register your custom validator before using it in any of the `create` functions. 696 | 697 | #### VArray.createOrThrow and VArray.tryCreate 698 | `static createOrThrow(validationSpec: string, value: T[], varName?: string): VArray | never` 699 | `static tryCreate(validationSpec: string, value: T[], varName?: string): VArray | never` 700 | 701 | Creates a new validated array value object or throws a `ValidationError` exception if supplied value is invalid. 702 | `validationSpec` is a string of following form `'[],[,unique] | custom:'`. 703 | 704 | `validationSpec` examples: 705 | 706 | - '0,100' 707 | - ',100' 708 | - '1,10,unique' 709 | - 'custom:includesSomething' 710 | 711 | If `minLength` is missing, 0 is used. 712 | If `varName` is supplied, it is mentioned in possible `ValidationError` thrown. 713 | 714 | #### VArray.create 715 | `static create(validationSpec: string, value: T[], varName?: string): VArray | null` 716 | 717 | Same as `VFloat.createOrThrow`, but instead of throwing on validation failure, it returns `null`. 718 | This method is useful if you don't care about the error message, but just want to know if validation succeeded or not. 719 | 720 | #### VArray.createOrError 721 | `static createOrError(validationSpec: string, value: T[], varName?: string): [VArray, null] | [null, Error]` 722 | 723 | Same as `VFloat.createOrThrow`, but instead of throwing on validation failure, it returns a tuple `[VArray, null]` on success and tuple `[null, Error]` on validation failure. 724 | This method is useful for Go language style of programming where you get a 2-tuple return value where the last element in tuple contains the possible error. 725 | For example: 726 | 727 | ```ts 728 | const [array, err] = VArray.createOrError<'1,10', number>('1,10', [1]); 729 | if (err) { 730 | // handle error here 731 | } 732 | ``` 733 | 734 | #### value 735 | `get value(): T[]` 736 | 737 | Returns the validated array. 738 | 739 | ### SemType 740 | `T` is the type of Semantic variable. 741 | 742 | ```ts 743 | type SemName = N extends `${infer Name}` ? `${Name}` : never; 744 | 745 | class SemType { 746 | constructor(semanticName: SemName, value: T); 747 | constructor(semType: { [K in SemName]: T }); 748 | get value(): T; 749 | } 750 | ``` 751 | 752 | #### constructor 753 | `constructor(semanticName: SemName, value: T)` 754 | `constructor(semType: { [K in SemName]: T });` 755 | 756 | Creates a new semantic variable with semantic name `semanticName` and with value of type `T`. 757 | 758 | #### value 759 | `get value(): T` 760 | Get the value of semantic variable. 761 | 762 | ### SemVInt 763 | 764 | ```ts 765 | type SemName = N extends `${infer Name}` ? `${Name}` : never; 766 | 767 | class SemVInt { 768 | static tryCreate( 769 | semanticName: SemName, 770 | validationSpec: IntValidationSpec, 771 | value: number, 772 | varName?: string 773 | ): SemVInt | never; // Throws on error 774 | 775 | static create( 776 | semanticName: SemName, 777 | validationSpec: IntValidationSpec, 778 | value: number 779 | ): SemVInt | null; // Returns null on error 780 | 781 | static createOrError( 782 | semanticName: SemName, 783 | validationSpec: IntValidationSpec, 784 | value: number, 785 | varName?: string 786 | ): [SemVInt, null] | [null, Error] // Returns a pair [null, Error] on error 787 | } 788 | ``` 789 | 790 | ### SemVFloat 791 | 792 | ```ts 793 | type SemName = N extends `${infer Name}` ? `${Name}` : never; 794 | 795 | class SemVFloat { 796 | static tryCreate( 797 | semanticName: SemName, 798 | validationSpec: FloatValidationSpec, 799 | value: number, 800 | varName?: string 801 | ): SemVFloat | never; // Throws on error 802 | 803 | static create( 804 | semanticName: SemName, 805 | validationSpec: FloatValidationSpec, 806 | value: number 807 | ): SemVFloat | null // Returns null on error 808 | 809 | static createOrError( 810 | semanticName: SemName, 811 | validationSpec: FloatValidationSpec, 812 | value: number, 813 | varName?: string 814 | ): [SemVFloat, null] | [null, Error]; // Returns a pair [null, Error] on error 815 | } 816 | ``` 817 | 818 | ### SemVString 819 | 820 | ```ts 821 | type SemName = N extends `${infer Name}` ? `${Name}` : never; 822 | 823 | class SemVString { 824 | static tryCreate( 825 | semanticName: SemName, 826 | validationSpec: string | string[], 827 | value: string, 828 | varName?: string 829 | ): SemVString | never; // Throws on error 830 | 831 | static create( 832 | semanticName: SemName, 833 | validationSpec: string | string[], 834 | value: string, 835 | varName?: string 836 | ): SemVString | null; // Returns null on error 837 | 838 | static createOrError( 839 | semanticName: SemName, 840 | validationSpec: string | string[], 841 | value: string, 842 | varName?: string 843 | ): [SemVString, null] | [null, Error]; // Returns a pair [null, Error] on error 844 | } 845 | ``` 846 | ### VSpecOf 847 | 848 | Extracts the validation spec type of the validated type. 849 | 850 | For example: 851 | 852 | ```ts 853 | type Month = VInt<'1,12'>; 854 | const monthVSpec: VSpecOf = '1,12'; 855 | const month: Month = VInt.tryCreate(monthVSpec, 1); 856 | 857 | type Percent = VFloat<'0,100'>; 858 | const percentVSpec: VSpecOf = '0,100'; 859 | const percent: Percent = VFloat.tryCreate(percentVSpec, 25.0); 860 | 861 | type Url = VString<['1,1024,lowercase', 'url', 'startsWith,https', 'endsWith,.html']>; 862 | const urlVSpec: VSpecOf = ['1,1024,lowercase', 'url', 'startsWith,https', 'endsWith,.html']; 863 | const url: Url = VString.tryCreate(urlVSpec, 'https://server.domain.com:8080/index.html'); 864 | ``` 865 | 866 | ## Feedback 867 | 868 | If you want to report a bug, please [create a new issue](https://github.com/pksilen/validated-types/issues) about that. 869 | 870 | If you want to request a new feature, for example, a new type of validator for string, please [create a new issue](https://github.com/pksilen/validated-types/issues) about that. 871 | 872 | ## License 873 | 874 | [MIT](https://github.com/pksilen/validated-types/blob/main/LICENSE) 875 | 876 | [license-badge]: https://img.shields.io/badge/license-MIT-green 877 | [license]: https://github.com/pksilen/validated-types/blob/master/LICENSE 878 | [version-badge]: https://img.shields.io/npm/v/validated-types.svg?style=flat-square 879 | [package]: https://www.npmjs.com/package/validated-types 880 | [downloads]: https://img.shields.io/npm/dm/validated-types 881 | [build]: https://img.shields.io/circleci/project/github/pksilen/validated-types/main.svg?style=flat-square 882 | [circleci]: https://circleci.com/gh/pksilen/validated-types/tree/main 883 | [coverage]: https://img.shields.io/codecov/c/github/pksilen/validated-types/main.svg?style=flat-square 884 | [codecov]: https://codecov.io/gh/pksilen/validated-types 885 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "validated-types", 3 | "version": "1.7.1", 4 | "description": "Validated integer, float, string and array types for Typescript", 5 | "author": { 6 | "name": "Petri Silen", 7 | "email": "petri.silen@silensoft.com" 8 | }, 9 | "engines": { 10 | "node": ">= 12.19" 11 | }, 12 | "license": "MIT", 13 | "main": "lib/index.js", 14 | "files": [ 15 | "lib" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/pksilen/validated-types.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/pksilen/validated-types/issues" 23 | }, 24 | "keywords": [ 25 | "validator", 26 | "validate", 27 | "validation", 28 | "string", 29 | "integer", 30 | "float", 31 | "array", 32 | "type", 33 | "typescript" 34 | ], 35 | "scripts": { 36 | "prebuild": "rimraf lib", 37 | "lint": "eslint ./src --cache --fix --ext .ts", 38 | "format": "prettier --write \"src/**/*.ts\"", 39 | "build": "tsc", 40 | "test": "jest", 41 | "test:coverage": "jest --coverage", 42 | "prepublishOnly": "npm run build && npm test", 43 | "prepare": "husky install" 44 | }, 45 | "dependencies": { 46 | "validator": "^13.6.0" 47 | }, 48 | "devDependencies": { 49 | "@types/jest": "^27.0.1", 50 | "@types/node": "^16.7.13", 51 | "@types/validator": "^13.6.3", 52 | "@typescript-eslint/eslint-plugin": "^4.31.0", 53 | "@typescript-eslint/parser": "^4.31.0", 54 | "eslint": "^7.32.0", 55 | "eslint-config-prettier": "^8.3.0", 56 | "eslint-plugin-import": "^2.24.2", 57 | "husky": "^7.0.2", 58 | "jest": "^27.1.1", 59 | "lint-staged": "^11.1.2", 60 | "prettier": "^2.3.2", 61 | "ts-jest": "^27.0.5", 62 | "typescript": "^4.7.4" 63 | }, 64 | "jest": { 65 | "moduleFileExtensions": [ 66 | "js", 67 | "json", 68 | "ts" 69 | ], 70 | "rootDir": "src", 71 | "testRegex": ".spec.ts$", 72 | "transform": { 73 | "^.+\\.(t|j)s$": "ts-jest" 74 | }, 75 | "coverageDirectory": "../coverage", 76 | "testEnvironment": "node" 77 | }, 78 | "lint-staged": { 79 | "*.ts": [ 80 | "npm run lint", 81 | "npm run format" 82 | ] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/array/VArray.ts: -------------------------------------------------------------------------------- 1 | import VBase from '../base/VBase'; 2 | import ValidationError from '../error/ValidationError'; 3 | 4 | export type ArrayValidationSpec = 5 | ValidationSpec extends `${infer MinLength},${infer MaxLength},unique` 6 | ? `${MinLength},${MaxLength},unique` 7 | : ValidationSpec extends `${infer MinLength},${infer MaxLength}` 8 | ? `${MinLength},${MaxLength}` 9 | : ValidationSpec extends `custom:${infer CustomValidatorName}` 10 | ? `custom:${CustomValidatorName}` 11 | : never; 12 | 13 | export default class VArray extends VBase { 14 | protected readonly validatedValue: T[]; 15 | 16 | // this will throw if invalid value is given that don't match the validation spec 17 | static createOrThrow( 18 | validationSpec: ArrayValidationSpec, 19 | value: T[], 20 | varName?: string 21 | ): VArray | never { 22 | return new VArray(validationSpec, value, varName); 23 | } 24 | 25 | // this will throw if invalid value is given that don't match the validation spec 26 | static tryCeate( 27 | validationSpec: ArrayValidationSpec, 28 | value: T[], 29 | varName?: string 30 | ): VArray | never { 31 | return new VArray(validationSpec, value, varName); 32 | } 33 | 34 | static create( 35 | validationSpec: ArrayValidationSpec, 36 | value: T[], 37 | varName?: string 38 | ): VArray | null { 39 | try { 40 | return new VArray(validationSpec, value, varName); 41 | } catch { 42 | return null; 43 | } 44 | } 45 | 46 | static createOrError( 47 | validationSpec: ArrayValidationSpec, 48 | value: T[], 49 | varName?: string 50 | ): [VArray, null] | [null, Error] { 51 | try { 52 | return [new VArray(validationSpec, value, varName), null]; 53 | } catch (error) { 54 | return [null, error as Error]; 55 | } 56 | } 57 | 58 | protected constructor( 59 | protected readonly validationSpec: ArrayValidationSpec, 60 | protected readonly arrayValue: T[], 61 | varName?: string 62 | ) { 63 | super(); 64 | const validationSpecAsStr = validationSpec as string; 65 | VBase.validateByCustomValidator(validationSpecAsStr, arrayValue, varName); 66 | VBase.validateLength(validationSpec, arrayValue, varName); 67 | this.validateUniqueArrayElements(validationSpec, arrayValue, varName); 68 | this.validatedValue = arrayValue; 69 | } 70 | 71 | private validateUniqueArrayElements(validationSpec: string, values: T[], varName?: string) { 72 | if (validationSpec.includes(',')) { 73 | const [, , unique] = validationSpec.split(','); 74 | 75 | if (unique === 'unique') { 76 | const set = new Set(); 77 | values.forEach((value) => { 78 | if (value instanceof VBase) { 79 | set.add(value.value); 80 | } else { 81 | set.add(value); 82 | } 83 | }); 84 | 85 | if (set.size < values.length) { 86 | throw new ValidationError( 87 | varName ? `Values in '${varName}' are not unique` : `Values are not unique` 88 | ); 89 | } 90 | } 91 | } 92 | } 93 | 94 | get value(): T[] { 95 | return this.validatedValue; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/array/Varray.spec.ts: -------------------------------------------------------------------------------- 1 | // noinspection MagicNumberJS 2 | 3 | import VArray from './VArray'; 4 | import VString from '../string/VString'; 5 | 6 | VArray.registerCustomValidator('has5', (value) => value.includes(5)); 7 | 8 | describe('VArray', () => { 9 | describe('createOrThrow', () => { 10 | it('should create a VArray object successfully when value matches validation spec', () => { 11 | const array: VArray<'0,10', number> = VArray.createOrThrow('0,10', [5]); 12 | expect(array.value).toEqual([5]); 13 | }); 14 | it('should create a VArray object successfully when custom validation function call evaluates to true for the value', () => { 15 | const array: VArray<'custom:has5', number> = VArray.createOrThrow('custom:has5', [4, 5]); 16 | expect(array.value).toEqual([4, 5]); 17 | }); 18 | it('should create a VArray object successfully when values are validated as unique', () => { 19 | const array: VArray<'0,10,unique', number> = VArray.createOrThrow('0,10,unique', [4, 5, 6]); 20 | expect(array.value).toEqual([4, 5, 6]); 21 | }); 22 | it('should throw validation error when values are other validated objects (VString) and are not unique', () => { 23 | const email: VString<'email'> = VString.createOrThrow('email', 'test@example.com'); 24 | const email2: VString<'email'> = VString.createOrThrow('email', 'test@example.com'); 25 | expect(() => { 26 | VArray.createOrThrow<'0,10,unique', VString<'email'>>('0,10,unique', [email, email2]); 27 | }).toThrow('Values are not unique'); 28 | }); 29 | it('should throw a ValidationError when values are not validated as unique', () => { 30 | expect(() => { 31 | VArray.createOrThrow<'0,10,unique', number>('0,10,unique', [4, 4, 6]); 32 | }).toThrow('Values are not unique'); 33 | }); 34 | it('should throw a ValidationError with varName when values are not validated as unique', () => { 35 | expect(() => { 36 | VArray.createOrThrow<'0,10,unique', number>('0,10,unique', [4, 4, 6], 'varName'); 37 | }).toThrow("Values in 'varName' are not unique"); 38 | }); 39 | }); 40 | describe('create', () => { 41 | it('should create a VArray object successfully when value matches validation spec', () => { 42 | const array: VArray<'0,10', number> | null = VArray.create('0,10', [5]); 43 | expect(array?.value).toEqual([5]); 44 | }); 45 | it('should return null when value does not match validation spec', () => { 46 | const array: VArray<'2,10', number> | null = VArray.create('2,10', [5]); 47 | expect(array).toBeNull(); 48 | }); 49 | }); 50 | describe('createOrError', () => { 51 | it('should create a VArray object successfully when value matches validation spec', () => { 52 | const [array, error]: [VArray<'1,10', number> | null, Error | null] = VArray.createOrError('1,10', [5]); 53 | expect(array?.value).toEqual([5]); 54 | expect(error).toBeNull(); 55 | }); 56 | it('should return error when value does not match validation spec', () => { 57 | const [float, error]: [VArray<'2,10', number> | null, Error | null] = VArray.createOrError('2,10', [5]); 58 | expect(float).toBeNull(); 59 | expect(error).toBeInstanceOf(Error); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/base/VBase.spec.ts: -------------------------------------------------------------------------------- 1 | import VBase from './VBase'; 2 | 3 | describe('VBase', () => { 4 | describe('registerCustomValidator', () => { 5 | it('should register new custom validator successfully', () => { 6 | VBase.registerCustomValidator('newValidator', (value) => value === 1); 7 | expect(VBase.getCustomValidator('newValidator')(1)).toEqual(true); 8 | expect(VBase.getCustomValidator('newValidator')(2)).toEqual(false); 9 | }); 10 | it('should throw if custom float validator with the same name has already been registered', () => { 11 | VBase.registerCustomValidator('newValidator2', (value) => value === 1); 12 | expect(() => { 13 | VBase.registerCustomValidator('newValidator2', () => true); 14 | }).toThrow("Validator 'newValidator2' already exists"); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/base/VBase.ts: -------------------------------------------------------------------------------- 1 | import ValidationSpecError from '../error/ValidationSpecError'; 2 | import ValidationError from '../error/ValidationError'; 3 | 4 | type CustomValidator = (value: T) => boolean; 5 | 6 | type CustomValidators = { 7 | [validatorName: string]: CustomValidator; 8 | }; 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 11 | export default abstract class VBase { 12 | private static readonly customValidators: CustomValidators = {}; 13 | 14 | static registerCustomValidator(validatorName: string, validateFunc: CustomValidator): void | never { 15 | if (this.customValidators[validatorName]) { 16 | throw new Error(`Validator '${validatorName}' already exists`); 17 | } 18 | 19 | this.customValidators[validatorName] = validateFunc; 20 | } 21 | 22 | static getCustomValidator(validatorName: string): CustomValidator { 23 | return VBase.customValidators[validatorName]; 24 | } 25 | 26 | protected static validateByCustomValidator( 27 | validationSpec: string, 28 | value: string | number | T[], 29 | varName?: string 30 | ): void | never { 31 | if (validationSpec.startsWith('custom:')) { 32 | const [, validatorName] = validationSpec.split(':'); 33 | if (!VBase.customValidators[validatorName]) { 34 | throw new ValidationSpecError('Custom validator not registered with name: ' + validatorName); 35 | } 36 | if (!VBase.customValidators[validatorName](value)) { 37 | throw new ValidationError( 38 | varName 39 | ? `Value in '${varName}' does not match custom validator: ${validatorName}` 40 | : `Value does not match custom validator: ${validatorName}` 41 | ); 42 | } 43 | } 44 | } 45 | 46 | protected static validateNumericRange( 47 | validationSpec: string, 48 | value: number, 49 | parseNumber: (str: string) => number, 50 | [defaultMinValue, defaultMaxValue]: [number, number], 51 | varName?: string 52 | ): void | never { 53 | if (validationSpec.includes(',')) { 54 | const [minValueStr, maxValueStr] = validationSpec.split(','); 55 | let minValue = parseNumber(minValueStr); 56 | let maxValue = parseNumber(maxValueStr); 57 | 58 | if (minValueStr === '') { 59 | minValue = defaultMinValue; 60 | } 61 | 62 | if (maxValueStr === '') { 63 | maxValue = defaultMaxValue; 64 | } 65 | 66 | if (isNaN(minValue)) { 67 | throw new ValidationSpecError('Invalid minValue specified in validation spec: ' + validationSpec); 68 | } 69 | 70 | if (isNaN(maxValue)) { 71 | throw new ValidationSpecError('Invalid maxValue specified in validation spec: ' + validationSpec); 72 | } 73 | 74 | if (value < minValue || value > maxValue) { 75 | throw new ValidationError( 76 | varName 77 | ? `Value in '${varName}' is not in allowed range: [${minValue}, ${maxValue}]` 78 | : `Value is not in allowed range: [${minValue}, ${maxValue}]` 79 | ); 80 | } 81 | } 82 | } 83 | 84 | protected static validatePositiveValue( 85 | validationSpec: string, 86 | value: number, 87 | varName?: string 88 | ): void | never { 89 | if (validationSpec === 'positive' && value <= 0) { 90 | throw new ValidationError( 91 | varName ? `Value in '${varName}' must be positive` : `Value must be positive` 92 | ); 93 | } 94 | } 95 | 96 | protected static validateNegativeValue( 97 | validationSpec: string, 98 | value: number, 99 | varName?: string 100 | ): void | never { 101 | if (validationSpec === 'negative' && value >= 0) { 102 | throw new ValidationError( 103 | varName ? `Value in '${varName}' must be negative` : `Value must be negative` 104 | ); 105 | } 106 | } 107 | 108 | protected static validateLength( 109 | validationSpec: string, 110 | value: string | T[], 111 | varName?: string 112 | ): void | never { 113 | if (validationSpec.includes(',')) { 114 | const [minLengthStr, maxLengthStr] = validationSpec.split(','); 115 | let minLength = parseInt(minLengthStr, 10); 116 | const maxLength = parseInt(maxLengthStr, 10); 117 | 118 | if (minLengthStr === '') { 119 | minLength = 0; 120 | } 121 | 122 | if (isNaN(minLength)) { 123 | throw new ValidationSpecError('Invalid minLength specified in validation spec'); 124 | } 125 | 126 | if (isNaN(maxLength)) { 127 | throw new ValidationSpecError('Invalid maxLength specified in validation spec'); 128 | } 129 | 130 | if (value.length < minLength) { 131 | throw new ValidationError( 132 | varName 133 | ? `Value in '${varName}' is shorter than required minimum length: ${minLength}` 134 | : `Value is shorter than required minimum length: ${minLength}` 135 | ); 136 | } 137 | 138 | if (value.length > maxLength) { 139 | throw new ValidationError( 140 | varName 141 | ? `Value in '${varName}' is longer than allowed maximum length: ${maxLength}` 142 | : `Value is longer than allowed maximum length: ${maxLength}` 143 | ); 144 | } 145 | } 146 | } 147 | 148 | abstract get value(): T; 149 | } 150 | -------------------------------------------------------------------------------- /src/base/VSpecOf.ts: -------------------------------------------------------------------------------- 1 | import VBase from './VBase'; 2 | import VInt from '../integer/VInt'; 3 | import VFloat from '../float/VFloat'; 4 | import VString from '../string/VString'; 5 | 6 | export type VSpecOf> = ValidatedType extends VString 7 | ? Spec 8 | : ValidatedType extends VInt 9 | ? Spec 10 | : ValidatedType extends VFloat 11 | ? Spec 12 | : never; 13 | -------------------------------------------------------------------------------- /src/error/ValidationError.ts: -------------------------------------------------------------------------------- 1 | export default class ValidationError extends Error {} 2 | -------------------------------------------------------------------------------- /src/error/ValidationSpecError.ts: -------------------------------------------------------------------------------- 1 | export default class ValidationSpecError extends Error {} 2 | -------------------------------------------------------------------------------- /src/float/VFloat.spec.ts: -------------------------------------------------------------------------------- 1 | // noinspection MagicNumberJS 2 | 3 | import VFloat from './VFloat'; 4 | 5 | VFloat.registerCustomValidator('is5_1', (value) => value === 5.1); 6 | 7 | describe('VFloat', () => { 8 | describe('createOrThrow', () => { 9 | it('should create a VFloat object successfully when value matches validation spec', () => { 10 | const float: VFloat<'0.5,10.5'> = VFloat.createOrThrow('0.5,10.5', 5.1); 11 | expect(float.value).toEqual(5.1); 12 | }); 13 | it('should create a VFloat object successfully when validation spec contains whitespace', () => { 14 | const float: VFloat<' 0.5 , 10.5 '> = VFloat.createOrThrow(' 0.5 , 10.5 ', 5.1); 15 | expect(float.value).toEqual(5.1); 16 | }); 17 | it('should create a VFloat object successfully when custom validation function call evaluates to true for the value', () => { 18 | const float: VFloat<'custom:is5_1'> = VFloat.createOrThrow('custom:is5_1', 5.1); 19 | expect(float.value).toEqual(5.1); 20 | }); 21 | it('should create a VFloat object successfully when value is validated as positive', () => { 22 | const float: VFloat<'positive'> = VFloat.createOrThrow('positive', 5.1); 23 | expect(float.value).toEqual(5.1); 24 | }); 25 | it('should create a VFloat object successfully when value is validated as negative', () => { 26 | const float: VFloat<'negative'> = VFloat.createOrThrow('negative', -5.1); 27 | expect(float.value).toEqual(-5.1); 28 | }); 29 | it('should throw ValidationError when value is greater than maxValue specified in validation spec', () => { 30 | expect(() => { 31 | VFloat.createOrThrow<'0.5,10.5'>('0.5,10.5', 20.1); 32 | }).toThrow('Value is not in allowed range: [0.5, 10.5]'); 33 | }); 34 | it('should throw ValidationError when value is less than minValue specified in validation spec', () => { 35 | expect(() => { 36 | VFloat.createOrThrow<'0.5,10.5'>('0.5,10.5', 0.4); 37 | }).toThrow('Value is not in allowed range: [0.5, 10.5]'); 38 | }); 39 | it('should throw ValidationError when value is not validated as positive', () => { 40 | expect(() => { 41 | VFloat.createOrThrow<'positive'>('positive', -0.4); 42 | }).toThrow('Value must be positive'); 43 | }); 44 | it('should throw ValidationError when value is not validated as negative', () => { 45 | expect(() => { 46 | VFloat.createOrThrow<'negative'>('negative', 0.4); 47 | }).toThrow('Value must be negative'); 48 | }); 49 | it('should throw ValidationError when custom validation function call evaluates to false for the value', () => { 50 | expect(() => { 51 | VFloat.createOrThrow<'custom:is5_1'>('custom:is5_1', 0.4); 52 | }).toThrow('Value does not match custom validator: is5_1'); 53 | }); 54 | it('should throw ValidationSpecError when minValue in validation spec is invalid', () => { 55 | expect(() => { 56 | VFloat.createOrThrow<'a,10.5'>('a,10.5', 0.4); 57 | }).toThrow('Invalid minValue specified in validation spec: a,10.5'); 58 | }); 59 | it('should throw ValidationSpecError when maxValue in validation spec is invalid', () => { 60 | expect(() => { 61 | VFloat.createOrThrow<'0.5,a'>('0.5,a', 0.4); 62 | }).toThrow('Invalid maxValue specified in validation spec: 0.5,a'); 63 | }); 64 | it('should throw ValidationSpecError when custom validator is not registered', () => { 65 | expect(() => { 66 | VFloat.createOrThrow<'custom:not_registered'>('custom:not_registered', 0.4); 67 | }).toThrow('Custom validator not registered with name: not_registered'); 68 | }); 69 | it('should use Number.MIN_VALUE as minValue when minValue in validation spec is missing', () => { 70 | const float = VFloat.createOrThrow<',10.5'>(',10.5', Number.MIN_VALUE); 71 | expect(float.value).toEqual(Number.MIN_VALUE); 72 | }); 73 | it('should use Number.MAX_VALUE as maxValue when maxValue in validation spec is missing', () => { 74 | const float = VFloat.createOrThrow<'10.5,'>('10.5,', Number.MAX_VALUE); 75 | expect(float.value).toEqual(Number.MAX_VALUE); 76 | }); 77 | it('should use Number.MIN_VALUE and Number.MAX_VALUE as minValue and maxValue when minValue and maxValue in validation spec are missing', () => { 78 | const float1 = VFloat.createOrThrow<','>(',', Number.MIN_VALUE); 79 | const float2 = VFloat.createOrThrow<','>(',', Number.MAX_VALUE); 80 | expect(float1.value).toEqual(Number.MIN_VALUE); 81 | expect(float2.value).toEqual(Number.MAX_VALUE); 82 | }); 83 | it('should throw ValidationSpecError with variable name when value is not in specified range', () => { 84 | expect(() => { 85 | VFloat.createOrThrow<'0,10'>('0,10', Number.MAX_VALUE, 'varName'); 86 | }).toThrow("Value in 'varName' is not in allowed range: [0, 10]"); 87 | }); 88 | it('should throw ValidationSpecError with variable name when value is not positive', () => { 89 | expect(() => { 90 | VFloat.createOrThrow<'positive'>('positive', -1.2, 'varName'); 91 | }).toThrow("Value in 'varName' must be positive"); 92 | }); 93 | it('should throw ValidationSpecError with variable name when value is not negative', () => { 94 | expect(() => { 95 | VFloat.createOrThrow<'negative'>('negative', 1.2, 'varName'); 96 | }).toThrow("Value in 'varName' must be negative"); 97 | }); 98 | it('should throw ValidationSpecError with variable name when using custom validator', () => { 99 | expect(() => { 100 | VFloat.createOrThrow<'custom:is5_1'>('custom:is5_1', 4, 'varName'); 101 | }).toThrow("Value in 'varName' does not match custom validator: is5_1"); 102 | }); 103 | }); 104 | describe('create', () => { 105 | it('should create a VFloat object successfully when value matches validation spec', () => { 106 | const possibleFloat: VFloat<'0.5,10.5'> | null = VFloat.create('0.5,10.5', 5.1); 107 | expect(possibleFloat?.value).toEqual(5.1); 108 | }); 109 | it('should return null when value does not match validation spec', () => { 110 | const possibleFloat: VFloat<'0.5,10.5'> | null = VFloat.create('0.5,10.5', 0.3); 111 | expect(possibleFloat).toBeNull(); 112 | }); 113 | }); 114 | describe('createOrError', () => { 115 | it('should create a VFloat object successfully when value matches validation spec', () => { 116 | const [float, error]: [VFloat<'0.5,10.5'> | null, Error | null] = VFloat.createOrError('0.5,10.5', 5.1); 117 | expect(float?.value).toEqual(5.1); 118 | expect(error).toBeNull(); 119 | }); 120 | it('should return error when value does not match validation spec', () => { 121 | const [float, error]: [VFloat<'0.5,10.5'> | null, Error | null] = VFloat.createOrError('0.5,10.5', 0.3); 122 | expect(float).toBeNull(); 123 | expect(error).toBeInstanceOf(Error); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /src/float/VFloat.ts: -------------------------------------------------------------------------------- 1 | import VBase from '../base/VBase'; 2 | 3 | export type FloatValidationSpec = 4 | ValidationSpec extends `${infer MinValue},${infer MaxValue}` 5 | ? `${MinValue},${MaxValue}` 6 | : ValidationSpec extends `custom:${infer CustomValidatorName}` 7 | ? `custom:${CustomValidatorName}` 8 | : ValidationSpec extends `${infer FloatValidatorName}` 9 | ? FloatValidatorName extends 'positive' | 'negative' 10 | ? `${FloatValidatorName}` 11 | : { errorMessage: `Invalid float validator name: ${FloatValidatorName}` } 12 | : never; 13 | 14 | export default class VFloat extends VBase { 15 | private readonly validatedValue: number; 16 | 17 | // this will throw if invalid value is given that don't match the validation spec 18 | static createOrThrow( 19 | validationSpec: FloatValidationSpec, 20 | value: number, 21 | varName?: string 22 | ): VFloat | never { 23 | return new VFloat(validationSpec, value, varName); 24 | } 25 | 26 | // this will throw if invalid value is given that don't match the validation spec 27 | static tryCreate( 28 | validationSpec: FloatValidationSpec, 29 | value: number, 30 | varName?: string 31 | ): VFloat | never { 32 | return new VFloat(validationSpec, value, varName); 33 | } 34 | 35 | static create( 36 | validationSpec: FloatValidationSpec, 37 | value: number, 38 | varName?: string 39 | ): VFloat | null { 40 | try { 41 | return new VFloat(validationSpec, value, varName); 42 | } catch { 43 | return null; 44 | } 45 | } 46 | 47 | static createOrError( 48 | validationSpec: FloatValidationSpec, 49 | value: number, 50 | varName?: string 51 | ): [VFloat, null] | [null, Error] { 52 | try { 53 | return [new VFloat(validationSpec, value, varName), null]; 54 | } catch (error) { 55 | return [null, error as Error]; 56 | } 57 | } 58 | 59 | protected constructor( 60 | protected readonly validationSpec: FloatValidationSpec, 61 | value: number, 62 | varName?: string 63 | ) { 64 | super(); 65 | const validationSpecAsStr = validationSpec as string; 66 | VBase.validateByCustomValidator(validationSpecAsStr, value, varName); 67 | VBase.validateNumericRange( 68 | validationSpecAsStr, 69 | value, 70 | parseFloat, 71 | [Number.MIN_VALUE, Number.MAX_VALUE], 72 | varName 73 | ); 74 | VBase.validatePositiveValue(validationSpecAsStr, value, varName); 75 | VBase.validateNegativeValue(validationSpecAsStr, value, varName); 76 | this.validatedValue = value; 77 | } 78 | 79 | get value(): number { 80 | return this.validatedValue; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ValidationError } from './error/ValidationError'; 2 | export { default as ValidationSpecError } from './error/ValidationSpecError'; 3 | export { default as VFloat } from './float/VFloat'; 4 | export { default as VInt } from './integer/VInt'; 5 | export { default as VString } from './string/VString'; 6 | export { default as VArray } from './array/VArray'; 7 | export { default as SemVar } from './semvar/SemVar'; 8 | export { default as SemType } from './semtype/SemType'; 9 | export { default as SemVFloat } from './semfloat/SemVFloat'; 10 | export { default as SemVInt } from './seminteger/SemVInt'; 11 | export { default as SemVString } from './semstring/SemVString'; 12 | export { VSpecOf } from './base/VSpecOf'; 13 | -------------------------------------------------------------------------------- /src/integer/VInt.spec.ts: -------------------------------------------------------------------------------- 1 | // noinspection MagicNumberJS 2 | 3 | import VInt from './VInt'; 4 | 5 | function useInt(int: VInt<'1,10'>) { 6 | // use int here 7 | console.log(int.value); 8 | } 9 | 10 | const int = VInt.createOrThrow<'1,10'>('1,10', 5); 11 | const maybeInt = VInt.create<'1,10'>('1,10', 12); 12 | 13 | useInt(int); // prints to console: 5 14 | useInt(maybeInt ?? VInt.createOrThrow('1,10', 10)); // prints to console: 10 15 | 16 | VInt.registerCustomValidator('is5', (value) => value === 5); 17 | 18 | function log(int: VInt<'0,10,'>) { 19 | // NOOP 20 | } 21 | 22 | const int1 = VInt.createOrThrow<'0,10,'>('0,10,', 5); 23 | const int2: VInt<'0,10,'> = int1; 24 | 25 | log(int2); 26 | 27 | VInt.createOrThrow<'negative'>('negative', -5); 28 | 29 | describe('VInt', () => { 30 | describe('createOrThrow', () => { 31 | it('should create a VInt object successfully when value matches validation spec', () => { 32 | const int1: VInt<'0,10'> = VInt.createOrThrow('0,10', 5); 33 | const int2: VInt<'0,10,'> = VInt.createOrThrow<'0,10,'>('0,10,', 5); 34 | expect(int1.value).toEqual(5); 35 | expect(int2.value).toEqual(5); 36 | }); 37 | it('should create a VInt object successfully when validation spec contains whitespace', () => { 38 | const int: VInt<' 0 , 10 '> = VInt.createOrThrow(' 0 , 10 ', 5); 39 | expect(int.value).toEqual(5); 40 | }); 41 | it('should create a VInt object successfully when validation spec contains divisibleByValue', () => { 42 | const int: VInt<'0,10,5'> = VInt.createOrThrow('0,10,5', 5); 43 | expect(int.value).toEqual(5); 44 | }); 45 | it('should create a VInt object successfully when custom validation function call evaluates to true for the value', () => { 46 | const int: VInt<'custom:is5'> = VInt.createOrThrow('custom:is5', 5); 47 | expect(int.value).toEqual(5); 48 | }); 49 | it('should throw ValidationError when value is not an integer', () => { 50 | expect(() => { 51 | VInt.createOrThrow<'0,10'>('0,10', 5.1); 52 | }).toThrow('Value is not an integer'); 53 | }); 54 | it('should throw ValidationError when value is greater than maxValue specified in validation spec', () => { 55 | expect(() => { 56 | VInt.createOrThrow<'0,10'>('0,10', 20); 57 | }).toThrow('Value is not in allowed range: [0, 10]'); 58 | }); 59 | it('should throw ValidationError when value is not divisible by value giving in validation spec', () => { 60 | expect(() => { 61 | VInt.createOrThrow<'0,10,5'>('0,10,5', 3); 62 | }).toThrow('Value is not divisible by: 5'); 63 | }); 64 | it('should throw ValidationSpecError when divisibleByValue is zero', () => { 65 | expect(() => { 66 | VInt.createOrThrow<'0,10,0'>('0,10,0', 0); 67 | }).toThrow('Invalid divisibleByValue specified in validation spec: 0,10,0'); 68 | }); 69 | it('should throw ValidationSpecError when divisibleByValue is invalid', () => { 70 | expect(() => { 71 | VInt.createOrThrow<'0,10,a'>('0,10,a', 0); 72 | }).toThrow('Invalid divisibleByValue specified in validation spec: 0,10,a'); 73 | }); 74 | it('should use Number.MIN_SAFE_INTEGER as minValue when minValue in validation spec is missing', () => { 75 | const int = VInt.createOrThrow<',10'>(',10', Number.MIN_SAFE_INTEGER); 76 | expect(int.value).toEqual(Number.MIN_SAFE_INTEGER); 77 | }); 78 | it('should use Number.MAX_SAFE_INTEGER as maxValue when maxValue invalidation spec is missing', () => { 79 | const int = VInt.createOrThrow<'10,'>('10,', Number.MAX_SAFE_INTEGER); 80 | expect(int.value).toEqual(Number.MAX_SAFE_INTEGER); 81 | }); 82 | it('should use Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER as minValue and maxValue when minValue and maxValue in validation spec are missing', () => { 83 | const int1 = VInt.createOrThrow<','>(',', Number.MIN_SAFE_INTEGER); 84 | const int2 = VInt.createOrThrow<','>(',', Number.MAX_SAFE_INTEGER); 85 | expect(int1.value).toEqual(Number.MIN_SAFE_INTEGER); 86 | expect(int2.value).toEqual(Number.MAX_SAFE_INTEGER); 87 | }); 88 | it('should throw ValidationSpecError with variable name when value is not an integer', () => { 89 | expect(() => { 90 | VInt.createOrThrow<'0,10'>('0,10', 5.5, 'varName'); 91 | }).toThrow("Value in 'varName' is not an integer"); 92 | }); 93 | it('should throw IntValidationSpecError with variable name when value is not divisible by given value', () => { 94 | expect(() => { 95 | VInt.createOrThrow<'0,10,5'>('0,10,5', 6, 'varName'); 96 | }).toThrow("Value in 'varName' is not divisible by: 5"); 97 | }); 98 | }); 99 | describe('create', () => { 100 | it('should create a VInt object successfully when value matches validation spec', () => { 101 | const possibleInt: VInt<'0,10'> | null = VInt.create('0,10', 5); 102 | expect(possibleInt?.value).toEqual(5); 103 | }); 104 | it('should return null when value does not match validation spec', () => { 105 | const possibleInt: VInt<'0,10'> | null = VInt.create('0,10', 11); 106 | expect(possibleInt).toBeNull(); 107 | }); 108 | }); 109 | describe('createOrError', () => { 110 | it('should create a VInt object successfully when value matches validation spec', () => { 111 | const [int, error] = VInt.createOrError<'5,10'>('5,10', 5); 112 | expect(int?.value).toEqual(5); 113 | expect(error).toBeNull(); 114 | }); 115 | it('should return error when value does not match validation spec', () => { 116 | const [int, error] = VInt.createOrError<'0,10'>('0,10', -1); 117 | expect(int).toBeNull(); 118 | expect(error).toBeInstanceOf(Error); 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /src/integer/VInt.ts: -------------------------------------------------------------------------------- 1 | import VBase from '../base/VBase'; 2 | import ValidationError from '../error/ValidationError'; 3 | import ValidationSpecError from '../error/ValidationSpecError'; 4 | 5 | export type IntValidationSpec = 6 | ValidationSpec extends `${infer MinValue},${infer MaxValue},${infer DivisibleByValue}` 7 | ? `${MinValue},${MaxValue},${DivisibleByValue}` 8 | : ValidationSpec extends `${infer MinValue},${infer MaxValue}` 9 | ? `${MinValue},${MaxValue}` 10 | : ValidationSpec extends `custom:${infer ValidatorName}` 11 | ? `custom:${ValidatorName}` 12 | : ValidationSpec extends `${infer IntValidatorName}` 13 | ? IntValidatorName extends 'positive' | 'negative' 14 | ? `${IntValidatorName}` 15 | : { errorMessage: `Invalid int validator name: ${IntValidatorName}` } 16 | : never; 17 | 18 | export default class VInt extends VBase { 19 | private readonly validatedValue: number; 20 | 21 | // this will throw if invalid value is given that don't match the validation spec 22 | static createOrThrow( 23 | validationSpec: IntValidationSpec, 24 | value: number, 25 | varName?: string 26 | ): VInt | never { 27 | return new VInt(validationSpec, value, varName); 28 | } 29 | 30 | // this will throw if invalid value is given that don't match the validation spec 31 | static tryCreate( 32 | validationSpec: IntValidationSpec, 33 | value: number, 34 | varName?: string 35 | ): VInt | never { 36 | return new VInt(validationSpec, value, varName); 37 | } 38 | 39 | static create( 40 | validationSpec: IntValidationSpec, 41 | value: number 42 | ): VInt | null { 43 | try { 44 | return new VInt(validationSpec, value); 45 | } catch { 46 | return null; 47 | } 48 | } 49 | 50 | static createOrError( 51 | validationSpec: IntValidationSpec, 52 | value: number, 53 | varName?: string 54 | ): [VInt, null] | [null, Error] { 55 | try { 56 | return [new VInt(validationSpec, value, varName), null]; 57 | } catch (error) { 58 | return [null, error as Error]; 59 | } 60 | } 61 | 62 | protected constructor( 63 | protected readonly validationSpec: IntValidationSpec, 64 | value: number, 65 | varName?: string 66 | ) { 67 | super(); 68 | const validationSpecAsStr = this.validationSpec as unknown as string; 69 | VBase.validateByCustomValidator(validationSpecAsStr, value, varName); 70 | VBase.validateNumericRange( 71 | validationSpecAsStr, 72 | value, 73 | parseInt, 74 | [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], 75 | varName 76 | ); 77 | VBase.validatePositiveValue(validationSpecAsStr, value, varName); 78 | VBase.validateNegativeValue(validationSpecAsStr, value, varName); 79 | VInt.validateDivisibleByValue(validationSpecAsStr, value, varName); 80 | VInt.validateInteger(value, varName); 81 | this.validatedValue = value; 82 | } 83 | 84 | get value(): number { 85 | return this.validatedValue; 86 | } 87 | 88 | private static validateDivisibleByValue( 89 | validationSpec: string, 90 | value: number, 91 | varName?: string 92 | ): void | never { 93 | if (validationSpec.includes(',')) { 94 | const [, , divisibleByValueStr] = validationSpec.split(','); 95 | const divisibleByValue = parseInt(divisibleByValueStr, 10); 96 | 97 | if (divisibleByValueStr && (isNaN(divisibleByValue) || divisibleByValue === 0)) { 98 | throw new ValidationSpecError( 99 | 'Invalid divisibleByValue specified in validation spec: ' + validationSpec 100 | ); 101 | } 102 | 103 | if (divisibleByValueStr && value % divisibleByValue !== 0) { 104 | throw new ValidationError( 105 | varName 106 | ? `Value in '${varName}' is not divisible by: ${divisibleByValue}` 107 | : `Value is not divisible by: ${divisibleByValue}` 108 | ); 109 | } 110 | } 111 | } 112 | 113 | private static validateInteger(value: number, varName?: string): void | never { 114 | if (!Number.isInteger(value)) { 115 | throw new ValidationError( 116 | varName ? `Value in '${varName}' is not an integer` : 'Value is not an integer' 117 | ); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/semfloat/SemVFloat.spec.ts: -------------------------------------------------------------------------------- 1 | import SemVFloat from './SemVFloat'; 2 | 3 | type LoanInterest = SemVFloat<'loanInterest', '0,100'>; 4 | 5 | describe('SemVFloat', () => { 6 | describe('tryCreate', () => { 7 | it('should create a semantic validated integer successfully', () => { 8 | const loanInterest: LoanInterest = SemVFloat.tryCreate('loanInterest', '0,100', 4.99); 9 | expect(loanInterest.value).toBeCloseTo(4.99, 2); 10 | }); 11 | }); 12 | describe('crate', () => { 13 | it('should create a semantic validated integer successfully', () => { 14 | const loanInterest: LoanInterest | null = SemVFloat.create('loanInterest', '0,100', 4.99); 15 | expect(loanInterest?.value).toBeCloseTo(4.99, 2); 16 | }); 17 | }); 18 | describe('crateOrError', () => { 19 | it('should create a semantic validated integer successfully', () => { 20 | const [loanInterest, error] = SemVFloat.createOrError<'loanInterest', '0,100'>( 21 | 'loanInterest', 22 | '0,100', 23 | 4.99 24 | ); 25 | expect(loanInterest?.value).toBeCloseTo(4.99, 2); 26 | expect(error).toBe(null); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/semfloat/SemVFloat.ts: -------------------------------------------------------------------------------- 1 | import SemType, { SemName } from '../semtype/SemType'; 2 | import VFloat, { FloatValidationSpec } from '../float/VFloat'; 3 | 4 | export default class SemVFloat { 5 | protected readonly semanticValue: SemType, Name>; 6 | 7 | // this will throw if invalid value is given that don't match the validation spec 8 | static tryCreate( 9 | semanticName: SemName, 10 | validationSpec: FloatValidationSpec, 11 | value: number, 12 | varName?: string 13 | ): SemVFloat | never { 14 | return new SemVFloat(semanticName, VFloat.tryCreate(validationSpec, value, varName)); 15 | } 16 | 17 | static create( 18 | semanticName: SemName, 19 | validationSpec: FloatValidationSpec, 20 | value: number 21 | ): SemVFloat | null { 22 | try { 23 | return new SemVFloat(semanticName, VFloat.tryCreate(validationSpec, value)); 24 | } catch { 25 | return null; 26 | } 27 | } 28 | 29 | static createOrError( 30 | semanticName: SemName, 31 | validationSpec: FloatValidationSpec, 32 | value: number, 33 | varName?: string 34 | ): [SemVFloat, null] | [null, Error] { 35 | try { 36 | return [new SemVFloat(semanticName, VFloat.tryCreate(validationSpec, value, varName)), null]; 37 | } catch (error) { 38 | return [null, error as Error]; 39 | } 40 | } 41 | 42 | constructor(semanticName: SemName, value: VFloat) { 43 | this.semanticValue = new SemType(semanticName, value); 44 | } 45 | 46 | get value(): number { 47 | return this.semanticValue.value.value; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/seminteger/SemVInt.spec.ts: -------------------------------------------------------------------------------- 1 | import SemVInt from './SemVInt'; 2 | 3 | type HttpPort = SemVInt<'httpPort', '1,65535'>; 4 | 5 | describe('SemVInt', () => { 6 | describe('tryCreate', () => { 7 | it('should create a semantic validated integer successfully', () => { 8 | const httpPort: HttpPort = SemVInt.tryCreate('httpPort', '1,65535', 8080); 9 | expect(httpPort.value).toBe(8080); 10 | }); 11 | }); 12 | describe('crate', () => { 13 | it('should create a semantic validated integer successfully', () => { 14 | const httpPort: HttpPort | null = SemVInt.create('httpPort', '1,65535', 8080); 15 | expect(httpPort?.value).toBe(8080); 16 | }); 17 | }); 18 | describe('crateOrError', () => { 19 | it('should create a semantic validated integer successfully', () => { 20 | const [httpPort, error] = SemVInt.createOrError<'httpPort', '1,65535'>('httpPort', '1,65535', 8080); 21 | expect(httpPort?.value).toBe(8080); 22 | expect(error).toBe(null); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/seminteger/SemVInt.ts: -------------------------------------------------------------------------------- 1 | import SemType, { SemName } from '../semtype/SemType'; 2 | import VInt, { IntValidationSpec } from '../integer/VInt'; 3 | 4 | export default class SemVInt { 5 | protected readonly semanticValue: SemType, Name>; 6 | 7 | // this will throw if invalid value is given that don't match the validation spec 8 | static tryCreate( 9 | semanticName: SemName, 10 | validationSpec: IntValidationSpec, 11 | value: number, 12 | varName?: string 13 | ): SemVInt | never { 14 | return new SemVInt(semanticName, VInt.tryCreate(validationSpec, value, varName)); 15 | } 16 | 17 | static create( 18 | semanticName: SemName, 19 | validationSpec: IntValidationSpec, 20 | value: number 21 | ): SemVInt | null { 22 | try { 23 | return new SemVInt(semanticName, VInt.tryCreate(validationSpec, value)); 24 | } catch { 25 | return null; 26 | } 27 | } 28 | 29 | static createOrError( 30 | semanticName: SemName, 31 | validationSpec: IntValidationSpec, 32 | value: number, 33 | varName?: string 34 | ): [SemVInt, null] | [null, Error] { 35 | try { 36 | return [new SemVInt(semanticName, VInt.tryCreate(validationSpec, value, varName)), null]; 37 | } catch (error) { 38 | return [null, error as Error]; 39 | } 40 | } 41 | 42 | constructor(semanticName: SemName, value: VInt) { 43 | this.semanticValue = new SemType(semanticName, value); 44 | } 45 | 46 | get value(): number { 47 | return this.semanticValue.value.value; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/semstring/SemVString.spec.ts: -------------------------------------------------------------------------------- 1 | import SemVString from './SemVString'; 2 | 3 | type LoginUrl = SemVString<'loginUrl', '1,8192,url'>; 4 | 5 | describe('SemVInt', () => { 6 | describe('tryCreate', () => { 7 | it('should create a semantic validated integer successfully', () => { 8 | const loginUrl: LoginUrl = SemVString.tryCreate('loginUrl', '1,8192,url', 'https://server.com'); 9 | expect(loginUrl.value).toBe('https://server.com'); 10 | }); 11 | }); 12 | describe('crate', () => { 13 | it('should create a semantic validated integer successfully', () => { 14 | const loginUrl: LoginUrl | null = SemVString.create('loginUrl', '1,8192,url', 'https://server.com'); 15 | expect(loginUrl?.value).toBe('https://server.com'); 16 | }); 17 | }); 18 | describe('crateOrError', () => { 19 | it('should create a semantic validated integer successfully', () => { 20 | const [loginUrl, error] = SemVString.createOrError<'loginUrl', '1,8192,url'>( 21 | 'loginUrl', 22 | '1,8192,url', 23 | 'https://server.com' 24 | ); 25 | expect(loginUrl?.value).toBe('https://server.com'); 26 | expect(error).toBe(null); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/semstring/SemVString.ts: -------------------------------------------------------------------------------- 1 | import SemType, { SemName } from '../semtype/SemType'; 2 | import VString, { 3 | FiveStringValidationSpecs, 4 | FourStringValidationSpecs, 5 | StringValidationSpecs, 6 | StringValidationSpecWithLength, 7 | ThreeStringValidationSpecs, 8 | TwoStringValidationSpecs, 9 | } from '../string/VString'; 10 | 11 | export default class SemVString { 12 | protected readonly semanticValue: SemType, Name>; 13 | 14 | // this will throw if invalid value is given that don't match the validation spec 15 | static tryCreate( 16 | semanticName: SemName, 17 | validationSpec: StringValidationSpecWithLength, 18 | value: string, 19 | varName?: string 20 | ): SemVString | never; 21 | 22 | // this will throw if invalid value is given that don't match the validation spec 23 | static tryCreate( 24 | semanticName: SemName, 25 | validationSpec: TwoStringValidationSpecs, 26 | value: string, 27 | varName?: string 28 | ): SemVString | never; 29 | 30 | // this will throw if invalid value is given that don't match the validation spec 31 | static tryCreate( 32 | semanticName: SemName, 33 | validationSpec: ThreeStringValidationSpecs, 34 | value: string, 35 | varName?: string 36 | ): SemVString | never; 37 | 38 | // this will throw if invalid value is given that don't match the validation spec 39 | static tryCreate( 40 | semanticName: SemName, 41 | validationSpec: FourStringValidationSpecs, 42 | value: string, 43 | varName?: string 44 | ): SemVString | never; 45 | 46 | static tryCreate( 47 | semanticName: SemName, 48 | validationSpec: VSpec extends string 49 | ? StringValidationSpecWithLength 50 | : StringValidationSpecs, 51 | value: string, 52 | varName?: string 53 | ): SemVString | never { 54 | return new SemVString(semanticName, new VString(validationSpec, value, varName)); 55 | } 56 | 57 | static create( 58 | semanticName: SemName, 59 | validationSpec: StringValidationSpecWithLength, 60 | value: string, 61 | varName?: string 62 | ): SemVString | null; 63 | 64 | static create( 65 | semanticName: SemName, 66 | validationSpec: TwoStringValidationSpecs, 67 | value: string, 68 | varName?: string 69 | ): SemVString | null; 70 | 71 | static create( 72 | semanticName: SemName, 73 | validationSpec: ThreeStringValidationSpecs, 74 | value: string, 75 | varName?: string 76 | ): SemVString | null; 77 | 78 | static create( 79 | semanticName: SemName, 80 | validationSpec: FourStringValidationSpecs, 81 | value: string, 82 | varName?: string 83 | ): SemVString | null; 84 | 85 | static create( 86 | semanticName: SemName, 87 | validationSpec: FiveStringValidationSpecs, 88 | value: string, 89 | varName?: string 90 | ): SemVString | null; 91 | 92 | static create( 93 | semanticName: SemName, 94 | validationSpec: VSpec extends string 95 | ? StringValidationSpecWithLength 96 | : StringValidationSpecs, 97 | value: string, 98 | varName?: string 99 | ): SemVString | null { 100 | try { 101 | return new SemVString(semanticName, new VString(validationSpec, value, varName)); 102 | } catch { 103 | return null; 104 | } 105 | } 106 | 107 | static createOrError( 108 | semanticName: SemName, 109 | validationSpec: StringValidationSpecWithLength, 110 | value: string, 111 | varName?: string 112 | ): [SemVString, null] | [null, Error]; 113 | 114 | static createOrError( 115 | semanticName: SemName, 116 | validationSpec: TwoStringValidationSpecs, 117 | value: string, 118 | varName?: string 119 | ): [SemVString, null] | [null, Error]; 120 | 121 | static createOrError( 122 | semanticName: SemName, 123 | validationSpec: ThreeStringValidationSpecs, 124 | value: string, 125 | varName?: string 126 | ): [SemVString, null] | [null, Error]; 127 | 128 | static createOrError( 129 | semanticName: SemName, 130 | validationSpec: FourStringValidationSpecs, 131 | value: string, 132 | varName?: string 133 | ): [SemVString, null] | [null, Error]; 134 | 135 | static createOrError( 136 | semanticName: SemName, 137 | validationSpec: FiveStringValidationSpecs, 138 | value: string, 139 | varName?: string 140 | ): [SemVString, null] | [null, Error]; 141 | 142 | static createOrError( 143 | semanticName: SemName, 144 | validationSpec: VSpec extends string 145 | ? StringValidationSpecWithLength 146 | : StringValidationSpecs, 147 | 148 | value: string, 149 | varName?: string 150 | ): [SemVString, null] | [null, Error] { 151 | try { 152 | return [new SemVString(semanticName, new VString(validationSpec, value, varName)), null]; 153 | } catch (error) { 154 | return [null, error as Error]; 155 | } 156 | } 157 | 158 | constructor(semanticName: SemName, value: VString) { 159 | this.semanticValue = new SemType(semanticName, value); 160 | } 161 | 162 | get value(): string { 163 | return this.semanticValue.value.value; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/semtype/SemType.spec.ts: -------------------------------------------------------------------------------- 1 | import SemType from './SemType'; 2 | 3 | function func(val: SemType) { 4 | // NOOP 5 | } 6 | 7 | type IsInternalCall = SemType; 8 | const test = new SemType({ isInternalCall: true }); 9 | func(test); 10 | 11 | describe('SemVar', () => { 12 | describe('constructor', () => { 13 | it('should create an SemVar object successfully', () => { 14 | const isInternalCall: IsInternalCall = new SemType({ isInternalCall: true }); 15 | expect(isInternalCall).toBeInstanceOf(SemType); 16 | }); 17 | }); 18 | describe('value', () => { 19 | it('should return the value encapsulated by SemVar object successfully', () => { 20 | const isInternalCall: IsInternalCall = new SemType('isInternalCall', true); 21 | expect(isInternalCall.value).toBe(true); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/semtype/SemType.ts: -------------------------------------------------------------------------------- 1 | export type SemName = N extends `${infer Name}` ? `${Name}` : never; 2 | 3 | export default class SemType { 4 | private readonly semanticValue: Type; 5 | protected readonly name: Name; 6 | 7 | constructor(semanticName: SemName, value: Type); 8 | constructor(semVar: { [K in SemName]: Type }); 9 | constructor(semNameOrVar: { [K in SemName]: Type } | SemName, value?: Type) { 10 | if (typeof semNameOrVar === 'object' || value === undefined) { 11 | this.semanticValue = Object.values(semNameOrVar)[0] as Type; 12 | this.name = Object.keys(semNameOrVar)[0] as Name; 13 | } else { 14 | this.semanticValue = value; 15 | this.name = semNameOrVar; 16 | } 17 | } 18 | 19 | get value(): Type { 20 | return this.semanticValue; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/semvar/SemVar.spec.ts: -------------------------------------------------------------------------------- 1 | import SemVar from './SemVar'; 2 | 3 | function func(val: SemVar) { 4 | // NOOP 5 | } 6 | 7 | type IsInternalCall = SemVar; 8 | const test = new SemVar({ isInternalCall: true }); 9 | func(test); 10 | 11 | describe('SemVar', () => { 12 | describe('constructor', () => { 13 | it('should create an SemVar object successfully', () => { 14 | const isInternalCall: IsInternalCall = new SemVar({ isInternalCall: true }); 15 | expect(isInternalCall).toBeInstanceOf(SemVar); 16 | }); 17 | }); 18 | describe('value', () => { 19 | it('should return the value encapsulated by SemVar object successfully', () => { 20 | const isInternalCall: IsInternalCall = new SemVar('isInternalCall', true); 21 | expect(isInternalCall.value).toBe(true); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/semvar/SemVar.ts: -------------------------------------------------------------------------------- 1 | export type SemName = N extends `${infer Name}` ? `${Name}` : never; 2 | 3 | export default class SemVar { 4 | private readonly semanticValue: Type; 5 | protected readonly name: Name; 6 | 7 | constructor(semanticName: SemName, value: Type); 8 | constructor(semVar: { [K in SemName]: Type }); 9 | constructor(semNameOrVar: { [K in SemName]: Type } | SemName, value?: Type) { 10 | if (typeof semNameOrVar === 'object' || value === undefined) { 11 | this.semanticValue = Object.values(semNameOrVar)[0] as Type; 12 | this.name = Object.keys(semNameOrVar)[0] as Name; 13 | } else { 14 | this.semanticValue = value; 15 | this.name = semNameOrVar; 16 | } 17 | } 18 | 19 | get value(): Type { 20 | return this.semanticValue; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/string/KnownLengthStringValidatorNames.ts: -------------------------------------------------------------------------------- 1 | export type KnownLengthStringValidatorNames = 2 | | 'boolean' 3 | | 'bic' 4 | | 'btcAddress' 5 | | 'creditCard' 6 | | 'ean' 7 | | 'email' 8 | | 'ethereumAddress' 9 | | 'hsl' 10 | | 'hexColor' 11 | | 'isin' 12 | | 'iban' 13 | | 'ipv4' 14 | | 'ipv6' 15 | | 'iso31661Alpha2' 16 | | 'iso31661Alpha3' 17 | | 'iso8601' 18 | | 'isrc' 19 | | 'issn' 20 | | 'jwt' 21 | | 'latLong' 22 | | 'macAddress' 23 | | 'mimeType' 24 | | 'port' 25 | | 'rgbColor' 26 | | 'semVer' 27 | | 'uuid' 28 | | 'postalCode' 29 | | 'creditCardExpiration' 30 | | 'cvc' 31 | | 'mobileNumber'; 32 | -------------------------------------------------------------------------------- /src/string/ParameterizedStringValidatorNames.ts: -------------------------------------------------------------------------------- 1 | export type ParameterizedStringValidatorNames = 2 | | 'includes' 3 | | 'match' 4 | | 'isOneOf' 5 | | 'isNoneOf' 6 | | 'startsWith' 7 | | 'endsWith' 8 | | 'numericRange'; 9 | -------------------------------------------------------------------------------- /src/string/UnknownLengthStringValidatorNames.ts: -------------------------------------------------------------------------------- 1 | export type UnknownLengthStringValidatorNames = 2 | | 'alpha' 3 | | 'alphanumeric' 4 | | 'ascii' 5 | | 'base32' 6 | | 'base58' 7 | | 'base64' 8 | | 'dataUri' 9 | | 'decimal' 10 | | 'fqdn' 11 | | 'md4' 12 | | 'md5' 13 | | 'sha1' 14 | | 'sha256' 15 | | 'sha384' 16 | | 'sha512' 17 | | 'crc32' 18 | | 'crc32b' 19 | | 'hex' 20 | | 'ipv4Range' 21 | | 'ipv6Range' 22 | | 'json' 23 | | 'lowercase' 24 | | 'magnetUri' 25 | | 'mongoId' 26 | | 'numeric' 27 | | 'octal' 28 | | 'uppercase' 29 | | 'strongPassword' 30 | | 'url'; 31 | -------------------------------------------------------------------------------- /src/string/VString.spec.ts: -------------------------------------------------------------------------------- 1 | // noinspection MagicNumberJS 2 | 3 | import VString from './VString'; 4 | import { VSpecOf } from '../base/VSpecOf'; 5 | 6 | VString.registerCustomValidator('is5', (value) => value === '5'); 7 | 8 | describe('VString', () => { 9 | describe('createOrThrow', () => { 10 | it('should create a VString object successfully when value matches validation spec containing min length and max length validators', () => { 11 | const string: VString<'0,10'> = VString.createOrThrow('0,10', 'abc'); 12 | expect(string.value).toEqual('abc'); 13 | }); 14 | it('should create a VString object successfully when value matches validation spec containing min length, max length and unknown length validators ', () => { 15 | const string: VString<'0,10,alpha'> = VString.createOrThrow('0,10,alpha', 'abc'); 16 | expect(string.value).toEqual('abc'); 17 | }); 18 | it('should create a VString object successfully when value matches validation spec containing known length validator', () => { 19 | const string: VString<'ipv4'> = VString.createOrThrow('ipv4', '127.0.0.1'); 20 | expect(string.value).toEqual('127.0.0.1'); 21 | }); 22 | it('should create a VString object successfully when value matches validation spec containing parameterized validator', () => { 23 | const string: VString<'0,10,includes,abc'> = VString.createOrThrow('0,10,includes,abc', ' abc'); 24 | expect(string.value).toEqual(' abc'); 25 | }); 26 | it('should create a VString object successfully when value matches multiple validation specs', () => { 27 | type Url = VString<['0,255,lowercase', 'url', 'startsWith,https', 'endsWith,.html']>; 28 | const urlSpec: VSpecOf = ['0,255,lowercase', 'url', 'startsWith,https', 'endsWith,.html']; 29 | 30 | const string: Url = VString.createOrThrow(urlSpec, 'https://apiserver.domain.com:8080/index.html'); 31 | 32 | expect(string.value).toEqual('https://apiserver.domain.com:8080/index.html'); 33 | }); 34 | it('should throw ValidationError when value has multiple validation specs and value does not match one of them', () => { 35 | expect(() => { 36 | // noinspection HttpUrlsUsage 37 | VString.createOrThrow<['0,255,lowercase', 'url', 'startsWith,https', 'endsWith,.html']>( 38 | ['0,255,lowercase', 'url', 'startsWith,https', 'endsWith,.html'], 39 | 'http://apiserver.domain.com:8080/index.html' 40 | ); 41 | }).toThrow('Value does not match validator: startsWith'); 42 | }); 43 | it('should create a VString object successfully when validation spec contains whitespace', () => { 44 | const string: VString<' 0 , 10 '> = VString.createOrThrow(' 0 , 10 ', 'abc'); 45 | expect(string.value).toEqual('abc'); 46 | }); 47 | it('should create a VString object successfully when custom validation function call evaluates to true for the value', () => { 48 | const string: VString<'custom:is5'> = VString.createOrThrow('custom:is5', '5'); 49 | expect(string.value).toEqual('5'); 50 | }); 51 | it('should throw ValidationError when value length is greater than maxLength specified in validation spec', () => { 52 | expect(() => { 53 | VString.createOrThrow<'0,5'>('0,5', '123456'); 54 | }).toThrow('Value is longer than allowed maximum length: 5'); 55 | }); 56 | it('should throw ValidationError when value length is less than minLength specified in validation spec', () => { 57 | expect(() => { 58 | VString.createOrThrow<'5,10'>('5,10', 'abc'); 59 | }).toThrow('Value is shorter than required minimum length: 5'); 60 | }); 61 | it('should throw ValidationError when value does not match unknown length validator', () => { 62 | expect(() => { 63 | VString.createOrThrow<'0,10,alpha'>(`0,10,alpha`, 'abc123'); 64 | }).toThrow('Value does not match validator: alpha'); 65 | }); 66 | it('should throw ValidationError when value does not match known length validator', () => { 67 | expect(() => { 68 | VString.createOrThrow<'boolean'>('boolean', 'abc123'); 69 | }).toThrow('Value does not match validator: boolean'); 70 | }); 71 | it('should throw ValidationError when value does not match parameterized validator', () => { 72 | expect(() => { 73 | VString.createOrThrow<'0,10,match,ab+'>('0,10,match,ab+', 'xyz'); 74 | }).toThrow('Value does not match validator: match'); 75 | }); 76 | it('should throw ValidationSpecError when minLength in validation spec is invalid', () => { 77 | expect(() => { 78 | VString.createOrThrow<'a,10'>('a,10', 'abc'); 79 | }).toThrow('Invalid minLength specified in validation spec'); 80 | }); 81 | it('should throw ValidationSpecError when maxLength in validation spec is invalid', () => { 82 | expect(() => { 83 | VString.createOrThrow<'0,a'>('0,a', 'abc'); 84 | }).toThrow('Invalid maxLength specified in validation spec'); 85 | }); 86 | it('should use 0 as minLength when minLength in validation spec is missing', () => { 87 | const string = VString.createOrThrow<',10'>(',10', ''); 88 | expect(string.value).toEqual(''); 89 | }); 90 | it('should throw ValidationSpecError when maxLength in validation spec is missing', () => { 91 | expect(() => { 92 | VString.createOrThrow<'10,'>('10,', 'abc'); 93 | }).toThrow('Invalid maxLength specified in validation spec'); 94 | }); 95 | it('should return VString object successfully when isOneOf validator is used', () => { 96 | const string = VString.createOrThrow<'0,10,isOneOf,["abc","xyz"]'>('0,10,isOneOf,["abc","xyz"]', 'abc'); 97 | expect(string.value).toEqual('abc'); 98 | }); 99 | it('should throw ValidationSpecError when parameter is not valid JSON string array', () => { 100 | expect(() => { 101 | VString.createOrThrow<'0,10,isOneOf,[abc,"xyz"]'>('0,10,isOneOf,[abc,"xyz"]', 'abc'); 102 | }).toThrow('Validator parameter must a JSON array of strings, for example ["abc", "xyz"]'); 103 | expect(() => { 104 | VString.createOrThrow<'0,10,isOneOf,null'>('0,10,isOneOf,null', 'abc'); 105 | }).toThrow('Validator parameter must a JSON array of strings, for example ["abc", "xyz"]'); 106 | expect(() => { 107 | VString.createOrThrow<'0,10,isOneOf,'>('0,10,isOneOf,', 'abc'); 108 | }).toThrow('Validator parameter must a JSON array of strings, for example ["abc", "xyz"]'); 109 | }); 110 | it('should throw ValidationError with varName when value does not match minLength', () => { 111 | expect(() => { 112 | VString.createOrThrow<'5,10,alpha'>('5,10,alpha', 'abc1', 'varName'); 113 | }).toThrow("Value in 'varName' is shorter than required minimum length: 5"); 114 | }); 115 | it('should throw ValidationError with varName when value does not match maxLength', () => { 116 | expect(() => { 117 | VString.createOrThrow<'1,2,alpha'>('1,2,alpha', 'abc', 'varName'); 118 | }).toThrow("Value in 'varName' is longer than allowed maximum length: 2"); 119 | }); 120 | it('should throw ValidationError with varName when value does not match validator', () => { 121 | expect(() => { 122 | VString.createOrThrow<'0,10,alpha'>(`0,10,alpha`, 'abc123', 'varName'); 123 | }).toThrow("Value in 'varName' does not match validator: alpha"); 124 | }); 125 | it('should return a VString object when value is successfully validated as credit card expiration', () => { 126 | const string = VString.createOrThrow<'creditCardExpiration'>(`creditCardExpiration`, '11/23'); 127 | expect(string.value).toEqual('11/23'); 128 | }); 129 | it('should return a VString object when value is successfully validated as CVC', () => { 130 | const string = VString.createOrThrow<'cvc'>(`cvc`, '999'); 131 | expect(string.value).toEqual('999'); 132 | }); 133 | it('should return a VString object when value is successfully validated with isNoneOf validator', () => { 134 | const string = VString.createOrThrow<'0,10,isNoneOf,[123,334]'>('0,10,isNoneOf,[123,334]', '999'); 135 | expect(string.value).toEqual('999'); 136 | }); 137 | }); 138 | describe('create', () => { 139 | it('should create a VString object successfully when value matches validation spec', () => { 140 | const possibleString: VString<'0,10'> | null = VString.create('0,10', 'abc'); 141 | expect(possibleString?.value).toEqual('abc'); 142 | }); 143 | it('should create a VString object successfully when value matches multiple validation specs', () => { 144 | const possibleString: VString<['0,255,lowercase', 'url', 'startsWith,https', 'endsWith,.html']> | null = 145 | VString.create( 146 | ['0,255,lowercase', 'url', 'startsWith,https', 'endsWith,.html'], 147 | 'https://apiserver.domain.com:8080/index.html' 148 | ); 149 | 150 | expect(possibleString?.value).toEqual('https://apiserver.domain.com:8080/index.html'); 151 | }); 152 | it('should return null when value does not match validation spec', () => { 153 | const possibleString: VString<'0,5'> | null = VString.create('0,5', 'abc1234'); 154 | expect(possibleString).toBeNull(); 155 | }); 156 | }); 157 | describe('createOrError', () => { 158 | it('should create a VString object successfully when value matches validation spec', () => { 159 | const [string, error]: [VString<'0,10'>, null] | [null, Error] = VString.createOrError('0,10', 'abc'); 160 | expect(string?.value).toEqual('abc'); 161 | expect(error).toBeNull(); 162 | }); 163 | it('should return null when value does not match validation spec', () => { 164 | const [string, error]: [VString<'5,10'>, null] | [null, Error] = VString.createOrError('5,10', 'abc'); 165 | expect(string).toBeNull(); 166 | expect(error).toBeInstanceOf(Error); 167 | }); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /src/string/VString.ts: -------------------------------------------------------------------------------- 1 | import { UnknownLengthStringValidatorNames } from './UnknownLengthStringValidatorNames'; 2 | import { KnownLengthStringValidatorNames } from './KnownLengthStringValidatorNames'; 3 | import { ParameterizedStringValidatorNames } from './ParameterizedStringValidatorNames'; 4 | import { stringValidators } from './stringValidators'; 5 | import VBase from '../base/VBase'; 6 | import ValidationError from '../error/ValidationError'; 7 | 8 | export type StringValidationSpecWithLength = 9 | ValidationSpec extends `${infer MinLength},${infer MaxLength},${infer StringValidatorName},${infer Parameter}` 10 | ? StringValidatorName extends ParameterizedStringValidatorNames 11 | ? `${MinLength},${MaxLength},${StringValidatorName},${Parameter}` 12 | : { errorMessage: `Invalid string validator name: ${StringValidatorName}` } 13 | : ValidationSpec extends `${infer MinLength},${infer MaxLength},${infer StringValidatorName}` 14 | ? StringValidatorName extends UnknownLengthStringValidatorNames 15 | ? `${MinLength},${MaxLength},${StringValidatorName}` 16 | : { errorMessage: `Invalid string validator name: ${StringValidatorName}` } 17 | : ValidationSpec extends `${infer MinLength},${infer MaxLength}` 18 | ? `${MinLength},${MaxLength}` 19 | : ValidationSpec extends `custom:${infer CustomValidatorName}` 20 | ? `custom:${CustomValidatorName}` 21 | : ValidationSpec extends `${infer KnownLengthStringValidatorName}` 22 | ? KnownLengthStringValidatorName extends KnownLengthStringValidatorNames 23 | ? `${KnownLengthStringValidatorName}` 24 | : { errorMessage: `Invalid string validator name: ${KnownLengthStringValidatorName}` } 25 | : never; 26 | 27 | export type StringValidationSpec = 28 | ValidationSpec extends `${infer StringValidatorName},${infer Parameter}` 29 | ? StringValidatorName extends ParameterizedStringValidatorNames 30 | ? `${StringValidatorName},${Parameter}` 31 | : { errorMessage: `Invalid string validator name: ${StringValidatorName}` } 32 | : ValidationSpec extends `${infer StringValidatorName}` 33 | ? StringValidatorName extends UnknownLengthStringValidatorNames 34 | ? `${StringValidatorName}` 35 | : { errorMessage: `Invalid string validator name: ${StringValidatorName}` } 36 | : ValidationSpec extends `custom:${infer CustomValidatorName}` 37 | ? `custom:${CustomValidatorName}` 38 | : ValidationSpec extends `${infer KnownLengthStringValidatorName}` 39 | ? KnownLengthStringValidatorName extends KnownLengthStringValidatorNames 40 | ? `${KnownLengthStringValidatorName}` 41 | : { errorMessage: `Invalid string validator name: ${KnownLengthStringValidatorName}` } 42 | : never; 43 | 44 | export type TwoStringValidationSpecs = [ 45 | StringValidationSpecWithLength, 46 | StringValidationSpec 47 | ]; 48 | 49 | export type ThreeStringValidationSpecs = [ 50 | ...TwoStringValidationSpecs, 51 | StringValidationSpec 52 | ]; 53 | 54 | export type FourStringValidationSpecs = [ 55 | ...ThreeStringValidationSpecs, 56 | StringValidationSpec 57 | ]; 58 | 59 | export type FiveStringValidationSpecs = [ 60 | ...FourStringValidationSpecs, 61 | StringValidationSpec 62 | ]; 63 | 64 | export type StringValidationSpecs = 65 | | FiveStringValidationSpecs 66 | | FourStringValidationSpecs 67 | | ThreeStringValidationSpecs 68 | | TwoStringValidationSpecs; 69 | 70 | export default class VString extends VBase { 71 | private readonly validatedValue: string; 72 | 73 | // this will throw if invalid value is given that don't match the validation spec 74 | static createOrThrow( 75 | validationSpec: StringValidationSpecWithLength, 76 | value: string, 77 | varName?: string 78 | ): VString | never; 79 | 80 | // this will throw if invalid value is given that don't match the validation spec 81 | static createOrThrow( 82 | validationSpec: TwoStringValidationSpecs, 83 | value: string, 84 | varName?: string 85 | ): VString | never; 86 | 87 | // this will throw if invalid value is given that don't match the validation spec 88 | static createOrThrow( 89 | validationSpec: ThreeStringValidationSpecs, 90 | value: string, 91 | varName?: string 92 | ): VString | never; 93 | 94 | // this will throw if invalid value is given that don't match the validation spec 95 | static createOrThrow( 96 | validationSpec: FourStringValidationSpecs, 97 | value: string, 98 | varName?: string 99 | ): VString | never; 100 | 101 | // this will throw if invalid value is given that don't match the validation spec 102 | static createOrThrow( 103 | validationSpec: FiveStringValidationSpecs, 104 | value: string, 105 | varName?: string 106 | ): VString | never; 107 | 108 | // this will throw if invalid value is given that don't match the validation spec 109 | static createOrThrow( 110 | validationSpec: VSpec extends string 111 | ? StringValidationSpecWithLength 112 | : StringValidationSpecs, 113 | value: string, 114 | varName?: string 115 | ): VString | never { 116 | return new VString(validationSpec, value, varName); 117 | } 118 | 119 | // this will throw if invalid value is given that don't match the validation spec 120 | static tryCreate( 121 | validationSpec: StringValidationSpecWithLength, 122 | value: string, 123 | varName?: string 124 | ): VString | never; 125 | 126 | // this will throw if invalid value is given that don't match the validation spec 127 | static tryCreate( 128 | validationSpec: TwoStringValidationSpecs, 129 | value: string, 130 | varName?: string 131 | ): VString | never; 132 | 133 | // this will throw if invalid value is given that don't match the validation spec 134 | static tryCreate( 135 | validationSpec: ThreeStringValidationSpecs, 136 | value: string, 137 | varName?: string 138 | ): VString | never; 139 | 140 | // this will throw if invalid value is given that don't match the validation spec 141 | static tryCreate( 142 | validationSpec: FourStringValidationSpecs, 143 | value: string, 144 | varName?: string 145 | ): VString | never; 146 | 147 | // this will throw if invalid value is given that don't match the validation spec 148 | static tryCreate( 149 | validationSpec: FiveStringValidationSpecs, 150 | value: string, 151 | varName?: string 152 | ): VString | never; 153 | 154 | // this will throw if invalid value is given that don't match the validation spec 155 | static tryCreate( 156 | validationSpec: VSpec extends string 157 | ? StringValidationSpecWithLength 158 | : StringValidationSpecs, 159 | value: string, 160 | varName?: string 161 | ): VString | never { 162 | return new VString(validationSpec, value, varName); 163 | } 164 | 165 | static create( 166 | validationSpec: StringValidationSpecWithLength, 167 | value: string, 168 | varName?: string 169 | ): VString | null; 170 | 171 | static create( 172 | validationSpec: TwoStringValidationSpecs, 173 | value: string, 174 | varName?: string 175 | ): VString | null; 176 | 177 | static create( 178 | validationSpec: ThreeStringValidationSpecs, 179 | value: string, 180 | varName?: string 181 | ): VString | null; 182 | 183 | static create( 184 | validationSpec: FourStringValidationSpecs, 185 | value: string, 186 | varName?: string 187 | ): VString | null; 188 | 189 | static create( 190 | validationSpec: FiveStringValidationSpecs, 191 | value: string, 192 | varName?: string 193 | ): VString | null; 194 | 195 | static create( 196 | validationSpec: VSpec extends string 197 | ? StringValidationSpecWithLength 198 | : StringValidationSpecs, 199 | value: string, 200 | varName?: string 201 | ): VString | null { 202 | try { 203 | return new VString(validationSpec, value, varName); 204 | } catch { 205 | return null; 206 | } 207 | } 208 | 209 | static createOrError( 210 | validationSpec: StringValidationSpecWithLength, 211 | value: string, 212 | varName?: string 213 | ): [VString, null] | [null, Error]; 214 | 215 | static createOrError( 216 | validationSpec: TwoStringValidationSpecs, 217 | value: string, 218 | varName?: string 219 | ): [VString, null] | [null, Error]; 220 | 221 | static createOrError( 222 | validationSpec: ThreeStringValidationSpecs, 223 | value: string, 224 | varName?: string 225 | ): [VString, null] | [null, Error]; 226 | 227 | static createOrError( 228 | validationSpec: FourStringValidationSpecs, 229 | value: string, 230 | varName?: string 231 | ): [VString, null] | [null, Error]; 232 | 233 | static createOrError( 234 | validationSpec: FiveStringValidationSpecs, 235 | value: string, 236 | varName?: string 237 | ): [VString, null] | [null, Error]; 238 | 239 | static createOrError( 240 | validationSpec: VSpec extends string 241 | ? StringValidationSpecWithLength 242 | : StringValidationSpecs, 243 | 244 | value: string, 245 | varName?: string 246 | ): [VString, null] | [null, Error] { 247 | try { 248 | return [new VString(validationSpec, value, varName), null]; 249 | } catch (error) { 250 | return [null, error as Error]; 251 | } 252 | } 253 | 254 | public constructor( 255 | protected readonly validationSpec: ValidationSpec extends string 256 | ? StringValidationSpecWithLength 257 | : StringValidationSpecs, 258 | value: string, 259 | varName?: string 260 | ) { 261 | super(); 262 | if (Array.isArray(validationSpec)) { 263 | VString.validateValueWithValidationSpec(validationSpec[0], value, varName, true); 264 | validationSpec.slice(1).forEach((vSpec) => { 265 | VString.validateValueWithValidationSpec(vSpec, value, varName, false); 266 | }); 267 | } else { 268 | VString.validateValueWithValidationSpec(validationSpec, value, varName, true); 269 | } 270 | 271 | this.validatedValue = value; 272 | } 273 | 274 | get value(): string { 275 | return this.validatedValue; 276 | } 277 | 278 | private static validateValueWithValidationSpec( 279 | validationSpec: string, 280 | value: string, 281 | varName: string | undefined, 282 | shouldValidateLength: boolean 283 | ) { 284 | if (shouldValidateLength) { 285 | VBase.validateLength(validationSpec, value, varName); 286 | } 287 | 288 | VBase.validateByCustomValidator(validationSpec, value, varName); 289 | VString.validateByValidator(validationSpec, value, shouldValidateLength, varName); 290 | } 291 | 292 | private static validateByValidator( 293 | validationSpec: string, 294 | value: string, 295 | shouldValidateLength: boolean, 296 | varName?: string 297 | ): void | number { 298 | let validatorName, parameter; 299 | 300 | if (validationSpec.startsWith('custom:')) { 301 | return; 302 | } 303 | 304 | if (validationSpec.includes(',')) { 305 | if (shouldValidateLength) { 306 | let restOfParameter; 307 | [, , validatorName, parameter, ...restOfParameter] = validationSpec.split(','); 308 | parameter = restOfParameter.length > 0 ? parameter + ',' + restOfParameter.join(',') : parameter; 309 | } else { 310 | [validatorName, parameter] = validationSpec.split(','); 311 | } 312 | } else { 313 | validatorName = validationSpec; 314 | } 315 | 316 | if (validatorName && !(stringValidators as any)[validatorName](value, parameter)) { 317 | throw new ValidationError( 318 | varName 319 | ? `Value in '${varName}' does not match validator: ${validatorName}` 320 | : `Value does not match validator: ${validatorName}` 321 | ); 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/string/parseJSONArrayParameter.ts: -------------------------------------------------------------------------------- 1 | import ValidationSpecError from '../error/ValidationSpecError'; 2 | 3 | export default function parseJSONArrayParameter(parameter: string | undefined): string[] { 4 | if (!parameter) { 5 | throw new ValidationSpecError( 6 | 'Validator parameter must a JSON array of strings, for example ["abc", "xyz"]' 7 | ); 8 | } 9 | 10 | try { 11 | const parameters = JSON.parse(parameter); 12 | if (!Array.isArray(parameters)) { 13 | throw new ValidationSpecError( 14 | 'Validator parameter must a JSON array of strings, for example ["abc", "xyz"]' 15 | ); 16 | } 17 | 18 | return parameters.map((param) => (typeof param === 'string' ? param : param.toString())); 19 | } catch { 20 | throw new ValidationSpecError( 21 | 'Validator parameter must a JSON array of strings, for example ["abc", "xyz"]' 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/string/stringValidators.spec.ts: -------------------------------------------------------------------------------- 1 | import validator from 'validator'; 2 | import { stringValidators } from './stringValidators'; 3 | import VString from './VString'; 4 | 5 | jest.mock('validator'); 6 | 7 | describe('stringValidators', () => { 8 | describe('boolean', () => { 9 | it('should validate boolean', () => { 10 | stringValidators.boolean('true'); 11 | expect(validator.isBoolean).toHaveBeenCalledTimes(1); 12 | expect(validator.isBoolean).toHaveBeenCalledWith('true'); 13 | }); 14 | }); 15 | describe('bic', () => { 16 | it('should validate BIC', () => { 17 | stringValidators.bic('bic'); 18 | expect(validator.isBIC).toHaveBeenCalledTimes(1); 19 | expect(validator.isBIC).toHaveBeenCalledWith('bic'); 20 | }); 21 | }); 22 | describe('btcAddress', () => { 23 | it('should validate BTC address', () => { 24 | stringValidators.btcAddress('btcAddress'); 25 | expect(validator.isBtcAddress).toHaveBeenCalledTimes(1); 26 | expect(validator.isBtcAddress).toHaveBeenCalledWith('btcAddress'); 27 | }); 28 | }); 29 | describe('creditCard', () => { 30 | it('should validate credit card number', () => { 31 | stringValidators.creditCard('creditCard'); 32 | expect(validator.isCreditCard).toHaveBeenCalledTimes(1); 33 | expect(validator.isCreditCard).toHaveBeenCalledWith('creditCard'); 34 | }); 35 | }); 36 | describe('ean', () => { 37 | it('should validate EAN code', () => { 38 | stringValidators.ean('ean'); 39 | expect(validator.isEAN).toHaveBeenCalledTimes(1); 40 | expect(validator.isEAN).toHaveBeenCalledWith('ean'); 41 | }); 42 | }); 43 | describe('email', () => { 44 | it('should validate email address', () => { 45 | stringValidators.email('email'); 46 | expect(validator.isEmail).toHaveBeenCalledTimes(1); 47 | expect(validator.isEmail).toHaveBeenCalledWith('email'); 48 | }); 49 | }); 50 | describe('ethereumAddress', () => { 51 | it('should validate Ethereum address', () => { 52 | stringValidators.ethereumAddress('ethereumAddress'); 53 | expect(validator.isEthereumAddress).toHaveBeenCalledTimes(1); 54 | expect(validator.isEthereumAddress).toHaveBeenCalledWith('ethereumAddress'); 55 | }); 56 | }); 57 | describe('hsl', () => { 58 | it('should validate HSL value', () => { 59 | stringValidators.hsl('hsl'); 60 | expect(validator.isHSL).toHaveBeenCalledTimes(1); 61 | expect(validator.isHSL).toHaveBeenCalledWith('hsl'); 62 | }); 63 | }); 64 | describe('hexColor', () => { 65 | it('should validate hex color value', () => { 66 | stringValidators.hexColor('color'); 67 | expect(validator.isHexColor).toHaveBeenCalledTimes(1); 68 | expect(validator.isHexColor).toHaveBeenCalledWith('color'); 69 | }); 70 | }); 71 | describe('isin', () => { 72 | it('should validate ISIN value', () => { 73 | stringValidators.isin('isin'); 74 | expect(validator.isISIN).toHaveBeenCalledTimes(1); 75 | expect(validator.isISIN).toHaveBeenCalledWith('isin'); 76 | }); 77 | }); 78 | describe('iban', () => { 79 | it('should validate IBAN value', () => { 80 | stringValidators.iban('iban'); 81 | expect(validator.isIBAN).toHaveBeenCalledTimes(1); 82 | expect(validator.isIBAN).toHaveBeenCalledWith('iban'); 83 | }); 84 | }); 85 | describe('ipv4', () => { 86 | it('should validate IP v4 address', () => { 87 | stringValidators.ipv4('ip'); 88 | expect(validator.isIP).toHaveBeenCalledTimes(1); 89 | expect(validator.isIP).toHaveBeenCalledWith('ip', 4); 90 | }); 91 | }); 92 | describe('ipv6', () => { 93 | it('should validate IP v6 address', () => { 94 | stringValidators.ipv6('ip'); 95 | expect(validator.isIP).toHaveBeenCalledWith('ip', 6); 96 | }); 97 | }); 98 | describe('iso31661Alpha2', () => { 99 | it('should validate ISO 31661 alpha 2 country code', () => { 100 | stringValidators.iso31661Alpha2('cc'); 101 | expect(validator.isISO31661Alpha2).toHaveBeenCalledTimes(1); 102 | expect(validator.isISO31661Alpha2).toHaveBeenCalledWith('cc'); 103 | }); 104 | }); 105 | describe('iso31661Alpha3', () => { 106 | it('should validate ISO 31661 alpha 3 country code', () => { 107 | stringValidators.iso31661Alpha3('cc'); 108 | expect(validator.isISO31661Alpha3).toHaveBeenCalledTimes(1); 109 | expect(validator.isISO31661Alpha3).toHaveBeenCalledWith('cc'); 110 | }); 111 | }); 112 | describe('iso8601', () => { 113 | it('should validate ISO 8601 timestamp', () => { 114 | stringValidators.iso8601('timestamp'); 115 | expect(validator.isISO8601).toHaveBeenCalledTimes(1); 116 | expect(validator.isISO8601).toHaveBeenCalledWith('timestamp'); 117 | }); 118 | }); 119 | describe('isrc', () => { 120 | it('should validate ISRC value', () => { 121 | stringValidators.isrc('isrc'); 122 | expect(validator.isISRC).toHaveBeenCalledTimes(1); 123 | expect(validator.isISRC).toHaveBeenCalledWith('isrc'); 124 | }); 125 | }); 126 | describe('issn', () => { 127 | it('should validate ISSN value', () => { 128 | stringValidators.issn('issn'); 129 | expect(validator.isISSN).toHaveBeenCalledTimes(1); 130 | expect(validator.isISSN).toHaveBeenCalledWith('issn'); 131 | }); 132 | }); 133 | describe('jwt', () => { 134 | it('should validate JWT', () => { 135 | stringValidators.jwt('jwt'); 136 | expect(validator.isJWT).toHaveBeenCalledTimes(1); 137 | expect(validator.isJWT).toHaveBeenCalledWith('jwt'); 138 | }); 139 | }); 140 | describe('latLong', () => { 141 | it('should validate latitude, longitude pair', () => { 142 | stringValidators.latLong('latLong'); 143 | expect(validator.isLatLong).toHaveBeenCalledTimes(1); 144 | expect(validator.isLatLong).toHaveBeenCalledWith('latLong'); 145 | }); 146 | }); 147 | describe('macAddress', () => { 148 | it('should validate MAC address', () => { 149 | stringValidators.macAddress('macAddress'); 150 | expect(validator.isMACAddress).toHaveBeenCalledTimes(1); 151 | expect(validator.isMACAddress).toHaveBeenCalledWith('macAddress'); 152 | }); 153 | }); 154 | describe('mimeType', () => { 155 | it('should validate MIME type', () => { 156 | stringValidators.mimeType('mimeType'); 157 | expect(validator.isMimeType).toHaveBeenCalledTimes(1); 158 | expect(validator.isMimeType).toHaveBeenCalledWith('mimeType'); 159 | }); 160 | }); 161 | describe('port', () => { 162 | it('should validate IP port', () => { 163 | stringValidators.port('port'); 164 | expect(validator.isPort).toHaveBeenCalledTimes(1); 165 | expect(validator.isPort).toHaveBeenCalledWith('port'); 166 | }); 167 | }); 168 | describe('rgbColor', () => { 169 | it('should validate RGB color', () => { 170 | stringValidators.rgbColor('rgbColor'); 171 | expect(validator.isRgbColor).toHaveBeenCalledTimes(1); 172 | expect(validator.isRgbColor).toHaveBeenCalledWith('rgbColor'); 173 | }); 174 | }); 175 | describe('semVer', () => { 176 | it('should validate semantic version', () => { 177 | stringValidators.semVer('semVer'); 178 | expect(validator.isSemVer).toHaveBeenCalledTimes(1); 179 | expect(validator.isSemVer).toHaveBeenCalledWith('semVer'); 180 | }); 181 | }); 182 | describe('uuid', () => { 183 | it('should validate UUID', () => { 184 | stringValidators.uuid('uuid'); 185 | expect(validator.isUUID).toHaveBeenCalledTimes(1); 186 | expect(validator.isUUID).toHaveBeenCalledWith('uuid'); 187 | }); 188 | }); 189 | describe('postalCode', () => { 190 | it('should validate postal code', () => { 191 | stringValidators.postalCode('postalCode'); 192 | expect(validator.isPostalCode).toHaveBeenCalledTimes(1); 193 | expect(validator.isPostalCode).toHaveBeenCalledWith('postalCode', 'any'); 194 | }); 195 | }); 196 | describe('mobileNumber', () => { 197 | it('should validate mobile phone number', () => { 198 | stringValidators.mobileNumber('mobileNumber'); 199 | expect(validator.isMobilePhone).toHaveBeenCalledTimes(1); 200 | expect(validator.isMobilePhone).toHaveBeenCalledWith('mobileNumber', 'any'); 201 | }); 202 | }); 203 | describe('alpha', () => { 204 | it('should validate alphabetic characters', () => { 205 | stringValidators.alpha('alpha'); 206 | expect(validator.isAlpha).toHaveBeenCalledTimes(1); 207 | expect(validator.isAlpha).toHaveBeenCalledWith('alpha'); 208 | }); 209 | }); 210 | describe('alphanumeric', () => { 211 | it('should validate alphanumeric characters', () => { 212 | stringValidators.alphanumeric('alphanumeric'); 213 | expect(validator.isAlphanumeric).toHaveBeenCalledTimes(1); 214 | expect(validator.isAlphanumeric).toHaveBeenCalledWith('alphanumeric'); 215 | }); 216 | }); 217 | describe('ascii', () => { 218 | it('should validate ascii characters', () => { 219 | stringValidators.ascii('ascii'); 220 | expect(validator.isAscii).toHaveBeenCalledTimes(1); 221 | expect(validator.isAscii).toHaveBeenCalledWith('ascii'); 222 | }); 223 | }); 224 | describe('base32', () => { 225 | it('should validate base32 value', () => { 226 | stringValidators.base32('base32'); 227 | expect(validator.isBase32).toHaveBeenCalledTimes(1); 228 | expect(validator.isBase32).toHaveBeenCalledWith('base32'); 229 | }); 230 | }); 231 | describe('base58', () => { 232 | it('should validate base58 value', () => { 233 | stringValidators.base58('base58'); 234 | expect(validator.isBase58).toHaveBeenCalledTimes(1); 235 | expect(validator.isBase58).toHaveBeenCalledWith('base58'); 236 | }); 237 | }); 238 | describe('base64', () => { 239 | it('should validate base64 value', () => { 240 | stringValidators.base64('base64'); 241 | expect(validator.isBase64).toHaveBeenCalledTimes(1); 242 | expect(validator.isBase64).toHaveBeenCalledWith('base64'); 243 | }); 244 | }); 245 | describe('dataUri', () => { 246 | it('should validate data URI', () => { 247 | stringValidators.dataUri('dataUri'); 248 | expect(validator.isDataURI).toHaveBeenCalledTimes(1); 249 | expect(validator.isDataURI).toHaveBeenCalledWith('dataUri'); 250 | }); 251 | }); 252 | describe('decimal', () => { 253 | it('should validate decimal value', () => { 254 | stringValidators.decimal('decimal'); 255 | expect(validator.isDecimal).toHaveBeenCalledTimes(1); 256 | expect(validator.isDecimal).toHaveBeenCalledWith('decimal'); 257 | }); 258 | }); 259 | describe('fqdn', () => { 260 | it('should validate FQDN', () => { 261 | stringValidators.fqdn('fqdn'); 262 | expect(validator.isFQDN).toHaveBeenCalledTimes(1); 263 | expect(validator.isFQDN).toHaveBeenCalledWith('fqdn'); 264 | }); 265 | }); 266 | describe('md4', () => { 267 | it('should validate MD4', () => { 268 | stringValidators.md4('md4'); 269 | expect(validator.isHash).toHaveBeenCalledTimes(1); 270 | expect(validator.isHash).toHaveBeenCalledWith('md4', 'md4'); 271 | }); 272 | }); 273 | describe('md5', () => { 274 | it('should validate MD5', () => { 275 | stringValidators.md5('md5'); 276 | expect(validator.isHash).toHaveBeenCalledWith('md5', 'md5'); 277 | }); 278 | }); 279 | describe('sha1', () => { 280 | it('should validate SHA1', () => { 281 | stringValidators.sha1('sha1'); 282 | expect(validator.isHash).toHaveBeenCalledWith('sha1', 'sha1'); 283 | }); 284 | }); 285 | describe('sha256', () => { 286 | it('should validate SHA256', () => { 287 | stringValidators.sha256('sha256'); 288 | expect(validator.isHash).toHaveBeenCalledWith('sha256', 'sha256'); 289 | }); 290 | }); 291 | describe('sha384', () => { 292 | it('should validate SHA384', () => { 293 | stringValidators.sha384('sha384'); 294 | expect(validator.isHash).toHaveBeenCalledWith('sha384', 'sha384'); 295 | }); 296 | }); 297 | describe('sha512', () => { 298 | it('should validate SHA512', () => { 299 | stringValidators.sha512('sha512'); 300 | expect(validator.isHash).toHaveBeenCalledWith('sha512', 'sha512'); 301 | }); 302 | }); 303 | describe('crc32', () => { 304 | it('should validate CRC32', () => { 305 | stringValidators.crc32('crc32'); 306 | expect(validator.isHash).toHaveBeenCalledWith('crc32', 'crc32'); 307 | }); 308 | }); 309 | describe('crc32b', () => { 310 | it('should validate CRC32b', () => { 311 | stringValidators.crc32b('crc32b'); 312 | expect(validator.isHash).toHaveBeenCalledWith('crc32b', 'crc32b'); 313 | }); 314 | }); 315 | describe('hex', () => { 316 | it('should validate hex value', () => { 317 | stringValidators.hex('hex'); 318 | expect(validator.isHexadecimal).toHaveBeenCalledTimes(1); 319 | expect(validator.isHexadecimal).toHaveBeenCalledWith('hex'); 320 | }); 321 | }); 322 | describe('ipv4Range', () => { 323 | it('should validate IPv4 range', () => { 324 | stringValidators.ipv4Range('ipv4Range'); 325 | expect(validator.isIPRange).toHaveBeenCalledTimes(1); 326 | expect(validator.isIPRange).toHaveBeenCalledWith('ipv4Range', 4); 327 | }); 328 | }); 329 | describe('ipv6Range', () => { 330 | it('should validate IPv6 range', () => { 331 | stringValidators.ipv6Range('ipv6Range'); 332 | expect(validator.isIPRange).toHaveBeenCalledWith('ipv6Range', 6); 333 | }); 334 | }); 335 | describe('json', () => { 336 | it('should validate JSON', () => { 337 | stringValidators.json('json'); 338 | expect(validator.isJSON).toHaveBeenCalledTimes(1); 339 | expect(validator.isJSON).toHaveBeenCalledWith('json'); 340 | }); 341 | }); 342 | describe('lowercase', () => { 343 | it('should validate lowercase characters', () => { 344 | stringValidators.lowercase('lowercase'); 345 | expect(validator.isLowercase).toHaveBeenCalledTimes(1); 346 | expect(validator.isLowercase).toHaveBeenCalledWith('lowercase'); 347 | }); 348 | }); 349 | describe('magnetUri', () => { 350 | it('should validate Magnet URI', () => { 351 | stringValidators.magnetUri('magnetUri'); 352 | expect(validator.isMagnetURI).toHaveBeenCalledTimes(1); 353 | expect(validator.isMagnetURI).toHaveBeenCalledWith('magnetUri'); 354 | }); 355 | }); 356 | describe('mongoId', () => { 357 | it('should validate Mongo id', () => { 358 | stringValidators.mongoId('mongoId'); 359 | expect(validator.isMongoId).toHaveBeenCalledTimes(1); 360 | expect(validator.isMongoId).toHaveBeenCalledWith('mongoId'); 361 | }); 362 | }); 363 | describe('numeric', () => { 364 | it('should validate numeric string', () => { 365 | stringValidators.numeric('numeric'); 366 | expect(validator.isNumeric).toHaveBeenCalledTimes(1); 367 | expect(validator.isNumeric).toHaveBeenCalledWith('numeric'); 368 | }); 369 | }); 370 | describe('octal', () => { 371 | it('should validate octal string', () => { 372 | stringValidators.octal('octal'); 373 | expect(validator.isOctal).toHaveBeenCalledTimes(1); 374 | expect(validator.isOctal).toHaveBeenCalledWith('octal'); 375 | }); 376 | }); 377 | describe('uppercase', () => { 378 | it('should validate uppercase character', () => { 379 | stringValidators.uppercase('uppercase'); 380 | expect(validator.isUppercase).toHaveBeenCalledTimes(1); 381 | expect(validator.isUppercase).toHaveBeenCalledWith('uppercase'); 382 | }); 383 | }); 384 | describe('strongPassword', () => { 385 | it('should validate strong password', () => { 386 | stringValidators.strongPassword('strongPassword'); 387 | expect(validator.isStrongPassword).toHaveBeenCalledTimes(1); 388 | expect(validator.isStrongPassword).toHaveBeenCalledWith('strongPassword'); 389 | }); 390 | }); 391 | describe('url', () => { 392 | it('should validate URL', () => { 393 | stringValidators.url('url'); 394 | expect(validator.isURL).toHaveBeenCalledTimes(1); 395 | expect(validator.isURL).toHaveBeenCalledWith('url'); 396 | }); 397 | }); 398 | describe('includes', () => { 399 | it('should validate string that includes another string', () => { 400 | const doesInclude = stringValidators.includes('url', 'ur'); 401 | expect(doesInclude).toEqual(true); 402 | }); 403 | it('should return false if not parameter supplied', () => { 404 | const doesInclude = stringValidators.includes('url'); 405 | expect(doesInclude).toEqual(false); 406 | }); 407 | }); 408 | describe('match', () => { 409 | it('should validate string that match regexp', () => { 410 | const doesMatch = stringValidators.match('uuu', 'u+'); 411 | expect(doesMatch).toEqual(true); 412 | }); 413 | it('should return false if not parameter supplied', () => { 414 | const doesMatch = stringValidators.match('url'); 415 | expect(doesMatch).toEqual(false); 416 | }); 417 | }); 418 | describe('isOneOf', () => { 419 | it('should validate string that contains at least one from the given list of strings', () => { 420 | const isOneOf = stringValidators.isOneOf('uuu', '["uuu", "aaa"]'); 421 | expect(isOneOf).toEqual(true); 422 | }); 423 | }); 424 | describe('isNoneOf', () => { 425 | it('should validate string that contains none from the given list of strings', () => { 426 | const isNoneOf = stringValidators.isNoneOf('bbb', '["uuu", "aaa"]'); 427 | expect(isNoneOf).toEqual(true); 428 | }); 429 | }); 430 | describe('startsWith', () => { 431 | it('should validate string that starts with another string', () => { 432 | const doesStart = stringValidators.startsWith('bbbaa', 'bb'); 433 | expect(doesStart).toEqual(true); 434 | }); 435 | it('should return false if not parameter supplied', () => { 436 | const doesStart = stringValidators.startsWith('url'); 437 | expect(doesStart).toEqual(false); 438 | }); 439 | }); 440 | describe('numericRange', () => { 441 | it('should validate string that belongs to numeric range', () => { 442 | const isValid = stringValidators.numericRange('5', '1-65535'); 443 | expect(isValid).toEqual(true); 444 | }); 445 | it('should return false if string does not belong to numeric range', () => { 446 | const isValid = stringValidators.numericRange('0', '1-65535'); 447 | expect(isValid).toEqual(false); 448 | }); 449 | it('should return false if string is not a number', () => { 450 | const isValid = stringValidators.numericRange('ff', '1-65535'); 451 | expect(isValid).toEqual(false); 452 | }); 453 | it('should throw ValidationSpecError if parameter is not a valid numeric range', () => { 454 | expect(() => { 455 | stringValidators.numericRange('ff', '-3'); 456 | }).toThrow( 457 | 'Validator parameter must a numeric range in format -, for example 1-65535' 458 | ); 459 | }); 460 | it('should throw ValidationSpecError if parameter is invalid', () => { 461 | expect(() => { 462 | stringValidators.numericRange('ff', '1:3'); 463 | }).toThrow( 464 | 'Validator parameter must a numeric range in format -, for example 1-65535' 465 | ); 466 | }); 467 | it('should return false if parameter not supplied', () => { 468 | expect(() => { 469 | stringValidators.numericRange('5'); 470 | }).toThrow( 471 | 'Validator parameter must a numeric range in format -, for example 1-65535' 472 | ); 473 | }); 474 | }); 475 | describe('endsWith', () => { 476 | it('should validate string that ends with another string', () => { 477 | const doesEnd = stringValidators.endsWith('aabbb', 'bb'); 478 | expect(doesEnd).toEqual(true); 479 | }); 480 | it('should return false if not parameter supplied', () => { 481 | const doesEnd = stringValidators.endsWith('url'); 482 | expect(doesEnd).toEqual(false); 483 | }); 484 | }); 485 | }); 486 | -------------------------------------------------------------------------------- /src/string/stringValidators.ts: -------------------------------------------------------------------------------- 1 | // noinspection MagicNumberJS 2 | 3 | import validator from 'validator'; 4 | import { KnownLengthStringValidatorNames } from './KnownLengthStringValidatorNames'; 5 | import { UnknownLengthStringValidatorNames } from './UnknownLengthStringValidatorNames'; 6 | import { ParameterizedStringValidatorNames } from './ParameterizedStringValidatorNames'; 7 | import parseJSONArrayParameter from './parseJSONArrayParameter'; 8 | import ValidationSpecError from '../error/ValidationSpecError'; 9 | 10 | export const stringValidators: { 11 | [ValidatorName in 12 | | KnownLengthStringValidatorNames 13 | | UnknownLengthStringValidatorNames 14 | | ParameterizedStringValidatorNames]: (value: string, parameter?: string) => boolean; 15 | } = { 16 | boolean: (value) => value.length <= 5 && validator.isBoolean(value), 17 | bic: (value) => value.length <= 11 && validator.isBIC(value), 18 | btcAddress: (value) => value.length <= 35 && validator.isBtcAddress(value), 19 | creditCard: (value) => value.length <= 19 && validator.isCreditCard(value), 20 | ean: (value) => value.length <= 13 && validator.isEAN(value), 21 | email: (value) => value.length <= 320 && validator.isEmail(value), 22 | ethereumAddress: (value) => value.length <= 42 && validator.isEthereumAddress(value), 23 | hsl: (value) => value.length <= 64 && validator.isHSL(value), 24 | hexColor: (value) => value.length <= 7 && validator.isHexColor(value), 25 | isin: (value) => value.length <= 12 && validator.isISIN(value), 26 | iban: (value) => value.length <= 42 && validator.isIBAN(value), 27 | ipv4: (value) => value.length <= 15 && validator.isIP(value, 4), 28 | ipv6: (value) => value.length <= 39 && validator.isIP(value, 6), 29 | iso31661Alpha2: (value) => value.length <= 2 && validator.isISO31661Alpha2(value), 30 | iso31661Alpha3: (value) => value.length <= 3 && validator.isISO31661Alpha3(value), 31 | iso8601: (value) => value.length <= 64 && validator.isISO8601(value), 32 | isrc: (value) => value.length <= 15 && validator.isISRC(value), 33 | issn: (value) => value.length <= 9 && validator.isISSN(value), 34 | jwt: (value) => value.length <= 8192 && validator.isJWT(value), 35 | latLong: (value) => value.length <= 64 && validator.isLatLong(value), 36 | macAddress: (value) => value.length <= 17 && validator.isMACAddress(value), 37 | mimeType: (value) => value.length <= 64 && validator.isMimeType(value), 38 | port: (value) => value.length <= 5 && validator.isPort(value), 39 | rgbColor: (value) => value.length <= 32 && validator.isRgbColor(value), 40 | semVer: (value) => value.length <= 32 && validator.isSemVer(value), 41 | uuid: (value) => value.length <= 36 && validator.isUUID(value), 42 | postalCode: (value) => value.length <= 32 && validator.isPostalCode(value, 'any'), 43 | creditCardExpiration: (value) => 44 | value.length <= 7 && !!value.match(/^(0[1-9]|1[0-2])\/(\d{2}|[2-9]\d{3})$/), 45 | cvc: (value) => value.length <= 4 && !!value.match(/^\d{3,4}$/), 46 | mobileNumber: (value) => value.length <= 32 && validator.isMobilePhone(value, 'any'), 47 | alpha: (value) => validator.isAlpha(value), 48 | alphanumeric: (value) => validator.isAlphanumeric(value), 49 | ascii: (value) => validator.isAscii(value), 50 | base32: (value) => validator.isBase32(value), 51 | base58: (value) => validator.isBase58(value), 52 | base64: (value) => validator.isBase64(value), 53 | dataUri: (value) => validator.isDataURI(value), 54 | decimal: (value) => validator.isDecimal(value), 55 | fqdn: (value) => validator.isFQDN(value), 56 | md4: (value) => validator.isHash(value, 'md4'), 57 | md5: (value) => validator.isHash(value, 'md5'), 58 | sha1: (value) => validator.isHash(value, 'sha1'), 59 | sha256: (value) => validator.isHash(value, 'sha256'), 60 | sha384: (value) => validator.isHash(value, 'sha384'), 61 | sha512: (value) => validator.isHash(value, 'sha512'), 62 | crc32: (value) => validator.isHash(value, 'crc32'), 63 | crc32b: (value) => validator.isHash(value, 'crc32b'), 64 | hex: (value) => validator.isHexadecimal(value), 65 | ipv4Range: (value) => validator.isIPRange(value, 4), 66 | ipv6Range: (value) => validator.isIPRange(value, 6), 67 | json: (value) => validator.isJSON(value), 68 | lowercase: (value) => validator.isLowercase(value), 69 | magnetUri: (value) => validator.isMagnetURI(value), 70 | mongoId: (value) => validator.isMongoId(value), 71 | numeric: (value) => validator.isNumeric(value), 72 | octal: (value) => validator.isOctal(value), 73 | uppercase: (value) => validator.isUppercase(value), 74 | strongPassword: (value) => validator.isStrongPassword(value), 75 | url: (value) => validator.isURL(value), 76 | includes: (value, parameter) => (parameter ? value.includes(parameter) : false), 77 | match: (value, parameter) => (parameter ? !!value.match(parameter) : false), 78 | isOneOf: (value, parameter) => { 79 | return parseJSONArrayParameter(parameter).includes(value); 80 | }, 81 | isNoneOf: (value, parameter) => !parseJSONArrayParameter(parameter).includes(value), 82 | startsWith: (value, parameter) => (parameter ? value.startsWith(parameter) : false), 83 | endsWith: (value, parameter) => (parameter ? value.endsWith(parameter) : false), 84 | numericRange: (value, parameter) => { 85 | if (parameter) { 86 | const [minValueString, maxValueString] = parameter.split('-'); 87 | if (minValueString && maxValueString) { 88 | const minValue = parseInt(minValueString, 10); 89 | const maxValue = parseInt(maxValueString, 10); 90 | if (isNaN(minValue) || isNaN(maxValue)) { 91 | throw new ValidationSpecError( 92 | 'Validator parameter must a numeric range in format -, for example 1-65535' 93 | ); 94 | } 95 | const parsedValue = parseInt(value, 10); 96 | return parsedValue >= minValue && parsedValue <= maxValue; 97 | } else { 98 | throw new ValidationSpecError( 99 | 'Validator parameter must a numeric range in format -, for example 1-65535' 100 | ); 101 | } 102 | } else { 103 | throw new ValidationSpecError( 104 | 'Validator parameter must a numeric range in format -, for example 1-65535' 105 | ); 106 | } 107 | }, 108 | }; 109 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2021"], 4 | "module": "commonjs", 5 | "target": "es2021", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "moduleResolution": "node", 11 | "declaration": true, 12 | "outDir": "./lib", 13 | "baseUrl": "./" 14 | }, 15 | 16 | "include": ["src"], 17 | "exclude": ["node_modules", "lib"] 18 | } 19 | --------------------------------------------------------------------------------