diff --git a/.autover/changes/976aa8d4-d143-4f7b-9bf5-762b166d7fb5.json b/.autover/changes/976aa8d4-d143-4f7b-9bf5-762b166d7fb5.json
new file mode 100644
index 0000000..d0aa4fa
--- /dev/null
+++ b/.autover/changes/976aa8d4-d143-4f7b-9bf5-762b166d7fb5.json
@@ -0,0 +1,11 @@
+{
+ "Projects": [
+ {
+ "Name": "Amazon.Extensions.Configuration.SystemsManager",
+ "Type": "Patch",
+ "ChangelogMessages": [
+ "Removed content type check for app config. We now always try to parse json first"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj b/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj
index fb57492..9f53018 100644
--- a/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj
+++ b/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj
@@ -38,7 +38,11 @@
-
+
+
+
+
+
true
..\..\public.snk
diff --git a/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs b/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs
index 094fde8..a1a2f82 100644
--- a/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs
+++ b/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Amazon.AppConfigData;
@@ -162,20 +163,17 @@ private async Task GetInitialConfigurationTokenAsync(IAmazonAppConfigDat
return (await appConfigClient.StartConfigurationSessionAsync(request).ConfigureAwait(false)).InitialConfigurationToken;
}
- private static IDictionary ParseConfig(string contentType, Stream configuration)
+ internal static IDictionary ParseConfig(string contentType, Stream configuration)
{
- // Content-Type has format "media-type; charset" or "media-type; boundary" (for multipart entities).
- if (contentType != null)
+ try
{
- contentType = contentType.Split(';')[0];
+ return JsonConfigurationParser.Parse(configuration);
}
-
- switch (contentType)
+ catch (JsonException ex)
{
- case "application/json":
- return JsonConfigurationParser.Parse(configuration);
- default:
- throw new NotImplementedException($"Not implemented AppConfig type: {contentType}");
+ throw new InvalidOperationException(
+ $"Failed to parse AppConfig content as JSON. Content-Type was '{contentType}'. {ex.Message}",
+ ex);
}
}
}
diff --git a/test/Amazon.Extensions.Configuration.SystemsManager.Integ/AppConfigEndToEndTests.cs b/test/Amazon.Extensions.Configuration.SystemsManager.Integ/AppConfigEndToEndTests.cs
index 2610f98..0f680c9 100644
--- a/test/Amazon.Extensions.Configuration.SystemsManager.Integ/AppConfigEndToEndTests.cs
+++ b/test/Amazon.Extensions.Configuration.SystemsManager.Integ/AppConfigEndToEndTests.cs
@@ -84,6 +84,42 @@ public async Task JsonWithCharsetConfiguration()
}
}
+ [Fact]
+ public async Task AppConfigWithOctetStreamContentType()
+ {
+ var configSettings = new Dictionary
+ {
+ { "key1", "value1" },
+ { "key2", "value2" },
+ { "database:connectionString", "server=localhost;database=test" },
+ { "api:timeout", "30" }
+ };
+
+ // Create AppConfig with application/octet-stream content type (simulates Parameter Store backend)
+ (string applicationId, string environmentId, string configProfileId) =
+ await CreateAppConfigResourcesAsync("OctetStreamTest", configSettings, "application/octet-stream");
+
+ try
+ {
+ var builder = new ConfigurationBuilder()
+ .AddAppConfig(applicationId, environmentId, configProfileId,
+ new AWSOptions { Region = RegionEndpoint.USWest2 },
+ TimeSpan.FromSeconds(5));
+
+ var configuration = builder.Build();
+
+ // Verify configuration loads correctly despite application/octet-stream content type
+ Assert.Equal("value1", configuration["key1"]);
+ Assert.Equal("value2", configuration["key2"]);
+ Assert.Equal("server=localhost;database=test", configuration["database:connectionString"]);
+ Assert.Equal("30", configuration["api:timeout"]);
+ }
+ finally
+ {
+ await CleanupAppConfigResourcesAsync(applicationId, environmentId, configProfileId);
+ }
+ }
+
private async Task CleanupAppConfigResourcesAsync(string applicationId, string environmentId, string configProfileId)
{
await _appConfigClient.DeleteEnvironmentAsync(new DeleteEnvironmentRequest {ApplicationId = applicationId, EnvironmentId = environmentId });
diff --git a/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Amazon.Extensions.Configuration.SystemsManager.Tests.csproj b/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Amazon.Extensions.Configuration.SystemsManager.Tests.csproj
index c0efff9..c222faf 100644
--- a/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Amazon.Extensions.Configuration.SystemsManager.Tests.csproj
+++ b/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Amazon.Extensions.Configuration.SystemsManager.Tests.csproj
@@ -4,6 +4,8 @@
net8.0
true
false
+ true
+ ..\..\public.snk
diff --git a/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigProcessorTests.cs b/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigProcessorTests.cs
new file mode 100644
index 0000000..5dcef29
--- /dev/null
+++ b/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigProcessorTests.cs
@@ -0,0 +1,148 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Amazon.Extensions.Configuration.SystemsManager.AppConfig;
+using Xunit;
+
+namespace Amazon.Extensions.Configuration.SystemsManager.Tests
+{
+ public class AppConfigProcessorTests
+ {
+ [Fact]
+ public void ParseConfig_ApplicationJson_ParsesSuccessfully()
+ {
+ var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
+
+ var result = AppConfigProcessor.ParseConfig("application/json", stream);
+
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("value2", result["key2"]);
+ }
+
+ [Fact]
+ public void ParseConfig_ApplicationOctetStream_ParsesJsonSuccessfully()
+ {
+ var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
+
+ var result = AppConfigProcessor.ParseConfig("application/octet-stream", stream);
+
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("value2", result["key2"]);
+ }
+
+ [Fact]
+ public void ParseConfig_ApplicationJsonWithCharset_ParsesSuccessfully()
+ {
+ var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
+
+ var result = AppConfigProcessor.ParseConfig("application/json; charset=utf-8", stream);
+
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("value2", result["key2"]);
+ }
+
+ [Fact]
+ public void ParseConfig_UnknownContentType_ParsesJsonSuccessfully()
+ {
+ var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
+
+ var result = AppConfigProcessor.ParseConfig("application/unknown", stream);
+
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("value2", result["key2"]);
+ }
+
+ [Fact]
+ public void ParseConfig_NullContentType_ParsesJsonSuccessfully()
+ {
+ var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
+
+ var result = AppConfigProcessor.ParseConfig(null, stream);
+
+ Assert.Equal("value1", result["key1"]);
+ Assert.Equal("value2", result["key2"]);
+ }
+
+ [Fact]
+ public void ParseConfig_NestedJson_ParsesSuccessfully()
+ {
+ var jsonContent = "{\"section1\":{\"key1\":\"value1\",\"key2\":\"value2\"},\"section2\":{\"key3\":\"value3\"}}";
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
+
+ var result = AppConfigProcessor.ParseConfig("application/octet-stream", stream);
+
+ Assert.Equal("value1", result["section1:key1"]);
+ Assert.Equal("value2", result["section1:key2"]);
+ Assert.Equal("value3", result["section2:key3"]);
+ }
+
+ [Fact]
+ public void ParseConfig_InvalidJson_ThrowsInvalidOperationException()
+ {
+ var invalidJsonContent = "{ invalid json content }";
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJsonContent));
+
+ var exception = Assert.Throws(() =>
+ AppConfigProcessor.ParseConfig("application/json", stream));
+
+ Assert.Contains("Failed to parse AppConfig content as JSON", exception.Message);
+ Assert.Contains("Content-Type was 'application/json'", exception.Message);
+ }
+
+ [Fact]
+ public void ParseConfig_InvalidJsonWithOctetStream_ThrowsInvalidOperationException()
+ {
+ var invalidJsonContent = "not json at all";
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJsonContent));
+
+ var exception = Assert.Throws(() =>
+ AppConfigProcessor.ParseConfig("application/octet-stream", stream));
+
+ Assert.Contains("Failed to parse AppConfig content as JSON", exception.Message);
+ Assert.Contains("Content-Type was 'application/octet-stream'", exception.Message);
+ }
+
+ [Fact]
+ public void ParseConfig_EmptyJson_ReturnsEmptyDictionary()
+ {
+ var jsonContent = "{}";
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
+
+ var result = AppConfigProcessor.ParseConfig("application/json", stream);
+
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void ParseConfig_EmptyStream_ThrowsInvalidOperationException()
+ {
+ using var stream = new MemoryStream();
+
+ var exception = Assert.Throws(() =>
+ AppConfigProcessor.ParseConfig("application/json", stream));
+
+ Assert.Contains("Failed to parse AppConfig content as JSON", exception.Message);
+ }
+ }
+}