Skip to content

Commit cc7eb66

Browse files
committed
Merge branch 'users/trivediyash/CFPDeleteMetadata' of https://github.com/Azure/azure-cosmos-dotnet-v3 into users/trivediyash/CFPDeleteMetadata
2 parents e6fe51c + c0f6f0b commit cc7eb66

File tree

48 files changed

+4770
-474
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4770
-474
lines changed

Microsoft.Azure.Cosmos.Encryption.Custom/src/AeadAes/SymmetricKey.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ internal class SymmetricKey
2323
/// <param name="rootKey">root key</param>
2424
internal SymmetricKey(byte[] rootKey)
2525
{
26-
// Key validation
27-
if (rootKey == null || rootKey.Length == 0)
26+
ArgumentValidation.ThrowIfNull(rootKey);
27+
28+
if (rootKey.Length == 0)
2829
{
29-
throw new ArgumentNullException(nameof(rootKey));
30+
throw new ArgumentException("The root key cannot be empty.", nameof(rootKey));
3031
}
3132

3233
this.rootKey = rootKey;

Microsoft.Azure.Cosmos.Encryption.Custom/src/Common/CosmosDiagnosticsContext.cs

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,84 @@
55
namespace Microsoft.Azure.Cosmos.Encryption.Custom
66
{
77
using System;
8+
using System.Collections.Generic;
9+
using System.Diagnostics;
10+
using System.Threading;
811

912
/// <summary>
10-
/// This is an empty implementation of CosmosDiagnosticsContext which has been plumbed through the DataEncryptionKeyProvider and EncryptionContainer.
11-
/// This may help adding diagnostics more easily in future.
13+
/// Lightweight diagnostics context for Custom Encryption extension.
14+
/// Manages Activity creation for OpenTelemetry integration.
1215
/// </summary>
1316
internal class CosmosDiagnosticsContext
1417
{
15-
private static readonly CosmosDiagnosticsContext UnusedSingleton = new ();
16-
private static readonly IDisposable UnusedScopeSingleton = new Scope();
18+
private static readonly ActivitySource ActivitySource = new ("Microsoft.Azure.Cosmos.Encryption.Custom");
1719

20+
/// <summary>
21+
/// Scope name prefix for MDE (Microsoft.Data.Encryption) encrypt operations.
22+
/// </summary>
23+
internal const string ScopeEncryptModeSelectionPrefix = "EncryptionProcessor.Encrypt.Mde.";
24+
25+
/// <summary>
26+
/// Scope name prefix for MDE (Microsoft.Data.Encryption) decrypt operations.
27+
/// </summary>
28+
internal const string ScopeDecryptModeSelectionPrefix = "EncryptionProcessor.Decrypt.Mde.";
29+
30+
internal CosmosDiagnosticsContext()
31+
{
32+
}
33+
34+
/// <summary>
35+
/// Creates a new diagnostics context instance.
36+
/// </summary>
1837
public static CosmosDiagnosticsContext Create(RequestOptions options)
1938
{
2039
_ = options;
21-
return CosmosDiagnosticsContext.UnusedSingleton;
40+
return new CosmosDiagnosticsContext();
2241
}
2342

24-
public IDisposable CreateScope(string scope)
43+
/// <summary>
44+
/// Creates a new diagnostic scope for Activity tracking.
45+
/// </summary>
46+
/// <param name="scope">The name of the scope.</param>
47+
/// <returns>A <see cref="Scope"/> that manages an Activity lifecycle.</returns>
48+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="scope"/> is null.</exception>
49+
/// <exception cref="ArgumentException">Thrown when <paramref name="scope"/> is empty or whitespace.</exception>
50+
/// <remarks>
51+
/// Use with a <c>using</c> statement to ensure proper disposal.
52+
/// </remarks>
53+
public Scope CreateScope(string scope)
2554
{
26-
_ = scope;
27-
return CosmosDiagnosticsContext.UnusedScopeSingleton;
55+
ArgumentValidation.ThrowIfNullOrWhiteSpace(scope, nameof(scope));
56+
57+
Activity activity = ActivitySource.HasListeners() ? ActivitySource.StartActivity(scope, ActivityKind.Internal) : null;
58+
59+
return new Scope(activity);
2860
}
2961

30-
private class Scope : IDisposable
62+
/// <summary>
63+
/// Represents a diagnostic scope for Activity tracking.
64+
/// Must be used with the 'using' pattern to ensure proper disposal.
65+
/// </summary>
66+
/// <remarks>
67+
/// Dispose() is idempotent - calling it multiple times will only dispose the Activity once.
68+
/// </remarks>
69+
public sealed class Scope : IDisposable
3170
{
71+
private readonly Activity activity;
72+
private bool isDisposed;
73+
74+
internal Scope(Activity activity)
75+
{
76+
this.activity = activity;
77+
}
78+
3279
public void Dispose()
3380
{
81+
if (!this.isDisposed)
82+
{
83+
this.isDisposed = true;
84+
this.activity?.Dispose();
85+
}
3486
}
3587
}
3688
}

Microsoft.Azure.Cosmos.Encryption.Custom/src/Common/CosmosJsonDotNetSerializer.cs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,7 @@ internal CosmosJsonDotNetSerializer(JsonSerializerSettings jsonSerializerSetting
4040
/// <returns>The object representing the deserialized stream</returns>
4141
public T FromStream<T>(Stream stream)
4242
{
43-
#if NET8_0_OR_GREATER
44-
ArgumentNullException.ThrowIfNull(stream);
45-
#else
46-
if (stream == null)
47-
{
48-
throw new ArgumentNullException(nameof(stream));
49-
}
50-
#endif
43+
ArgumentValidation.ThrowIfNull(stream);
5144

5245
if (typeof(Stream).IsAssignableFrom(typeof(T)))
5346
{
@@ -87,6 +80,41 @@ public MemoryStream ToStream<T>(T input)
8780
return streamPayload;
8881
}
8982

83+
/// <summary>
84+
/// Serializes an object directly into the provided output stream (which remains open).
85+
/// </summary>
86+
/// <typeparam name="T">Type of object being serialized.</typeparam>
87+
/// <param name="input">Object to serialize.</param>
88+
/// <param name="output">Destination stream. Must be writable. The stream is not disposed by this method.</param>
89+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="output"/> is <c>null</c>.</exception>
90+
/// <exception cref="ArgumentException">Thrown when <paramref name="output"/> is not writable.</exception>
91+
/// <remarks>
92+
/// <para>This method serializes the object directly to the provided stream without creating an intermediate MemoryStream,
93+
/// reducing memory allocations for large objects.</para>
94+
/// <para>After writing, the stream position will be at the end of the written content.
95+
/// Callers are responsible for resetting the stream position if needed for subsequent reads.</para>
96+
/// </remarks>
97+
public void WriteToStream<T>(T input, Stream output)
98+
{
99+
ArgumentValidation.ThrowIfNull(output);
100+
101+
if (!output.CanWrite)
102+
{
103+
throw new ArgumentException("Output stream must be writable", nameof(output));
104+
}
105+
106+
using (StreamWriter streamWriter = new (output, encoding: CosmosJsonDotNetSerializer.DefaultEncoding, bufferSize: 1024, leaveOpen: true))
107+
using (JsonTextWriter writer = new (streamWriter))
108+
{
109+
writer.ArrayPool = JsonArrayPool.Instance;
110+
writer.Formatting = Newtonsoft.Json.Formatting.None;
111+
JsonSerializer jsonSerializer = this.GetSerializer();
112+
jsonSerializer.Serialize(writer, input);
113+
writer.Flush();
114+
streamWriter.Flush();
115+
}
116+
}
117+
90118
/// <summary>
91119
/// JsonSerializer has hit a race conditions with custom settings that cause null reference exception.
92120
/// To avoid the race condition a new JsonSerializer is created for each call
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
//------------------------------------------------------------
4+
5+
namespace Microsoft.Azure.Cosmos.Encryption.Custom
6+
{
7+
using System.IO;
8+
using System.Threading.Tasks;
9+
10+
/// <summary>
11+
/// Extension methods for Stream to provide compatibility across different .NET versions.
12+
/// </summary>
13+
internal static class StreamExtensions
14+
{
15+
/// <summary>
16+
/// Asynchronously disposes the stream in a version-compatible way.
17+
/// Uses DisposeAsync on .NET 8.0+ and falls back to synchronous Dispose on earlier versions.
18+
/// </summary>
19+
/// <param name="stream">The stream to dispose.</param>
20+
/// <returns>A ValueTask representing the asynchronous dispose operation.</returns>
21+
public static ValueTask DisposeCompatAsync(this Stream stream)
22+
{
23+
#if NET8_0_OR_GREATER
24+
return stream.DisposeAsync();
25+
#else
26+
stream.Dispose();
27+
return default;
28+
#endif
29+
}
30+
}
31+
}

Microsoft.Azure.Cosmos.Encryption.Custom/src/CosmosDataEncryptionKeyProvider.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,7 @@ public async Task InitializeAsync(
148148
throw new InvalidOperationException($"{nameof(CosmosDataEncryptionKeyProvider)} has already been initialized.");
149149
}
150150

151-
#if NET8_0_OR_GREATER
152-
ArgumentNullException.ThrowIfNull(database);
153-
#else
154-
if (database == null)
155-
{
156-
throw new ArgumentNullException(nameof(database));
157-
}
158-
#endif
151+
ArgumentValidation.ThrowIfNull(database);
159152

160153
ContainerResponse containerResponse = await database.CreateContainerIfNotExistsAsync(
161154
containerId,

Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKey.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,7 @@ public static DataEncryptionKey Create(
102102
byte[] rawKey,
103103
string encryptionAlgorithm)
104104
{
105-
#if NET8_0_OR_GREATER
106-
ArgumentNullException.ThrowIfNull(rawKey);
107-
#else
108-
if (rawKey == null)
109-
{
110-
throw new ArgumentNullException(nameof(rawKey));
111-
}
112-
#endif
105+
ArgumentValidation.ThrowIfNull(rawKey);
113106

114107
#pragma warning disable CS0618 // Type or member is obsolete
115108
if (!string.Equals(encryptionAlgorithm, CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized))

Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyContainerCore.cs

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,14 @@ public override async Task<ItemResponse<DataEncryptionKeyProperties>> CreateData
5757
ItemRequestOptions requestOptions = null,
5858
CancellationToken cancellationToken = default)
5959
{
60-
if (string.IsNullOrEmpty(id))
61-
{
62-
throw new ArgumentNullException(nameof(id));
63-
}
60+
ArgumentValidation.ThrowIfNullOrEmpty(id);
6461

6562
if (!CosmosEncryptionAlgorithm.VerifyIfSupportedAlgorithm(encryptionAlgorithm))
6663
{
6764
throw new ArgumentException(string.Format("Unsupported Encryption Algorithm {0}", encryptionAlgorithm), nameof(encryptionAlgorithm));
6865
}
6966

70-
#if NET8_0_OR_GREATER
71-
ArgumentNullException.ThrowIfNull(encryptionKeyWrapMetadata);
72-
#else
73-
if (encryptionKeyWrapMetadata == null)
74-
{
75-
throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata));
76-
}
77-
#endif
67+
ArgumentValidation.ThrowIfNull(encryptionKeyWrapMetadata);
7868

7969
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
8070

@@ -159,14 +149,7 @@ public override async Task<ItemResponse<DataEncryptionKeyProperties>> RewrapData
159149
ItemRequestOptions requestOptions = null,
160150
CancellationToken cancellationToken = default)
161151
{
162-
#if NET8_0_OR_GREATER
163-
ArgumentNullException.ThrowIfNull(newWrapMetadata);
164-
#else
165-
if (newWrapMetadata == null)
166-
{
167-
throw new ArgumentNullException(nameof(newWrapMetadata));
168-
}
169-
#endif
152+
ArgumentValidation.ThrowIfNull(newWrapMetadata);
170153

171154
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
172155

Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyContainerInlineCore.cs

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,7 @@ public override Task<ItemResponse<DataEncryptionKeyProperties>> ReadDataEncrypti
5656
ItemRequestOptions requestOptions = null,
5757
CancellationToken cancellationToken = default)
5858
{
59-
if (string.IsNullOrEmpty(id))
60-
{
61-
throw new ArgumentNullException(nameof(id));
62-
}
59+
ArgumentValidation.ThrowIfNullOrEmpty(id);
6360

6461
return TaskHelper.RunInlineIfNeededAsync(() =>
6562
this.dataEncryptionKeyContainerCore.ReadDataEncryptionKeyAsync(id, requestOptions, cancellationToken));
@@ -73,19 +70,8 @@ public override Task<ItemResponse<DataEncryptionKeyProperties>> RewrapDataEncryp
7370
ItemRequestOptions requestOptions = null,
7471
CancellationToken cancellationToken = default)
7572
{
76-
if (string.IsNullOrEmpty(id))
77-
{
78-
throw new ArgumentNullException(nameof(id));
79-
}
80-
81-
#if NET8_0_OR_GREATER
82-
ArgumentNullException.ThrowIfNull(newWrapMetadata);
83-
#else
84-
if (newWrapMetadata == null)
85-
{
86-
throw new ArgumentNullException(nameof(newWrapMetadata));
87-
}
88-
#endif
73+
ArgumentValidation.ThrowIfNullOrEmpty(id);
74+
ArgumentValidation.ThrowIfNull(newWrapMetadata);
8975

9076
return TaskHelper.RunInlineIfNeededAsync(() =>
9177
this.dataEncryptionKeyContainerCore.RewrapDataEncryptionKeyAsync(id, newWrapMetadata, encryptionAlgorithm, requestOptions, cancellationToken));

Microsoft.Azure.Cosmos.Encryption.Custom/src/DecryptableFeedResponse.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,7 @@ internal static DecryptableFeedResponse<T> CreateResponse(
4545
ResponseMessage responseMessage,
4646
IReadOnlyCollection<T> resource)
4747
{
48-
#if NET8_0_OR_GREATER
49-
ArgumentNullException.ThrowIfNull(responseMessage);
50-
#else
51-
if (responseMessage == null)
52-
{
53-
throw new ArgumentNullException(nameof(responseMessage));
54-
}
55-
#endif
48+
ArgumentValidation.ThrowIfNull(responseMessage);
5649

5750
using (responseMessage)
5851
{

0 commit comments

Comments
 (0)