diff --git a/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.net10.0.cs b/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.net10.0.cs index a5c19e147021..9e3b19cb707b 100644 --- a/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.net10.0.cs +++ b/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.net10.0.cs @@ -39,6 +39,8 @@ public ConfigurationClient(System.Uri endpoint, Azure.Core.TokenCredential crede public virtual Azure.Response ArchiveSnapshot(string snapshotName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task> ArchiveSnapshotAsync(string snapshotName, Azure.MatchConditions matchConditions, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task> ArchiveSnapshotAsync(string snapshotName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual Azure.Pageable CheckConfigurationSettings(Azure.Data.AppConfiguration.SettingSelector selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual Azure.AsyncPageable CheckConfigurationSettingsAsync(Azure.Data.AppConfiguration.SettingSelector selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Data.AppConfiguration.CreateSnapshotOperation CreateSnapshot(Azure.WaitUntil wait, string snapshotName, Azure.Data.AppConfiguration.ConfigurationSnapshot snapshot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task CreateSnapshotAsync(Azure.WaitUntil wait, string snapshotName, Azure.Data.AppConfiguration.ConfigurationSnapshot snapshot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Response DeleteConfigurationSetting(Azure.Data.AppConfiguration.ConfigurationSetting setting, bool onlyIfUnchanged = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } diff --git a/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.net8.0.cs b/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.net8.0.cs index a5c19e147021..9e3b19cb707b 100644 --- a/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.net8.0.cs +++ b/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.net8.0.cs @@ -39,6 +39,8 @@ public ConfigurationClient(System.Uri endpoint, Azure.Core.TokenCredential crede public virtual Azure.Response ArchiveSnapshot(string snapshotName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task> ArchiveSnapshotAsync(string snapshotName, Azure.MatchConditions matchConditions, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task> ArchiveSnapshotAsync(string snapshotName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual Azure.Pageable CheckConfigurationSettings(Azure.Data.AppConfiguration.SettingSelector selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual Azure.AsyncPageable CheckConfigurationSettingsAsync(Azure.Data.AppConfiguration.SettingSelector selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Data.AppConfiguration.CreateSnapshotOperation CreateSnapshot(Azure.WaitUntil wait, string snapshotName, Azure.Data.AppConfiguration.ConfigurationSnapshot snapshot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task CreateSnapshotAsync(Azure.WaitUntil wait, string snapshotName, Azure.Data.AppConfiguration.ConfigurationSnapshot snapshot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Response DeleteConfigurationSetting(Azure.Data.AppConfiguration.ConfigurationSetting setting, bool onlyIfUnchanged = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } diff --git a/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.netstandard2.0.cs b/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.netstandard2.0.cs index 00c574d418dd..4a6ece9fff15 100644 --- a/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.netstandard2.0.cs +++ b/sdk/appconfiguration/Azure.Data.AppConfiguration/api/Azure.Data.AppConfiguration.netstandard2.0.cs @@ -39,6 +39,8 @@ public ConfigurationClient(System.Uri endpoint, Azure.Core.TokenCredential crede public virtual Azure.Response ArchiveSnapshot(string snapshotName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task> ArchiveSnapshotAsync(string snapshotName, Azure.MatchConditions matchConditions, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task> ArchiveSnapshotAsync(string snapshotName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual Azure.Pageable CheckConfigurationSettings(Azure.Data.AppConfiguration.SettingSelector selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual Azure.AsyncPageable CheckConfigurationSettingsAsync(Azure.Data.AppConfiguration.SettingSelector selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Data.AppConfiguration.CreateSnapshotOperation CreateSnapshot(Azure.WaitUntil wait, string snapshotName, Azure.Data.AppConfiguration.ConfigurationSnapshot snapshot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task CreateSnapshotAsync(Azure.WaitUntil wait, string snapshotName, Azure.Data.AppConfiguration.ConfigurationSnapshot snapshot, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Response DeleteConfigurationSetting(Azure.Data.AppConfiguration.ConfigurationSetting setting, bool onlyIfUnchanged = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } diff --git a/sdk/appconfiguration/Azure.Data.AppConfiguration/assets.json b/sdk/appconfiguration/Azure.Data.AppConfiguration/assets.json index 5c1ba589ed34..729fa8b7d8be 100644 --- a/sdk/appconfiguration/Azure.Data.AppConfiguration/assets.json +++ b/sdk/appconfiguration/Azure.Data.AppConfiguration/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "net", "TagPrefix": "net/appconfiguration/Azure.Data.AppConfiguration", - "Tag": "net/appconfiguration/Azure.Data.AppConfiguration_0ef5fd0bc3" + "Tag": "net/appconfiguration/Azure.Data.AppConfiguration_5393940842" } diff --git a/sdk/appconfiguration/Azure.Data.AppConfiguration/src/ConfigurationClient.cs b/sdk/appconfiguration/Azure.Data.AppConfiguration/src/ConfigurationClient.cs index ea6c3218b5f7..67a4accd144c 100644 --- a/sdk/appconfiguration/Azure.Data.AppConfiguration/src/ConfigurationClient.cs +++ b/sdk/appconfiguration/Azure.Data.AppConfiguration/src/ConfigurationClient.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using System.Web; using Azure.Core; using Azure.Core.Pipeline; using Microsoft.TypeSpec.Generator.Customizations; @@ -751,6 +753,63 @@ HttpMessage NextPageRequest(MatchConditions conditions, int? pageSizeHint, strin return new ConditionalPageableImplementation(FirstPageRequest, NextPageRequest, ParseGetConfigurationSettingsResponse, Pipeline, ClientDiagnostics, "ConfigurationClient.GetConfigurationSettings", context); } + /// + /// Checks if one or more entities that match the options specified in the passed-in have changed. + /// + /// Options used to select a set of entities from the configuration store. + /// A controlling the request lifetime. + /// A pageable collection of pages with empty values collections. + public virtual AsyncPageable CheckConfigurationSettingsAsync(SettingSelector selector, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(selector, nameof(selector)); + + var pageableImplementation = CheckConfigurationSettingsPageableImplementation(selector, cancellationToken); + + return new AsyncConditionalPageable(pageableImplementation); + } + + /// + /// Checks if one or more entities that match the options specified in the passed-in have changed. + /// + /// Set of options for selecting from the configuration store. + /// A controlling the request lifetime. + /// A pageable collection of pages with empty values collections. + public virtual Pageable CheckConfigurationSettings(SettingSelector selector, CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(selector, nameof(selector)); + + var pageableImplementation = CheckConfigurationSettingsPageableImplementation(selector, cancellationToken); + + return new ConditionalPageable(pageableImplementation); + } + + private ConditionalPageableImplementation CheckConfigurationSettingsPageableImplementation(SettingSelector selector, CancellationToken cancellationToken) + { + var key = selector.KeyFilter; + var label = selector.LabelFilter; + var dateTime = selector.AcceptDateTime?.UtcDateTime.ToString(AcceptDateTimeFormat, CultureInfo.InvariantCulture); + var tags = selector.TagsFilter; + + RequestContext context = CreateRequestContext(ErrorOptions.Default, cancellationToken); + IEnumerable fieldsString = selector.Fields.Split(); + + context.AddClassifier(304, false); + + HttpMessage FirstPageRequest(MatchConditions conditions, int? pageSizeHint) + { + return CreateCheckKeyValuesRequest(key, label, _syncToken, null, dateTime, fieldsString, null, conditions, tags, context); + } + + HttpMessage NextPageRequest(MatchConditions conditions, int? pageSizeHint, string nextLink) + { + HttpMessage message = CreateNextGetConfigurationSettingsRequest(nextLink, key, label, _syncToken, null, dateTime, fieldsString, null, conditions, tags, context); + message.Request.Method = RequestMethod.Head; + return message; + } + + return new ConditionalPageableImplementation(FirstPageRequest, NextPageRequest, ParseCheckConfigurationSettingsResponse, Pipeline, ClientDiagnostics, "ConfigurationClient.CheckConfigurationSettings", context); + } + /// /// Retrieves one or more entities for snapshots based on name. /// @@ -1518,6 +1577,22 @@ public virtual void UpdateSyncToken(string token) return (values, nextLink); } + private (List Values, string NextLink) ParseCheckConfigurationSettingsResponse(Response response) + { + var values = new List(); + string nextLink = null; + + // The "Link" header is formatted as: + // ; rel="next" + if (response.Headers.TryGetValue("Link", out string linkHeader)) + { + int nextLinkEndIndex = linkHeader.IndexOf('>'); + nextLink = linkHeader.Substring(1, nextLinkEndIndex - 1); + } + + return (values, nextLink); + } + private static RequestContext CreateRequestContext(ErrorOptions errorOptions, CancellationToken cancellationToken) { return new RequestContext() diff --git a/sdk/appconfiguration/Azure.Data.AppConfiguration/tests/ConfigurationLiveTests.cs b/sdk/appconfiguration/Azure.Data.AppConfiguration/tests/ConfigurationLiveTests.cs index ef9a7ba55c8d..3c3ae6ad4ea8 100644 --- a/sdk/appconfiguration/Azure.Data.AppConfiguration/tests/ConfigurationLiveTests.cs +++ b/sdk/appconfiguration/Azure.Data.AppConfiguration/tests/ConfigurationLiveTests.cs @@ -1360,6 +1360,268 @@ public async Task GetBatchSettingIfChangedWithModifiedPageSync() Assert.AreEqual(2, pagesCount); } + [RecordedTest] + [AsyncOnly] + [ServiceVersion(Min = ConfigurationClientOptions.ServiceVersion.V2023_10_01)] + public async Task CheckBatchSettingIfChangedWithUnmodifiedPage() + { + ConfigurationClient service = GetClient(skipClientInstrumentation: true); + + const int expectedEvents = 105; + var key = await SetMultipleKeys(service, expectedEvents); + + SettingSelector selector = new SettingSelector { KeyFilter = key }; + var matchConditionsList = new List(); + + await foreach (Page page in service.CheckConfigurationSettingsAsync(selector).AsPages()) + { + Response response = page.GetRawResponse(); + var matchConditions = new MatchConditions() + { + IfNoneMatch = response.Headers.ETag + }; + + matchConditionsList.Add(matchConditions); + } + + int pagesCount = 0; + + await foreach (Page page in service.CheckConfigurationSettingsAsync(selector).AsPages(matchConditionsList)) + { + Response response = page.GetRawResponse(); + + Assert.AreEqual(304, response.Status); + Assert.IsEmpty(page.Values); + + pagesCount++; + } + + Assert.AreEqual(2, pagesCount); + } + + [RecordedTest] + [SyncOnly] + [ServiceVersion(Min = ConfigurationClientOptions.ServiceVersion.V2023_10_01)] + public async Task CheckBatchSettingIfChangedWithUnmodifiedPageSync() + { + ConfigurationClient service = GetClient(skipClientInstrumentation: true); + + const int expectedEvents = 105; + var key = await SetMultipleKeys(service, expectedEvents); + + SettingSelector selector = new SettingSelector { KeyFilter = key }; + var matchConditionsList = new List(); + + foreach (Page page in service.CheckConfigurationSettings(selector).AsPages()) + { + Response response = page.GetRawResponse(); + var matchConditions = new MatchConditions() + { + IfNoneMatch = response.Headers.ETag + }; + + matchConditionsList.Add(matchConditions); + } + + int pagesCount = 0; + + foreach (Page page in service.CheckConfigurationSettings(selector).AsPages(matchConditionsList)) + { + Response response = page.GetRawResponse(); + + Assert.AreEqual(304, response.Status); + Assert.IsEmpty(page.Values); + + pagesCount++; + } + + Assert.AreEqual(2, pagesCount); + } + + [RecordedTest] + [AsyncOnly] + [ServiceVersion(Min = ConfigurationClientOptions.ServiceVersion.V2023_10_01)] + public async Task CheckBatchSettingIfChangedWithUnmodifiedPageDoesNotLogWarningAsync() + { + ConfigurationClient service = GetClient(skipClientInstrumentation: true); + + const int expectedEvents = 105; + var key = await SetMultipleKeys(service, expectedEvents); + + SettingSelector selector = new SettingSelector { KeyFilter = key }; + var matchConditionsList = new List(); + + var pagesEnumerator = service.CheckConfigurationSettingsAsync(selector).AsPages().GetAsyncEnumerator(); + await pagesEnumerator.MoveNextAsync(); + + Page firstPage = pagesEnumerator.Current; + + var matchConditions = new MatchConditions() + { + IfNoneMatch = firstPage.GetRawResponse().Headers.ETag + }; + matchConditionsList.Add(matchConditions); + + string logMessage = null; + Action warningLog = (_, text) => + { + logMessage = text; + }; + + pagesEnumerator = service.CheckConfigurationSettingsAsync(selector).AsPages(matchConditionsList).GetAsyncEnumerator(); + + using (var listener = new AzureEventSourceListener(warningLog, EventLevel.Warning)) + { + await pagesEnumerator.MoveNextAsync(); + firstPage = pagesEnumerator.Current; + Assert.AreEqual(304, firstPage.GetRawResponse().Status); + } + + Assert.Null(logMessage); + } + + [RecordedTest] + [SyncOnly] + [ServiceVersion(Min = ConfigurationClientOptions.ServiceVersion.V2023_10_01)] + public async Task CheckBatchSettingIfChangedWithUnmodifiedPageDoesNotLogWarningSync() + { + ConfigurationClient service = GetClient(skipClientInstrumentation: true); + + const int expectedEvents = 105; + var key = await SetMultipleKeys(service, expectedEvents); + + SettingSelector selector = new SettingSelector { KeyFilter = key }; + var matchConditionsList = new List(); + + var pagesEnumerator = service.CheckConfigurationSettings(selector).AsPages().GetEnumerator(); + pagesEnumerator.MoveNext(); + + Page firstPage = pagesEnumerator.Current; + + var matchConditions = new MatchConditions() + { + IfNoneMatch = firstPage.GetRawResponse().Headers.ETag + }; + matchConditionsList.Add(matchConditions); + + string logMessage = null; + Action warningLog = (_, text) => + { + logMessage = text; + }; + + pagesEnumerator = service.CheckConfigurationSettings(selector).AsPages(matchConditionsList).GetEnumerator(); + + using (var listener = new AzureEventSourceListener(warningLog, EventLevel.Warning)) + { + pagesEnumerator.MoveNext(); + firstPage = pagesEnumerator.Current; + Assert.AreEqual(304, firstPage.GetRawResponse().Status); + } + + Assert.Null(logMessage); + } + + [RecordedTest] + [AsyncOnly] + [ServiceVersion(Min = ConfigurationClientOptions.ServiceVersion.V2023_10_01)] + public async Task CheckBatchSettingIfChangedWithModifiedPage() + { + ConfigurationClient service = GetClient(skipClientInstrumentation: true); + + const int expectedEvents = 105; + var key = await SetMultipleKeys(service, expectedEvents); + + SettingSelector selector = new SettingSelector { KeyFilter = key }; + var matchConditionsList = new List(); + ConfigurationSetting lastSetting = null; + + await foreach (Page page in service.GetConfigurationSettingsAsync(selector).AsPages()) + { + Response response = page.GetRawResponse(); + var matchConditions = new MatchConditions() + { + IfNoneMatch = response.Headers.ETag + }; + + matchConditionsList.Add(matchConditions); + lastSetting = page.Values.Last(); + } + + lastSetting.Value += "1"; + await service.SetConfigurationSettingAsync(lastSetting); + + int pagesCount = 0; + + await foreach (Page page in service.CheckConfigurationSettingsAsync(selector).AsPages(matchConditionsList)) + { + Response response = page.GetRawResponse(); + + if (pagesCount == 0) + { + Assert.AreEqual(304, response.Status); + } + else + { + Assert.AreEqual(200, response.Status); + } + + pagesCount++; + } + + Assert.AreEqual(2, pagesCount); + } + + [RecordedTest] + [SyncOnly] + [ServiceVersion(Min = ConfigurationClientOptions.ServiceVersion.V2023_10_01)] + public async Task CheckBatchSettingIfChangedWithModifiedPageSync() + { + ConfigurationClient service = GetClient(skipClientInstrumentation: true); + + const int expectedEvents = 105; + var key = await SetMultipleKeys(service, expectedEvents); + + SettingSelector selector = new SettingSelector { KeyFilter = key }; + var matchConditionsList = new List(); + ConfigurationSetting lastSetting = null; + + foreach (Page page in service.GetConfigurationSettings(selector).AsPages()) + { + Response response = page.GetRawResponse(); + var matchConditions = new MatchConditions() + { + IfNoneMatch = response.Headers.ETag + }; + + matchConditionsList.Add(matchConditions); + lastSetting = page.Values.Last(); + } + + lastSetting.Value += "1"; + await service.SetConfigurationSettingAsync(lastSetting); + + int pagesCount = 0; + + foreach (Page page in service.CheckConfigurationSettings(selector).AsPages(matchConditionsList)) + { + Response response = page.GetRawResponse(); + + if (pagesCount == 0) + { + Assert.AreEqual(304, response.Status); + } + else + { + Assert.AreEqual(200, response.Status); + } + + pagesCount++; + } + + Assert.AreEqual(2, pagesCount); + } + [RecordedTest] public async Task GetBatchSettingAny() {