diff --git a/sdk/core/Azure.Core/src/Shared/OperationInternal.cs b/sdk/core/Azure.Core/src/Shared/OperationInternal.cs index 1d7420fe6913..92f2e102334c 100644 --- a/sdk/core/Azure.Core/src/Shared/OperationInternal.cs +++ b/sdk/core/Azure.Core/src/Shared/OperationInternal.cs @@ -78,6 +78,15 @@ public OperationInternal( _operation = operation; } + /// + /// Sets the state immediately. + /// + /// The used to set and other members. + public void SetState(OperationState state) + { + ApplyStateAsync(false, state.RawResponse, state.HasCompleted, state.HasSucceeded, state.OperationFailedException, throwIfFailed: false).EnsureCompleted(); + } + protected override async ValueTask UpdateStateAsync(bool async, CancellationToken cancellationToken) { OperationState state = await _operation.UpdateStateAsync(async, cancellationToken).ConfigureAwait(false); diff --git a/sdk/core/Azure.Core/src/Shared/OperationInternalBase.cs b/sdk/core/Azure.Core/src/Shared/OperationInternalBase.cs index 816417adf1f3..5f92b89275c2 100644 --- a/sdk/core/Azure.Core/src/Shared/OperationInternalBase.cs +++ b/sdk/core/Azure.Core/src/Shared/OperationInternalBase.cs @@ -179,7 +179,7 @@ private async ValueTask UpdateStatusAsync(bool async, CancellationToke } } - protected async ValueTask ApplyStateAsync(bool async, Response response, bool hasCompleted, bool hasSucceeded, RequestFailedException? requestFailedException) + protected async ValueTask ApplyStateAsync(bool async, Response response, bool hasCompleted, bool hasSucceeded, RequestFailedException? requestFailedException, bool throwIfFailed = true) { RawResponse = response; @@ -198,7 +198,13 @@ protected async ValueTask ApplyStateAsync(bool async, Response respons (async ? await _diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false) : _diagnostics.CreateRequestFailedException(response)); - throw OperationFailedException; + + if (throwIfFailed) + { + throw OperationFailedException; + } + + return response; } protected static TimeSpan GetServerDelay(Response response, TimeSpan pollingInterval) diff --git a/sdk/core/Azure.Core/src/Shared/OperationInternalOfT.cs b/sdk/core/Azure.Core/src/Shared/OperationInternalOfT.cs index 1e31ddc23e8d..9bc88ec9fb1f 100644 --- a/sdk/core/Azure.Core/src/Shared/OperationInternalOfT.cs +++ b/sdk/core/Azure.Core/src/Shared/OperationInternalOfT.cs @@ -168,6 +168,19 @@ public async ValueTask> WaitForCompletionAsync(TimeSpan pollingInter return Response.FromValue(Value, rawResponse); } + /// + /// Sets the state immediately. + /// + /// The used to set and other members. + public void SetState(OperationState state) + { + if (state.HasCompleted && state.HasSucceeded) + { + Value = state.Value!; + } + ApplyStateAsync(false, state.RawResponse, state.HasCompleted, state.HasSucceeded, state.OperationFailedException, throwIfFailed: false).EnsureCompleted(); + } + protected override async ValueTask UpdateStateAsync(bool async, CancellationToken cancellationToken) { OperationState state = await _operation.UpdateStateAsync(async, cancellationToken).ConfigureAwait(false); diff --git a/sdk/core/Azure.Core/tests/OperationInternalTests.cs b/sdk/core/Azure.Core/tests/OperationInternalTests.cs index 8cf1d67820be..dcd2dacc6035 100644 --- a/sdk/core/Azure.Core/tests/OperationInternalTests.cs +++ b/sdk/core/Azure.Core/tests/OperationInternalTests.cs @@ -87,6 +87,68 @@ public void RawResponseInitialization() } } + [Test] + public void SetStateSucceeds() + { + var operationInternal = CreateOperation(isOfT, UpdateResult.Pending); + if (operationInternal is OperationInternal oi) + { + oi.SetState(OperationState.Success(mockResponse)); + } + else if (operationInternal is OperationInternal oit) + { + oit.SetState(OperationState.Success(mockResponse, 1)); + } + + Assert.IsTrue(operationInternal.HasCompleted); + if (operationInternal is OperationInternal oit2) + { + Assert.IsTrue(oit2.HasValue); + Assert.AreEqual(1, oit2.Value); + } + } + + [Test] + public void SetStateIsPending() + { + var operationInternal = CreateOperation(isOfT, UpdateResult.Pending); + if (operationInternal is OperationInternal oi) + { + oi.SetState(OperationState.Pending(mockResponse)); + } + else if (operationInternal is OperationInternal oit) + { + oit.SetState(OperationState.Pending(mockResponse)); + } + + Assert.IsFalse(operationInternal.HasCompleted); + if (operationInternal is OperationInternal oit2) + { + Assert.IsFalse(oit2.HasValue); + } + } + + [Test] + public void SetStateFails() + { + var operationInternal = CreateOperation(isOfT, UpdateResult.Pending); + if (operationInternal is OperationInternal oi) + { + oi.SetState(OperationState.Failure(mockResponse)); + } + else if (operationInternal is OperationInternal oit) + { + oit.SetState(OperationState.Failure(mockResponse)); + } + + Assert.IsTrue(operationInternal.HasCompleted); + if (operationInternal is OperationInternal oit2) + { + Assert.IsFalse(oit2.HasValue); + Assert.Throws(() => _ = oit2.Value); + } + } + [Test] public async Task UpdateStatusWhenOperationIsPending([Values(true, false)] bool async) { diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/Azure.Security.KeyVault.Certificates.csproj b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/Azure.Security.KeyVault.Certificates.csproj index 03c6659be8ce..65f8c989bfca 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/Azure.Security.KeyVault.Certificates.csproj +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/Azure.Security.KeyVault.Certificates.csproj @@ -35,6 +35,8 @@ + + diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificateProperties.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificateProperties.cs index 9e5b5f8b7292..84031a8ca6df 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificateProperties.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificateProperties.cs @@ -158,18 +158,14 @@ internal void ReadProperty(JsonProperty prop) } } - private void ParseId(Uri idToParse) + private void ParseId(Uri id) { - // We expect an identifier with either 3 or 4 segments: host + collection + name [+ version] - if (idToParse.Segments.Length != 3 && idToParse.Segments.Length != 4) - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Invalid ObjectIdentifier: {0}. Bad number of segments: {1}", idToParse, idToParse.Segments.Length)); + KeyVaultIdentifier identifier = KeyVaultIdentifier.ParseWithCollection(id, "certificates"); - if (!string.Equals(idToParse.Segments[1], "certificates" + "/", StringComparison.OrdinalIgnoreCase)) - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Invalid ObjectIdentifier: {0}. segment [1] should be 'certificates/', found '{1}'", idToParse, idToParse.Segments[1])); - - VaultUri = new Uri($"{idToParse.Scheme}://{idToParse.Authority}"); - Name = idToParse.Segments[2].Trim('/'); - Version = (idToParse.Segments.Length == 4) ? idToParse.Segments[3].TrimEnd('/') : null; + Id = id; + VaultUri = identifier.VaultUri; + Name = identifier.Name; + Version = identifier.Version; } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/DeleteCertificateOperation.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/DeleteCertificateOperation.cs index 74344bde665c..1cd477595499 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/DeleteCertificateOperation.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/DeleteCertificateOperation.cs @@ -2,35 +2,39 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Azure.Core.Pipeline; namespace Azure.Security.KeyVault.Certificates { /// /// A long-running operation for or . /// - public class DeleteCertificateOperation : Operation + public class DeleteCertificateOperation : Operation, IOperation { private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2); private readonly KeyVaultPipeline _pipeline; + private readonly OperationInternal _operationInternal; private readonly DeletedCertificate _value; - private Response _response; - private bool _completed; internal DeleteCertificateOperation(KeyVaultPipeline pipeline, Response response) { _pipeline = pipeline; _value = response.Value ?? throw new InvalidOperationException("The response does not contain a value."); - _response = response.GetRawResponse(); + _operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(DeleteCertificateOperation), new[] + { + new KeyValuePair("secret", _value.Name), // Retained for backward compatibility. + new KeyValuePair("certificate", _value.Name), + }); - // The recoveryId is only returned if soft-delete is enabled. + // The recoveryId is only returned if soft delete is enabled. if (_value.RecoveryId is null) { - _completed = true; + // If soft delete is not enabled, deleting is immediate so set success accordingly. + _operationInternal.SetState(OperationState.Success(response.GetRawResponse())); } } @@ -50,33 +54,20 @@ protected DeleteCertificateOperation() {} public override DeletedCertificate Value => _value; /// - public override bool HasCompleted => _completed; + public override bool HasCompleted => _operationInternal.HasCompleted; /// public override bool HasValue => true; /// - public override Response GetRawResponse() => _response; + public override Response GetRawResponse() => _operationInternal.RawResponse; /// public override Response UpdateStatus(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteCertificateOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, CertificateClient.DeletedCertificatesPath, _value.Name); - _completed = CheckCompleted(_response); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return _operationInternal.UpdateStatus(cancellationToken); } return GetRawResponse(); @@ -85,22 +76,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa /// public override async ValueTask UpdateStatusAsync(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteCertificateOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, CertificateClient.DeletedCertificatesPath, _value.Name).ConfigureAwait(false); - _completed = await CheckCompletedAsync(_response).ConfigureAwait(false); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false); } return GetRawResponse(); @@ -114,34 +92,27 @@ public override ValueTask> WaitForCompletionAsync(C public override ValueTask> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) => this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken); - private async ValueTask CheckCompletedAsync(Response response) + async ValueTask IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken) { - switch (response.Status) - { - case 200: - case 403: // Access denied but proof the certificate was deleted. - return true; - - case 404: - return false; + Response response = async + ? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, CertificateClient.DeletedCertificatesPath, _value.Name).ConfigureAwait(false) + : _pipeline.GetResponse(RequestMethod.Get, cancellationToken, CertificateClient.DeletedCertificatesPath, _value.Name); - default: - throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false); - } - } - private bool CheckCompleted(Response response) - { switch (response.Status) { case 200: case 403: // Access denied but proof the certificate was deleted. - return true; + return OperationState.Success(response); case 404: - return false; + return OperationState.Pending(response); default: - throw _pipeline.Diagnostics.CreateRequestFailedException(response); + RequestFailedException ex = async + ? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false) + : _pipeline.Diagnostics.CreateRequestFailedException(response); + + return OperationState.Failure(response, ex); } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/RecoverDeletedCertificateOperation.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/RecoverDeletedCertificateOperation.cs index 80c40e27f37a..5414ca1d3822 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/RecoverDeletedCertificateOperation.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/RecoverDeletedCertificateOperation.cs @@ -2,30 +2,33 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Azure.Core.Pipeline; namespace Azure.Security.KeyVault.Certificates { /// /// A long-running operation for or . /// - public class RecoverDeletedCertificateOperation : Operation + public class RecoverDeletedCertificateOperation : Operation, IOperation { private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2); private readonly KeyVaultPipeline _pipeline; + private readonly OperationInternal _operationInternal; private readonly KeyVaultCertificateWithPolicy _value; - private Response _response; - private bool _completed; internal RecoverDeletedCertificateOperation(KeyVaultPipeline pipeline, Response response) { _pipeline = pipeline; _value = response.Value ?? throw new InvalidOperationException("The response does not contain a value."); - _response = response.GetRawResponse(); + _operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(RecoverDeletedCertificateOperation), new[] + { + new KeyValuePair("secret", _value.Name), // Retained for backward compatibility. + new KeyValuePair("certificate", _value.Name), + }); } /// Initializes a new instance of for mocking. @@ -44,33 +47,20 @@ protected RecoverDeletedCertificateOperation() {} public override KeyVaultCertificateWithPolicy Value => _value; /// - public override bool HasCompleted => _completed; + public override bool HasCompleted => _operationInternal.HasCompleted; /// public override bool HasValue => true; /// - public override Response GetRawResponse() => _response; + public override Response GetRawResponse() => _operationInternal.RawResponse; /// public override Response UpdateStatus(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedCertificateOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, CertificateClient.CertificatesPath, _value.Name, "/", _value.Properties.Version); - _completed = CheckCompleted(_response); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return _operationInternal.UpdateStatus(cancellationToken); } return GetRawResponse(); @@ -79,22 +69,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa /// public override async ValueTask UpdateStatusAsync(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedCertificateOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, CertificateClient.CertificatesPath, _value.Name, "/", _value.Properties.Version).ConfigureAwait(false); - _completed = await CheckCompletedAsync(_response).ConfigureAwait(false); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false); } return GetRawResponse(); @@ -108,34 +85,27 @@ public override ValueTask> WaitForComple public override ValueTask> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) => this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken); - private async ValueTask CheckCompletedAsync(Response response) + async ValueTask IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken) { - switch (response.Status) - { - case 200: - case 403: // Access denied but proof the certificate was recovered. - return true; - - case 404: - return false; + Response response = async + ? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, CertificateClient.CertificatesPath, _value.Name, "/", _value.Properties.Version).ConfigureAwait(false) + : _pipeline.GetResponse(RequestMethod.Get, cancellationToken, CertificateClient.CertificatesPath, _value.Name, "/", _value.Properties.Version); - default: - throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false); - } - } - private bool CheckCompleted(Response response) - { switch (response.Status) { case 200: case 403: // Access denied but proof the certificate was recovered. - return true; + return OperationState.Success(response); case 404: - return false; + return OperationState.Pending(response); default: - throw _pipeline.Diagnostics.CreateRequestFailedException(response); + RequestFailedException ex = async + ? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false) + : _pipeline.Diagnostics.CreateRequestFailedException(response); + + return OperationState.Failure(response, ex); } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.KeyVault.Keys.csproj b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.KeyVault.Keys.csproj index bd3b0e7fb47d..f50eee3c3ce3 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.KeyVault.Keys.csproj +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.KeyVault.Keys.csproj @@ -38,6 +38,8 @@ + + diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/DeleteKeyOperation.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/DeleteKeyOperation.cs index 15cf60e342f4..582b86c6add8 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/DeleteKeyOperation.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/DeleteKeyOperation.cs @@ -2,35 +2,39 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Azure.Core.Pipeline; namespace Azure.Security.KeyVault.Keys { /// /// A long-running operation for or . /// - public class DeleteKeyOperation : Operation + public class DeleteKeyOperation : Operation, IOperation { private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2); private readonly KeyVaultPipeline _pipeline; + private readonly OperationInternal _operationInternal; private readonly DeletedKey _value; - private Response _response; - private bool _completed; internal DeleteKeyOperation(KeyVaultPipeline pipeline, Response response) { _pipeline = pipeline; _value = response.Value ?? throw new InvalidOperationException("The response does not contain a value."); - _response = response.GetRawResponse(); + _operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(DeleteKeyOperation), new[] + { + new KeyValuePair("secret", _value.Name), // Retained for backward compatibility. + new KeyValuePair("key", _value.Name), + }); - // The recoveryId is only returned if soft-delete is enabled. + // The recoveryId is only returned if soft delete is enabled. if (_value.RecoveryId is null) { - _completed = true; + // If soft delete is not enabled, deleting is immediate so set success accordingly. + _operationInternal.SetState(OperationState.Success(response.GetRawResponse())); } } @@ -50,33 +54,20 @@ protected DeleteKeyOperation() {} public override DeletedKey Value => _value; /// - public override bool HasCompleted => _completed; + public override bool HasCompleted => _operationInternal.HasCompleted; /// public override bool HasValue => true; /// - public override Response GetRawResponse() => _response; + public override Response GetRawResponse() => _operationInternal.RawResponse; /// public override Response UpdateStatus(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteKeyOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, KeyClient.DeletedKeysPath, _value.Name); - _completed = CheckCompleted(_response); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return _operationInternal.UpdateStatus(cancellationToken); } return GetRawResponse(); @@ -85,22 +76,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa /// public override async ValueTask UpdateStatusAsync(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteKeyOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, KeyClient.DeletedKeysPath, _value.Name).ConfigureAwait(false); - _completed = await CheckCompletedAsync(_response).ConfigureAwait(false); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false); } return GetRawResponse(); @@ -114,34 +92,27 @@ public override ValueTask> WaitForCompletionAsync(Cancellat public override ValueTask> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) => this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken); - private async ValueTask CheckCompletedAsync(Response response) + async ValueTask IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken) { - switch (response.Status) - { - case 200: - case 403: // Access denied but proof the key was deleted. - return true; - - case 404: - return false; + Response response = async + ? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, KeyClient.DeletedKeysPath, _value.Name).ConfigureAwait(false) + : _pipeline.GetResponse(RequestMethod.Get, cancellationToken, KeyClient.DeletedKeysPath, _value.Name); - default: - throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false); - } - } - private bool CheckCompleted(Response response) - { switch (response.Status) { case 200: case 403: // Access denied but proof the key was deleted. - return true; + return OperationState.Success(response); case 404: - return false; + return OperationState.Pending(response); default: - throw _pipeline.Diagnostics.CreateRequestFailedException(response); + RequestFailedException ex = async + ? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false) + : _pipeline.Diagnostics.CreateRequestFailedException(response); + + return OperationState.Failure(response, ex); } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyProperties.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyProperties.cs index 654f55a072c3..08085b4dd655 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyProperties.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyProperties.cs @@ -138,20 +138,15 @@ public KeyProperties(Uri id) /// /// Parses the key identifier into the , , and of the key. /// - /// The key vault object identifier. - internal void ParseId(Uri idToParse) + /// The key vault object identifier. + internal void ParseId(Uri id) { - // We expect an identifier with either 3 or 4 segments: host + collection + name [+ version] - if (idToParse.Segments.Length != 3 && idToParse.Segments.Length != 4) - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Invalid ObjectIdentifier: {0}. Bad number of segments: {1}", idToParse, idToParse.Segments.Length)); + KeyVaultIdentifier identifier = KeyVaultIdentifier.ParseWithCollection(id, "keys"); - if (!string.Equals(idToParse.Segments[1], "keys" + "/", StringComparison.OrdinalIgnoreCase)) - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Invalid ObjectIdentifier: {0}. segment [1] should be 'keys/', found '{1}'", idToParse, idToParse.Segments[1])); - - Id = idToParse; - VaultUri = new Uri($"{idToParse.Scheme}://{idToParse.Authority}"); - Name = idToParse.Segments[2].Trim('/'); - Version = (idToParse.Segments.Length == 4) ? idToParse.Segments[3].TrimEnd('/') : null; + Id = id; + VaultUri = identifier.VaultUri; + Name = identifier.Name; + Version = identifier.Version; } internal void ReadProperty(JsonProperty prop) diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/RecoverDeletedKeyOperation.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/RecoverDeletedKeyOperation.cs index 7681dfec6211..7852bffc472e 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/RecoverDeletedKeyOperation.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/RecoverDeletedKeyOperation.cs @@ -2,30 +2,33 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Azure.Core.Pipeline; namespace Azure.Security.KeyVault.Keys { /// /// A long-running operation for or . /// - public class RecoverDeletedKeyOperation : Operation + public class RecoverDeletedKeyOperation : Operation, IOperation { private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2); private readonly KeyVaultPipeline _pipeline; + private readonly OperationInternal _operationInternal; private readonly KeyVaultKey _value; - private Response _response; - private bool _completed; internal RecoverDeletedKeyOperation(KeyVaultPipeline pipeline, Response response) { _pipeline = pipeline; _value = response.Value ?? throw new InvalidOperationException("The response does not contain a value."); - _response = response.GetRawResponse(); + _operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(RecoverDeletedKeyOperation), new[] + { + new KeyValuePair("secret", _value.Name), // Retained for backward compatibility. + new KeyValuePair("key", _value.Name), + }); } /// Initializes a new instance of for mocking. @@ -44,33 +47,20 @@ protected RecoverDeletedKeyOperation() {} public override KeyVaultKey Value => _value; /// - public override bool HasCompleted => _completed; + public override bool HasCompleted => _operationInternal.HasCompleted; /// public override bool HasValue => true; /// - public override Response GetRawResponse() => _response; + public override Response GetRawResponse() => _operationInternal.RawResponse; /// public override Response UpdateStatus(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedKeyOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, KeyClient.KeysPath, _value.Name, "/", _value.Properties.Version); - _completed = CheckCompleted(_response); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return _operationInternal.UpdateStatus(cancellationToken); } return GetRawResponse(); @@ -79,22 +69,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa /// public override async ValueTask UpdateStatusAsync(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedKeyOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, KeyClient.KeysPath, _value.Name, "/", _value.Properties.Version).ConfigureAwait(false); - _completed = await CheckCompletedAsync(_response).ConfigureAwait(false); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false); } return GetRawResponse(); @@ -108,34 +85,27 @@ public override ValueTask> WaitForCompletionAsync(Cancella public override ValueTask> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) => this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken); - private async ValueTask CheckCompletedAsync(Response response) + async ValueTask IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken) { - switch (response.Status) - { - case 200: - case 403: // Access denied but proof the key was recovered. - return true; - - case 404: - return false; + Response response = async + ? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, KeyClient.KeysPath, _value.Name, "/", _value.Properties.Version).ConfigureAwait(false) + : _pipeline.GetResponse(RequestMethod.Get, cancellationToken, KeyClient.KeysPath, _value.Name, "/", _value.Properties.Version); - default: - throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false); - } - } - private bool CheckCompleted(Response response) - { switch (response.Status) { case 200: case 403: // Access denied but proof the key was recovered. - return true; + return OperationState.Success(response); case 404: - return false; + return OperationState.Pending(response); default: - throw _pipeline.Diagnostics.CreateRequestFailedException(response); + RequestFailedException ex = async + ? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false) + : _pipeline.Diagnostics.CreateRequestFailedException(response); + + return OperationState.Failure(response, ex); } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj index 0fba7ca7bd07..f04b10d59a74 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj @@ -1,4 +1,4 @@ - + This is the Microsoft Azure Key Vault Secrets client library @@ -37,6 +37,8 @@ + + diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/DeleteSecretOperation.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/DeleteSecretOperation.cs index 98e42d5e9ae2..b99e1bbe2928 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/DeleteSecretOperation.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/DeleteSecretOperation.cs @@ -2,35 +2,35 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Azure.Core.Pipeline; namespace Azure.Security.KeyVault.Secrets { /// /// A long-running operation for or . /// - public class DeleteSecretOperation : Operation + public class DeleteSecretOperation : Operation, IOperation { private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2); private readonly KeyVaultPipeline _pipeline; + private readonly OperationInternal _operationInternal; private readonly DeletedSecret _value; - private Response _response; - private bool _completed; internal DeleteSecretOperation(KeyVaultPipeline pipeline, Response response) { _pipeline = pipeline; _value = response.Value ?? throw new InvalidOperationException("The response does not contain a value."); - _response = response.GetRawResponse(); + _operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(DeleteSecretOperation), new[] { new KeyValuePair("secret", _value.Name) }); - // The recoveryId is only returned if soft-delete is enabled. + // The recoveryId is only returned if soft delete is enabled. if (_value.RecoveryId is null) { - _completed = true; + // If soft delete is not enabled, deleting is immediate so set success accordingly. + _operationInternal.SetState(OperationState.Success(response.GetRawResponse())); } } @@ -50,33 +50,20 @@ protected DeleteSecretOperation() {} public override DeletedSecret Value => _value; /// - public override bool HasCompleted => _completed; + public override bool HasCompleted => _operationInternal.HasCompleted; /// public override bool HasValue => true; /// - public override Response GetRawResponse() => _response; + public override Response GetRawResponse() => _operationInternal.RawResponse; /// public override Response UpdateStatus(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteSecretOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, SecretClient.DeletedSecretsPath, _value.Name); - _completed = CheckCompleted(_response); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return _operationInternal.UpdateStatus(cancellationToken); } return GetRawResponse(); @@ -85,22 +72,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa /// public override async ValueTask UpdateStatusAsync(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteSecretOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, SecretClient.DeletedSecretsPath, _value.Name).ConfigureAwait(false); - _completed = await CheckCompletedAsync(_response).ConfigureAwait(false); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false); } return GetRawResponse(); @@ -114,34 +88,27 @@ public override ValueTask> WaitForCompletionAsync(Cancel public override ValueTask> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) => this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken); - private async ValueTask CheckCompletedAsync(Response response) + async ValueTask IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken) { - switch (response.Status) - { - case 200: - case 403: // Access denied but proof the secret was deleted. - return true; - - case 404: - return false; + Response response = async + ? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, SecretClient.DeletedSecretsPath, _value.Name).ConfigureAwait(false) + : _pipeline.GetResponse(RequestMethod.Get, cancellationToken, SecretClient.DeletedSecretsPath, _value.Name); - default: - throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false); - } - } - private bool CheckCompleted(Response response) - { switch (response.Status) { case 200: case 403: // Access denied but proof the secret was deleted. - return true; + return OperationState.Success(response); case 404: - return false; + return OperationState.Pending(response); default: - throw _pipeline.Diagnostics.CreateRequestFailedException(response); + RequestFailedException ex = async + ? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false) + : _pipeline.Diagnostics.CreateRequestFailedException(response); + + return OperationState.Failure(response, ex); } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/ObjectId.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/ObjectId.cs deleted file mode 100644 index 2747a46de33b..000000000000 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/ObjectId.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Globalization; - -namespace Azure.Security.KeyVault.Secrets -{ - internal struct ObjectId - { - public Uri Id { get; set; } - - public Uri VaultUri { get; set; } - - public string Name { get; set; } - - public string Version { get; set; } - - public void ParseId(string collection, string id) => ParseId(collection, new Uri(id, UriKind.Absolute)); - - public void ParseId(string collection, Uri id) - { - Id = id; - - // We expect an identifier with either 3 or 4 segments: host + collection + name [+ version] - if (Id.Segments.Length != 3 && Id.Segments.Length != 4) - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Invalid ObjectIdentifier: {0}. Bad number of segments: {1}", id, Id.Segments.Length)); - - if (!string.Equals(Id.Segments[1], collection + "/", StringComparison.OrdinalIgnoreCase)) - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Invalid ObjectIdentifier: {0}. segment [1] should be '{1}/', found '{2}'", id, collection, Id.Segments[1])); - - VaultUri = new Uri($"{Id.Scheme}://{Id.Authority}"); - Name = Id.Segments[2].Trim('/'); - Version = (Id.Segments.Length == 4) ? Id.Segments[3].TrimEnd('/') : null; - } - } -} diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/RecoverDeletedSecretOperation.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/RecoverDeletedSecretOperation.cs index 26d061a1ca6a..ee6a832a53e4 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/RecoverDeletedSecretOperation.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/RecoverDeletedSecretOperation.cs @@ -2,30 +2,29 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Azure.Core.Pipeline; namespace Azure.Security.KeyVault.Secrets { /// /// A long-running operation for or . /// - public class RecoverDeletedSecretOperation : Operation + public class RecoverDeletedSecretOperation : Operation, IOperation { private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2); private readonly KeyVaultPipeline _pipeline; + private readonly OperationInternal _operationInternal; private readonly SecretProperties _value; - private Response _response; - private bool _completed; internal RecoverDeletedSecretOperation(KeyVaultPipeline pipeline, Response response) { _pipeline = pipeline; _value = response.Value ?? throw new InvalidOperationException("The response does not contain a value."); - _response = response.GetRawResponse(); + _operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(RecoverDeletedSecretOperation), new[] { new KeyValuePair("secret", _value.Name) }); } /// Initializes a new instance of for mocking. @@ -44,33 +43,20 @@ protected RecoverDeletedSecretOperation() {} public override SecretProperties Value => _value; /// - public override bool HasCompleted => _completed; + public override bool HasCompleted => _operationInternal.HasCompleted; /// public override bool HasValue => true; /// - public override Response GetRawResponse() => _response; + public override Response GetRawResponse() => _operationInternal.RawResponse; /// public override Response UpdateStatus(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedSecretOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, SecretClient.SecretsPath, _value.Name, "/", _value.Version); - _completed = CheckCompleted(_response); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return _operationInternal.UpdateStatus(cancellationToken); } return GetRawResponse(); @@ -79,22 +65,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa /// public override async ValueTask UpdateStatusAsync(CancellationToken cancellationToken = default) { - if (!_completed) + if (!HasCompleted) { - using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedSecretOperation)}.{nameof(UpdateStatus)}"); - scope.AddAttribute("secret", _value.Name); - scope.Start(); - - try - { - _response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, SecretClient.SecretsPath, _value.Name, "/", _value.Version).ConfigureAwait(false); - _completed = await CheckCompletedAsync(_response).ConfigureAwait(false); - } - catch (Exception e) - { - scope.Failed(e); - throw; - } + return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false); } return GetRawResponse(); @@ -108,34 +81,27 @@ public override ValueTask> WaitForCompletionAsync(Can public override ValueTask> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) => this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken); - private async ValueTask CheckCompletedAsync(Response response) + async ValueTask IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken) { - switch (response.Status) - { - case 200: - case 403: // Access denied but proof the secret was recovered. - return true; - - case 404: - return false; + Response response = async + ? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, SecretClient.SecretsPath, _value.Name, "/", _value.Version).ConfigureAwait(false) + : _pipeline.GetResponse(RequestMethod.Get, cancellationToken, SecretClient.SecretsPath, _value.Name, "/", _value.Version); - default: - throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false); - } - } - private bool CheckCompleted(Response response) - { switch (response.Status) { case 200: case 403: // Access denied but proof the secret was recovered. - return true; + return OperationState.Success(response); case 404: - return false; + return OperationState.Pending(response); default: - throw _pipeline.Diagnostics.CreateRequestFailedException(response); + RequestFailedException ex = async + ? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false) + : _pipeline.Diagnostics.CreateRequestFailedException(response); + + return OperationState.Failure(response, ex); } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClient.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClient.cs index 13dbd5ed4099..88cc54903c65 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClient.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClient.cs @@ -159,7 +159,7 @@ public virtual AsyncPageable GetPropertiesOfSecretVersionsAsyn { Argument.AssertNotNullOrEmpty(name, nameof(name)); - Uri firstPageUri = new Uri(VaultUri, $"{SecretsPath}{name}/versions?api-version={_pipeline.ApiVersion}"); + Uri firstPageUri = _pipeline.CreateFirstPageUri($"{SecretsPath}{name}/versions"); return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => _pipeline.GetPageAsync(firstPageUri, nextLink, () => new SecretProperties(), "SecretClient.GetPropertiesOfSecretVersions", cancellationToken)); } @@ -186,7 +186,7 @@ public virtual Pageable GetPropertiesOfSecretVersions(string n { Argument.AssertNotNullOrEmpty(name, nameof(name)); - Uri firstPageUri = new Uri(VaultUri, $"{SecretsPath}{name}/versions?api-version={_pipeline.ApiVersion}"); + Uri firstPageUri = _pipeline.CreateFirstPageUri($"{SecretsPath}{name}/versions"); return PageResponseEnumerator.CreateEnumerable(nextLink => _pipeline.GetPage(firstPageUri, nextLink, () => new SecretProperties(), "SecretClient.GetPropertiesOfSecretVersions", cancellationToken)); } @@ -204,7 +204,7 @@ public virtual Pageable GetPropertiesOfSecretVersions(string n /// The server returned an error. See for details returned from the server. public virtual AsyncPageable GetPropertiesOfSecretsAsync(CancellationToken cancellationToken = default) { - Uri firstPageUri = new Uri(VaultUri, SecretsPath + $"?api-version={_pipeline.ApiVersion}"); + Uri firstPageUri = _pipeline.CreateFirstPageUri(SecretsPath); return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => _pipeline.GetPageAsync(firstPageUri, nextLink, () => new SecretProperties(), "SecretClient.GetPropertiesOfSecrets", cancellationToken)); } @@ -222,7 +222,7 @@ public virtual AsyncPageable GetPropertiesOfSecretsAsync(Cance /// The server returned an error. See for details returned from the server. public virtual Pageable GetPropertiesOfSecrets(CancellationToken cancellationToken = default) { - Uri firstPageUri = new Uri(VaultUri, SecretsPath + $"?api-version={_pipeline.ApiVersion}"); + Uri firstPageUri = _pipeline.CreateFirstPageUri(SecretsPath); return PageResponseEnumerator.CreateEnumerable(nextLink => _pipeline.GetPage(firstPageUri, nextLink, () => new SecretProperties(), "SecretClient.GetPropertiesOfSecrets", cancellationToken)); } @@ -545,7 +545,7 @@ public virtual Response GetDeletedSecret(string name, Cancellatio /// The server returned an error. See for details returned from the server. public virtual AsyncPageable GetDeletedSecretsAsync(CancellationToken cancellationToken = default) { - Uri firstPageUri = new Uri(VaultUri, DeletedSecretsPath + $"?api-version={_pipeline.ApiVersion}"); + Uri firstPageUri = _pipeline.CreateFirstPageUri(DeletedSecretsPath); return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => _pipeline.GetPageAsync(firstPageUri, nextLink, () => new DeletedSecret(), "SecretClient.GetDeletedSecrets", cancellationToken)); } @@ -562,7 +562,7 @@ public virtual AsyncPageable GetDeletedSecretsAsync(CancellationT /// The server returned an error. See for details returned from the server. public virtual Pageable GetDeletedSecrets(CancellationToken cancellationToken = default) { - Uri firstPageUri = new Uri(VaultUri, DeletedSecretsPath + $"?api-version={_pipeline.ApiVersion}"); + Uri firstPageUri = _pipeline.CreateFirstPageUri(DeletedSecretsPath); return PageResponseEnumerator.CreateEnumerable(nextLink => _pipeline.GetPage(firstPageUri, nextLink, () => new DeletedSecret(), "SecretClient.GetDeletedSecrets", cancellationToken)); } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretProperties.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretProperties.cs index f8a68384ab70..0aba21069c53 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretProperties.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretProperties.cs @@ -25,7 +25,6 @@ public class SecretProperties : IJsonDeserializable, IJsonSerializable private static readonly JsonEncodedText s_attributesPropertyNameBytes = JsonEncodedText.Encode(AttributesPropertyName); private static readonly JsonEncodedText s_tagsPropertyNameBytes = JsonEncodedText.Encode(TagsPropertyName); - private ObjectId _identifier; private SecretAttributes _attributes; private Dictionary _tags; private string _keyId; @@ -44,7 +43,7 @@ public SecretProperties(string name) { Argument.AssertNotNullOrEmpty(name, nameof(name)); - _identifier.Name = name; + Name = name; } /// @@ -56,28 +55,28 @@ public SecretProperties(Uri id) { Argument.AssertNotNull(id, nameof(id)); - _identifier.ParseId("secrets", id); + ParseId(id); } /// /// Gets the secret identifier. /// - public Uri Id { get => _identifier.Id; internal set => _identifier.Id = value; } + public Uri Id { get; internal set; } /// /// Gets the Key Vault base . /// - public Uri VaultUri { get => _identifier.VaultUri; internal set => _identifier.VaultUri = value; } + public Uri VaultUri { get; internal set; } /// /// Gets the name of the secret. /// - public string Name { get => _identifier.Name; internal set => _identifier.Name = value; } + public string Name { get; internal set; } /// /// Gets the version of the secret. /// - public string Version { get => _identifier.Version; internal set => _identifier.Version = value; } + public string Version { get; internal set; } /// /// Gets or sets the content type of the secret value such as "text/plain" for a password. @@ -142,6 +141,20 @@ public Uri KeyId /// public IDictionary Tags => LazyInitializer.EnsureInitialized(ref _tags); + /// + /// Parses the key identifier into the , , and of the key. + /// + /// The key vault object identifier. + internal void ParseId(Uri id) + { + KeyVaultIdentifier identifier = KeyVaultIdentifier.ParseWithCollection(id, "secrets"); + + Id = id; + VaultUri = identifier.VaultUri; + Name = identifier.Name; + Version = identifier.Version; + } + internal void ReadProperties(JsonElement json) { foreach (JsonProperty prop in json.EnumerateObject()) @@ -155,7 +168,9 @@ internal void ReadProperty(JsonProperty prop) switch (prop.Name) { case IdPropertyName: - _identifier.ParseId("secrets", prop.Value.GetString()); + string id = prop.Value.GetString(); + Id = new Uri(id); + ParseId(Id); break; case ContentTypePropertyName: diff --git a/sdk/keyvault/Azure.Security.KeyVault.Shared/src/KeyVaultIdentifier.cs b/sdk/keyvault/Azure.Security.KeyVault.Shared/src/KeyVaultIdentifier.cs index c1036544012a..19244fb0135b 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Shared/src/KeyVaultIdentifier.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Shared/src/KeyVaultIdentifier.cs @@ -32,6 +32,17 @@ public static KeyVaultIdentifier Parse(Uri id) throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Invalid ObjectIdentifier: {0}. Bad number of segments: {1}", id, id.Segments.Length)); } + public static KeyVaultIdentifier ParseWithCollection(Uri id, string collection) + { + KeyVaultIdentifier identifier = Parse(id); + if (!string.Equals(identifier.Collection, collection, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Invalid ObjectIdentifier: {0}. segment [1] should be '{1}/', found '{2}'", id, collection, identifier.Collection)); + } + + return identifier; + } + public static bool TryParse(Uri id, out KeyVaultIdentifier identifier) { if (id is null)