diff --git a/src/AzureAppConfigurationImpl.ts b/src/AzureAppConfigurationImpl.ts index faaf2524..3c682502 100644 --- a/src/AzureAppConfigurationImpl.ts +++ b/src/AzureAppConfigurationImpl.ts @@ -6,11 +6,11 @@ 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"; +import { SettingSelector } from "./types"; export class AzureAppConfigurationImpl extends Map implements AzureAppConfiguration { private adapters: IKeyValueAdapter[] = []; @@ -109,12 +109,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[] = []; + 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); + } + + 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 de3b6da3..5da5fd18 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[]; /** * Specifies prefixes to be trimmed from the keys of all key-values retrieved from Azure App Configuration. diff --git a/src/KeyFilter.ts b/src/KeyFilter.ts deleted file mode 100644 index ff7366ea..00000000 --- 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 ebb8361a..00000000 --- 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 b2dc467b..524dbec7 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 new file mode 100644 index 00000000..0b51f905 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,44 @@ +// 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 }; + +/** + * 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" +} diff --git a/test/load.test.ts b/test/load.test.ts index 3e3d4c10..a0b89691 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"); + }); + +});