Skip to content

Commit baff7c6

Browse files
committed
feat: add "match" util
1 parent 6b4219b commit baff7c6

File tree

3 files changed

+169
-11
lines changed

3 files changed

+169
-11
lines changed

src/pages/_error.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { Box } from 'components/atoms/Box'
22
import type { NextPage } from 'next'
3+
import { __, match } from 'utils/match'
34

45
type Props = {
56
statusCode?: number
67
}
78

89
const Error: NextPage<Props, unknown> = ({ statusCode }) => {
9-
const content = (() => {
10-
if (statusCode === 404) return 'Oops, missing page'
11-
if (statusCode) return `An error ${statusCode} occurred on the server`
12-
else return 'An error occurred on the client'
13-
})()
10+
const content = match(statusCode)
11+
.if(__.isEqual(404), () => 'Oops, missing page')
12+
.if(__.isDefined, code => `An error ${code} occurred on the server`)
13+
.else(() => 'An error occurred on the client')
1414

1515
return (
1616
<Box
@@ -27,13 +27,23 @@ const Error: NextPage<Props, unknown> = ({ statusCode }) => {
2727
}
2828

2929
Error.getInitialProps = ({ err, res }): Props => {
30-
const statusCode = (() => {
31-
if (err) return err.statusCode
32-
if (res) return res.statusCode
33-
else return 404
34-
})()
30+
const statusCode = match({
31+
error: err,
32+
response: res,
33+
})
34+
.if(
35+
({ error }) => __.isDefined(error),
36+
({ error }) => error?.statusCode
37+
)
38+
.if(
39+
({ response }) => __.isDefined(response),
40+
({ response }) => response?.statusCode
41+
)
42+
.else(() => 404)
3543

36-
return { statusCode }
44+
return {
45+
statusCode,
46+
}
3747
}
3848

3949
export default Error

src/utils/match/helpers.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export const __ = {
2+
isEqual<T extends string | number | boolean>(value: T) {
3+
return (
4+
check: T extends boolean | null
5+
? boolean | null
6+
: T extends null | string
7+
? string | null
8+
: T extends null | number
9+
? number | null
10+
: T extends string | number
11+
? string | number
12+
: T extends string
13+
? string
14+
: T extends number
15+
? number
16+
: boolean
17+
): boolean => {
18+
return value === (check as unknown as T)
19+
}
20+
},
21+
22+
isTruthy<T>(value: T) {
23+
return !!value
24+
},
25+
isNil<T>(a: T) {
26+
return a === undefined || a === null
27+
},
28+
isDefined<T>(a: T): boolean {
29+
return a !== undefined && a !== null
30+
},
31+
isArray(x: any): boolean {
32+
return !!x && x.constructor === Array
33+
},
34+
isObject(x: any) {
35+
return !!x && x.constructor === Object
36+
},
37+
isEmpty<T>(a: T): boolean {
38+
if (typeof a === 'string') return !a.length
39+
if (typeof a === 'number' || typeof a === 'boolean') return false
40+
if (__.isArray(a)) return !(a as unknown as []).length
41+
if (__.isObject(a)) return !Object.keys(a).length
42+
else return true
43+
},
44+
}

src/utils/match/index.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// 🙏 shoutout to -> https://github.com/gvergnaud/ts-pattern
2+
3+
type Union<a, b> = [b] extends [a] ? a : [a] extends [b] ? b : a | b
4+
5+
type Match<Value, InferredOutput = never> = {
6+
/**
7+
* if the `predicate` func returns true, the value returned by the `handler` will be the one returned when calling `else`.
8+
*
9+
* The matching `handler` func will be invoked only after `else` is called.
10+
*/
11+
if: <Output>(
12+
predicate: (value: NonNullable<Value>) => boolean,
13+
handler: (value: NonNullable<Value>) => Output
14+
) => Match<Value, Union<InferredOutput, Output>>
15+
/**
16+
* takes a function allowing one to return a fallback value in case no match were found
17+
*/
18+
else: <O>(fallback: () => O) => Union<InferredOutput, O>
19+
}
20+
21+
/**
22+
* Entry point to create some sort of a matching expression
23+
*
24+
* It returns a Match builder, on which you can chain several
25+
* `.if(predicate, handler)` clauses
26+
*
27+
* @example
28+
*
29+
* import { match, __ } from 'match'
30+
*
31+
* type Status = 'idle' | 'loading' | 'success' | 'error'
32+
*
33+
* const predicate: boolean
34+
* const status: Status
35+
*
36+
* const someExample = match(status)
37+
* .if(__.isEqual<Status>('idle'), s => <p>is {s}</p>) // s === idle
38+
* .if(__.isEqual<Status>('loading'), s => <p>is {s}</p>) // s === loading
39+
* .else(() => <p>no matches were found</p>)
40+
*
41+
* const otherExample = match({ status, predicate })
42+
* .if(
43+
* ({ status, predicate }) => status === 'success' && predicate,
44+
* () => <p>ok</p>
45+
* )
46+
* .if(
47+
* ({ status, predicate }) => status === 'error' && !predicate,
48+
* () => <p>great</p>
49+
* )
50+
* .else(() => <p>no matches were found</p>)
51+
*/
52+
/* eslint-disable fp/no-mutation, fp/no-let */
53+
function match<Value, InferredOutput = never>(value?: Value) {
54+
let getMatchingHandlerValue: () => any = () => undefined
55+
let hasFoundMatch = false
56+
let hasPredicatePassed = false
57+
58+
function makeMatchBuilder(passedValue: NonNullable<Value>) {
59+
const matchBuilder = {} as Match<Value, InferredOutput>
60+
61+
function if_<Output>(
62+
predicate: (value: NonNullable<Value>) => boolean,
63+
handler: (value: NonNullable<Value>) => Output
64+
): Match<Value, Union<InferredOutput, Output>> {
65+
try {
66+
if (predicate(passedValue)) hasPredicatePassed = true
67+
} catch (error) {}
68+
69+
if (!hasFoundMatch && hasPredicatePassed) {
70+
hasFoundMatch = true
71+
getMatchingHandlerValue = () => handler(passedValue)
72+
}
73+
74+
return makeMatchBuilder(passedValue) as unknown as Match<
75+
Value,
76+
Union<InferredOutput, Output>
77+
>
78+
}
79+
80+
function else_<E>(fallback: () => E) {
81+
let handlerValue
82+
let hasError = false
83+
try {
84+
handlerValue = getMatchingHandlerValue()
85+
} catch (error) {
86+
hasError = true
87+
console.error('Match: There was a problem inside your handler function 👇')
88+
console.error(error)
89+
}
90+
91+
return hasError || !hasFoundMatch ? fallback() : handlerValue
92+
}
93+
94+
matchBuilder.if = if_
95+
matchBuilder.else = else_
96+
97+
return matchBuilder
98+
}
99+
100+
return makeMatchBuilder(value as NonNullable<Value>)
101+
}
102+
103+
export { match }
104+
export { __ } from './helpers'

0 commit comments

Comments
 (0)