Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ public virtual AsyncPageable<ConfigurationSetting> GetConfigurationSettingsAsync

var pageableImplementation = GetConfigurationSettingsPageableImplementation(selector, cancellationToken);

return new AsyncConditionalPageable(pageableImplementation);
return new AsyncConditionalPageable<ConfigurationSetting>(pageableImplementation);
}

/// <summary>
Expand All @@ -721,10 +721,10 @@ public virtual Pageable<ConfigurationSetting> GetConfigurationSettings(SettingSe

var pageableImplementation = GetConfigurationSettingsPageableImplementation(selector, cancellationToken);

return new ConditionalPageable(pageableImplementation);
return new ConditionalPageable<ConfigurationSetting>(pageableImplementation);
}

private ConditionalPageableImplementation GetConfigurationSettingsPageableImplementation(SettingSelector selector, CancellationToken cancellationToken)
private ConditionalPageableImplementation<ConfigurationSetting> GetConfigurationSettingsPageableImplementation(SettingSelector selector, CancellationToken cancellationToken)
{
var key = selector.KeyFilter;
var label = selector.LabelFilter;
Expand All @@ -747,7 +747,7 @@ HttpMessage NextPageRequest(MatchConditions conditions, int? pageSizeHint, strin
return CreateNextGetConfigurationSettingsRequest(nextLink, key, label, _syncToken, null, dateTime, fieldsString, null, conditions, tags, context);
}

return new ConditionalPageableImplementation(FirstPageRequest, NextPageRequest, ParseGetConfigurationSettingsResponse, Pipeline, ClientDiagnostics, "ConfigurationClient.GetConfigurationSettings", context);
return new ConditionalPageableImplementation<ConfigurationSetting>(FirstPageRequest, NextPageRequest, ParseGetConfigurationSettingsResponse, Pipeline, ClientDiagnostics, "ConfigurationClient.GetConfigurationSettings", context);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static IAsyncEnumerable<Page<ConfigurationSetting>> AsPages(this AsyncPag
{
Argument.AssertNotNull(conditions, nameof(conditions));

var conditionalPageable = pageable as AsyncConditionalPageable;
var conditionalPageable = pageable as AsyncConditionalPageable<ConfigurationSetting>;

if (conditionalPageable is null)
{
Expand Down Expand Up @@ -66,7 +66,7 @@ public static IEnumerable<Page<ConfigurationSetting>> AsPages(this Pageable<Conf
{
Argument.AssertNotNull(conditions, nameof(conditions));

var conditionalPageable = pageable as ConditionalPageable;
var conditionalPageable = pageable as ConditionalPageable<ConfigurationSetting>;

if (conditionalPageable is null)
{
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Azure.Core;

namespace Azure.Data.AppConfiguration
{
/// <summary>
/// Options that allow users to configure the requests sent to the App Configuration feature flags service.
/// </summary>
[CodeGenSuppress("FeatureFlagClientOptions", typeof(ServiceVersion))]
public partial class FeatureFlagClientOptions : ClientOptions
{
private const ServiceVersion LatestVersion = ServiceVersion.V2023_11_01;
private const string AzConfigUsGovCloudHostName = "azconfig.azure.us";
private const string AzConfigChinaCloudHostName = "azconfig.azure.cn";
private const string AppConfigUsGovCloudHostName = "appconfig.azure.us";
private const string AppConfigChinaCloudHostName = "appconfig.azure.cn";

/// <summary>
/// The versions of the App Configuration service supported by this client library.
/// </summary>
public enum ServiceVersion
{
#pragma warning disable CA1707 // Identifiers should not contain underscores
/// <summary>
/// Version 1.0.
/// </summary>
V1_0 = 0,

/// <summary>
/// Version 2023-10-01.
/// </summary>
V2023_10_01 = 1,

/// <summary>
/// Version 2023-11-01.
/// </summary>
V2023_11_01 = 2
}

/// <summary>
/// Gets or sets the Audience to use for authentication with Microsoft Entra. The audience is not considered when using a shared key.
/// </summary>
public AppConfigurationAudience? Audience { get; set; }

internal string Version { get; }

/// <summary>
/// Initializes a new instance of the <see cref="FeatureFlagClientOptions"/>
/// class.
/// </summary>
/// <param name="version">
/// The <see cref="ServiceVersion"/> of the service API used when
/// making requests.
/// </param>
public FeatureFlagClientOptions(ServiceVersion version = LatestVersion)
{
Version = version switch
{
ServiceVersion.V1_0 => "1.0",
ServiceVersion.V2023_10_01 => "2023-10-01",
ServiceVersion.V2023_11_01 => "2023-11-01",

_ => throw new NotSupportedException()
};
this.ConfigureLogging();
}

internal string GetDefaultScope(Uri uri)
{
if (string.IsNullOrEmpty(Audience?.ToString()))
{
string host = uri.GetComponents(UriComponents.Host, UriFormat.SafeUnescaped);
return host switch
{
_ when host.EndsWith(AzConfigUsGovCloudHostName, StringComparison.InvariantCultureIgnoreCase) || host.EndsWith(AppConfigUsGovCloudHostName, StringComparison.InvariantCultureIgnoreCase)
=> $"{AppConfigurationAudience.AzureGovernment}/.default",
_ when host.EndsWith(AzConfigChinaCloudHostName, StringComparison.InvariantCultureIgnoreCase) || host.EndsWith(AppConfigChinaCloudHostName, StringComparison.InvariantCultureIgnoreCase)
=> $"{AppConfigurationAudience.AzureChina}/.default",
_ => $"{AppConfigurationAudience.AzurePublicCloud}/.default"
};
}

return $"{Audience}/.default";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;

namespace Azure.Data.AppConfiguration
{
public partial class FeatureFlagClient
{
private const string AcceptDateTimeFormat = "R";

private static Response<FeatureFlag> CreateResponse(Response response)
{
var options = ModelReaderWriterOptions.Json;

FeatureFlag result = FeatureFlag.DeserializeFeatureFlag(response.Content.ToObjectFromJson<JsonElement>(), options);
return Response.FromValue(result, response);
}

private static Response<FeatureFlag> CreateResourceModifiedResponse(Response response)
{
return new NoBodyResponse<FeatureFlag>(response);
}

private static void ParseConnectionString(string connectionString, out Uri uri, out string credential, out byte[] secret)
{
Debug.Assert(connectionString != null); // callers check this

var parsed = ConnectionString.Parse(connectionString);

uri = new Uri(parsed.GetRequired("Endpoint"));
credential = parsed.GetRequired("Id");
try
{
secret = Convert.FromBase64String(parsed.GetRequired("Secret"));
}
catch (FormatException)
{
throw new InvalidOperationException("Specified Secret value isn't a valid base64 string");
}
}

private HttpMessage CreateNextGetFeatureFlagsRequest(string nextLink, string syncToken, string acceptDatetime, MatchConditions matchConditions, RequestContext context)
{
HttpMessage message = Pipeline.CreateMessage(context, PipelineMessageClassifier200);
Request request = message.Request;
request.Method = RequestMethod.Get;
RawRequestUriBuilder uri = new RawRequestUriBuilder();
uri.Reset(_endpoint);
uri.AppendRawNextLink(nextLink, false);
request.Uri = uri;
if (syncToken != null)
{
request.Headers.SetValue("Sync-Token", syncToken);
}
if (acceptDatetime != null)
{
request.Headers.SetValue("Accept-Datetime", acceptDatetime);
}
if (matchConditions != null)
{
request.Headers.Add(matchConditions);
}
request.Headers.SetValue("Accept", "application/problem+json, application/vnd.microsoft.appconfig.kvset+json");
return message;
}

private HttpMessage CreateNextGetRevisionsRequest(string nextLink, string syncToken, string acceptDatetime, RequestContext context)
{
HttpMessage message = Pipeline.CreateMessage(context, PipelineMessageClassifier200);
Request request = message.Request;
request.Method = RequestMethod.Get;
RawRequestUriBuilder uri = new RawRequestUriBuilder();
uri.Reset(_endpoint);
uri.AppendRawNextLink(nextLink, false);
if (syncToken != null)
{
request.Headers.SetValue("Sync-Token", syncToken);
}
if (acceptDatetime != null)
{
request.Headers.SetValue("Accept-Datetime", acceptDatetime);
}
request.Headers.SetValue("Accept", "application/problem+json, application/vnd.microsoft.appconfig.kvset+json");
return message;
}

/// <summary>
/// Parses the response of a <see cref="GetFeatureFlags(FeatureFlagSelector, CancellationToken)"/> request.
/// The "@nextLink" JSON property is not reliable since the service does not return a response body for 304
/// responses. This method also attempts to extract the next link address from the "Link" header.
/// </summary>
private (List<FeatureFlag> Values, string NextLink) ParseGetGetFeatureFlagsResponse(Response response)
{
var values = new List<FeatureFlag>();
string nextLink = null;

if (response.Status == 200)
{
var document = response.ContentStream != null ? JsonDocument.Parse(response.ContentStream) : JsonDocument.Parse(response.Content);

if (document.RootElement.TryGetProperty("items", out var itemsValue))
{
foreach (var jsonItem in itemsValue.EnumerateArray())
{
FeatureFlag setting = FeatureFlag.DeserializeFeatureFlag(jsonItem, default);
values.Add(setting);
}
}

if (document.RootElement.TryGetProperty("@nextLink", out var nextLinkValue))
{
nextLink = nextLinkValue.GetString();
}
}

// The "Link" header is formatted as:
// <nextLink>; rel="next"
if (nextLink == null && response.Headers.TryGetValue("Link", out string linkHeader))
{
int nextLinkEndIndex = linkHeader.IndexOf('>');
nextLink = linkHeader.Substring(1, nextLinkEndIndex - 1);
}

return (values, nextLink);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading