From 4e7c0e38448c25b97ed25ff163cea767679513cd Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Mon, 27 Nov 2023 13:31:34 +0800 Subject: [PATCH 1/4] Dedup exact same selectors while keep original precedence --- src/AzureAppConfigurationImpl.ts | 16 ++++++++++++++-- src/AzureAppConfigurationOptions.ts | 22 ++++------------------ src/types.ts | 24 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 src/types.ts diff --git a/src/AzureAppConfigurationImpl.ts b/src/AzureAppConfigurationImpl.ts index faaf252..cf64b75 100644 --- a/src/AzureAppConfigurationImpl.ts +++ b/src/AzureAppConfigurationImpl.ts @@ -11,6 +11,7 @@ import { LabelFilter } from "./LabelFilter"; import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter"; import { CorrelationContextHeaderName } from "./requestTracing/constants"; import { createCorrelationContextHeader, requestTracingEnabled } from "./requestTracing/utils"; +import { SettingSelector } from "./types"; export class AzureAppConfigurationImpl extends Map implements AzureAppConfiguration { private adapters: IKeyValueAdapter[] = []; @@ -109,12 +110,23 @@ export class AzureAppConfigurationImpl extends Map implements A } } -function getValidSelectors(selectors?: { keyFilter: string, labelFilter?: string }[]) { +function getValidSelectors(selectors?: SettingSelector[]) { if (!selectors || selectors.length === 0) { // Default selector: key: *, label: \0 return [{ keyFilter: KeyFilter.Any, labelFilter: LabelFilter.Null }]; } - return selectors.map(selectorCandidate => { + + // below code dedupes selectors by keyFilter and labelFilter, the latter selector wins + const dedupedSelectors: SettingSelector[] = []; + const reversedSelectors = [...selectors].reverse(); + for (const selector of reversedSelectors) { + if (!dedupedSelectors.find(s => s.keyFilter === selector.keyFilter && s.labelFilter === selector.labelFilter)) { + dedupedSelectors.push(selector); + } + } + dedupedSelectors.reverse(); + + return dedupedSelectors.map(selectorCandidate => { const selector = { ...selectorCandidate }; if (!selector.keyFilter) { throw new Error("Key filter cannot be null or empty."); diff --git a/src/AzureAppConfigurationOptions.ts b/src/AzureAppConfigurationOptions.ts index 11a9ba5..a9c244f 100644 --- a/src/AzureAppConfigurationOptions.ts +++ b/src/AzureAppConfigurationOptions.ts @@ -3,31 +3,17 @@ import { AppConfigurationClientOptions } from "@azure/app-configuration"; import { AzureAppConfigurationKeyVaultOptions } from "./keyvault/AzureAppConfigurationKeyVaultOptions"; +import { SettingSelector } from "./types"; export const MaxRetries = 2; export const MaxRetryDelayInMs = 60000; export interface AzureAppConfigurationOptions { /** - * Specify what key-values to include in the configuration provider. include multiple sets of key-values - * - * @property keyFilter: - * The key filter to apply when querying Azure App Configuration for key-values. - * An asterisk `*` can be added to the end to return all key-values whose key begins with the key filter. - * e.g. key filter `abc*` returns all key-values whose key starts with `abc`. - * A comma `,` can be used to select multiple key-values. Comma separated filters must exactly match a key to select it. - * Using asterisk to select key-values that begin with a key filter while simultaneously using comma separated key filters is not supported. - * E.g. the key filter `abc*,def` is not supported. The key filters `abc*` and `abc,def` are supported. - * For all other cases the characters: asterisk `*`, comma `,`, and backslash `\` are reserved. Reserved characters must be escaped using a backslash (\). - * e.g. the key filter `a\\b\,\*c*` returns all key-values whose key starts with `a\b,*c`. - * - * @property labelFilter: - * The label filter to apply when querying Azure App Configuration for key-values. - * By default, the "null label" will be used, matching key-values without a label. - * The characters asterisk `*` and comma `,` are not supported. - * Backslash `\` character is reserved and must be escaped using another backslash `\`. + * Specify what key-values to include in the configuration provider. + * If no selectors are specified then all key-values with no label will be included. */ - selectors?: { keyFilter: string, labelFilter?: string }[]; + selectors?: SettingSelector[]; trimKeyPrefixes?: string[]; clientOptions?: AppConfigurationClientOptions; keyVaultOptions?: AzureAppConfigurationKeyVaultOptions; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..4347098 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** + * SettingSelector is used to select key-values from Azure App Configuration. + * It is used to filter key-values based on keys and labels. + * + * @property keyFilter: + * The key filter to apply when querying Azure App Configuration for key-values. + * An asterisk `*` can be added to the end to return all key-values whose key begins with the key filter. + * e.g. key filter `abc*` returns all key-values whose key starts with `abc`. + * A comma `,` can be used to select multiple key-values. Comma separated filters must exactly match a key to select it. + * Using asterisk to select key-values that begin with a key filter while simultaneously using comma separated key filters is not supported. + * E.g. the key filter `abc*,def` is not supported. The key filters `abc*` and `abc,def` are supported. + * For all other cases the characters: asterisk `*`, comma `,`, and backslash `\` are reserved. Reserved characters must be escaped using a backslash (\). + * e.g. the key filter `a\\b\,\*c*` returns all key-values whose key starts with `a\b,*c`. + * + * @property labelFilter: + * The label filter to apply when querying Azure App Configuration for key-values. + * By default, the "null label" will be used, matching key-values without a label. + * The characters asterisk `*` and comma `,` are not supported. + * Backslash `\` character is reserved and must be escaped using another backslash `\`. + */ +export type SettingSelector = { keyFilter: string, labelFilter?: string }; From 0556c26c9a758fd2c91a1a4a58ce0702157e55a2 Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Mon, 27 Nov 2023 13:35:58 +0800 Subject: [PATCH 2/4] move KeyFilter LabelFilter into types.ts --- src/AzureAppConfigurationImpl.ts | 3 +-- src/KeyFilter.ts | 6 ------ src/LabelFilter.ts | 6 ------ src/index.ts | 3 +-- src/types.ts | 20 ++++++++++++++++++++ 5 files changed, 22 insertions(+), 16 deletions(-) delete mode 100644 src/KeyFilter.ts delete mode 100644 src/LabelFilter.ts diff --git a/src/AzureAppConfigurationImpl.ts b/src/AzureAppConfigurationImpl.ts index cf64b75..a707cfd 100644 --- a/src/AzureAppConfigurationImpl.ts +++ b/src/AzureAppConfigurationImpl.ts @@ -6,8 +6,7 @@ import { AzureAppConfiguration } from "./AzureAppConfiguration"; import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions"; import { IKeyValueAdapter } from "./IKeyValueAdapter"; import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter"; -import { KeyFilter } from "./KeyFilter"; -import { LabelFilter } from "./LabelFilter"; +import { KeyFilter, LabelFilter } from "./types"; import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter"; import { CorrelationContextHeaderName } from "./requestTracing/constants"; import { createCorrelationContextHeader, requestTracingEnabled } from "./requestTracing/utils"; diff --git a/src/KeyFilter.ts b/src/KeyFilter.ts deleted file mode 100644 index ff7366e..0000000 --- a/src/KeyFilter.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -export enum KeyFilter { - Any = "*" -} \ No newline at end of file diff --git a/src/LabelFilter.ts b/src/LabelFilter.ts deleted file mode 100644 index ebb8361..0000000 --- a/src/LabelFilter.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -export enum LabelFilter { - Null = "\0" -} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index b2dc467..524dbec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,5 +3,4 @@ export { load } from "./load"; export { AzureAppConfiguration } from "./AzureAppConfiguration"; -export { KeyFilter } from "./KeyFilter"; -export { LabelFilter } from "./LabelFilter"; \ No newline at end of file +export { KeyFilter, LabelFilter } from "./types"; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 4347098..0b51f90 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,3 +22,23 @@ * Backslash `\` character is reserved and must be escaped using another backslash `\`. */ export type SettingSelector = { keyFilter: string, labelFilter?: string }; + +/** + * KeyFilter is used to filter key-values based on keys. + * + * @property Any: + * Matches all key-values. + */ +export enum KeyFilter { + Any = "*" +} + +/** + * LabelFilter is used to filter key-values based on labels. + * + * @property Null: + * Matches key-values without a label. + */ +export enum LabelFilter { + Null = "\0" +} From 84b46bc865bcfc771d479b6f7fb901e1561c9169 Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Mon, 27 Nov 2023 13:39:28 +0800 Subject: [PATCH 3/4] add test case --- test/load.test.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/load.test.ts b/test/load.test.ts index 3e3d4c1..a0b8969 100644 --- a/test/load.test.ts +++ b/test/load.test.ts @@ -151,4 +151,24 @@ describe("load", function () { expect(settings.has("TestKey")).eq(true); expect(settings.get("TestKey")).eq("TestValueForProd"); }); -}) + + it("should dedup exact same selectors but keeping the precedence", async () => { + const connectionString = createMockedConnectionString(); + const settings = await load(connectionString, { + selectors: [{ + keyFilter: "Test*", + labelFilter: "Prod" + }, { + keyFilter: "Test*", + labelFilter: "Test" + }, { + keyFilter: "Test*", + labelFilter: "Prod" + }] + }); + expect(settings).not.undefined; + expect(settings.has("TestKey")).eq(true); + expect(settings.get("TestKey")).eq("TestValueForProd"); + }); + +}); From 7178bb09152b49c4d1d98375abae309602b01b11 Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Wed, 29 Nov 2023 13:46:59 +0800 Subject: [PATCH 4/4] use straightforward impl --- src/AzureAppConfigurationImpl.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AzureAppConfigurationImpl.ts b/src/AzureAppConfigurationImpl.ts index a707cfd..3c68250 100644 --- a/src/AzureAppConfigurationImpl.ts +++ b/src/AzureAppConfigurationImpl.ts @@ -117,13 +117,13 @@ function getValidSelectors(selectors?: SettingSelector[]) { // below code dedupes selectors by keyFilter and labelFilter, the latter selector wins const dedupedSelectors: SettingSelector[] = []; - const reversedSelectors = [...selectors].reverse(); - for (const selector of reversedSelectors) { - if (!dedupedSelectors.find(s => s.keyFilter === selector.keyFilter && s.labelFilter === selector.labelFilter)) { - dedupedSelectors.push(selector); + for (const selector of selectors) { + const existingSelectorIndex = dedupedSelectors.findIndex(s => s.keyFilter === selector.keyFilter && s.labelFilter === selector.labelFilter); + if (existingSelectorIndex >= 0) { + dedupedSelectors.splice(existingSelectorIndex, 1); } + dedupedSelectors.push(selector); } - dedupedSelectors.reverse(); return dedupedSelectors.map(selectorCandidate => { const selector = { ...selectorCandidate };