Skip to content

Commit

Permalink
feat: dynamic definitions tree (#822)
Browse files Browse the repository at this point in the history
  • Loading branch information
ST-DDT committed Apr 19, 2022
1 parent d94159f commit 069f4d1
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 53 deletions.
11 changes: 6 additions & 5 deletions src/definitions/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export type LocaleDefinition = {
[module in keyof Definitions]?: Partial<Definitions[module]>;
} & {
// Unsupported & custom modules
[group: string]: Record<string, any> | string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[group: string]: any;
};

/**
Expand All @@ -78,8 +79,8 @@ export type LocaleDefinition = {
* that don't require prior getter generation in the future.
*/
export type DefinitionTypes = {
readonly title: string;
readonly separator: string;
readonly title: 'metadata';
readonly separator: 'metadata';
} & {
readonly [module in keyof Definitions]: Array<keyof Definitions[module]>;
};
Expand All @@ -89,8 +90,8 @@ export type DefinitionTypes = {
* that needs to have a fallback generated in Faker.loadDefinitions().
*/
export const DEFINITIONS: DefinitionTypes = {
title: '',
separator: '',
title: 'metadata',
separator: 'metadata',

address: ADDRESS,
animal: ANIMAL,
Expand Down
80 changes: 46 additions & 34 deletions src/faker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ export class Faker {
locale: UsableLocale;
localeFallback: UsableLocale;

// Will be lazy init
readonly definitions: LocaleDefinition = {} as LocaleDefinition;
readonly definitions: LocaleDefinition = this.initDefinitions();

seedValue?: number | number[];

Expand Down Expand Up @@ -98,43 +97,56 @@ export class Faker {
this.locales = opts.locales;
this.locale = opts.locale || 'en';
this.localeFallback = opts.localeFallback || 'en';

this.loadDefinitions();
}

/**
* Load the definitions contained in the locales file for the given types.
*
* Background: Certain localization sets contain less data then others.
* In the case of a missing definition, use the localeFallback's values
* to substitute the missing data.
* Creates a Proxy based LocaleDefinition that virtually merges the locales.
*/
private loadDefinitions(): void {
// TODO @Shinigami92 2022-01-11: Find a way to load this even more dynamically
// In a way so that we don't accidentally miss a definition
for (const [moduleName, entryNames] of Object.entries(DEFINITIONS)) {
if (typeof entryNames === 'string') {
// For 'title' and 'separator'
Object.defineProperty(this.definitions, moduleName, {
get: (): unknown /* string */ =>
this.locales[this.locale][moduleName] ??
this.locales[this.localeFallback][moduleName],
});
continue;
}

if (this.definitions[moduleName] == null) {
this.definitions[moduleName] = {};
private initDefinitions(): LocaleDefinition {
// Returns the first LocaleDefinition[key] in any locale
const resolveBaseData = (key: keyof LocaleDefinition): unknown =>
this.locales[this.locale][key] ?? this.locales[this.localeFallback][key];

// Returns the first LocaleDefinition[module][entry] in any locale
const resolveModuleData = (
module: keyof LocaleDefinition,
entry: string
): unknown =>
this.locales[this.locale][module]?.[entry] ??
this.locales[this.localeFallback][module]?.[entry];

// Returns a proxy that can return the entries for a module (if it exists)
const moduleLoader = (
module: keyof LocaleDefinition
): Record<string, unknown> | undefined => {
if (resolveBaseData(module)) {
return new Proxy(
{},
{
get(target, entry: string): unknown {
return resolveModuleData(module, entry);
},
}
);
} else {
return undefined;
}

for (const entryName of entryNames) {
Object.defineProperty(this.definitions[moduleName], entryName, {
get: (): unknown =>
this.locales[this.locale][moduleName]?.[entryName] ??
this.locales[this.localeFallback][moduleName]?.[entryName],
});
}
}
};

return new Proxy({} as LocaleDefinition, {
get(target: LocaleDefinition, module: string): unknown {
let result = target[module];
if (result) {
return result;
} else if (DEFINITIONS[module] === 'metadata') {
return resolveBaseData(module);
} else {
result = moduleLoader(module);
target[module] = result;
return result;
}
},
});
}

seed(seed?: number | number[]): void {
Expand Down
41 changes: 27 additions & 14 deletions test/faker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,37 @@ describe('faker', () => {
);
});

describe('title', () => {
it.each(Object.keys(faker.locales))('title (%s)', (locale) => {
faker.locale = locale;
expect(faker.definitions.title).toBe(faker.locales[locale].title);
describe('definitions', () => {
describe('title', () => {
it.each(Object.keys(faker.locales))('title (%s)', (locale) => {
faker.locale = locale;
expect(faker.definitions.title).toBe(faker.locales[locale].title);
});
});
});

describe('separator', () => {
it.each(Object.keys(faker.locales))('separator (%s)', (locale) => {
faker.locale = locale;
expect(faker.definitions.separator).toBeTypeOf('string');
describe('separator', () => {
it.each(Object.keys(faker.locales))('separator (%s)', (locale) => {
faker.locale = locale;
expect(faker.definitions.separator).toBeTypeOf('string');
});

it('separator (with fallback)', () => {
// Use a language that doesn't have a separator specified
expect(faker.locales['en_US'].separator).toBeUndefined();
// Check that the fallback works
expect(faker.definitions.separator).toBe(faker.locales['en'].separator);
});
});

it('separator (with fallback)', () => {
// Use a language that doesn't have a separator specified
expect(faker.locales['en_US'].separator).toBeUndefined();
// Check that the fallback works
expect(faker.definitions.separator).toBe(faker.locales['en'].separator);
it('locale definition accessability', () => {
// Metadata
expect(faker.definitions.title).toBeDefined();
// Standard modules
expect(faker.definitions.address.city_name).toBeDefined();
// Custom modules
expect(faker.definitions.business.credit_card_types).toBeDefined();
expect(faker.definitions.missing).toBeUndefined();
expect(faker.definitions.business.missing).toBeUndefined();
});
});

Expand Down

0 comments on commit 069f4d1

Please sign in to comment.