Using fp-ts and newtype-ts: implementation
Tags: typescript, functional
Published: 2021-03-24 12:00:25

Here's the final type definition we got in the previous article:

```ts
interface UnverifiedUser {
  readonly type: 'UnverifiedUser'
  readonly firstName: NonEmptyString50
  readonly lastName: NonEmptyString50
  readonly emailAddress: EmailAddress
  readonly middleNameInitial: Option<Char>
  readonly remainingReadings: PositiveInteger
}

interface VerifiedUser extends Omit<UnverifiedUser, 'type' | 'remainingReadings'> {
  readonly type: 'VerifiedUser'
  readonly verifiedDate: Timestamp
}

type User = UnverifiedUser | VerifiedUser
```

In this article, we are going to write the function that takes some `unknown` data as input, and returns either a list of error messages or a `User` as output.

For each new type we defined previously, we'll write a [type guard](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) function and a "validation" function (we'll call it a _parser_), that will use the type guard to return either an error message or a value with the correct type.

Finally, we'll compose all these functions to create the `User` data object if the input is valid, or get a list of error messages otherwise.

The objective is to write the implementation of the following function:

```ts
declare function parseUser(input: unknown): Either<string[], User>
```

## Type guards and parsers

The parsers are functions that take an `unknown` value and return either a list of error messages, or a validated value for the domain.

```ts
import { NonEmptyArray } from 'fp-ts/NonEmptyArray'
import { Either } from 'fp-ts/Either'

type Validation<A> = Either<NonEmptyArray<string>, A>

type Parser<A> = (value: unknown) => Validation<A>
```

I chose to use a `NonEmptyArray` for the list of error messages because the list cannot be empty. If the list is empty then it means there is no error, so the "right" side of the `Either` data type should be used, and not the "left" one that holds the list of errors.

We want a bunch of `Validation<A>` objects that we are going to combine in some way to create a `Validation<User>`.

### First and last names

Let's write a type guard function to validate that an `unknown` value is actually a `NonEmptyString50`:

```ts
import { isNonEmptyString } from 'newtype-ts/lib/NonEmptyString'

function isNonEmptyString50(s: unknown): s is NonEmptyString50 {
  return typeof s === 'string' && isNonEmptyString(s) && s.length <= 50
}
```

First we make sure that the value provided is a `string`. Then, we use the `isNonEmptyString` type guard provided by `newtype-ts` to make sure the string is not empty. Finally, we make sure that its size is lower than 51 characters.

Let's use this type guard to build a `Parser<NonEmptyString50>` for the first and last names:

```ts
import * as E from 'fp-ts/Either'
import * as NEA from 'fp-ts/NonEmptyArray'

const parseName = (label: string) =>
  E.fromPredicate(
    isNonEmptyString50,
    invalidValue => NEA.of(`${label} value must be a string (size between 1 and 50 chars), got: ${invalidValue}`)
  )

const parseFirstName: Parser<NonEmptyString50> = parseName('First name')

const parseLastName:  Parser<NonEmptyString50> = parseName('Last name')
```

I chose to provide a human-readable error message as a `string`. I could've used an object instead, something such as:

```ts
enum ErrorType {
  InvalidNonEmptyString50
}

const parseName = (label: string) =>
  E.fromPredicate(
    isNonEmptyString50,
    invalidValue => NEA.of({
      errorType: ErrorType.InvalidNonEmptyString50,
      value: invalidValue
    })
  )
```

Then, let the caller use this object to build a human-readable (with localization support?) error message. Here, I favored simplicity and directly set the error message in the `Validation<NonEmptyString50>` data type.

### Email address

```ts
function isEmailAddress(s: unknown): s is EmailAddress {
  // https://stackoverflow.com/a/201378/5202773
  const emailPattern = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/i
  return typeof s === 'string' && emailPattern.test(s)
}
```

Nothing extraordinary here (besides this beautiful regular expression). We make sure the value is a `string` that has a certain pattern. Let's write the associated parser function:

```ts
const parseEmailAddress: Parser<EmailAddress> =
  E.fromPredicate(
    isEmailAddress,
    invalidValue => NEA.of(`Email address value must be a valid email address, got: ${invalidValue}`)
  )
```

### Middle name initial

