# @codeparticle/formal

> A simple data and form validation library with a wide range of possibilities.

### 🔧 Installation

```sh
yarn add @codeparticle/formal
```

## Usage

Formal makes it simple to validate arbitrary data with whatever rules you'd like. It also guarantees that when things fail, you know why, and have a plan B in place.

Here's a playful example:

```ts
import { Validator } from '@codeparticle/formal'
import { isString, minLength } from '@codeparticle/lib/rules'

const techPitchValidator = Validator.of(isString, minLength(32), maxLength(256))

const validPitch = "It's like your favorite social network, but for dogs"
const invalidPitch = "It's an AI to rule us all"

const validResult = techPitchValidator
  .validate(validPitch)
  .map((str) => str.toUpperCase())
// returns Success("IT'S LIKE YOUR FAVORITE SOCIAL NETWORK, BUT FOR DOGS") - maps can change successfully checked values

const invalidResult = techPitchValidator
  .validate(invalidPitch)
  .map((str) => str.toUpperCase())
// returns Fail('Must be at least 32 characters') - maps have no effect on failures

validResult.then({
  onSuccess: () => alert("We love it kid. Here's 5 million."),
  onFail: (errs) => console.log(errs), // can also simply be console.log
})

invalidResult.then({
  onSuccess: () => alert("We love it kid. Here's 5 million."),
  onFail: (errs) => errs.map(console.log), // 'Must be at least 32 characters'
})
```

### validateObject

For validating forms or large API responses, formal exposes a `validateObject` utility. Here's an example
of a form with a required name and email field.

```ts
import { rules, validateObject } from '@codeparticle/formal'

const { isNonEmptyString, isValidEmail } = rules

const validateForm = validateObject({
  name: [isNonEmptyString],
  email: [isNonEmptyString, isValidEmail],
})

const formValues = {
  name: 'hello',
  email: '',
}

validateForm(formValues)
```

calling this will return a schema like this:

```ts
 {
    hasErrors: true,
    errors: {
      email: ['Value must not be empty.', 'Must be a valid email']
    },
    values: {
      name: 'hello',
      email: ''
    }
 }
```

formal is flexible to your style, and exposes a `pipeValidators` function for writing validations in a more functional way. It condenses multiple checks into a function that encloses a value into a `Success` or `Fail` container.

Once run, these validation containers are supplied with an `isSuccess` property for use in filters,with the ability to reach for the internally held `value`. While not recommended for control flow, it's useful in cases where you're running validation over a long list of items, as well as in writing test cases.

```ts
import { pipeValidators, rules } from '@codeparticle/formal'
const { isString, minLength } = rules

// ...
const isLongString = pipeValidators([isString, minLength(50)])

values
  .filter((val) => isLongString(val).isSuccess)
  .map((container) => container.value)
  .map((str) => console.log(str))

// this technique can make testing a breeze.

// here, we want all of our objects to have a common property; maybe a required prop in a react component.

// while a bit contrived here, this method makes tests over complex, nested objects
// or other data simple to do.

const testObjects = [
  { required: 'present' },
  { required: 'present' },
  { required: 'wrong' },
]

const check = pipeValidators([isObject, hasProp('required')])

for (const test of testObjects) {
  expect(check(test).isSuccess).toBe(true) // passes

  expect(check(test).value).toBe('present') // fails
}
```

### Built-in Validators

Formal has a small set of useful checks built in to validate simple data.

```ts
import {
  // Basic validations that take no arguments when used in Validator.of //
  isString,
  isNumber,
  isObject,
  isArray,
  isNonEmptyString,
  isNonEmptyObject,
  isNonEmptyArray,
  isValidEmail, // Only validates format, not ownership.

  /**
   * checks if something is equal to another value.
   * if you're using this in validateObject to check that two fields match,
   * use it as [values => isEqualTo(values['otherFormField'])]
   */
  isEqualTo,

  // Validations that take arguments before being supplied to Validator or pipeValidators() //


  // Check that a value matches a given regex. matchesRegex(/[A-Z]) || matchesRegex(RegExp('[A-Z]'))
  matchesRegex,
  // Check that a string is x-characters long. Takes a value for the minimum. `minLength(10)`
  minLength,
  // Check that a string is no more than x-characters long. Takes a value for the maximum. `maxLength(50)`
  maxLength,
  // Check that a number is less than a certain amount. Takes a value for the minimum. `lessThan(50)`
  lessThan,
  // Check that a number is less than a certain amount. Takes a value for the maximum. `greaterThan(50)`
  greaterThan,
  // check that an object has a certain property. Takes a drilldown path supplied as strings. `hasProp('fieldName', 'subfield')`
  hasProp,
  // check that an object has a property at the given drilldown path, then return a Success object with its value. `getProp('fieldName', 'subfield')`
  getProp
} from '@codeparticle/formal';
...
```

