-
-
Notifications
You must be signed in to change notification settings - Fork 932
/
locale-proxy.ts
115 lines (101 loc) · 3.48 KB
/
locale-proxy.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
import type { LocaleDefinition } from '../definitions';
import { FakerError } from '../errors/faker-error';
/**
* A proxy for LocaleDefinition that marks all properties as required and throws an error when an entry is accessed that is not defined.
*/
export type LocaleProxy = Readonly<{
[key in keyof LocaleDefinition]-?: LocaleProxyCategory<LocaleDefinition[key]>;
}>;
type LocaleProxyCategory<T> = Readonly<{
[key in keyof T]-?: LocaleProxyEntry<T[key]>;
}>;
type LocaleProxyEntry<T> = unknown extends T ? T : Readonly<NonNullable<T>>;
const throwReadOnlyError: () => never = () => {
throw new FakerError('You cannot edit the locale data on the faker instance');
};
/**
* Creates a proxy for LocaleDefinition that throws an error if an undefined property is accessed.
*
* @param locale The locale definition to create the proxy for.
*/
export function createLocaleProxy(locale: LocaleDefinition): LocaleProxy {
const proxies = {} as LocaleDefinition;
return new Proxy(locale, {
has(): true {
// Categories are always present (proxied), that's why we return true.
return true;
},
get(
target: LocaleDefinition,
categoryName: keyof LocaleDefinition
): LocaleDefinition[keyof LocaleDefinition] {
if (typeof categoryName === 'symbol' || categoryName === 'nodeType') {
return target[categoryName];
}
if (categoryName in proxies) {
return proxies[categoryName];
}
return (proxies[categoryName] = createCategoryProxy(
categoryName,
target[categoryName]
));
},
set: throwReadOnlyError,
deleteProperty: throwReadOnlyError,
}) as LocaleProxy;
}
/**
* Checks that the value is not null or undefined and throws an error if it is.
*
* @param value The value to check.
* @param path The path to the locale data.
*/
export function assertLocaleData<T>(
value: T,
...path: string[]
): asserts value is NonNullable<T> {
if (value === null) {
throw new FakerError(
`The locale data for '${path.join('.')}' aren't applicable to this locale.
If you think this is a bug, please report it at: https://github.com/faker-js/faker`
);
} else if (value === undefined) {
throw new FakerError(
`The locale data for '${path.join('.')}' are missing in this locale.
Please contribute the missing data to the project or use a locale/Faker instance that has these data.
For more information see https://fakerjs.dev/guide/localization.html`
);
}
}
/**
* Creates a proxy for a category that throws an error when accessing an undefined property.
*
* @param categoryName The name of the category.
* @param categoryData The module to create the proxy for.
*/
function createCategoryProxy<
TCategoryData extends Record<string | symbol, unknown>,
>(
categoryName: string,
categoryData: TCategoryData = {} as TCategoryData
): Required<TCategoryData> {
return new Proxy(categoryData, {
has(target: TCategoryData, entryName: keyof TCategoryData): boolean {
const value = target[entryName];
return value != null;
},
get(
target: TCategoryData,
entryName: keyof TCategoryData
): TCategoryData[keyof TCategoryData] {
const value = target[entryName];
if (typeof entryName === 'symbol' || entryName === 'nodeType') {
return value;
}
assertLocaleData(value, categoryName, entryName.toString());
return value;
},
set: throwReadOnlyError,
deleteProperty: throwReadOnlyError,
}) as Required<TCategoryData>;
}