Skip to content

Commit 993551d

Browse files
committed
feat(#136): adds '_extensionName' option on ParsingExtension to avoid duplicate runs
1 parent facd13b commit 993551d

File tree

3 files changed

+58
-12
lines changed

3 files changed

+58
-12
lines changed

app-config-core/src/parsed-value.test.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ describe('parseValue', () => {
6060
return false;
6161
};
6262

63+
const appendExtension = (suffix: string): ParsingExtension => (value) => {
64+
if (typeof value === 'string') {
65+
return (parse) => parse(value + suffix);
66+
}
67+
68+
return false;
69+
};
70+
71+
const namedAppendExtension = (suffix: string): ParsingExtension => {
72+
const extension = appendExtension(suffix);
73+
74+
return Object.assign(extension, {
75+
extensionName: 'namedAppendExtension',
76+
});
77+
};
78+
6379
const secretExtension: ParsingExtension = (_, [keyType, key]) => {
6480
if (keyType === InObject && key === '$secret') {
6581
return (parse) => parse('revealed!', { shouldFlatten: true });
@@ -175,11 +191,21 @@ describe('parseValue', () => {
175191
it('allows the same extension to be applied twice', async () => {
176192
const source = new LiteralSource('string');
177193
const parsed = await parseValue(await source.readValue(), source, [
178-
uppercaseExtension,
179-
uppercaseExtension,
194+
appendExtension('-appended'),
195+
appendExtension('-appended2'),
196+
]);
197+
198+
expect(parsed.toJSON()).toEqual('string-appended-appended2');
199+
});
200+
201+
it('respects extensionName', async () => {
202+
const source = new LiteralSource('string');
203+
const parsed = await parseValue(await source.readValue(), source, [
204+
namedAppendExtension('-appended'),
205+
namedAppendExtension('-appended2'),
180206
]);
181207

182-
expect(parsed.toJSON()).toEqual('STRING');
208+
expect(parsed.toJSON()).toEqual('string-appended');
183209
});
184210

185211
it('allows the same extension apply at different levels of a tree', async () => {

app-config-core/src/parsed-value.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,18 @@ export type ParsingExtensionKey =
1717
| [typeof InArray, number]
1818
| [typeof Root];
1919

20-
export type ParsingExtension = (
21-
value: Json,
22-
key: ParsingExtensionKey,
23-
context: ParsingExtensionKey[],
24-
) => false | ParsingExtensionTransform;
20+
export interface ParsingExtension {
21+
(value: Json, key: ParsingExtensionKey, context: ParsingExtensionKey[]):
22+
| ParsingExtensionTransform
23+
| false;
24+
25+
/**
26+
* A globally unique string that identifies what parsing extension this is.
27+
*
28+
* Used to avoid running the same extension twice when included twice.
29+
*/
30+
extensionName?: string;
31+
}
2532

2633
export type ParsingExtensionTransform = (
2734
parse: (
@@ -351,7 +358,7 @@ async function parseValueInner(
351358
context: ParsingExtensionKey[],
352359
root: Json,
353360
parent?: JsonObject | Json[],
354-
visitedExtensions: ParsingExtension[] = [],
361+
visitedExtensions: Set<ParsingExtension | string> = new Set(),
355362
): Promise<ParsedValue> {
356363
const [currentKey] = context.slice(-1);
357364
const contextualKeys = context.slice(0, context.length - 1);
@@ -367,13 +374,20 @@ async function parseValueInner(
367374
// for this reason, we pass "parse" as a function to extensions, so they can recurse as needed
368375
for (const extension of extensions) {
369376
// we track visitedExtensions so that calling `parse` in an extension doesn't hit that same extension with the same value
370-
if (visitedExtensions.includes(extension)) continue;
377+
if (visitedExtensions.has(extension)) continue;
378+
if (extension.extensionName && visitedExtensions.has(extension.extensionName)) continue;
371379

372380
const applicable = extension(value, currentKey, contextualKeys);
373381

374-
if (applicable && !applicableExtension) {
382+
if (applicable) {
375383
applicableExtension = applicable;
376-
visitedExtensions.push(extension);
384+
visitedExtensions.add(extension);
385+
386+
if (extension.extensionName) {
387+
visitedExtensions.add(extension.extensionName);
388+
}
389+
390+
break;
377391
}
378392
}
379393

app-config-extension-utils/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ export function composeExtensions(extensions: ParsingExtension[]): ParsingExtens
1414
};
1515
}
1616

17+
export function named(name: string, parsingExtension: ParsingExtension): ParsingExtension {
18+
Object.defineProperty(parsingExtension, '_extensionName', { value: name });
19+
20+
return parsingExtension;
21+
}
22+
1723
export function forKey(
1824
key: string | string[],
1925
parsingExtension: ParsingExtension,

0 commit comments

Comments
 (0)