├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
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 | [](https://sonarcloud.io/dashboard?id=pksilen_validated-types)
10 | [](https://sonarcloud.io/dashboard?id=pksilen_validated-types)
11 | [](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