npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@bavaria2tm/validator

v0.0.15

Published

The validation of data is an essential part of an application, used in api endpoints, input data validation, ... just to name a few examples. Most commonly in nodejs the express validators, joi validators, or class validators are used.

Downloads

854

Readme

Validators

Description

The validation of data is an essential part of an application, used in api endpoints, input data validation, ... just to name a few examples. Most commonly in nodejs the express validators, joi validators, or class validators are used.

In our case we needed something with type save validation similar to class validators but combined with the method chaining and flexibility of joi validators.Furthermore we need to use the validators in frontend and backend and have to be therefor independent of a library. Because of all of these reasons we decided to create our on validators.

Links

What is a Class Schema in our Use Case?

A class schema is a TypeScript class that serves as a blueprint for the data we send or receive between different parts of our application, such as between the client and server. Beyond just describing the shape of the data, our Validators also have built-in rules for validating and transforming this data. This ensures that the data is not only correctly formatted but also meets specific conditions before we process it further. Think of it as a smart data package that knows how to check itself for errors and even correct some of them automatically.

Usage as Class Schema's

The advantages of Class Schemas are that they have strict type safety and are cleaner then the object validators in highting in the IDE (class definitions pop out more then constants)

1. Define a Schema Class

class UserSchema extends ValidatorSchema<IUser> {
  //0 - validate the name, which is MANDATORY
  // > Note: by default all fields are "REQUIRED"
  name = Validator.String()
    .isNotEmpty()
    .minLength(5)
    .hasNoSpace() // example of VALIDATORS
    .trim()
    .toLowerCase()
    .encodeBase64(); // example of TRANSFORMERS

  //1 - OPTIONAL field
  // > Note: alternatively use ".optionalIf((o: IUser) => ...)"
  nickName = Validator.String().optional();

  //2 - set DEFAULT value
  age = Validator.Number().isPositive().default(-1);

  //3 - conditional "IF" validation
  adult = Validator.Boolean().validateIf((value: boolean) => adult);

  //4 - validate an ARRAY, of strings
  friends = Validator.Array()
    .isNotEmpty()
    .maxLength(10)
    .shuffle()
    .each(Validator.String().trim());

  //5 - validate an OBJECT, external class schema
  contact = Validator.Object().hasNoNestedObjects().inspect(ContactSchema); // nested object of "IAddress"

  //7 - validate an ARRAY, of OBJECTS
  friendContacts = Validator.Array().isNotEmpty().inspectEach(ContactSchema);
}

class ContactSchema extends ValidatorSchema<IContact> {
  phone = Validator.String().isNotEmpty();
  email = Validator.String().isNotEmpty().isEmail();
}

2. Validate By Class Schema

Use the ValidatorSchema that was created before to validate the data with one of the below approaches.

//0 - validate by boolean
const isValid: boolean = Validator.Schema.isValid(data, UserSchema);
if (isValid) console.log(" > user is valid");
else console.log(" > user is invalid");

//1 - validate with getting the detailed infos
// > interface IValidatorError {
// >   property : string;
// >   value    : boolean;
// >   errors   : string[];
// >   children : IValidatorError[];
// > }
const validationErrors: IValidatorError[] = Validator.Schema.validate(
  data,
  UserSchema
);
console.log(" > validationErrors: ", validationErrors);

Note: the transformers are also executed during the validation

3. Process By Class Schema

Use the UserSchema that was created before to process / clean the data based on the transformers.

// process the user data by the schema
// > Note: this will execute logic like trim values or apply default values,
// > and more. It will return THROW an ERROR for an invalid object.
const userData: IUser = Validator.Schema.transform(requestBodyData, UserSchema);

Note: the validation is also executed during the processing, so if the data is invalid we get the error

Usage of Object Schemas

The advantages of schemas are, that they give high type safety (it forces us to have a validator for each value and forces the exact validator type). The disadvantage is we can only have a single validation chain for each value.

Note: use the Schema validation for configs and other partial inline validations

1. Define a Object Schema

const userSchema: ValidatorSchema<IUser> = {
  //0 - simple validators
  name: Validator.String().isNotEmpty().trim().toLowerCase(),
  nickName: Validator.String().optional(),

  //1 - validate an ARRAY, of strings
  friends: Validator.Array()
    .each(Validator.String().trim())
    .isNotEmpty()
    .maxLength(10)
    .shuffle(),

  //2 - validate an OBJECT, as INLINE OBJECT
  address: Validator.Object()
    .inspect({
      phone: Validator.String().toLowerCase(),
      email: Validator.String().toLowerCase()
    })
    .hasNoNestedObjects(),

  //3 - validate an OBJECT, as INLINE
  contact: {
    phone: Validator.String().toLowerCase(),
    email: Validator.String().toLowerCase()
  }
};

Note: the schemas can have inline objects / nested validators, but you can not have a child schema

2. Validate By Object Schema

Use the Schema that was created before to validate the data with one of the below approaches.

//0 - validate by boolean
const isDataValid: boolean = Validator.Schema.isValid(data, userSchema);
if (isDataValid) console.log(" > user is valid");
else console.log(" > user is invalid");

