-
Notifications
You must be signed in to change notification settings - Fork 1
/
base64.ts
50 lines (46 loc) · 1.73 KB
/
base64.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { pipe } from 'fp-ts/function';
import * as RE from 'fp-ts/ReaderEither';
import { ask, asks, validate, VariableDecoder, withOpt } from './VariableDecoder';
type Options = {
/**
* Length of the buffer (in bytes)
*/
length?: number | undefined;
/**
* Maximum length of the buffer that will be accepted (in bytes, includes)
*/
maxLength?: number | undefined;
/**
* Minimum length of the buffer that will be accepted (in bytes, includes)
*/
minLength?: number | undefined;
};
/**
* Decodes base64 string to a `Buffer`.
*
* An empty string will be rejected.
*
* N.B. Base64 split the binary data into 6-byte chunks while `Buffer` is a sequence of 8-bytes chunks.
*/
const base64 = (options: Options = {}): VariableDecoder<Buffer> =>
pipe(
RE.Do,
RE.bind('value', () => ask()),
RE.bind('buffer', () => asks((value) => Buffer.from(value, 'base64'))),
RE.chain(validate(({ value }) => value !== '', 'must be a non-empty base64 string')),
RE.chain(
validate(
({ buffer, value }) => buffer.toString('base64') === value,
'must be a valid base64 string that can be converted to Buffer',
),
),
RE.map(({ buffer }) => buffer),
withOpt(options.length)((len) => RE.chain(validate((buf) => buf.length === len, `must be ${len} bytes`))),
withOpt(options.maxLength)((max) =>
RE.chain(validate((buf) => buf.length > max, `must be smaller than or equal to ${max} bytes`)),
),
withOpt(options.minLength)((min) =>
RE.chain(validate((buf) => buf.length < min, `must be greater than or equal to ${min} bytes`)),
),
);
export { base64 };