Skip to content

Commit 27f0177

Browse files
committed
Merge remote-tracking branch 'origin/test-main' into copilot/merge-test-main-into-test-release-v2
2 parents b56e175 + fddb0fe commit 27f0177

File tree

7 files changed

+242
-145
lines changed

7 files changed

+242
-145
lines changed

examples/console-app/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/web-app/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 191 additions & 131 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@azure/app-configuration-provider",
3-
"version": "2.4.0",
3+
"version": "2.4.1",
44
"description": "The JavaScript configuration provider for Azure App Configuration",
55
"files": [
66
"dist/",

src/appConfigurationImpl.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
333333
if (current[lastSegment] !== undefined) {
334334
throw new InvalidOperationError(`Ambiguity occurs when constructing configuration object from key '${key}', value '${value}'. The key should not be part of another key.`);
335335
}
336-
// set value to the last segment
337-
current[lastSegment] = value;
336+
// Deep copy object values to avoid mutating the original objects in #configMap via shared references.
337+
current[lastSegment] = typeof value === "object" && value !== null ? structuredClone(value) : value;
338338
}
339339
return data;
340340
}
@@ -492,10 +492,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
492492
// Use a Map to deduplicate configuration settings by key. When multiple selectors return settings with the same key,
493493
// the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
494494
const loadedSettings: Map<string, ConfigurationSetting> = new Map<string, ConfigurationSetting>();
495-
// deep copy selectors to avoid modification if current client fails
496-
const selectorsToUpdate: PagedSettingsWatcher[] = JSON.parse(
497-
JSON.stringify(selectors)
498-
);
495+
// Deep copy selectors to avoid modification if current client fails.
496+
const selectorsToUpdate: PagedSettingsWatcher[] = structuredClone(selectors);
499497

500498
for (const selector of selectorsToUpdate) {
501499
let settings: ConfigurationSetting[] = [];

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
export const VERSION = "2.4.0";
4+
export const VERSION = "2.4.1";

test/load.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ const mockedKVs = [{
101101
key: "keyWithEmptyTag",
102102
value: "valueWithEmptyTag",
103103
tags: {"emptyTag": ""}
104+
}, {
105+
key: "app6_markets",
106+
value: JSON.stringify({ gb: { url: "https://example.com/gb" }, dk: { url: "https://example.com/dk" } }),
107+
contentType: "application/json"
108+
}, {
109+
key: "app6_markets.markets.dk",
110+
value: JSON.stringify({ currency: "DKK" }),
111+
contentType: "application/json"
112+
}, {
113+
key: "app6_markets.markets.gb",
114+
value: JSON.stringify({ currency: "GBP" }),
115+
contentType: "application/json"
104116
}
105117
].map(createMockedKeyValue);
106118

@@ -579,6 +591,33 @@ describe("load", function () {
579591
}).to.throw("Invalid separator '%'. Supported values: '.', ',', ';', '-', '_', '__', '/', ':'.");
580592
});
581593

594+
it("should call constructConfigurationObject twice without throwing for JSON content-type keys with overlapping hierarchy", async () => {
595+
const connectionString = createMockedConnectionString();
596+
const settings = await load(connectionString, {
597+
selectors: [{
598+
keyFilter: "app6_*"
599+
}],
600+
trimKeyPrefixes: ["app6_"]
601+
});
602+
expect(settings).not.undefined;
603+
604+
// First call should succeed
605+
const data1 = settings.constructConfigurationObject({ separator: "." });
606+
expect(data1).not.undefined;
607+
expect(data1.markets.gb.url).eq("https://example.com/gb");
608+
expect(data1.markets.dk.url).eq("https://example.com/dk");
609+
expect(data1.markets.markets.dk.currency).eq("DKK");
610+
expect(data1.markets.markets.gb.currency).eq("GBP");
611+
612+
// Second call should also succeed (idempotent)
613+
const data2 = settings.constructConfigurationObject({ separator: "." });
614+
expect(data2).not.undefined;
615+
expect(data2.markets.gb.url).eq("https://example.com/gb");
616+
expect(data2.markets.dk.url).eq("https://example.com/dk");
617+
expect(data2.markets.markets.dk.currency).eq("DKK");
618+
expect(data2.markets.markets.gb.currency).eq("GBP");
619+
});
620+
582621
it("should load key values from snapshot", async () => {
583622
const snapshotName = "Test";
584623
const snapshotResponses = new Map([

0 commit comments

Comments
 (0)