/
webAuthority.ts
118 lines (114 loc) · 3.64 KB
/
webAuthority.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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
import { buildAlphaNumericPercentArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder';
import { constant } from './constant';
import { domain } from './domain';
import { ipV4 } from './ipV4';
import { ipV4Extended } from './ipV4Extended';
import { ipV6 } from './ipV6';
import { nat } from './nat';
import { oneof } from './oneof';
import { option } from './option';
import { stringOf } from './stringOf';
import { tuple } from './tuple';
import type { SizeForArbitrary } from './_internals/helpers/MaxLengthFromMinLength';
/** @internal */
function hostUserInfo(size: SizeForArbitrary): Arbitrary<string> {
const others = ['-', '.', '_', '~', '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=', ':'];
return stringOf(buildAlphaNumericPercentArbitrary(others), { size });
}
/** @internal */
function userHostPortMapper([u, h, p]: [string | null, string, number | null]): string {
return (u === null ? '' : `${u}@`) + h + (p === null ? '' : `:${p}`);
}
/** @internal */
function userHostPortUnmapper(value: unknown): [string | null, string, number | null] {
if (typeof value !== 'string') {
throw new Error('Unsupported');
}
const atPosition = value.indexOf('@');
const user = atPosition !== -1 ? value.substring(0, atPosition) : null;
const portRegex = /:(\d+)$/;
const m = portRegex.exec(value);
const port = m !== null ? Number(m[1]) : null;
const host =
m !== null ? value.substring(atPosition + 1, value.length - m[1].length - 1) : value.substring(atPosition + 1);
return [user, host, port];
}
/** @internal */
function bracketedMapper(s: string): string {
return `[${s}]`;
}
/** @internal */
function bracketedUnmapper(value: unknown): string {
if (typeof value !== 'string' || value[0] !== '[' || value[value.length - 1] !== ']') {
throw new Error('Unsupported');
}
return value.substring(1, value.length - 1);
}
/**
* Constraints to be applied on {@link webAuthority}
* @remarks Since 1.14.0
* @public
*/
export interface WebAuthorityConstraints {
/**
* Enable IPv4 in host
* @defaultValue false
* @remarks Since 1.14.0
*/
withIPv4?: boolean;
/**
* Enable IPv6 in host
* @defaultValue false
* @remarks Since 1.14.0
*/
withIPv6?: boolean;
/**
* Enable extended IPv4 format
* @defaultValue false
* @remarks Since 1.17.0
*/
withIPv4Extended?: boolean;
/**
* Enable user information prefix
* @defaultValue false
* @remarks Since 1.14.0
*/
withUserInfo?: boolean;
/**
* Enable port suffix
* @defaultValue false
* @remarks Since 1.14.0
*/
withPort?: boolean;
/**
* Define how large the generated values should be (at max)
* @remarks Since 2.22.0
*/
size?: Exclude<SizeForArbitrary, 'max'>;
}
/**
* For web authority
*
* According to {@link https://www.ietf.org/rfc/rfc3986.txt | RFC 3986} - `authority = [ userinfo "@" ] host [ ":" port ]`
*
* @param constraints - Constraints to apply when building instances
*
* @remarks Since 1.14.0
* @public
*/
export function webAuthority(constraints?: WebAuthorityConstraints): Arbitrary<string> {
const c = constraints || {};
const size = c.size;
const hostnameArbs = [
domain({ size }),
...(c.withIPv4 === true ? [ipV4()] : []),
...(c.withIPv6 === true ? [ipV6().map(bracketedMapper, bracketedUnmapper)] : []),
...(c.withIPv4Extended === true ? [ipV4Extended()] : []),
];
return tuple(
c.withUserInfo === true ? option(hostUserInfo(size)) : constant(null),
oneof(...hostnameArbs),
c.withPort === true ? option(nat(65535)) : constant(null),
).map(userHostPortMapper, userHostPortUnmapper);
}