The `newtype-ts` library already provides a type guard to validate a single character: `isChar`. Sadly, there is no [type predicate](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates) in the [current version (v0.3.4) of this function](https://github.com/gcanti/newtype-ts/blob/4c4c94c4f21664871b2a8e3f031fa052f71157e0/src/Char.ts#L16), so we'll have to make a type assertion to tell TypeScript that it's indeed a `Char` if the function returns true, and not just a regular `string`.

This parser is a bit more complicated than the previous ones because the middle name initial is an optional property. We want to write a `Parser<Option<Char>>`.

First we create an `Option<string>` from an `unknown` value. Next, we need to make sure this `string` has only 1 character, by using the `isChar` type guard. We could use `Option.map` in order to access the `string` value that we need to validate into a `Char`:

```ts
import * as O from 'fp-ts/Option'
import { isChar } from 'newtype-ts/lib/Char'

const parseMiddleNameInitial: O.Option<Parser<Char>> = flow(
  O.fromPredicate((s: unknown): s is string => typeof s === 'string'),
  O.map(E.fromPredicate(
    isChar,
    invalidValue => NEA.of(`Middle name initial value must be a single character, got: ${invalidValue}`)
  ))
) as O.Option<Parser<Char>>
```

The problem here is that we end up with a `Option<Parser<Char>>`, but we want a `Parser<Option<Char>>`. To invert the order while still applying the `E.fromPredicate` function, we can use _traverse_:

```ts
const parseMiddleNameInitial: Parser<O.Option<Char>> = flow(
  O.fromPredicate((s: unknown): s is string => typeof s === 'string'),
  O.traverse(E.either)(
    E.fromPredicate(
      isChar,
      invalidValue => NEA.of(`Middle name initial value must be a single character, got: ${invalidValue}`)
    )
  )
) as Parser<O.Option<Char>>
```

Some data types are not traversable (e.g. `IO`), so the `traverse` function may not be available. Here, `Option` is traversable and `E.either` is an instance of [Applicative](https://github.com/fantasyland/fantasy-land#applicative), so we can use it to transform an `Option<Either<string>>` into a `Parser<Option<Char>>`.

### Remaining readings

This one needs 2 validation steps:
- First we need to make sure the value is a `number`,
- Then check that this `number` is actually a `PositiveInteger`. We can do that by using the type guard provided by `newtype-ts`.

```ts
import { isPositiveInteger } from 'newtype-ts/lib/PositiveInteger'

const parseRemainingReadings: Parser<PositiveInteger> = flow(
  E.fromPredicate(
    (n: unknown): n is number => typeof n === 'number',
    invalidValue => NEA.of(`Remaining readings value must be a number, got: ${invalidValue}`)
  ),
  E.chain(
    E.fromPredicate(
      isPositiveInteger,
      invalidValue => NEA.of(`Remaining readings value must be a positive integer, got: ${invalidValue}`)
    )
  )
) as Parser<PositiveInteger>
```

Same as `isChar`, `isPositiveInteger` doesn't use a type predicate in the version I'm using (v0.3.4), so we need to use a type assertion to tell TypeScript that it's actually a `PositiveInteger`.

### Verified date

Last parser to write! We need a type guard to make sure the value provided is a `Timestamp`, i.e. an `Integer` comprised between -8640000000000000 and 8640000000000000.

```ts
import { isInteger } from 'newtype-ts/lib/Integer'

function isTimestamp(t: unknown): t is Timestamp {
  return typeof t === 'number' && isInteger(t) && t >= -8640000000000000 && t <= 8640000000000000
}
```

Finally, we can write the parser simply with:

```ts
const parseTimestamp: Parser<Timestamp> =
  E.fromPredicate(
    isTimestamp,
    invalidValue => NEA.of(`Timestamp value must be a valid timestamp (integer between -8640000000000000 and 8640000000000000), got: ${invalidValue}`)
  )
```

### User-like object

One last step before we move on to the constructors and composition of all these parsers. The input value is `unknown`. We have to make sure this input is actually an object that "looks like a user" before trying to validate each of its properties. In other words, we need a parser that takes `unknown` and returns a `Validation<UserLike>`:

```ts
interface UserLike {
  readonly firstName: unknown
  readonly lastName: unknown
  readonly emailAddress: unknown
  readonly middleNameInitial?: unknown
  readonly remainingReadings?: unknown
  readonly verifiedDate?: unknown
}

// or, using mapped types and type intersection
type UserLike =
  Record<'firstName' | 'lastName' | 'emailAddress', unknown> &
  Partial<Record<'middleNameInitial' | 'verifiedDate' | 'remainingReadings', unknown>>
```

The type guard:

```ts
function isUserLike(value: unknown): value is UserLike {
  return (
    typeof value === 'object' &&
    value !== null &&
    'firstName' in value &&
    'lastName' in value &&
    'emailAddress' in value
  )
}
```

And the parser:

```ts
const parseUserLike: Parser<UserLike> =
  E.fromPredicate(
    isUserLike,
    invalidValue => NEA.of(`Input value must have at least firstName, lastName and emailAddress properties, got: ${JSON.stringify(invalidValue)}`)
  )
```

## Constructors

Before combining all these parsers to build a `Validation<User>`, we'll declare 2 constructors, one for each type of user (unverified and verified):

```ts
const unverifiedUser = (fields: Omit<UnverifiedUser, 'type'>): User => ({
  type: 'UnverifiedUser',
  ...fields
})

const verifiedUser = (fields: Omit<VerifiedUser, 'type'>): User => ({
  type: 'VerifiedUser',
  ...fields
})
```

We'll build a valid object that is almost a user (thanks to the parsers we wrote), then we'll call one of these constructors to finalize the creation of a `User`.

## Functions composition

It's time to glue all these pieces together!

Let's start by getting a `UserLike` object:

```ts
import { pipe } from 'fp-ts/function'

const parseUser: (input: unknown) => Validation<User> =
  flow(
    parseUserLike,
    // Validation<UserLike>
  )
```

Next, we need to validate the properties that are common to both unverified and verified users, i.e. first and last names, email address, and middle name initial.

We could _chain_ the parsers to validate these properties one by one:

```ts
// { firstName: NonEmptyString50, lastName: NonEmptyString50, emailAddress: EmailAddress, middleNameInitial: O.Option<Char>, verifiedDate?: unknown, remainingReadings?: unknown }
type UserLikePartiallyValid =
  Pick<User, 'firstName' | 'lastName' | 'emailAddress' | 'middleNameInitial'> &
  Pick<UserLike, 'remainingReadings' | 'verifiedDate'>

const parseUser: (input: unknown) => Validation<User> =
  flow(
    parseUserLike,
    E.chain(userLikeObject => pipe(
      parseFirstName(userLikeObject.firstName),
      E.map(firstName => ({ ...userLikeObject, firstName }))
    )),
    E.chain(userLikeObject => pipe(
      parseLastName(userLikeObject.lastName),
      E.map(lastName => ({ ...userLikeObject, lastName }))
    )),
    E.chain(userLikeObject => pipe(
      parseEmailAddress(userLikeObject.emailAddress),
      E.map(emailAddress => ({ ...userLikeObject, emailAddress }))
    )),
    E.chain(userLikeObject => pipe(
      parseMiddleNameInitial(userLikeObject.middleNameInitial),
      E.map(middleNameInitial => ({ ...userLikeObject, middleNameInitial }))
    )) // Validation<UserLikePartiallyValid>
  )
```

But this is very verbose, and there's a lot of repetition. In addition, we can't aggregate all the error messages: the chain breaks as soon as an error occurs.

In theory, we could run the 4 parsers in parallel since they are all independent. We can use `Apply`, and more specifically the `sequenceS` function provided by `fp-ts`, to make the following transformation:

```ts
// use Apply to transform:
type From<A, B, C> = {
  a: Validation<A>
  b: Validation<B>
  c: Validation<C>
}
// into:
type To<A, B, C> = Validation<{
  a: A
  b: B
  c: C
}>
```

This will allow us to build the same object as above (a `UserLikePartiallyValid`), but with fewer steps. Additionally, the error messages will be aggregated instead of fast-failing at the first error encountered.

```ts
import * as A from 'fp-ts/Apply'
import * as E from 'fp-ts/Either'
import * as NEA from 'fp-ts/NonEmptyArray'

// This Applicative instance allows for error messages to be aggregated,
// using the Semigroup instance of NonEmptyArray.
const validationApplicativeInstance =
  E.getApplicativeValidation(NEA.getSemigroup<string>())

// This is where the From -> To transformation occurs
const validateStruct = A.sequenceS(validationApplicativeInstance)
```

This is how we can use `validateStruct` on the `UserLike` object:

```ts
const validateCommonProperties = ({
  firstName,
  lastName,
  emailAddress,
  middleNameInitial,
  ...rest
}: UserLike): Validation<UserLikePartiallyValid> =>
  pipe(
    validateStruct({
      firstName:         parseFirstName(firstName),
      middleNameInitial: parseMiddleNameInitial(middleNameInitial),
      lastName:          parseLastName(lastName),
      emailAddress:      parseEmailAddress(emailAddress)
    }),
    E.map(validProperties => ({ ...validProperties, ...rest }))
  )

const parseUser: (input: unknown) => Validation<User> =
  flow(
    parseUserLike,
    E.chain(validateCommonProperties) // Validation<UserLikePartiallyValid>
  )
```

At this point, all the properties common to both `UnverifiedUser` and `VerifiedUser` have been validated. Now, we need to detect if the user is verified or not. In our domain, we consider a user to be verified if it has a `verifiedDate` property defined:

```ts
const detectUserVerification = <A>({
  onUnverified,
  onVerified
}: {
  onUnverified: (userLikeObject: UserLikePartiallyValid) => A
  onVerified: (userLikeObject: UserLikePartiallyValid & { verifiedDate: unknown }) => A
}) => ({ verifiedDate, ...rest }: UserLikePartiallyValid): A =>
  pipe(
    O.fromNullable(verifiedDate),
    O.fold(
      () =>           onUnverified(rest),
      verifiedDate => onVerified({ ...rest, verifiedDate })
    )
  )
```

The object parameter (with the `onUnverified` and `onVerified` properties) allows for a continuation, with a feel of _pattern matching_. If the `UserLikePartiallyValid` object looks like an unverified user, then we need to check the `remainingReadings` property in the "onUnverified" path. Otherwise, we need to make sure the `verifiedDate` property is valid in the "onVerified" path. In both cases, we end up with a `Validation<User>`, which is exactly what we were looking for!

```ts
const validateUnverifiedUser = (userLikeObject: UserLikePartiallyValid): Validation<User> =>
  pipe(
    parseRemainingReadings(userLikeObject.remainingReadings),
    E.map(remainingReadings => unverifiedUser({ ...userLikeObject, remainingReadings }))
  )

const validateVerifiedUser = (userLikeObject: UserLikePartiallyValid & { verifiedDate: unknown }): Validation<User> =>
  pipe(
    parseTimestamp(userLikeObject.verifiedDate),
    E.map(verifiedDate => verifiedUser({ ...userLikeObject, verifiedDate }))
  )

const parseUser: (input: unknown) => Validation<User> =
  flow(
    parseUserLike,
    E.chain(validateCommonProperties),
    E.chain(
      detectUserVerification({
        onUnverified: validateUnverifiedUser,
        onVerified:   validateVerifiedUser
      })
    ) // Validation<User>
  )
```

And that's it! We can now validate any kind of input value, and get a list of error messages, or a valid `User` object:

```ts
const res0 = parseUser(42)
/*
Left([
  'Input value must have at least firstName, lastName and emailAddress properties, got: 42'
])
*/

const res1 = parseUser({ firstName: 'Bob' })
/*
Left([
  'Input value must have at least firstName, lastName and emailAddress properties, got: {"firstName":"Bob"}'
])
*/

const res2 = parseUser({ firstName: 'Bob', lastName: 42, emailAddress: 'foo' })
/*
Left([
  'Last name value must be a string (size between 1 and 50 chars), got: 42',
  'Email address value must be a valid email address, got: foo'
])
*/

const res3 = parseUser({
  firstName: 'Bob',
  middleNameInitial: 'B',
  lastName: 'Barker',
  emailAddress: 'test@yes.com',
  remainingReadings: 3
})
/*
Right({ type: 'UnverifiedUser', middleNameInitial: Some('B'), ... })
*/

const res4 = parseUser({
  firstName: 'Bob',
  middleNameInitial: 'B',
  lastName: 'Barker',
  emailAddress: 'test@yes.com',
  verifiedDate: 1615339130200
})
/*
Right({ type: 'VerifiedUser', middleNameInitial: None, ... })
*/
```

The source code is available on the [ruizb/domain-modeling-ts](https://github.com/ruizb/domain-modeling-ts/blob/master/method-1/implementation.ts) GitHub repository.

## Summary

If you've made it this far, congratulations!

In the previous article, we built a type definition that described the domain constraints and logic. Here, we created type guards, parsers and constructors for the new types. Then, we combined these elements together to go from an `unknown` input to either a list of error messages or a valid `User` object. We can safely use this `User` in the functions containing the business logic of our domain!

We used `newtype-ts` and `fp-ts` to do that. But what if I told you there's already a library in this ecosystem that is specifically useful for data validation? In the next and final article of this series, we'll use this library (spoiler alert: it's `io-ts`) for both the implementation and type definition!