//1 - validate with getting the detailed infos
const validationErrors: IValidatorError[] = Validator.Schema.validate(
  data,
  userSchema
);
console.log(" > validationErrors: ", validationErrors);

//2 - validate inline
const validationErrors: IValidatorError[] = Validator.Schema.validate(data, {
  name: Validator.String().isNotEmpty().trim().toLowerCase(),
  nickName: Validator.String().optional()
  // ... add other validators
});

Note: the transformers are also executed during the validation

3. Process By Object Schema

Use the objectSchema that was created before to process / clean the data based on the transformers.

//0 - process the user data by the schema
// > Note: this will execute logic like trim values or apply default values,
// > and more. It will return THROW an ERROR for an invalid object.
const userData: IUser = Validator.Schema.transform(requestBodyData, userSchema);

//1 - transform inline
const validationErrors: IValidatorError[] = Validator.Schema.transform(data, {
  name: Validator.String().isNotEmpty().trim().toLowerCase(),
  nickName: Validator.String().optional()
  // ... add other validators
});

Note: the validation is also executed during the processing, so if the data is invalid we get the error

Other Usages

1. Direct Execution

The validators can also be executed directly

//0 - validate
const isValid: boolean = Validator.String()
  .isNotEmpty()
  .isLowerCase()
  .isValid("My Cat");

//1 - processing value
const processedValue: string = Validator.String()
  .isNotEmpty()
  .toLowerCase()
  .process("My Cat");

2. Use Validators for Angular FormControl

The validators can even be used with Angular FormGroup / FormControls and can provide the following standard required by angular

// standard required by angular
export function passwordValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const password: string = control.value; // ... check "control.value"
    return this.isValidPassword(password)
      ? { weekPassword: "Password is week" }
      : null;
  };
}

To convert the validator into a FormControls Validator use the approach below

//0 - get the validator as an wrapped "angular validator"
// > Note: returns "(control: AbstractControl): ValidationErrors | null"
const angularValidator: ValidatorFn = Validator.String().isPassword().ngForm();

//1 - use the validator
const signupForm: FormGroup<ILoginForm> = new FormGroup<ILoginForm>({
  password: new FormControl<string>("", angularValidator),
  name: new FormControl<string>("", Validator.String().isNotEmpty().ngForm()) // use it directly inline
  // ...
});

Appendix

Appendix A: Validation Result

The validation returns the following result. Be aware that arrays and objects can have children.

interface IValidatorError {
  property: string;
  value: boolean;
  errors: string[];
  children: IValidatorError[];
}

If the following example data is validated, it would give the following result below.

// data which has invalid entries
const data = {
  name: "Yoda",
  age: -1, // negative number is invalid
  hobbies: ["swimming", undefined, "running"], // undefined is invalid
  address: {
    country: null, // null is not a valid country
    zipCode: 45678
  }
};

// validation result
const validationResult: Partial<IValidatorError>[] = [
  {
    property: "age",
    value: -1,
    errors: ["No negative value"],
    children: []
  },
  {
    property: "hobbies",
    value: ["swimming", undefined, 22, "running"],
    errors: ["Some items are invalid"],
    children: [
      {
        property: "1", // index in the array
        value: undefined,
        errors: ["Item is undefined"],
        children: []
      }
    ]
  },
  {
    property: "address",
    value: { country: undefined, zipCode: 45678 },
    errors: ["Object is invalid"],
    children: [
      {
        property: "country", // property of the object
        value: null,
        errors: ["Has to be a valid string"],
        children: []
      }
    ]
  }
];

Appendix B: Advanced Usage

The below example is advanced and can be used as following: lets assume the frontend is sending a user's password in an encrypted format, then the below validator would be executed as following.

  1. Check if we got any string value isNotEmpty()

  2. Decrypt the received password decryptASE128('9sdsdafsdafafh8asdsdafsdaffh9h89')

  3. Validate if the password is secure enough isPassword()

  4. Hash the password, as we only need the hash to compare it transform((value: string) => Util.Hash(value))

class UserSchema extends ValidatorSchema<IUser> {
  password = Validator.String()
    .isNotEmpty()
    .decryptASE128("9sdsdafsdafafh8asdsdafsdaffh9h89")
    .isPassword()
    .map((value: string) => Util.Hash(value));
}

Appendix C: Validator Utility

The Validator Utility is a handy tool that offers simple and effective validation for various entities such as emails, domains, passwords, etc. It provides two core functions for handling validation tasks, allowing you to either simply check validity or obtain the specific reasons for validation failures.

Each validator has the following two methods

  1. isValid(entity: string): boolean, use this function when you only need to know if an entity is valid or not. The function returns a boolean value.

    if (Validator.Email.isValid(email)) {
      console.log("Email is valid!");
    } else {
      console.log("Email is invalid!");
    }
  2. validate(entity: string): string[], use this function when you need to know the reasons for validation failure, such as when you need to display specific feedback to the user. The function returns an array of strings that describe the validation errors.

    //0 - validate the mail
    const errors: string[] = Validator.Email.validate(email);
    
    //1 - show the errors
    if (errors.length === 0) {
      console.log("Email is valid!");
    } else {
      console.log("Email is invalid for the following reasons: ", errors);
    }