Like Souperman, I really like alt
here and like user1713450 I like io-ts
here as well. Even if the input is unknown
we can define what we care about and code against that. One of the things I really like about alt
is its flexibility when we need to add more conditions. Say you want to check on a new property then you just add the new alt. The getCategory
function stays very readable.
import * as O from 'fp-ts/Option'
import {pipe} from 'fp-ts/function'
import * as t from 'io-ts'
import * as A from 'fp-ts/Array'
type Category = {
id: string
slug: string
}
const PossibleCategory = t.union([
t.partial({
id:t.string,
slug:t.string
}),
t.undefined])
type PossibleCategory = t.TypeOf<typeof PossibleCategory>
const getCategory = (possibleCategory: PossibleCategory, categories: Category[]) => pipe(
categoryById(possibleCategory, categories),
O.alt(() => categoryBySlug(possibleCategory, categories))
)
const categoryById = (possibleCategory: PossibleCategory, categories: Category[]):O.Option<Category> => pipe(
O.fromNullable(possibleCategory?.id),
O.chain(id => pipe(categories, A.findFirst(c => c.id === id)))
)
const categoryBySlug = (possibleCategory: PossibleCategory, categories: Category[]): O.Option<Category> => pipe(
O.fromNullable(possibleCategory?.slug),
O.chain(slug => pipe(categories, A.findFirst(c => c.slug === slug)))
)
The second scenario does make the getCategory
function somewhat less readable. As mentioned by cherryblossum, it goes the fold
route.
import * as O from 'fp-ts/Option'
import {pipe, flow, identity} from 'fp-ts/function'
import * as t from 'io-ts'
import * as E from 'fp-ts/Either'
type Category = {
id: string
slug: string
}
const PossibleCategory = t.union([
t.partial({
id:t.string,
slug:t.string
}),
t.undefined])
type PossibleCategory = t.TypeOf<typeof PossibleCategory>
type GetCategory = (x:string) => E.Either<Error, O.Option<Category>>
// placeholders for db calls
const getCategoryById:GetCategory = (x:string) => E.right(O.none)
const getCategoryBySlug:GetCategory = (x:string) => E.right(O.none)
declare const categories: Category[];
const getCategory = (possibleCategory: PossibleCategory) => pipe(
categoryById(possibleCategory),
E.chain(
O.fold(
() => categoryBySlug(possibleCategory),
c => E.right(O.some(c))
)
)
)
const categoryById = (possibleCategory: PossibleCategory) => pipe(
O.fromNullable(possibleCategory?.id),
O.map(
flow(
getCategoryById,
E.chainOptionK(() => new Error('id not found'))(identity),
)
),
O.sequence(E.Monad),
)
const categoryBySlug = (possibleCategory:PossibleCategory)=> pipe(
O.fromNullable(possibleCategory?.slug),
O.map(
flow(
getCategoryBySlug,
E.chainOptionK(() => new Error('slug not found'))(identity),
)
),
O.sequence(E.Monad)
)