Resource acquisition with Typescript, Reader Monad and FP-TS
Tags: typescript, fpts, readermonad, functional
Published: 2021-02-15 16:46:21

How many times you have to converting/mapping something from-to different domains?

This post shows an example of how is possible to make a conversion from a string to a custom mapped type with all benefit of compile-time type safeness, functional programming and easy to test.

## Introduction

Suppose we are in a Node environment and we want to map a string URL into a custom interface.

Node.js is shipped with an `URL` class capable of parsing an URL string and extract all bit of pieces: https://nodejs.org/api/url.html

Accessing the query parameters of the URL is very straightforward:
```ts
interface UriParams {
  partyId: string
  passphrase: string
  username: string
  ticket: string
}

function convertToUriParams(u: string) {
  const { searchParams: sp } = new URL(u, 'http://0.0.0.0/')

  const fallbacks = {
    partyId: uuid(),
    passphrase: '',
    username: 'Anoymous',
    ticket: '',
  }

  const entries = Object.entries(fallbacks)
  const params = entries.map(([k, f]: [string, string | boolean]) => [k, sp.get(k) ?? f])

  return Object.fromEntries(params)
}

convertToUriParams('party?partyId=42&passphrase=fidelio')
```

But even if this approach is very simple there are some downsides...

1. it is not fully testable
2. compile type safety is absent
3. it changes variable state
4. every change breaks SOLID principles.

How can rewrite it taking advantage of FP and Typescript type checking?


## Reader Monad

Reader Monad is FP Monad capable to access (_read_) a resource and extracting all needed parts by taking full advantage of DI in a pure functional fashion, for more detail please read [Reader Monad](https://dev.to/gcanti/getting-started-with-fp-ts-reader-1ie5). 

But how can map this monad with our `UriParams`?

Borrowing Reader from [fp-ts](https://gcanti.github.io/fp-ts/modules/Reader.ts.html) lib we can start refactoring and encapsulating URL's `searchParams` as a dependency. Then we can access underlying data with something like that:

```ts
import * as R from 'fp-ts/Reader'

const { searchParams } = new URL('ws?userId=42', 'http://0.0.0.0/')

const getParamOr = (k: string, def = ''): R.Reader<URLSearchParams, string> =>
  R.asks((url: URLSearchParams) => url.get(k) ?? def)

// ...
const username = getParamOr('passphrase', 'fidelio')(searchParams) // => 'fidelio'
```

But how can apply this pattern to the full `UriParams` interface?

Looking from docs a possible solution could be to _lift_ our Reader with `sequenceS` and rewriting the previous example in order to accept non-empty structs too:

```ts
import * as R from 'fp-ts/Reader'
import { sequenceS } from 'fp-ts/Apply'

const factoryReader = (uuid: string) => ({
  partyId: getParamOr('partyId', uuid),
  passphrase: getParamOr('passphrase', ''),
  username: getParamOr('username', 'Anoymous'),
  ticket: getParamOr('ticket', ''),
})

// ...
sequenceS(R.reader)(factoryReader('zzz-yyy-xxx')),

```

Our code now is already more robust and flexible than before. 

### Adding specialization

Let try to add a new feature: how can we deal with fields that don't belong 1:1 to the URL string but are indirectly connected, for instance, with another field like `partyId` as a boolean?
Fair enough, with a Monad we can write a new _accessor_ `isEmpty` that:

```ts
// ...
import { pipe } from 'fp-ts/pipeable'

const isEmpty = (k: string): R.Reader<URLSearchParams, boolean> => pipe(
  getParamOr(k, ''),
  R.map((s) => '' === s)
)
```
And then add this to our `factoryReader`:

```ts

import * as R from 'fp-ts/Reader'
import { pipe } from 'fp-ts/pipeable'
import { sequenceS } from 'fp-ts/Apply'

interface UriParams {
  partyId: string
  passphrase: string
  username: string
  ticket: string
  isNew: boolean
}

const factoryReader = (uuid: string) => ({
  partyId: getParamOr('partyId', uuid),
  passphrase: getParamOr('passphrase', ''),
  username: getParamOr('username', 'Anoymous'),
  ticket: getParamOr('ticket', ''),
  isNew: isEmpty('partyId'), // 
})

// ...
const parser = sequenceS(R.reader)(factoryReader(uuid)),
parser(searchParams)
```

### Invocation improvements

We have gained a lot of code improvement for now, but the actual implementation of `factoryReader` accepts only `URL` instances.
 
How can we rethink our `factoryReader` to accept a string and to inject automatically a `searchParam` instead? 

Yes, we can... We can solve this problem using `local` method (a sort of `contramap`) and wrapping `sequenceS` with a `pipe` inside a new function:

```ts
export const extractUriParams = (uuid: string): R.Reader<string, UriParams> => pipe(
  sequenceS(R.reader)(factoryReader(uuid)),
  R.local((u: URL) => u.searchParams),
  R.local((url: string) => new URL(url, 'http://0.0.0.0/'))
)

// ...
extractUriParams('xxx-yyy-zzz')('party/?partyId=321&passphrase=fidelio&b=2&c=3')
```

## Conclusion
For the full code please refer to this link Codesandbox
{% codesandbox gracious-leakey-sq423 runonclick=1 %}

I hope that you enjoy this post and if so please, share, like-it, and/or drop a comment. Thank you!