### Customizing built-in or existing checks

Sometimes, the messages included with built-in or existing checks need to be modified after the fact. Formal supports this via the `withMessage` function.

`withMessage` creates a new copy of the rule, so don't worry about accidentally overwriting something important when using it. Like `createRule`, you can supply a string, or a function that returns a string.

```ts
import { withMessage, rules } from '@codeparticle/formal'

const withAdminFormErrorMessage = withMessage(
  `Admins must enter an administrator ID.`
)
const withUserFormErrorMessage = withMessage(
  `Users must enter their first and last name to sign up`
)

const withInternationalizedErrorMessage = withMessage(
  intl.formatMessage('form.error.message')
)

const withNewMessageFn = withMessage(
  (badValue) => `${badValue} is invalid for this field.`
)

const adminFormFieldCheck = withAdminFormErrorMessage(rules.isNonEmptyString)

const userFormFieldCheck = withUserFormErrorMessage(rules.isNonEmptyString)

const internationalizedFieldCheck = withInternationalizedErrorMessage(
  rules.isString
)

const customMessageFunctionFieldCheck = withNewMessageFn(rules.isNonEmptyString)
```

## Creating your own validators

Formal gives you the ability to create your own rules to supply to `Validator`. There are two ways to do so.

Using `createRule` is a quick way to check things that _don't change the value that comes out_.

`createRule` takes two (required) options - a condition function, and a message. The message can be a string, or it can be a function that returns a string, in case you'd like to tailor your messages.

```ts
import { createRule } from '@codeparticle/formal'
export const containsMatchingString = (match) =>
  createRule({
    condition: (str) => str.includes(match),
    message: (failedStr) => `Value ${failedStr} does not include today's date`,
  })
```

createRule also allows more customized checks through an optional parameter called `transform`
that allows for a transformation of the value _before_ it's handed off to the next validation check.

```ts
import { createRule } from '../rule'
import { hasProp } from './has-prop'

// below is the actual source code of the built-in getProp function.
export const getProp = (property) =>
  createRule({
    condition: (obj) => hasProp(property).check(obj).isSuccess,
    message: `Property '${property}' does not exist`,
    // transform the object to the value of the successfully found property
    // before handing off to the next check / function.
    transform: (obj) => obj[property],
  })
```

## Usage with Typescript

This package exports TS type definitions in the `/types` folder.

```ts
import {
  // interfaces for the Success and Fail classes
  Success,
  Fail,
  // used to specify the argument structure to `createValidator`
  CustomValidationOptions,
  // options supplied to Validator.then
  ValidationActions,
  // alias for `Success | Fail` in cases where both are needed.
  ValidationM,
  // aliases for a single or array of validation rules provided to Validator.of
  ValidationRule,
  // alias for a function that returns a Success or Failure.
  ValidationCheck,
  ValidationRuleSet,
  // interface for the Validator class
  Validator,
} from '@codeparticle/formal/types'
```

[![Build Status](https://travis-ci.org/codeparticle/codeparticle-formal.svg?branch=master)](https://travis-ci.org/codeparticle/codeparticle-formal)
[![NPM version](https://img.shields.io/npm/v/@codeparticle/codeparticle-formal.svg)](https://www.npmjs.com/package/@codeparticle/codeparticle-formal)
![Downloads](https://img.shields.io/npm/dm/@codeparticle/codeparticle-formal.svg)
[![Standard Version](https://img.shields.io/badge/release-standard%20version-brightgreen.svg)](https://github.com/conventional-changelog/standard-version)
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)

---

### 🥂 License

[MIT](./LICENSE.md) as always
