fluentvalidation-ts

website·Indexed 32 days ago

https://fluentvalidation-ts.alexpotter.dev

A TypeScript validation library featuring a fluent API for building rule chains. It includes a variety of built-in rules such as .notEmpty, .emailAddress, .inclusiveBetween, and .must, as well as support for asynchronous validation via AsyncValidator and integration with tools like React Hook Form.

Tokens
14.9K
Snippets
71
Records
100
Agent score
51%

What's inside fluentvalidation-ts

  1. Overview of fluentvalidation-ts

    fluentvalidation-ts is a strongly-typed, framework-agnostic validation library for TypeScript models. It provides a fluent API for building validation rules, features zero dependencies, and maintains a small bundle size. It supports built-in validation rules and allows developers to create custom reusable rules for complex validation requirements.
  2. Overview of fluentvalidation-ts compatibility

    fluentvalidation-ts is a lightweight, framework-agnostic validation library designed for TypeScript. It has no dependencies and can be used with any front-end framework, though it is specifically designed to integrate well with React form libraries like Formik and React Hook Form.
  3. Perform asynchronous validation with .mustAsync

    The .mustAsync rule allows for custom asynchronous validation logic, such as performing API requests to check if a value is unique. This rule is only available when extending from AsyncValidator rather than the standard Validator. It functions identically to the .must rule, but requires a predicate function that returns a Promise<boolean>.
    import { AsyncValidator } from 'fluentvalidation-ts';
    
    type FormModel = {
      username: string;
    };
    
    class FormValidator extends AsyncValidator<FormModel> {
      constructor() {
        super();
    
        this.ruleFor('username').mustAsync(
          async (username) => await api.usernameIsAvailable(username)
        );
      }
    }
    
    const formValidator = new FormValidator();
    await formValidator.validateAsync({ username: 'ajp_dev123' });
  4. Handle multiple conditional rules in a single chain

    When multiple .when or .unless calls are used in a single rule chain, each condition applies only to the rules defined between it and the previous condition. This allows for granular control over different segments of a validation chain for the same property.
    this.ruleFor('accountBalance')
      .greaterThanOrEqualTo(0)
      .unless((formModel) => formModel.allowOverdrafts) // Applies to greaterThanOrEqualTo(0)
      .greaterThanOrEqualTo(100)
      .unless((formModel) => !formModel.subjectToMinimumBalance); // Applies to greaterThanOrEqualTo(100)
  5. Handle validation errors for nested object properties

    When a property is an object validated via .setValidator, the resulting error object mirrors the structure of the model. If a nested property fails, the error object contains a nested object with the specific property errors. However, if a rule is applied directly to the object property itself (using .ruleFor) and fails before the .setValidator rule, the error value will be a string instead of a nested object.
    import { Validator } from 'fluentvalidation-ts';
    
    type ContactDetails = { name: string; emailAddress: string; };
    class ContactDetailsValidator extends Validator<ContactDetails> {
      constructor() {
        super();
        this.ruleFor('name').notEmpty().withMessage('Please enter your name');
      }
    }
    
    const contactDetailsValidator = new ContactDetailsValidator();
    
    type FormModel = { contactDetails: ContactDetails | null };
    class FormValidator extends Validator<FormModel> {
      constructor() {
        super();
        this.ruleFor('contactDetails')
          .notNull() // If this fails, result is { contactDetails: 'Value cannot be null' }
          .setValidator(() => contactDetailsValidator); // If this fails, result is { contactDetails: { name: '...' } }
      }
    }
  6. Implement a nested validator that depends on the base model

    Use the producer function in .setValidator to pass values from the base model into the constructor of the nested validator. This is useful for conditional validation where the requirements of a nested object depend on a flag or value in the parent model.
    import { Validator } from 'fluentvalidation-ts';
    
    type ContactDetails = {
      name: string;
      emailAddress: string | null;
    };
    
    class ContactDetailsValidator extends Validator<ContactDetails> {
      constructor(emailAddressIsRequired: boolean) {
        super();
        this.ruleFor('name').notEmpty();
        this.ruleFor('emailAddress')
          .notNull()
          .when(() => emailAddressIsRequired);
        this.ruleFor('emailAddress').emailAddress();
      }
    }
    
    type FormModel = {
      signUpToMailingList: boolean;
      contactDetails: ContactDetails;
    };
    
    class FormValidator extends Validator<FormModel> {
      constructor() {
        super();
        // The producer function provides access to the base model (formModel)
        this.ruleFor('contactDetails').setValidator(
          (formModel) => new ContactDetailsValidator(formModel.signUpToMailingList)
        );
      }
    }
  7. Validate a property using a simple predicate with .must()

    The .must rule ensures a property is valid based on a predicate function. The predicate receives the property value and the full model, returning true if valid and false if invalid. If no custom message is provided, the default error message is 'Value is not valid'.
    import { Validator } from 'fluentvalidation-ts';
    
    type FormModel = {
      yearsInCurrentJob: number;
      age: number;
    };
    
    class FormValidator extends Validator<FormModel> {
      constructor() {
        super();
        // Predicate dependent on both value and model
        this.ruleFor('yearsInCurrentJob').must(
          (yearsInCurrentJob, formModel) => yearsInCurrentJob < formModel.age
        );
      }
    }
  8. Validate nested objects with setValidator

    To validate a nested object property, create a separate Validator class for the nested type and link it to the parent validator using the .setValidator() rule. The resulting error object will mirror the nested structure of the model.
    type Pet = { name: string; species: string; };
    
    class PetValidator extends Validator<Pet> {
      constructor() {
        super();
        this.ruleFor('name').notEmpty().maxLength(100);
        this.ruleFor('species').notEmpty().maxLength(100);
      }
    }
    const petValidator = new PetValidator();
    
    // In the parent FormValidator:
    this.ruleFor('pet')
      .notNull()
      .setValidator(() => petValidator);
    
    // Result for invalid pet: { pet: { name: 'Value cannot be empty' } }
  9. Ensure a property is null using the .null rule

    The .null rule validates that a property value is null. By default, it also considers undefined values as valid. If you need to validate specifically for undefined only, use the .undefined rule instead.
    import { Validator } from 'fluentvalidation-ts';
    
    type FormModel = {
      apiError?: string | null;
    };
    
    class FormValidator extends Validator<FormModel> {
      constructor() {
        super();
        // Validates that apiError is either null or undefined
        this.ruleFor('apiError').null();
      }
    }
    
    const formValidator = new FormValidator();
    formValidator.validate({ apiError: null });      // Valid
    formValidator.validate({ apiError: undefined }); // Valid
    formValidator.validate({ apiError: 'Error' });    // Invalid: { apiError: 'Value must be null' }
  10. Create a basic validator with fluentvalidation-ts

    To validate a TypeScript model, extend the Validator base class and define rules for properties in the constructor using the .ruleFor() method. Use the .validate() method on an instance of the validator to check a model; it returns an object where keys are the invalid field names and values are the error messages.
    import { Validator } from 'fluentvalidation-ts';
    
    type FormModel = {
      name: string;
      age: number;
    };
    
    class FormValidator extends Validator<FormModel> {
      constructor() {
        super();
        this.ruleFor('name').notEmpty().maxLength(100);
        this.ruleFor('age').greaterThanOrEqualTo(0);
      }
    }
    
    const formValidator = new FormValidator();
    const invalid: FormModel = { name: '', age: 26 };
    console.log(formValidator.validate(invalid)); // { name: 'Value cannot be empty' }
  11. Transform property values before validation with .ruleForTransformed()

    Use .ruleForTransformed() to convert a property value (e.g., a string from a text input) into a different type before applying validation rules. This allows you to use type-specific validators (like .greaterThanOrEqualTo()) on the transformed value.
    const numberOrNull = (value: string) =>
      isNaN(Number(value)) ? null : Number(value);
    
    this.ruleForTransformed('age', numberOrNull)
      .notNull()
      .withMessage('Please enter a number')
      .greaterThanOrEqualTo(0)
      .withMessage('Please enter a non-negative number');
  12. Define validation rules using .ruleFor

    The .ruleFor method on the Validator class is used to create rule chains for specific properties of a model. It is called within the validator's constructor. The method is strongly typed, ensuring that only existing properties of the model can be targeted. Calling .ruleFor returns a rule chain builder that allows you to attach built-in validation rules, custom error messages, and execution conditions.
    import { Validator } from 'fluentvalidation-ts';
    
    type FormModel = {
      name: string;
      isEmployed: boolean;
      jobTitle: string | null;
    };
    
    class FormValidator extends Validator<FormModel> {
      constructor() {
        super();
    
        // Basic rule chain for the 'name' property
        this.ruleFor('name').notEmpty().maxLength(100);
      }
    }