aboutsummaryrefslogtreecommitdiff
path: root/src/internal/locale-proxy.ts
blob: 3e77a93a4cdbcfbd33902054b61c8a4b09167c6d (plain)
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
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.
  If this is a custom Faker instance, please make sure all required locales are used e.g. '[de_AT, de, en, base]'.
  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>;
}