https://github.com/canerdemirci/cnr-validation
Validation in programming is a crucial process that ensures the accuracy and reliability of data input and output. It involves checking the validity of data against predefined rules and constraints. For instance, validating a user's email address might include ensuring it adheres to a specific format, such as "[email address removed]". By implementing validation techniques, developers can prevent errors, improve user experience, and maintain data integrity within their applications.
I have written a validation library in TypeScript for Node.js. I have also written tests for the library. There are currently two rule classes in the library: one for strings and one for numbers. These classes do type checking in their constructor and have various property functions that validate the class’s value. These functions return their class so that functions can be chained. If the value is not valid, they return an error. Errors are customizable. There is also a class called Validation. This class takes a validation model and, when you validate the model, it returns validation results.
I know there are professional packages for validation, but I wanted to create my own for TypeScript practice. I found opportunities to use TypeScript types, interfaces, and abstract classes. The package is open for development. I would like to add more validation methods, such as phone, IP, and URL, but I don't have time right now. Research is needed for these validations because each one has different formats, and the standards vary from country to country.
Example Using of the Package
import { useEffect, useState } from 'react'
import { NumberRule, StringRule, Validation, ValidationModel, ValidationResult } from 'cnr-validation'
import './App.css'
import { RiErrorWarningFill } from 'react-icons/ri'
function App() {
const validationModel: ValidationModel = {
name: () => new StringRule(name).required().min(5).max(10).endsWith('er'),
age: () => new NumberRule(age).required().min(18).max(120),
email: () => new StringRule(email).required().email(),
bio: () => new StringRule(bio).max(100)
}
const [name, setName] = useState<string>('')
const [age, setAge] = useState<number>(18)
const [email, setEmail] = useState<string>('')
const [bio, setBio] = useState<string>('')
const [validationResult, setValidationResult] = useState<ValidationResult>(validate())
useEffect(() => {
setValidationResult(validate())
}, [name, age, email, bio])
function validate(): ValidationResult {
return new Validation(validationModel).validate()
}
function handleFormSubmit(e: any) {
e.preventDefault()
for (let r in validationResult) {
if (!validationResult[r].success) {
alert('Form is not valid\n' + JSON.stringify({
name: validationResult.name?.error?.message,
age: validationResult.age?.error?.message,
email: validationResult.email?.error?.message,
bio: validationResult.bio?.error?.message,
}))
return
}
}
alert('Form is valid\n' + JSON.stringify({
name: name,
age: age,
email: email,
bio: bio,
}))
}
return (
<div className="main-container">
<header>
<h2>VALIDATION TEST FORM</h2>
</header>
<form className="example-form" onSubmit={handleFormSubmit}>
<div className="form-group">
<div>
<label htmlFor="name">Name</label>
<div
className="input-error-message"
style={{ display: validationResult.name?.error?.message ? 'flex' : 'none' }}
>
<span>{validationResult.name?.error?.message}</span>
<RiErrorWarningFill />
</div>
</div>
<input type="text" name="name" id="name" value={name}
onChange={(e: any) => setName(e.target.value)} />
</div>
<div className="form-group">
<div>
<label htmlFor="age">Age</label>
<div
className="input-error-message"
style={{ display: validationResult.age?.error?.message ? 'flex' : 'none' }}
>
<span>{validationResult.age?.error?.message}</span>
<RiErrorWarningFill />
</div>
</div>
<input type="number" name="age" id="age" value={age}
onChange={(e: any) => setAge(parseInt(e.target.value))} />
</div>
<div className="form-group">
<div>
<label htmlFor="email">Email</label>
<div
className="input-error-message"
style={{ display: validationResult.email?.error?.message ? 'flex' : 'none' }}
>
<span>{validationResult.email?.error?.message}</span>
<RiErrorWarningFill />
</div>
</div>
<input type="text" name="email" id="email" value={email}
onChange={(e: any) => setEmail(e.target.value)} />
</div>
<div className="form-group">
<div>
<label htmlFor="bio">Bio</label>
<div
className="input-error-message"
style={{ display: validationResult.bio?.error?.message ? 'flex' : 'none' }}
>
<span>{validationResult.bio?.error?.message}</span>
<RiErrorWarningFill />
</div>
</div>
<textarea name="bio" id="bio" value={bio} rows={4}
onChange={(e: any) => setBio(e.target.value)} />
</div>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App
Tests of the Package
String type checking tests - [✔] - 6 test
Type | Value | Description | Expected | Result | Passed | Error Code | Error Name | Error Description |
number | 32 | String type test | false | false | [✔] | 200 | InvalidTypeError | Value must be a string |
undefined | undefined | String type test | false | false | [✔] | 200 | InvalidTypeError | Value must be a string |
null | null | String type test | false | false | [✔] | 200 | InvalidTypeError | Value must be a string |
boolean | false | String type test | false | false | [✔] | 200 | InvalidTypeError | Value must be a string |
NaN | NaN | String type test | false | false | [✔] | 200 | InvalidTypeError | Value must be a string |
array | a,b,c,1,2,3 | String type test with an array | false | false | [✔] | 200 | InvalidTypeError | Value must be a string |
String basic tests - [✔] - 25 test
Type | Value | Description | Expected | Result | Passed | Error Code | Error Name | Error Description |
string | | String required test | false | false | [✔] | 201 | RequiredError | Value is required |
string | abc | String required test | true | true | [✔] | - | - | - |
string | abc | String min length test - min: 4 | false | false | [✔] | 300 | MinimumLengthError | Value's character length can't be less than 4 |
string | abcd | String min length test - min: 4 | true | true | [✔] | - | - | - |
string | | String min length test - min: 1 | false | false | [✔] | 300 | MinimumLengthError | Value's character length can't be less than 1 |
string | abcde | String max length test - max: 4 | false | false | [✔] | 301 | MaximumLengthError | Value's character length can't be more than 4 |
string | abcd | String max length test - max: 4 | true | true | [✔] | - | - | - |
string | abc | String max length test - max: 4 | true | true | [✔] | - | - | - |
string | abcd | String length test - length: 5 | false | false | [✔] | 302 | LengthError | Value's character length must be exactly 5 long |
string | abcdef | String length test - length: 5 | false | false | [✔] | 302 | LengthError | Value's character length must be exactly 5 long |
string | abcde | String length test - length: 5 | true | true | [✔] | - | - | - |
string | abc_HELLO | String starts with test [exactly] - with: abc_ | true | true | [✔] | - | - | - |
string | xyztHELLO | String starts with test [exactly] - with: abc_ | false | false | [✔] | 304 | StartsWithError | Value must start with exactly abc_ |
string | ABC_HELLO | String starts with test [exactly] - with: abc_ | false | false | [✔] | 304 | StartsWithError | Value must start with exactly abc_ |
string | ABC_HELLO | String starts with test [not exactly] - with: abc_ | true | true | [✔] | - | - | - |
string | Abc_HELLO | String starts with test [not exactly] - with: abc_ | true | true | [✔] | - | - | - |
string | öÖçÇiİüÜ#+?=-HELLO | String starts with test [exactly] - with: öÖçÇiİüÜ#+?=- | true | true | [✔] | - | - | - |
string | öÖçÇiİüÜ#+?-HELLO | String starts with test [not exactly] - with: ööççiiüü#+?=- | true | true | [✔] | - | - | - |
string | HELLO_guys | String ends with test [exactly] - with: _guys | true | true | [✔] | - | - | - |
string | HELLOgirls | String ends with test [exactly] - with: _guys | false | false | [✔] | 305 | EndsWithError | Value must end with exactly _guys |
string | HELLO_GUYS | String ends with test [exactly] - with: _guys | false | false | [✔] | 305 | EndsWithError | Value must end with exactly _guys |
string | HELLO_GUYS | String ends with test [not exactly] - with: _guys | true | true | [✔] | - | - | - |
string | HELLO_Guys | String ends with test [not exactly] - with: _guys | true | true | [✔] | - | - | - |
string | HELLOöÖçÇiİüÜ#+?=- | String ends with test [exactly] - with: öÖçÇiİüÜ#+?=- | true | true | [✔] | - | - | - |
string | HELLOöÖçÇiİüÜ#+?- | String ends with test [not exactly] - with: ööççiiüü#+?=- | true | true | [✔] | - | - | - |
String email tests - [✔] - 10 test
Type | Value | Description | Expected | Result | Passed | Error Code | Error Name | Error Description |
string | | Email validation test | false | false | [✔] | 303 | InvalidEmailError | Value must contain the @ character |
string | a | Email validation test | false | false | [✔] | 303 | InvalidEmailError | Value must contain the @ character |
string | abc.com | Email validation test | false | false | [✔] | 303 | InvalidEmailError | Value must contain the @ character |
string | abc@ | Email validation test | false | false | [✔] | 303 | InvalidEmailError | Value must contain a dot at domain |
string | abc@. | Email validation test | false | false | [✔] | 303 | InvalidEmailError | Value must contain domain (like abc.com) |
string | abc@.com | Email validation test | false | false | [✔] | 303 | InvalidEmailError | Value must contain domain (like abc.com) |
string | abc@xyz | Email validation test | false | false | [✔] | 303 | InvalidEmailError | Value must contain a dot at domain |
string | abc@xyz. | Email validation test | false | false | [✔] | 303 | InvalidEmailError | Value must contain domain (like abc.com) |
string | ab c@xy z. co m | Email validation test | false | false | [✔] | 303 | InvalidEmailError | Value mustn't contain any whitespace characters. |
string | abc@xyz.com | Email validation test | true | true | [✔] | - | - | - |
String custom tests - [✔] - 4 test
Type | Value | Description | Expected | Result | Passed | Error Code | Error Name | Error Description |
string | abc | Custom validation test - Must be a numeric character | false | false | [✔] | - | Error | Value must include a number |
string | ab9c | Custom validation test - Must be a numeric character | true | true | [✔] | - | - | - |
string | abc@xyz.com | Regex validation test - Email regex | true | true | [✔] | - | - | - |
string | abc.com | Regex validation test - Email regex | false | false | [✔] | - | Error | Email format is not valid |
String rule chaining tests - [✔] - 7 test
Type | Value | Description | Expected | Result | Passed | Error Code | Error Name | Error Description |
string | a_bc@xyz.net | Chaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) test | true | true | [✔] | - | - | - |
string | a_bc@xyz.com | Chaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) test | false | false | [✔] | - | Error | Must end with net |
string | abc@xyz.com | Chaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) test | false | false | [✔] | - | Error | Must include _ |
string | abc@xyz.comXXXXXXXXXO | Chaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) test | false | false | [✔] | 301 | MaximumLengthError | Value's character length can't be more than 20 |
string | a@x.c | Chaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) test | false | false | [✔] | 300 | MinimumLengthError | Value's character length can't be less than 11 |
string | | Chaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) test | false | false | [✔] | 201 | RequiredError | Value is required |
string | 32 | Chaining-1 required-min-max-email-custom (Must include _)-regex (Must end with net) test | false | false | [✔] | 200 | InvalidTypeError | Value must be a string |
Number type checking tests - [✔] - 9 test
Type | Value | Description | Expected | Result | Passed | Error Code | Error Name | Error Description |
number | -32 | Number type test - negative | true | true | [✔] | - | - | - |
number | 32 | Number type test - positive | true | true | [✔] | - | - | - |
number | 32.55 | Number type test float | true | true | [✔] | - | - | - |
string | 32 | Number type test - string | false | false | [✔] | 200 | InvalidTypeError | Value must be a number |
undefined | undefined | Number type test | false | false | [✔] | 200 | InvalidTypeError | Value must be a number |
null | null | Number type test | false | false | [✔] | 200 | InvalidTypeError | Value must be a number |
boolean | false | Number type test | false | false | [✔] | 200 | InvalidTypeError | Value must be a number |
NaN | NaN | Number type test | false | false | [✔] | 200 | InvalidTypeError | Value must be a number |
array | a,b,c,1,2,3 | Number type test with an array | false | false | [✔] | 200 | InvalidTypeError | Value must be a number |
Number tests - [✔] - 13 test
Type | Value | Description | Expected | Result | Passed | Error Code | Error Name | Error Description |
undefined | undefined | Number required test | false | false | [✔] | 200 | InvalidTypeError | Value must be a number |
number | 32 | Number required test | true | true | [✔] | - | - | - |
number | 32 | Number min value test | false | false | [✔] | 400 | MinNumberError | Value can't be less than 40 |
number | 40 | Number min value test | true | true | [✔] | - | - | - |
number | 40 | Number min value test | false | false | [✔] | 400 | MinNumberError | Value can't be less than 40.75 |
number | 40.75 | Number min value test | true | true | [✔] | - | - | - |
number | 45 | Number max value test | false | false | [✔] | 401 | MaxNumberError | Value can't be more than 40 |
number | 40 | Number max value test | true | true | [✔] | - | - | - |
number | 41 | Number max value test | false | false | [✔] | 401 | MaxNumberError | Value can't be more than 40.75 |
number | 40.75 | Number max value test | true | true | [✔] | - | - | - |
number | 8.75 | Number equal value test | false | false | [✔] | 402 | EqualNumberError | Value must be equal to 8 |
number | 8 | Number equal value test | true | true | [✔] | - | - | - |
number | 8 | Number equal value test - 40/5=8? | true | true | [✔] | - | - | - |
Custom number tests - [✔] - 1 test
Type | Value | Description | Expected | Result | Passed | Error Code | Error Name | Error Description |
number | 30 | Number custom test 100/4=25? | false | false | [✔] | - | Error | Value must be 1/4 of 100 |
Number rule chaining tests - [✔] - 5 test
Type | Value | Description | Expected | Result | Passed | Error Code | Error Name | Error Description |
number | 50 | Number chain rules: required, min(25), max(50), equal(50), custom(100/2) | true | true | [✔] | - | - | - |
number | 40 | Number chain rules: required, min(25), max(50), equal(50), custom(100/2) | false | false | [✔] | 402 | EqualNumberError | Value must be equal to 50 |
number | 20 | Number chain rules: required, min(25), max(50), equal(50), custom(100/2) | false | false | [✔] | 400 | MinNumberError | Value can't be less than 25 |
number | 70 | Number chain rules: required, min(25), max(50), equal(50), custom(100/2) | false | false | [✔] | 401 | MaxNumberError | Value can't be more than 50 |
number | undefined | Number chain rules: required, min(25), max(50), equal(50), custom(100/2) | false | false | [✔] | 200 | InvalidTypeError | Value must be a number |
Subscribe to my newsletter
Read articles from caner demirci directly inside your inbox. Subscribe to the newsletter, and don't miss out.