From 441b232847027d4a16063c6661f44c2b37ea970e Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Sat, 19 Oct 2024 06:57:12 -0500 Subject: [PATCH 01/12] Added support for Dapr data processor - still working on unit tests Signed-off-by: Whit Waldo --- src/Dapr.Client/BindingRequest.cs | 1 + src/Dapr.Client/BindingResponse.cs | 1 + .../Data/Attributes/DataOperationAttribute.cs | 48 +++++ src/Dapr.Common/Data/DataPipeline.cs | 102 +++++++++++ src/Dapr.Common/Data/DataPipelineFactory.cs | 59 ++++++ .../Extensions/DaprDataPipelineBuilder.cs | 49 +++++ ...taPipelineRegistrationBuilderExtensions.cs | 172 ++++++++++++++++++ .../Extensions/IDaprDataPipelineBuilder.cs | 21 +++ .../Data/Extensions/IDaprServiceBuilder.cs | 27 +++ .../Operations/DaprDataOperationPayload.cs | 51 ++++++ .../Data/Operations/IDaprDataOperation.cs | 50 +++++ .../Operations/IDaprDataProcessingBuilder.cs | 23 +++ .../Compression/Gzip/DaprGzipCompressor.cs | 58 ++++++ .../Compression/IDaprDataCompressor.cs | 21 +++ .../Integrity/Checksum/DaprSha256Validator.cs | 85 +++++++++ .../Providers/Integrity/IDaprDataValidator.cs | 21 +++ .../Masking/DaprRegularExpressionMasker.cs | 67 +++++++ .../Providers/Masking/IDaprDataMasker.cs | 29 +++ .../DaprSystemTextJsonSerializer.cs | 55 ++++++ .../Serialization/IDaprDataSerializer.cs | 21 +++ .../Providers/Versioning/DaprVersioner.cs | 68 +++++++ .../Versioning/IDaprDataVersioner.cs | 33 ++++ .../BulkPublishEventApiTest.cs | 2 + .../Attributes/DataOperationAttributeTests.cs | 107 +++++++++++ .../Data/DataPipelineFactoryTests.cs | 106 +++++++++++ .../Data/DataPipelineTests.cs | 121 ++++++++++++ .../Compression/GzipCompressorTest.cs | 43 +++++ .../Integrity/DaprSha256ValidatorTest.cs | 50 +++++ 28 files changed, 1491 insertions(+) create mode 100644 src/Dapr.Common/Data/Attributes/DataOperationAttribute.cs create mode 100644 src/Dapr.Common/Data/DataPipeline.cs create mode 100644 src/Dapr.Common/Data/DataPipelineFactory.cs create mode 100644 src/Dapr.Common/Data/Extensions/DaprDataPipelineBuilder.cs create mode 100644 src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs create mode 100644 src/Dapr.Common/Data/Extensions/IDaprDataPipelineBuilder.cs create mode 100644 src/Dapr.Common/Data/Extensions/IDaprServiceBuilder.cs create mode 100644 src/Dapr.Common/Data/Operations/DaprDataOperationPayload.cs create mode 100644 src/Dapr.Common/Data/Operations/IDaprDataOperation.cs create mode 100644 src/Dapr.Common/Data/Operations/IDaprDataProcessingBuilder.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Compression/Gzip/DaprGzipCompressor.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Compression/IDaprDataCompressor.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Integrity/Checksum/DaprSha256Validator.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Integrity/IDaprDataValidator.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Serialization/IDaprDataSerializer.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Versioning/DaprVersioner.cs create mode 100644 src/Dapr.Common/Data/Operations/Providers/Versioning/IDaprDataVersioner.cs create mode 100644 test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs create mode 100644 test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs create mode 100644 test/Dapr.Common.Test/Data/DataPipelineTests.cs create mode 100644 test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs create mode 100644 test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs diff --git a/src/Dapr.Client/BindingRequest.cs b/src/Dapr.Client/BindingRequest.cs index 5448588ac..3056e4cc6 100644 --- a/src/Dapr.Client/BindingRequest.cs +++ b/src/Dapr.Client/BindingRequest.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; +using Dapr.Common; namespace Dapr.Client { diff --git a/src/Dapr.Client/BindingResponse.cs b/src/Dapr.Client/BindingResponse.cs index f362533e4..f097b7352 100644 --- a/src/Dapr.Client/BindingResponse.cs +++ b/src/Dapr.Client/BindingResponse.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; +using Dapr.Common; namespace Dapr.Client { diff --git a/src/Dapr.Common/Data/Attributes/DataOperationAttribute.cs b/src/Dapr.Common/Data/Attributes/DataOperationAttribute.cs new file mode 100644 index 000000000..432997d4a --- /dev/null +++ b/src/Dapr.Common/Data/Attributes/DataOperationAttribute.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common.Data.Operations; + +namespace Dapr.Common.Data.Attributes; + +/// +/// Attribute-based approach for indicating which data operations should be performed on a type and in what order. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] +public sealed class DataOperationAttribute : Attribute +{ + /// + /// Contains the various data operation types available and the order in which to apply them. + /// + public readonly IReadOnlyList DataOperationTypes; + + /// + /// Initializes a new . + /// + /// + /// + public DataOperationAttribute(params Type[] dataOperationTypes) + { + var registeredTypes = new List(); + + foreach (var type in dataOperationTypes) + { + if (!typeof(IDaprDataOperation).IsAssignableFrom(type)) + throw new DaprException($"Unable to register data preparation operation as {nameof(type)} does not implement `IDataOperation`"); + + registeredTypes.Add(type); + } + + DataOperationTypes = registeredTypes; + } +} diff --git a/src/Dapr.Common/Data/DataPipeline.cs b/src/Dapr.Common/Data/DataPipeline.cs new file mode 100644 index 000000000..7bedac899 --- /dev/null +++ b/src/Dapr.Common/Data/DataPipeline.cs @@ -0,0 +1,102 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common.Data.Operations; + +namespace Dapr.Common.Data; + +/// +/// Processes the data using the provided providers. +/// +public class DataPipeline +{ + private readonly List operations; + + /// + /// Used to initialize a new . + /// + public DataPipeline(IEnumerable operations) + { + this.operations = operations.ToList(); + } + + /// + /// Processes the data in the order of the provided list of . + /// + /// The data to evaluate. + /// The evaluated data. + public async Task> ProcessAsync(TInput input) + { + object? currentData = input; + var combinedMetadata = new Dictionary(); + const string operationNameKey = "Ops"; + + foreach (var operation in operations) + { + var method = operation.GetType().GetMethod("ExecuteAsync"); + if (method is null || currentData is null) + continue; + + var genericMethod = method.MakeGenericMethod(typeof(TInput), typeof(TOutput)); + var result = await ((Task>)genericMethod.Invoke(operation, new object[] { currentData })!); + currentData = result.Payload; + foreach (var kvp in result.Metadata) + { + //Append the operation name if given that key + if (kvp.Key == operationNameKey) + { + if (combinedMetadata.TryGetValue(operationNameKey, out var operationName)) + { + combinedMetadata[operationNameKey] = operationName + $",{kvp.Value}"; + } + else + { + combinedMetadata[operationNameKey] = kvp.Value; + } + } + + combinedMetadata[kvp.Key] = kvp.Value; + } + } + + return new DaprDataOperationPayload((TOutput?)currentData) { Metadata = combinedMetadata }; + } + + /// + /// Processes the reverse of the data in the order of the provided list of . + /// + /// The data to evaluate. + /// The evaluated data. + public async Task> ReverseProcessAsync(TOutput input) + { + object? currentData = input; + var combinedMetadata = new Dictionary(); + + for (int i = operations.Count - 1; i >= 0; i--) + { + var method = operations[i].GetType().GetMethod("ReverseAsync"); + if (method is null || currentData is null) + continue; + + var genericMethod = method.MakeGenericMethod(typeof(TOutput), typeof(TInput)); + var result = await (Task>)genericMethod.Invoke(operations[i], new[] { currentData })!; + currentData = result.Payload; + foreach (var kvp in result.Metadata) + { + combinedMetadata[kvp.Key] = kvp.Value; + } + } + + return new DaprDataOperationPayload((TInput?)currentData) { Metadata = combinedMetadata }; + } +} diff --git a/src/Dapr.Common/Data/DataPipelineFactory.cs b/src/Dapr.Common/Data/DataPipelineFactory.cs new file mode 100644 index 000000000..84e6929ee --- /dev/null +++ b/src/Dapr.Common/Data/DataPipelineFactory.cs @@ -0,0 +1,59 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Reflection; +using Dapr.Common.Data.Attributes; +using Dapr.Common.Data.Operations; +using Microsoft.Extensions.DependencyInjection; + +namespace Dapr.Common.Data; + +/// +/// Used to create a data pipeline specific to a given type using the ordered operation services indicated in the +/// attribute on that type. +/// +public sealed class DataPipelineFactory +{ + /// + /// The service provider used to pull the registered data operation services. + /// + private readonly IServiceProvider serviceProvider; + + /// + /// Used to instantiate a . + /// + public DataPipelineFactory(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + /// + /// Creates a pipeline with the attributes specified for a given type. + /// + /// The type to create the pipeline for. + /// + public DataPipeline CreatePipeline() + { + var attribute = typeof(T).GetCustomAttribute(); + if (attribute == null) + { + return new DataPipeline(new List>()); + } + + var operations = attribute.DataOperationTypes + .Select(type => serviceProvider.GetRequiredService(type)) + .ToList(); + + return new DataPipeline(operations); + } +} diff --git a/src/Dapr.Common/Data/Extensions/DaprDataPipelineBuilder.cs b/src/Dapr.Common/Data/Extensions/DaprDataPipelineBuilder.cs new file mode 100644 index 000000000..bbb2a7bd0 --- /dev/null +++ b/src/Dapr.Common/Data/Extensions/DaprDataPipelineBuilder.cs @@ -0,0 +1,49 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common.Data.Operations; +using Microsoft.Extensions.DependencyInjection; + +namespace Dapr.Common.Data.Extensions; + +/// +/// Used by the fluent registration builder to configure a Dapr data pipeline. +/// +public class DaprDataPipelineBuilder : IDaprDataPipelineBuilder +{ + /// + /// The registered services on the builder. + /// + public IServiceCollection Services { get; } + + /// + /// Used to initialize a new . + /// + public DaprDataPipelineBuilder(IServiceCollection services) + { + Services = services; + } +} + +/// +/// Used to perform fluent registrations on the Dapr data processing pipeline. +/// +public class DaprDataProcessingPipelineBuilder : DaprDataPipelineBuilder, IDaprDataProcessingBuilder +{ + /// + /// Used to initialize a new . + /// + public DaprDataProcessingPipelineBuilder(IServiceCollection services) : base(services) + { + } +} diff --git a/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs b/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs new file mode 100644 index 000000000..349f04cf4 --- /dev/null +++ b/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs @@ -0,0 +1,172 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common.Data.Operations; +using Dapr.Common.Data.Operations.Providers.Compression; +using Dapr.Common.Data.Operations.Providers.Integrity; +using Dapr.Common.Data.Operations.Providers.Masking; +using Dapr.Common.Data.Operations.Providers.Serialization; +using Dapr.Common.Data.Operations.Providers.Versioning; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Dapr.Common.Data.Extensions; + +/// +/// Contains the dependency injection registration extension for the Dapr data pipeline operations. +/// +public static class DaprDataPipelineRegistrationBuilderExtensions +{ + /// + /// Registers a Dapr data processing pipeline. + /// + /// + /// + public static IDaprDataProcessingBuilder AddDaprDataProcessingPipeline(this IDaprServiceBuilder builder) + { + return new DaprDataProcessingPipelineBuilder(builder.Services); + } + + /// + /// Registers the specified Dapr operation services. + /// + /// + /// + /// + /// + /// + public static IDaprDataProcessingBuilder WithDaprOperation(this IDaprDataProcessingBuilder builder, + ServiceLifetime lifetime) + where TService : class, IDaprDataOperation + { + switch (lifetime) + { + case ServiceLifetime.Singleton: + builder.Services.TryAddSingleton(); + break; + case ServiceLifetime.Scoped: + builder.Services.TryAddScoped(); + break; + case ServiceLifetime.Transient: + builder.Services.TryAddTransient(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, null); + } + + return builder; + } + + /// + /// Registers the specified + /// + /// The builder to register the type on. + /// The data operation factory used to register the data operation service. + /// The lifetime the service should be registered for. + public static IDaprDataProcessingBuilder WithDaprOperation(this IDaprDataProcessingBuilder builder, + Func operationFactory, ServiceLifetime lifetime) + where TService : class, IDaprDataOperation + { + switch (lifetime) + { + case ServiceLifetime.Singleton: + builder.Services.TryAddSingleton(operationFactory); + break; + case ServiceLifetime.Scoped: + builder.Services.TryAddScoped(operationFactory); + break; + case ServiceLifetime.Transient: + builder.Services.TryAddTransient(operationFactory); + break; + default: + throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, null); + } + + return builder; + } + + /// + /// Adds a serializer data operation. + /// + public static IDaprDataProcessingBuilder WithSerializer(this IDaprDataProcessingBuilder builder, + ServiceLifetime lifetime) + where TService : class, IDaprDataOperation => + builder.WithDaprOperation(lifetime); + + /// + /// Adds a serializer data operation. + /// + public static IDaprDataProcessingBuilder WithSerializer(this IDaprDataProcessingBuilder builder, + Func> serializerFactory, ServiceLifetime lifetime) => + builder.WithDaprOperation, TInput, string>(serializerFactory, lifetime); + + /// + /// Adds a compression data operation. + /// + public static IDaprDataProcessingBuilder WithCompressor(this IDaprDataProcessingBuilder builder, + ServiceLifetime lifetime) + where TService : class, IDaprDataCompressor => + builder.WithDaprOperation(lifetime); + + /// + /// Adds a compressor data operation. + /// + public static IDaprDataProcessingBuilder WithCompressor(this IDaprDataProcessingBuilder builder, + Func compressorFactory, ServiceLifetime lifetime) => + builder.WithDaprOperation, ReadOnlyMemory>(compressorFactory, lifetime); + + /// + /// Adds a data integrity operation. + /// + public static IDaprDataProcessingBuilder WithIntegrity(this IDaprDataProcessingBuilder builder, + ServiceLifetime serviceLifetime) + where TService : class, IDaprDataValidator + => builder.WithDaprOperation(serviceLifetime); + + /// + /// Adds a data integrity operation using a factory that provides an . + /// + public static IDaprDataProcessingBuilder WithIntegrity(this IDaprDataProcessingBuilder builder, + Func validatorFactory, ServiceLifetime serviceLifetime) => + builder.WithDaprOperation, ReadOnlyMemory>(validatorFactory, serviceLifetime); + + /// + /// Adds a data masking operation. + /// + public static IDaprDataProcessingBuilder WithMasking(this IDaprDataProcessingBuilder builder, + IDaprDataMasker masker, ServiceLifetime serviceLifetime) + where TService : class, IDaprDataMasker + => builder.WithDaprOperation(serviceLifetime); + + /// + /// Adds a data masking operation using a factory that provides an . + /// + public static IDaprDataProcessingBuilder WithMasking(this IDaprDataProcessingBuilder builder, + Func maskerFactory, ServiceLifetime serviceLifetime) => + builder.WithDaprOperation(maskerFactory, serviceLifetime); + + /// + /// Adds a data versioning operation. + /// + public static IDaprDataProcessingBuilder WithVersioning(this IDaprDataProcessingBuilder builder, + IDaprDataVersioner versioner, ServiceLifetime serviceLifetime) + where TService : class, IDaprDataVersioner + => builder.WithDaprOperation(serviceLifetime); + + /// + /// Adds a data versioning operation. + /// + public static IDaprDataProcessingBuilder WithVersioning(this IDaprDataProcessingBuilder builder, + Func versionerFactory, ServiceLifetime serviceLifetime) + => builder.WithDaprOperation(versionerFactory, serviceLifetime); +} diff --git a/src/Dapr.Common/Data/Extensions/IDaprDataPipelineBuilder.cs b/src/Dapr.Common/Data/Extensions/IDaprDataPipelineBuilder.cs new file mode 100644 index 000000000..7d044594b --- /dev/null +++ b/src/Dapr.Common/Data/Extensions/IDaprDataPipelineBuilder.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Extensions; + +/// +/// Used to build out registration functionality for a Dapr data pipeline +/// +public interface IDaprDataPipelineBuilder : IDaprServiceBuilder +{ +} diff --git a/src/Dapr.Common/Data/Extensions/IDaprServiceBuilder.cs b/src/Dapr.Common/Data/Extensions/IDaprServiceBuilder.cs new file mode 100644 index 000000000..481e91b2b --- /dev/null +++ b/src/Dapr.Common/Data/Extensions/IDaprServiceBuilder.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Microsoft.Extensions.DependencyInjection; + +namespace Dapr.Common.Data.Extensions; + +/// +/// Responsible for registering Dapr service functionality. +/// +public interface IDaprServiceBuilder +{ + /// + /// The registered services on the builder. + /// + public IServiceCollection Services { get; } +} diff --git a/src/Dapr.Common/Data/Operations/DaprDataOperationPayload.cs b/src/Dapr.Common/Data/Operations/DaprDataOperationPayload.cs new file mode 100644 index 000000000..eac575e13 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/DaprDataOperationPayload.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations; + +/// +/// Contains the result of a Dapr data operation. +/// +/// The type of the payload. +public record DaprDataOperationPayload +{ + /// + /// Initializes a . + /// + /// The resulting payload following the operation. + public DaprDataOperationPayload(T payload) + { + Payload = payload; + } + + /// + /// Initializes a . + /// + /// The resulting payload following the operation. + /// The name of the operation that produced this as a result. + public DaprDataOperationPayload(T payload, string operationName) + { + Payload = payload; + Metadata["Ops"] = operationName; + } + + /// + /// The result of the operation. + /// + public T Payload { get; init; } + + /// + /// The metadata produced by the operation. + /// + public Dictionary Metadata { get; init; } = new(); +} diff --git a/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs b/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs new file mode 100644 index 000000000..cb244d618 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations; + +/// +/// Represents a data operation. +/// +public interface IDaprDataOperation +{ + /// + /// The name of the operation. + /// + string Name { get; } +} + +/// +/// Represents a data operation with generic input and output types. +/// +/// The type of the input data. +/// The type of the output data. +public interface IDaprDataOperation : IDaprDataOperation +{ + /// + /// Executes the data processing operation. + /// + /// The input data. + /// Cancellation token. + /// The output data and metadata for the operation. + Task> ExecuteAsync(TInput input, CancellationToken cancellationToken = default); + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + Task> ReverseAsync(DaprDataOperationPayload input, + CancellationToken cancellationToken); +} diff --git a/src/Dapr.Common/Data/Operations/IDaprDataProcessingBuilder.cs b/src/Dapr.Common/Data/Operations/IDaprDataProcessingBuilder.cs new file mode 100644 index 000000000..98ba7b02d --- /dev/null +++ b/src/Dapr.Common/Data/Operations/IDaprDataProcessingBuilder.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common.Data.Extensions; + +namespace Dapr.Common.Data.Operations; + +/// +/// Provides a root builder for the Dapr processing functionality facilitating a more fluent-style registration. +/// +public interface IDaprDataProcessingBuilder : IDaprServiceBuilder +{ +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Compression/Gzip/DaprGzipCompressor.cs b/src/Dapr.Common/Data/Operations/Providers/Compression/Gzip/DaprGzipCompressor.cs new file mode 100644 index 000000000..cca523dd9 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Compression/Gzip/DaprGzipCompressor.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.IO.Compression; + +namespace Dapr.Common.Data.Operations.Providers.Compression.Gzip; + +/// +public class DaprGzipCompressor : IDaprDataCompressor +{ + /// + /// The name of the operation. + /// + public string Name => "Dapr.Compression.Gzip"; + + /// + /// Executes the data processing operation. + /// + /// The input data. + /// Cancellation token. + /// The output data and metadata for the operation. + public async Task>> ExecuteAsync(ReadOnlyMemory input, CancellationToken cancellationToken = default) + { + using var outputStream = new MemoryStream(); + await using (var gzipStream = new GZipStream(outputStream, CompressionMode.Compress)) + { + await gzipStream.WriteAsync(input, cancellationToken); + } + + //Replace the existing payload with the compressed payload + return new DaprDataOperationPayload>(outputStream.ToArray(), Name); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public async Task>> ReverseAsync(DaprDataOperationPayload> input, CancellationToken cancellationToken) + { + using var inputStream = new MemoryStream(input.Payload.ToArray()); + await using var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress); + using var outputStream = new MemoryStream(); + await gzipStream.CopyToAsync(outputStream, cancellationToken); + return new DaprDataOperationPayload>(outputStream.ToArray()); + } +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Compression/IDaprDataCompressor.cs b/src/Dapr.Common/Data/Operations/Providers/Compression/IDaprDataCompressor.cs new file mode 100644 index 000000000..6206e79cb --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Compression/IDaprDataCompressor.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations.Providers.Compression; + +/// + /// Identifies an operation that provides a data compression capability. + /// +public interface IDaprDataCompressor : IDaprDataOperation, ReadOnlyMemory> +{ +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Integrity/Checksum/DaprSha256Validator.cs b/src/Dapr.Common/Data/Operations/Providers/Integrity/Checksum/DaprSha256Validator.cs new file mode 100644 index 000000000..835877e24 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Integrity/Checksum/DaprSha256Validator.cs @@ -0,0 +1,85 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Security.Cryptography; + +namespace Dapr.Common.Data.Operations.Providers.Integrity.Checksum; + +/// +/// Provides a data integrity validation service using an SHA256 hash. +/// +public class DaprSha256Validator : IDaprDataValidator +{ + /// + /// The name of the operation. + /// + public string Name => "Dapr.Integrity.Checksum"; + + /// + /// Executes the data processing operation. + /// + /// The input data. + /// Cancellation token. + /// The output data and metadata for the operation. + public async Task>> ExecuteAsync(ReadOnlyMemory input, CancellationToken cancellationToken = default) + { + var checksum = await CalculateChecksumAsync(input, cancellationToken); + var result = new DaprDataOperationPayload>(input, Name); + result.Metadata.Add(GetChecksumKey(), checksum); + return result; + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public async Task>> ReverseAsync(DaprDataOperationPayload> input, CancellationToken cancellationToken) + { + var checksumKey = GetChecksumKey(); + if (input.Metadata.TryGetValue(checksumKey, out var checksum)) + { + var newChecksum = await CalculateChecksumAsync(input.Payload, cancellationToken); + if (!string.Equals(checksum, newChecksum)) + { + throw new DaprException("Data integrity check failed. Checksums do not match."); + } + } + + //If there's no checksum metadata or it matches, just continue with the next operation + return new DaprDataOperationPayload>(input.Payload); + } + + /// + /// Creates the SHA256 representing the checksum on the value. + /// + /// The data to create the hash from. + /// Cancellation token. + /// A task containing the base64 hash value. + private async static Task CalculateChecksumAsync(ReadOnlyMemory data, CancellationToken cancellationToken) + { + using var sha256 = SHA256.Create(); + await using var memoryStream = new MemoryStream(data.Length); + await memoryStream.WriteAsync(data, cancellationToken); + memoryStream.Position = 0; + var hash = await sha256.ComputeHashAsync(memoryStream, cancellationToken); + return Convert.ToBase64String(hash); + } + + /// + /// Get the key used to store the hash in the metadata. + /// + /// The key value. + private string GetChecksumKey() => $"{Name}-hash"; +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Integrity/IDaprDataValidator.cs b/src/Dapr.Common/Data/Operations/Providers/Integrity/IDaprDataValidator.cs new file mode 100644 index 000000000..5e59f8614 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Integrity/IDaprDataValidator.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations.Providers.Integrity; + +/// +/// Identifies an operation that provides data integrity validation. +/// +public interface IDaprDataValidator : IDaprDataOperation, ReadOnlyMemory> +{ +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs b/src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs new file mode 100644 index 000000000..f3eb8ddef --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs @@ -0,0 +1,67 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Text.RegularExpressions; + +namespace Dapr.Common.Data.Operations.Providers.Masking; + +/// +/// Performs a masking operation on the provided input. +/// +public class DaprRegularExpressionMasker : IDaprDataMasker +{ + private readonly Dictionary patterns = new(); + + /// + /// The name of the operation. + /// + public string Name => "Dapr.Masking.RegularExpression"; + + /// + /// Executes the data processing operation. + /// + /// The input data. + /// Cancellation token. + /// The output data and metadata for the operation. + public Task> ExecuteAsync(string input, + CancellationToken cancellationToken = default) + { + var updatedValue = input; + foreach (var pattern in patterns) + { + cancellationToken.ThrowIfCancellationRequested(); + updatedValue = pattern.Key.Replace(input, pattern.Value); + } + + var payloadResult = new DaprDataOperationPayload(updatedValue, Name); + return Task.FromResult(payloadResult); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) => Task.FromResult(new DaprDataOperationPayload(input.Payload)); + + /// + /// Registers a pattern to match against. + /// + /// The regular expression to match to. + /// The string to place the matching value with. + public void RegisterMatch(Regex pattern, string replacement) + { + patterns.Add(pattern, replacement); + } +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs b/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs new file mode 100644 index 000000000..bef6366c2 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs @@ -0,0 +1,29 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Text.RegularExpressions; + +namespace Dapr.Common.Data.Operations.Providers.Masking; + +/// +/// Identifies an operation that provides a data masking capability. +/// +public interface IDaprDataMasker : IDaprDataOperation +{ + /// + /// Registers a pattern to match against. + /// + /// The regular expression to match to. + /// The string to place the matching value with. + void RegisterMatch(Regex pattern, string replacement); +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs b/src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs new file mode 100644 index 000000000..87fe4f310 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs @@ -0,0 +1,55 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Text.Json; + +namespace Dapr.Common.Data.Operations.Providers.Serialization; + +/// +/// Provides serialization capabilities using System.Text.Json. +/// +public class DaprSystemTextJsonSerializer : IDaprDataSerializer +{ + /// + /// The name of the operation. + /// + public string Name => "Dapr.Serialization.SystemTextJsonSerializer"; + + /// + /// Executes the data processing operation. + /// + /// The input data. + /// Cancellation token. + /// The output data and metadata for the operation. + public Task> ExecuteAsync(T input, CancellationToken cancellationToken = default) + { + var jsonResult = JsonSerializer.Serialize(input); + var result = new DaprDataOperationPayload(jsonResult, Name); + + return Task.FromResult(result); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public Task> ReverseAsync(DaprDataOperationPayload input, + CancellationToken cancellationToken) + { + var value = JsonSerializer.Deserialize(input.Payload); + var result = new DaprDataOperationPayload(value); + return Task.FromResult(result); + } +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Serialization/IDaprDataSerializer.cs b/src/Dapr.Common/Data/Operations/Providers/Serialization/IDaprDataSerializer.cs new file mode 100644 index 000000000..b2e0f143a --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Serialization/IDaprDataSerializer.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations.Providers.Serialization; + +/// +/// Identifies an operation that provides a data serialization capability. +/// +public interface IDaprDataSerializer : IDaprDataOperation +{ +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Versioning/DaprVersioner.cs b/src/Dapr.Common/Data/Operations/Providers/Versioning/DaprVersioner.cs new file mode 100644 index 000000000..6af8607ee --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Versioning/DaprVersioner.cs @@ -0,0 +1,68 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations.Providers.Versioning; + +/// +/// Default implementation of an for handling data versioning operations. +/// +public class DaprVersioner : IDaprDataVersioner +{ + private readonly Dictionary> _upgraders = new(); + + /// + /// The name of the operation. + /// + public string Name => "Dapr.Versioning.DaprVersioner"; + + /// + /// Executes the data processing operation. + /// + /// The input data. + /// Cancellation token. + /// The output data and metadata for the operation. + public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + /// The current version of the data. + /// + public int CurrentVersion { get; } + + + /// + /// Registers an upgrade function for a specific version. + /// + /// The version to upgrade from. + /// The function to upgrade the data. + /// The type of data to upgrade. + public void RegisterUpgrade(int fromVersion, Func upgradeFunc) + { + _upgraders[fromVersion] = upgradeFunc; + } + + private record VersionedData(int Version, T Data); +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Versioning/IDaprDataVersioner.cs b/src/Dapr.Common/Data/Operations/Providers/Versioning/IDaprDataVersioner.cs new file mode 100644 index 000000000..1148d1f1a --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Versioning/IDaprDataVersioner.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations.Providers.Versioning; + +/// +/// Identifies an operation that provides a data versioning capability. +/// +public interface IDaprDataVersioner : IDaprDataOperation +{ + /// + /// The current version of the data. + /// + int CurrentVersion { get; } + + /// + /// Registers an upgrade function for a specific version. + /// + /// The version to upgrade from. + /// The function to upgrade the data. + /// The type of data to upgrade. + void RegisterUpgrade(int fromVersion, Func upgradeFunc); +} diff --git a/test/Dapr.Client.Test/BulkPublishEventApiTest.cs b/test/Dapr.Client.Test/BulkPublishEventApiTest.cs index 74d617a20..323cb4295 100644 --- a/test/Dapr.Client.Test/BulkPublishEventApiTest.cs +++ b/test/Dapr.Client.Test/BulkPublishEventApiTest.cs @@ -11,6 +11,8 @@ // limitations under the License. // ------------------------------------------------------------------------ +using Dapr.Common; + namespace Dapr.Client.Test { using System; diff --git a/test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs b/test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs new file mode 100644 index 000000000..54f22fae2 --- /dev/null +++ b/test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Dapr.Common.Data.Attributes; +using Dapr.Common.Data.Operations; +using Xunit; + +namespace Dapr.Common.Test.Data.Attributes; + +public class DataOperationAttributeTests +{ + [Fact] + public void DataOperationAttribute_ShouldThrowExceptionForInvalidTypes() + { + // Arrange & Act & Assert + Assert.Throws(() => new DataOperationAttribute(typeof(InvalidOperation))); + } + + [Fact] + public void DataOperationAttribute_ShouldRegisterValidTypes() + { + // Arrange & Act + var attribute = new DataOperationAttribute(typeof(MockOperation1), typeof(MockOperation2)); + + // Assert + Assert.Equal(2, attribute.DataOperationTypes.Count); + Assert.Contains(typeof(MockOperation1), attribute.DataOperationTypes); + Assert.Contains(typeof(MockOperation2), attribute.DataOperationTypes); + } + + private class InvalidOperation + { + } + + private class MockOperation1 : IDaprDataOperation + { + public Task> ExecuteAsync(string input, CancellationToken cancellationToken) + { + var result = new DaprDataOperationPayload($"{input}-processed1") + { + Metadata = new Dictionary { { "Operation1", "Processed" } } + }; + return Task.FromResult(result); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public Task> ReverseAsync(DaprDataOperationPayload input, + CancellationToken cancellationToken) + { + var result = new DaprDataOperationPayload(input.Payload.Replace("-processed1", "")) + { + Metadata = new Dictionary { { "Operation1", "Reversed" } } + }; + return Task.FromResult(result); + } + + /// + /// The name of the operation. + /// + public string Name => "Test"; + } + + private class MockOperation2 : IDaprDataOperation + { + /// + /// The name of the operation. + /// + public string Name => "Test"; + + /// + /// Executes the data processing operation. + /// + /// The input data. + /// Cancellation token. + /// The output data and metadata for the operation. + public Task> ExecuteAsync(string input, + CancellationToken cancellationToken = default) + { + var result = new DaprDataOperationPayload($"{input}-processed2") + { + Metadata = new Dictionary { { "Operation2", "Processed" } } + }; + return Task.FromResult(result); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public Task> ReverseAsync(DaprDataOperationPayload input, + CancellationToken cancellationToken) + { + var result = new DaprDataOperationPayload(input.Payload.Replace("-processed2", "")) + { + Metadata = new Dictionary { { "Operation2", "Reversed" } } + }; + return Task.FromResult(result); + } + } +} diff --git a/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs new file mode 100644 index 000000000..1021bd76d --- /dev/null +++ b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Dapr.Common.Data; +using Dapr.Common.Data.Attributes; +using Dapr.Common.Data.Operations; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Dapr.Common.Test.Data; + +public class DataPipelineFactoryTests +{ + [Fact] + public void CreatePipeline_ShouldCreatePipelineWithCorrectOperations() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + + // Act + var pipeline = factory.CreatePipeline(); + + // Assert + Assert.NotNull(pipeline); + } + + [DataOperation(typeof(MockOperation1), typeof(MockOperation2))] + private class TestDataProcessor + { + } + +private class MockOperation1 : IDaprDataOperation + { + public Task> ExecuteAsync(string input, CancellationToken cancellationToken) + { + var result = new DaprDataOperationPayload($"{input}-processed1") + { + Metadata = new Dictionary { { "Operation1", "Processed" } } + }; + return Task.FromResult(result); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) + { + var result = new DaprDataOperationPayload(input.Payload.Replace("-processed1", "")) + { + Metadata = new Dictionary { { "Operation1", "Reversed" } } + }; + return Task.FromResult(result); + } + + /// + /// The name of the operation. + /// + public string Name => "Test"; + } + + private class MockOperation2 : IDaprDataOperation + { + /// + /// The name of the operation. + /// + public string Name => "Test"; + + /// + /// Executes the data processing operation. + /// + /// The input data. + /// Cancellation token. + /// The output data and metadata for the operation. + public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) + { + var result = new DaprDataOperationPayload($"{input}-processed2") + { + Metadata = new Dictionary { { "Operation2", "Processed" } } + }; + return Task.FromResult(result); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) + { + var result = new DaprDataOperationPayload(input.Payload.Replace("-processed2", "")) + { + Metadata = new Dictionary { { "Operation2", "Reversed" } } + }; + return Task.FromResult(result); + } + } +} diff --git a/test/Dapr.Common.Test/Data/DataPipelineTests.cs b/test/Dapr.Common.Test/Data/DataPipelineTests.cs new file mode 100644 index 000000000..241a52e4d --- /dev/null +++ b/test/Dapr.Common.Test/Data/DataPipelineTests.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Dapr.Common.Data; +using Dapr.Common.Data.Operations; +using Xunit; + +namespace Dapr.Common.Test.Data; + +public class DataPipelineTests +{ + [Fact] + public async Task ProcessAsync_ShouldProcessOperationsInOrder() + { + // Arrange + var operations = new List + { + new MockOperation1(), + new MockOperation2() + }; + var pipeline = new DataPipeline(operations); + + // Act + var result = await pipeline.ProcessAsync("input"); + + // Assert + Assert.Equal("input-processed1-processed2", result.Payload); + Assert.Contains("Operation1", result.Metadata); + Assert.Contains("Operation2", result.Metadata); + } + + [Fact] + public async Task ReverseAsync_ShouldReverseOperationsInOrder() + { + // Arrange + var operations = new List + { + new MockOperation1(), + new MockOperation2() + }; + var pipeline = new DataPipeline(operations); + + // Act + var result = await pipeline.ReverseProcessAsync("input-processed1-processed2"); + + // Assert + Assert.Equal("input", result.Payload); + Assert.Contains("Operation1", result.Metadata); + Assert.Contains("Operation2", result.Metadata); + } + + private class MockOperation1 : IDaprDataOperation + { + public Task> ExecuteAsync(string input, CancellationToken cancellationToken) + { + var result = new DaprDataOperationPayload($"{input}-processed1") + { + Metadata = new Dictionary { { "Operation1", "Processed" } } + }; + return Task.FromResult(result); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) + { + var result = new DaprDataOperationPayload(input.Payload.Replace("-processed1", "")) + { + Metadata = new Dictionary { { "Operation1", "Reversed" } } + }; + return Task.FromResult(result); + } + + /// + /// The name of the operation. + /// + public string Name => "Test"; + } + + private class MockOperation2 : IDaprDataOperation + { + /// + /// The name of the operation. + /// + public string Name => "Test"; + + /// + /// Executes the data processing operation. + /// + /// The input data. + /// Cancellation token. + /// The output data and metadata for the operation. + public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) + { + var result = new DaprDataOperationPayload($"{input}-processed2") + { + Metadata = new Dictionary { { "Operation2", "Processed" } } + }; + return Task.FromResult(result); + } + + /// + /// Reverses the data operation. + /// + /// The processed input data being reversed. + /// Cancellation token. + /// The reversed output data and metadata for the operation. + public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) + { + var result = new DaprDataOperationPayload(input.Payload.Replace("-processed2", "")) + { + Metadata = new Dictionary { { "Operation2", "Reversed" } } + }; + return Task.FromResult(result); + } + } +} diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs new file mode 100644 index 000000000..0d70c2871 --- /dev/null +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs @@ -0,0 +1,43 @@ +using Dapr.Common.Data.Operations.Providers.Compression.Gzip; + +namespace Dapr.Common.Test.Data.Operators.Providers.Compression; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +public class DaprGzipCompressorTests +{ + [Fact] + public async Task ExecuteAsync_ShouldCompressData() + { + // Arrange + var compressor = new DaprGzipCompressor(); + var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); + + // Act + var result = await compressor.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + Assert.NotEqual(input, result.Payload); + } + + [Fact] + public async Task ReverseAsync_ShouldDecompressData() + { + // Arrange + var compressor = new DaprGzipCompressor(); + var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); + var compressedResult = await compressor.ExecuteAsync(input); + + // Act + var result = await compressor.ReverseAsync(compressedResult, CancellationToken.None); + + // Assert + Assert.NotNull(result); + Assert.Equal(input.ToArray(), result.Payload.ToArray()); + } +} + diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs new file mode 100644 index 000000000..9cbb4c377 --- /dev/null +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Dapr.Common.Data.Operations.Providers.Integrity.Checksum; +using Xunit; + +namespace Dapr.Common.Test.Data.Operators.Providers.Integrity; + +public class DaprSha256ValidatorTests +{ + [Fact] + public async Task ExecuteAsync_ShouldCalculateChecksum() + { + // Arrange + var validator = new DaprSha256Validator(); + var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); + + // Act + var result = await validator.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + Assert.True(result.Metadata.ContainsKey("Checksum")); + } + + [Fact] + public async Task ReverseAsync_ShouldValidateChecksum() + { + // Arrange + var validator = new DaprSha256Validator(); + var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); + var result = await validator.ExecuteAsync(input); + + // Act & Assert + await validator.ReverseAsync(result, CancellationToken.None); + } + + [Fact] + public async Task ReverseAsync_ShouldThrowExceptionForInvalidChecksum() + { + // Arrange + var validator = new DaprSha256Validator(); + var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); + var result = await validator.ExecuteAsync(input); + result = result with { Payload = new ReadOnlyMemory(new byte[] { 6, 7, 8, 9, 0 }) }; + + // Act & Assert + await Assert.ThrowsAsync(() => validator.ReverseAsync(result, CancellationToken.None)); + } +} From 4c521a9c14b25ee53b067d0863b184dea809d2c6 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Sat, 19 Oct 2024 06:58:27 -0500 Subject: [PATCH 02/12] Adding missed unit test Signed-off-by: Whit Waldo --- ...taPipelineRegistrationBuilderExtensions.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs diff --git a/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs b/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs new file mode 100644 index 000000000..6404a0560 --- /dev/null +++ b/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs @@ -0,0 +1,87 @@ +using Dapr.Common.Data.Extensions; +using Dapr.Common.Data.Operations; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Dapr.Common.Test.Data.Extensions; + +public class DaprDataPipelineRegistrationBuilderExtensionsTests +{ + [Fact] + public void AddDaprDataProcessingPipeline_ShouldReturnDaprDataProcessingPipelineBuilder() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataPipelineBuilder(services); + + // Act + var result = builder.AddDaprDataProcessingPipeline(); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public void WithDaprOperation_ShouldRegisterSingletonService() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithDaprOperation(ServiceLifetime.Singleton); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var service = serviceProvider.GetService(); + Assert.NotNull(service); + } + + [Fact] + public void WithDaprOperation_ShouldRegisterScopedService() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithDaprOperation(ServiceLifetime.Scoped); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + using (var scope = serviceProvider.CreateScope()) + { + var service = scope.ServiceProvider.GetService(); + Assert.NotNull(service); + } + } + + [Fact] + public void WithDaprOperation_ShouldRegisterTransientService() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithDaprOperation(ServiceLifetime.Transient); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var service1 = serviceProvider.GetService(); + var service2 = serviceProvider.GetService(); + Assert.NotNull(service1); + Assert.NotNull(service2); + Assert.NotSame(service1, service2); + } + + private class MockOperation : IDaprDataOperation + { + /// + /// The name of the operation. + /// + public string Name => "Test"; + } +} + From 008c2a7ae9a5a27c8348c08afe5d73d7a2c1eb0d Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Sat, 19 Oct 2024 15:39:20 -0500 Subject: [PATCH 03/12] Adding unit tests - still a few remaining failures Signed-off-by: Whit Waldo --- src/Dapr.Common/Data/DataPipeline.cs | 20 ++++--- .../Data/Operations/IDaprDataOperation.cs | 2 +- .../Masking/DaprRegularExpressionMasker.cs | 4 +- .../DaprSystemTextJsonSerializer.cs | 20 ++++++- .../Compression/GzipCompressorTest.cs | 2 + .../Integrity/DaprSha256ValidatorTest.cs | 16 ++++- .../DaprRegularExpressionMapperTest.cs | 60 +++++++++++++++++++ .../DaprSystemTextJsonSerializerTest.cs | 45 ++++++++++++++ 8 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs create mode 100644 test/Dapr.Common.Test/Data/Operators/Providers/Serialization/DaprSystemTextJsonSerializerTest.cs diff --git a/src/Dapr.Common/Data/DataPipeline.cs b/src/Dapr.Common/Data/DataPipeline.cs index 7bedac899..ff7053313 100644 --- a/src/Dapr.Common/Data/DataPipeline.cs +++ b/src/Dapr.Common/Data/DataPipeline.cs @@ -34,8 +34,9 @@ public DataPipeline(IEnumerable operations) /// Processes the data in the order of the provided list of . /// /// The data to evaluate. + /// Cancellation token. /// The evaluated data. - public async Task> ProcessAsync(TInput input) + public async Task> ProcessAsync(TInput input, CancellationToken cancellationToken = default) { object? currentData = input; var combinedMetadata = new Dictionary(); @@ -46,10 +47,13 @@ public DataPipeline(IEnumerable operations) var method = operation.GetType().GetMethod("ExecuteAsync"); if (method is null || currentData is null) continue; - - var genericMethod = method.MakeGenericMethod(typeof(TInput), typeof(TOutput)); - var result = await ((Task>)genericMethod.Invoke(operation, new object[] { currentData })!); + + var task = (Task)method.Invoke(operation, new object[] { currentData, cancellationToken })!; + await task.ConfigureAwait(false); + var resultProperty = task.GetType().GetProperty("Result"); + var result = (DaprDataOperationPayload)resultProperty?.GetValue(task)!; currentData = result.Payload; + foreach (var kvp in result.Metadata) { //Append the operation name if given that key @@ -64,7 +68,7 @@ public DataPipeline(IEnumerable operations) combinedMetadata[operationNameKey] = kvp.Value; } } - + combinedMetadata[kvp.Key] = kvp.Value; } } @@ -76,8 +80,9 @@ public DataPipeline(IEnumerable operations) /// Processes the reverse of the data in the order of the provided list of . /// /// The data to evaluate. + /// Cancellation token. /// The evaluated data. - public async Task> ReverseProcessAsync(TOutput input) + public async Task> ReverseProcessAsync(TOutput input, CancellationToken cancellationToken = default) { object? currentData = input; var combinedMetadata = new Dictionary(); @@ -88,8 +93,7 @@ public DataPipeline(IEnumerable operations) if (method is null || currentData is null) continue; - var genericMethod = method.MakeGenericMethod(typeof(TOutput), typeof(TInput)); - var result = await (Task>)genericMethod.Invoke(operations[i], new[] { currentData })!; + var result = await (Task>)method.Invoke(operations[i], new[] { currentData, cancellationToken })!; currentData = result.Payload; foreach (var kvp in result.Metadata) { diff --git a/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs b/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs index cb244d618..0cdde9eb8 100644 --- a/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs +++ b/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs @@ -46,5 +46,5 @@ public interface IDaprDataOperation : IDaprDataOperation /// Cancellation token. /// The reversed output data and metadata for the operation. Task> ReverseAsync(DaprDataOperationPayload input, - CancellationToken cancellationToken); + CancellationToken cancellationToken = default); } diff --git a/src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs b/src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs index f3eb8ddef..44799bc3e 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs @@ -53,7 +53,9 @@ public Task> ExecuteAsync(string input, /// The processed input data being reversed. /// Cancellation token. /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) => Task.FromResult(new DaprDataOperationPayload(input.Payload)); + public Task> ReverseAsync(DaprDataOperationPayload input, + CancellationToken cancellationToken = default) => + Task.FromResult(new DaprDataOperationPayload(input.Payload)); /// /// Registers a pattern to match against. diff --git a/src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs b/src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs index 87fe4f310..678d1a595 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs @@ -20,6 +20,11 @@ namespace Dapr.Common.Data.Operations.Providers.Serialization; /// public class DaprSystemTextJsonSerializer : IDaprDataSerializer { + /// + /// Optionally provided . + /// + private JsonSerializerOptions? options = new (JsonSerializerDefaults.Web); + /// /// The name of the operation. /// @@ -33,7 +38,7 @@ public class DaprSystemTextJsonSerializer : IDaprDataSerializer /// The output data and metadata for the operation. public Task> ExecuteAsync(T input, CancellationToken cancellationToken = default) { - var jsonResult = JsonSerializer.Serialize(input); + var jsonResult = JsonSerializer.Serialize(input, options); var result = new DaprDataOperationPayload(jsonResult, Name); return Task.FromResult(result); @@ -46,10 +51,19 @@ public Task> ExecuteAsync(T input, Cancellation /// Cancellation token. /// The reversed output data and metadata for the operation. public Task> ReverseAsync(DaprDataOperationPayload input, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { - var value = JsonSerializer.Deserialize(input.Payload); + var value = JsonSerializer.Deserialize(input.Payload, options); var result = new DaprDataOperationPayload(value); return Task.FromResult(result); } + + /// + /// Used to provide a to the operation. + /// + /// The configuration options to use. + public void UseOptions(JsonSerializerOptions jsonSerializerOptions) + { + this.options = jsonSerializerOptions; + } } diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs index 0d70c2871..6c0231e9f 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs @@ -22,6 +22,8 @@ public async Task ExecuteAsync_ShouldCompressData() // Assert Assert.NotNull(result); Assert.NotEqual(input, result.Payload); + Assert.True(result.Metadata.ContainsKey("Ops")); + Assert.Equal(compressor.Name, result.Metadata["Ops"]); } [Fact] diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs index 9cbb4c377..d3f36e856 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Dapr.Common.Data.Operations; using Dapr.Common.Data.Operations.Providers.Integrity.Checksum; using Xunit; @@ -20,7 +21,20 @@ public async Task ExecuteAsync_ShouldCalculateChecksum() // Assert Assert.NotNull(result); - Assert.True(result.Metadata.ContainsKey("Checksum")); + Assert.True(result.Metadata.ContainsKey($"{validator.Name}-hash")); + Assert.True(result.Metadata.ContainsKey("Ops")); + Assert.Equal(validator.Name, result.Metadata["Ops"]); + } + + [Fact] + public async Task ReverseAsync_ShouldValidateChecksumWithoutMetadataHeader() + { + // Arrange + var validator = new DaprSha256Validator(); + var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); + var result = new DaprDataOperationPayload>(input); + + await validator.ReverseAsync(result, CancellationToken.None); } [Fact] diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs new file mode 100644 index 000000000..6e3e4d6c7 --- /dev/null +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs @@ -0,0 +1,60 @@ +using System; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Dapr.Common.Data.Operations.Providers.Masking; +using Xunit; + +namespace Dapr.Common.Test.Data.Operators.Providers.Masking; + +public class DaprRegularExpressionMapperTest +{ + [Fact] + public async Task ExecuteAsync_ShouldRunMapperCorrectly() + { + //Arrange + var mapper = new DaprRegularExpressionMasker(); + const string input = "This is not a real social security number: 012-34-5678"; + mapper.RegisterMatch(new Regex(@"\d{3}-\d{2}-\d{4}"), "***-**-****"); + + //Act + var result = await mapper.ExecuteAsync(input); + + //Assert + Assert.Equal("This is not a real social security number: ***-**-****", result.Payload); + Assert.True(result.Metadata.ContainsKey("Ops")); + Assert.Equal(mapper.Name, result.Metadata["Ops"]); + } + + [Fact] + public async Task ExecuteAsync_ThrowIsCancellationTokenCancelled() + { + //Arrange + var mapper = new DaprRegularExpressionMasker(); + const string input = "This is not a real social security number: 012-34-5678"; + mapper.RegisterMatch(new Regex(@"\d{3}-\d{2}-\d{4}"), "***-**-****"); + + //Act + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); //CancelAsync is only in .NET 8 and up + + //Assert + await Assert.ThrowsAsync(() => mapper.ExecuteAsync(input, cancellationTokenSource.Token)); + } + + [Fact] + public async Task ReverseAsync_ShouldNotDoAnythingToPayload() + { + //Arrange + var mapper = new DaprRegularExpressionMasker(); + const string input = "This is a date: 2/18/2026"; + mapper.RegisterMatch(new Regex(@"\d{3}-\d{2}-\d{4}"), "***-**-****"); + + //Act + var result = await mapper.ExecuteAsync(input); + var reverseResult = await mapper.ReverseAsync(result); + + //Assert + Assert.Equal(input, reverseResult.Payload); + } +} diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/DaprSystemTextJsonSerializerTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/DaprSystemTextJsonSerializerTest.cs new file mode 100644 index 000000000..ced74793d --- /dev/null +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/DaprSystemTextJsonSerializerTest.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using Dapr.Common.Data.Operations; +using Dapr.Common.Data.Operations.Providers.Serialization; +using Xunit; + +namespace Dapr.Common.Test.Data.Operators.Providers.Serialization; + +public class DaprSystemTextJsonSerializerTest +{ + [Fact] + public async Task ExecuteAsync_ShouldSerialize() + { + //Arrange + var validator = new DaprSystemTextJsonSerializer(); + var input = new TestObject("Test", 15); + + //Act + var result = await validator.ExecuteAsync(input); + + //Assert + Assert.NotNull(result); + Assert.Equal("{\"name\":\"Test\",\"count\":15}", result.Payload); + Assert.True(result.Metadata.ContainsKey("Ops")); + Assert.Equal(validator.Name, result.Metadata["Ops"]); + } + + [Fact] + public async Task ReverseAsync_ShouldDeserialize() + { + //Arrange + var validator = new DaprSystemTextJsonSerializer(); + const string input = "{\"name\":\"Test\",\"count\":15}"; + var payload = new DaprDataOperationPayload(input); + + //Act + var result = await validator.ReverseAsync(payload); + + //Assert + Assert.NotNull(result); + var expected = new TestObject("Test", 15); + Assert.Equal(expected, result.Payload); + } + + private record TestObject(string Name, int Count); +} From 0fcd4f5e48e0d7b0ce0ea180286b094f55be79a6 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 22 Oct 2024 06:12:40 -0500 Subject: [PATCH 04/12] Building out unit tests, made some perf changes. Signed-off-by: Whit Waldo --- src/Dapr.Common/Dapr.Common.csproj | 4 + src/Dapr.Common/Data/DaprDataPipeline.cs | 201 ++++++++++++++++ src/Dapr.Common/Data/DataPipeline.cs | 106 --------- src/Dapr.Common/Data/DataPipelineFactory.cs | 53 ++++- ...taPipelineRegistrationBuilderExtensions.cs | 154 ++++++------- .../Data/Extensions/DictionaryExtensions.cs | 24 ++ .../IDaprDataProcessingBuilder.cs | 4 +- ...tionPayload.cs => DaprOperationPayload.cs} | 17 +- ...ersioner.cs => IDaprByteBasedOperation.cs} | 18 +- .../Data/Operations/IDaprDataOperation.cs | 4 +- .../Operations/IDaprStringBasedOperation.cs | 21 ++ .../IDaprStringByteTransitionOperation.cs | 21 ++ .../IDaprTStringTransitionOperation.cs | 21 ++ ...aprGzipCompressor.cs => GzipCompressor.cs} | 12 +- .../Compression/IDaprDataCompressor.cs | 6 +- .../Providers/Encoding/IDaprDataEncoder.cs | 21 ++ .../Utf8Encoder.cs} | 43 ++-- .../{Checksum => }/DaprSha256Validator.cs | 12 +- .../Providers/Integrity/IDaprDataValidator.cs | 2 +- .../Providers/Masking/IDaprDataMasker.cs | 10 +- ...onMasker.cs => RegularExpressionMasker.cs} | 13 +- .../Serialization/IDaprDataSerializer.cs | 2 +- ...ializer.cs => SystemTextJsonSerializer.cs} | 12 +- .../Attributes/DataOperationAttributeTests.cs | 16 +- .../Data/DaprDataPipelineTests.cs | 121 ++++++++++ .../Data/DataPipelineFactoryTests.cs | 214 +++++++++--------- .../Data/DataPipelineTests.cs | 121 ---------- ...taPipelineRegistrationBuilderExtensions.cs | 152 +++++++++++-- .../Compression/GzipCompressorTest.cs | 10 +- .../Integrity/DaprSha256ValidatorTest.cs | 16 +- .../DaprRegularExpressionMapperTest.cs | 8 +- ...est.cs => SystemTextJsonSerializerTest.cs} | 10 +- 32 files changed, 883 insertions(+), 566 deletions(-) create mode 100644 src/Dapr.Common/Data/DaprDataPipeline.cs delete mode 100644 src/Dapr.Common/Data/DataPipeline.cs create mode 100644 src/Dapr.Common/Data/Extensions/DictionaryExtensions.cs rename src/Dapr.Common/Data/{Operations => Extensions}/IDaprDataProcessingBuilder.cs (92%) rename src/Dapr.Common/Data/Operations/{DaprDataOperationPayload.cs => DaprOperationPayload.cs} (69%) rename src/Dapr.Common/Data/Operations/{Providers/Versioning/IDaprDataVersioner.cs => IDaprByteBasedOperation.cs} (52%) create mode 100644 src/Dapr.Common/Data/Operations/IDaprStringBasedOperation.cs create mode 100644 src/Dapr.Common/Data/Operations/IDaprStringByteTransitionOperation.cs create mode 100644 src/Dapr.Common/Data/Operations/IDaprTStringTransitionOperation.cs rename src/Dapr.Common/Data/Operations/Providers/Compression/{Gzip/DaprGzipCompressor.cs => GzipCompressor.cs} (76%) create mode 100644 src/Dapr.Common/Data/Operations/Providers/Encoding/IDaprDataEncoder.cs rename src/Dapr.Common/Data/Operations/Providers/{Versioning/DaprVersioner.cs => Encoding/Utf8Encoder.cs} (51%) rename src/Dapr.Common/Data/Operations/Providers/Integrity/{Checksum => }/DaprSha256Validator.cs (84%) rename src/Dapr.Common/Data/Operations/Providers/Masking/{DaprRegularExpressionMasker.cs => RegularExpressionMasker.cs} (83%) rename src/Dapr.Common/Data/Operations/Providers/Serialization/{DaprSystemTextJsonSerializer.cs => SystemTextJsonSerializer.cs} (83%) create mode 100644 test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs delete mode 100644 test/Dapr.Common.Test/Data/DataPipelineTests.cs rename test/Dapr.Common.Test/Data/Operators/Providers/Serialization/{DaprSystemTextJsonSerializerTest.cs => SystemTextJsonSerializerTest.cs} (73%) diff --git a/src/Dapr.Common/Dapr.Common.csproj b/src/Dapr.Common/Dapr.Common.csproj index 31af3952c..767cf1b41 100644 --- a/src/Dapr.Common/Dapr.Common.csproj +++ b/src/Dapr.Common/Dapr.Common.csproj @@ -13,4 +13,8 @@ + + + + diff --git a/src/Dapr.Common/Data/DaprDataPipeline.cs b/src/Dapr.Common/Data/DaprDataPipeline.cs new file mode 100644 index 000000000..239f662fa --- /dev/null +++ b/src/Dapr.Common/Data/DaprDataPipeline.cs @@ -0,0 +1,201 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Text; +using Dapr.Common.Data.Extensions; +using Dapr.Common.Data.Operations; + +namespace Dapr.Common.Data; + +/// +/// Processes the data using the provided providers. +/// +internal sealed class DaprDataPipeline +{ + /// + /// The metadata key containing the operations. + /// + private const string OperationKey = "ops"; + + private readonly StringBuilder operationNameBuilder = new(); + private readonly IDaprTStringTransitionOperation? genericToStringOp; + private readonly List stringOps = new(); + private readonly IDaprStringByteTransitionOperation? stringToByteOp; + private readonly List byteOps = new(); + + /// + /// Used to initialize a new . + /// + public DaprDataPipeline(IEnumerable operations) + { + foreach (var op in operations) + { + switch (op) + { + case IDaprTStringTransitionOperation genToStrOp when genericToStringOp is not null: + throw new DaprException( + $"Multiple types are declared for the conversion of the data to a string in the data pipeline for {typeof(TInput)} - only one is allowed"); + case IDaprTStringTransitionOperation genToStrOp: + genericToStringOp = genToStrOp; + break; + case IDaprStringBasedOperation strOp: + stringOps.Add(strOp); + break; + case IDaprStringByteTransitionOperation strToByte when stringToByteOp is not null: + throw new DaprException( + $"Multiple types are declared for the pipeline conversion from a string to a byte array for {typeof(TInput)} - only one is allowed"); + case IDaprStringByteTransitionOperation strToByte: + stringToByteOp = strToByte; + break; + case IDaprByteBasedOperation byteOp: + byteOps.Add(byteOp); + break; + } + } + + if (genericToStringOp is null) + { + throw new DaprException( + $"A pipeline operation must be specified to convert a {typeof(TInput)} into a serializable string"); + } + + if (stringToByteOp is null) + { + throw new DaprException( + $"A pipeline operation must be specified to convert a {typeof(TInput)} into a byte array"); + } + } + + /// + /// Processes the data in the order of the provided list of . + /// + /// The data to evaluate. + /// Cancellation token. + /// The evaluated data. + public async Task>> ProcessAsync(TInput input, CancellationToken cancellationToken = default) + { + //Combines the metadata across each operation to be returned with the result + var combinedMetadata = new Dictionary(); + + //Start by serializing the input to a string + var serializationPayload = await genericToStringOp!.ExecuteAsync(input, cancellationToken); + combinedMetadata.MergeFrom(serializationPayload.Metadata); + AppendOperationName(genericToStringOp.Name); + + //Run through any provided string-based operations + var stringPayload = new DaprOperationPayload(serializationPayload.Payload); + foreach (var strOp in stringOps) + { + stringPayload = await strOp.ExecuteAsync(stringPayload.Payload, cancellationToken); + combinedMetadata.MergeFrom(stringPayload.Metadata); + AppendOperationName(strOp.Name); + } + + //Encode the string payload to a byte array + var encodedPayload = await stringToByteOp!.ExecuteAsync(stringPayload.Payload, cancellationToken); + combinedMetadata.MergeFrom(encodedPayload.Metadata); + AppendOperationName(stringToByteOp.Name); + + //Run through any provided byte-based operations + var bytePayload = new DaprOperationPayload>(encodedPayload.Payload); + foreach (var byteOp in byteOps) + { + bytePayload = await byteOp.ExecuteAsync(bytePayload.Payload, cancellationToken); + combinedMetadata.MergeFrom(bytePayload.Metadata); + AppendOperationName(byteOp.Name); + } + + //Persist the op names to the metadata + combinedMetadata[OperationKey] = operationNameBuilder.ToString(); + + //Create a payload that combines the payload and metadata + var resultPayload = new DaprOperationPayload>(bytePayload.Payload) + { + Metadata = combinedMetadata + }; + return resultPayload; + } + + /// + /// Processes the reverse of the data in the order of the provided list of . + /// + /// The data to process in reverse. + /// The metadata providing the mechanism(s) used to encode the data. + /// Cancellation token. + /// The evaluated data. + public async Task> ReverseProcessAsync(ReadOnlyMemory payload, Dictionary metadata, CancellationToken cancellationToken = default) + { + //First, perform byte-based operations + var inboundPayload = new DaprOperationPayload>(payload) { Metadata = metadata }; + var byteBasedResult = await ReverseByteOperationsAsync(inboundPayload, cancellationToken); + + //Convert this back to a string from a byte array + var stringResult = await stringToByteOp!.ReverseAsync(byteBasedResult, cancellationToken); + + //Perform the string-based operations + var stringBasedResult = await ReverseStringOperationsAsync(stringResult, cancellationToken); + + //Convert from a string back into its generic type + var genericResult = await genericToStringOp!.ReverseAsync(stringBasedResult, cancellationToken); + + return genericResult; + } + + /// + /// Performs a reversal operation for the string-based operations. + /// + /// The payload to run the reverse operation against. + /// Cancellation token. + /// + private async Task> ReverseStringOperationsAsync(DaprOperationPayload payload, + CancellationToken cancellationToken) + { + stringOps.Reverse(); + foreach (var op in stringOps) + { + payload = await op.ReverseAsync(payload, cancellationToken); + } + + return payload; + } + + /// + /// Performs a reversal operation for the byte-based operations. + /// + /// The current state of the payload. + /// Cancellation token. + /// The most up-to-date payload. + private async Task>> + ReverseByteOperationsAsync(DaprOperationPayload> payload, + CancellationToken cancellationToken) + { + byteOps.Reverse(); + foreach (var op in byteOps) + { + payload = await op.ReverseAsync(payload, cancellationToken); + } + + return payload; + } + + /// + /// Appends the operation name to the string. + /// + /// The name of the operation to append. + private void AppendOperationName(string name) + { + if (operationNameBuilder.Length > 0) + operationNameBuilder.Append(','); + operationNameBuilder.Append(name); + } +} diff --git a/src/Dapr.Common/Data/DataPipeline.cs b/src/Dapr.Common/Data/DataPipeline.cs deleted file mode 100644 index ff7053313..000000000 --- a/src/Dapr.Common/Data/DataPipeline.cs +++ /dev/null @@ -1,106 +0,0 @@ -// ------------------------------------------------------------------------ -// Copyright 2024 The Dapr Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ------------------------------------------------------------------------ - -using Dapr.Common.Data.Operations; - -namespace Dapr.Common.Data; - -/// -/// Processes the data using the provided providers. -/// -public class DataPipeline -{ - private readonly List operations; - - /// - /// Used to initialize a new . - /// - public DataPipeline(IEnumerable operations) - { - this.operations = operations.ToList(); - } - - /// - /// Processes the data in the order of the provided list of . - /// - /// The data to evaluate. - /// Cancellation token. - /// The evaluated data. - public async Task> ProcessAsync(TInput input, CancellationToken cancellationToken = default) - { - object? currentData = input; - var combinedMetadata = new Dictionary(); - const string operationNameKey = "Ops"; - - foreach (var operation in operations) - { - var method = operation.GetType().GetMethod("ExecuteAsync"); - if (method is null || currentData is null) - continue; - - var task = (Task)method.Invoke(operation, new object[] { currentData, cancellationToken })!; - await task.ConfigureAwait(false); - var resultProperty = task.GetType().GetProperty("Result"); - var result = (DaprDataOperationPayload)resultProperty?.GetValue(task)!; - currentData = result.Payload; - - foreach (var kvp in result.Metadata) - { - //Append the operation name if given that key - if (kvp.Key == operationNameKey) - { - if (combinedMetadata.TryGetValue(operationNameKey, out var operationName)) - { - combinedMetadata[operationNameKey] = operationName + $",{kvp.Value}"; - } - else - { - combinedMetadata[operationNameKey] = kvp.Value; - } - } - - combinedMetadata[kvp.Key] = kvp.Value; - } - } - - return new DaprDataOperationPayload((TOutput?)currentData) { Metadata = combinedMetadata }; - } - - /// - /// Processes the reverse of the data in the order of the provided list of . - /// - /// The data to evaluate. - /// Cancellation token. - /// The evaluated data. - public async Task> ReverseProcessAsync(TOutput input, CancellationToken cancellationToken = default) - { - object? currentData = input; - var combinedMetadata = new Dictionary(); - - for (int i = operations.Count - 1; i >= 0; i--) - { - var method = operations[i].GetType().GetMethod("ReverseAsync"); - if (method is null || currentData is null) - continue; - - var result = await (Task>)method.Invoke(operations[i], new[] { currentData, cancellationToken })!; - currentData = result.Payload; - foreach (var kvp in result.Metadata) - { - combinedMetadata[kvp.Key] = kvp.Value; - } - } - - return new DaprDataOperationPayload((TInput?)currentData) { Metadata = combinedMetadata }; - } -} diff --git a/src/Dapr.Common/Data/DataPipelineFactory.cs b/src/Dapr.Common/Data/DataPipelineFactory.cs index 84e6929ee..2d8a5697a 100644 --- a/src/Dapr.Common/Data/DataPipelineFactory.cs +++ b/src/Dapr.Common/Data/DataPipelineFactory.cs @@ -22,7 +22,7 @@ namespace Dapr.Common.Data; /// Used to create a data pipeline specific to a given type using the ordered operation services indicated in the /// attribute on that type. /// -public sealed class DataPipelineFactory +internal sealed class DataPipelineFactory { /// /// The service provider used to pull the registered data operation services. @@ -38,22 +38,63 @@ public DataPipelineFactory(IServiceProvider serviceProvider) } /// - /// Creates a pipeline with the attributes specified for a given type. + /// Creates a pipeline used to serialize a given type. /// /// The type to create the pipeline for. /// - public DataPipeline CreatePipeline() + public DaprDataPipeline CreateEncodingPipeline() { var attribute = typeof(T).GetCustomAttribute(); if (attribute == null) { - return new DataPipeline(new List>()); + return new DaprDataPipeline(new List>()); } var operations = attribute.DataOperationTypes - .Select(type => serviceProvider.GetRequiredService(type)) + .Select(type => serviceProvider.GetRequiredService(type) as IDaprDataOperation) + .Where(op => op is not null) + .Cast() .ToList(); - return new DataPipeline(operations); + return new DaprDataPipeline(operations); + } + + /// + /// Creates a pipeline used to reverse a previously applied pipeline operation using the provided + /// operation names from the metadata. + /// + /// The metadata payload used to determine the order of operations. + /// The type to deserialize to. + /// A pipeline configured for reverse processing. + /// + public DaprDataPipeline CreateDecodingPipeline(Dictionary metadata) + { + const string operationKey = "ops"; + if (!metadata.TryGetValue(operationKey, out var opNames)) + { + throw new DaprException( + $"Unable to decode payload as its metadata is missing the key (\"${operationKey}\") containing the operation order"); + } + + //Run through the names backwards in the order of the operations as named in the metadata + var operations = new List(opNames.Split(',').Reverse()); + + var matchingDataOperations = serviceProvider.GetServices() + .Where(op => operations.Contains(op.Name)) + .ToList(); + + if (matchingDataOperations.Count != operations.Count) + { + //Identify which names are missing + foreach (var op in matchingDataOperations) + { + operations.Remove(op.Name); + } + + throw new DaprException( + $"Registered services were not located for the following operation names present in the metadata: {String.Join(',', operations)}"); + } + + return new DaprDataPipeline(matchingDataOperations); } } diff --git a/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs b/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs index 349f04cf4..9ea199190 100644 --- a/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs +++ b/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs @@ -16,7 +16,6 @@ using Dapr.Common.Data.Operations.Providers.Integrity; using Dapr.Common.Data.Operations.Providers.Masking; using Dapr.Common.Data.Operations.Providers.Serialization; -using Dapr.Common.Data.Operations.Providers.Versioning; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -37,69 +36,11 @@ public static IDaprDataProcessingBuilder AddDaprDataProcessingPipeline(this IDap return new DaprDataProcessingPipelineBuilder(builder.Services); } - /// - /// Registers the specified Dapr operation services. - /// - /// - /// - /// - /// - /// - public static IDaprDataProcessingBuilder WithDaprOperation(this IDaprDataProcessingBuilder builder, - ServiceLifetime lifetime) - where TService : class, IDaprDataOperation - { - switch (lifetime) - { - case ServiceLifetime.Singleton: - builder.Services.TryAddSingleton(); - break; - case ServiceLifetime.Scoped: - builder.Services.TryAddScoped(); - break; - case ServiceLifetime.Transient: - builder.Services.TryAddTransient(); - break; - default: - throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, null); - } - - return builder; - } - - /// - /// Registers the specified - /// - /// The builder to register the type on. - /// The data operation factory used to register the data operation service. - /// The lifetime the service should be registered for. - public static IDaprDataProcessingBuilder WithDaprOperation(this IDaprDataProcessingBuilder builder, - Func operationFactory, ServiceLifetime lifetime) - where TService : class, IDaprDataOperation - { - switch (lifetime) - { - case ServiceLifetime.Singleton: - builder.Services.TryAddSingleton(operationFactory); - break; - case ServiceLifetime.Scoped: - builder.Services.TryAddScoped(operationFactory); - break; - case ServiceLifetime.Transient: - builder.Services.TryAddTransient(operationFactory); - break; - default: - throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, null); - } - - return builder; - } - /// /// Adds a serializer data operation. /// public static IDaprDataProcessingBuilder WithSerializer(this IDaprDataProcessingBuilder builder, - ServiceLifetime lifetime) + ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : class, IDaprDataOperation => builder.WithDaprOperation(lifetime); @@ -107,29 +48,32 @@ public static IDaprDataProcessingBuilder WithSerializer(this IDaprData /// Adds a serializer data operation. /// public static IDaprDataProcessingBuilder WithSerializer(this IDaprDataProcessingBuilder builder, - Func> serializerFactory, ServiceLifetime lifetime) => + Func> serializerFactory, + ServiceLifetime lifetime = ServiceLifetime.Singleton) => builder.WithDaprOperation, TInput, string>(serializerFactory, lifetime); /// /// Adds a compression data operation. /// public static IDaprDataProcessingBuilder WithCompressor(this IDaprDataProcessingBuilder builder, - ServiceLifetime lifetime) + ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : class, IDaprDataCompressor => builder.WithDaprOperation(lifetime); - + /// /// Adds a compressor data operation. /// public static IDaprDataProcessingBuilder WithCompressor(this IDaprDataProcessingBuilder builder, - Func compressorFactory, ServiceLifetime lifetime) => - builder.WithDaprOperation, ReadOnlyMemory>(compressorFactory, lifetime); + Func compressorFactory, + ServiceLifetime lifetime = ServiceLifetime.Singleton) => + builder.WithDaprOperation, ReadOnlyMemory>(compressorFactory, + lifetime); /// /// Adds a data integrity operation. /// public static IDaprDataProcessingBuilder WithIntegrity(this IDaprDataProcessingBuilder builder, - ServiceLifetime serviceLifetime) + ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) where TService : class, IDaprDataValidator => builder.WithDaprOperation(serviceLifetime); @@ -137,14 +81,16 @@ public static IDaprDataProcessingBuilder WithIntegrity(this IDaprDataP /// Adds a data integrity operation using a factory that provides an . /// public static IDaprDataProcessingBuilder WithIntegrity(this IDaprDataProcessingBuilder builder, - Func validatorFactory, ServiceLifetime serviceLifetime) => - builder.WithDaprOperation, ReadOnlyMemory>(validatorFactory, serviceLifetime); + Func validatorFactory, + ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) => + builder.WithDaprOperation, ReadOnlyMemory>(validatorFactory, + serviceLifetime); /// /// Adds a data masking operation. /// public static IDaprDataProcessingBuilder WithMasking(this IDaprDataProcessingBuilder builder, - IDaprDataMasker masker, ServiceLifetime serviceLifetime) + ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) where TService : class, IDaprDataMasker => builder.WithDaprOperation(serviceLifetime); @@ -152,21 +98,69 @@ public static IDaprDataProcessingBuilder WithMasking(this IDaprDataPro /// Adds a data masking operation using a factory that provides an . /// public static IDaprDataProcessingBuilder WithMasking(this IDaprDataProcessingBuilder builder, - Func maskerFactory, ServiceLifetime serviceLifetime) => + Func maskerFactory, + ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) => builder.WithDaprOperation(maskerFactory, serviceLifetime); - + /// - /// Adds a data versioning operation. + /// Registers the specified Dapr operation services. /// - public static IDaprDataProcessingBuilder WithVersioning(this IDaprDataProcessingBuilder builder, - IDaprDataVersioner versioner, ServiceLifetime serviceLifetime) - where TService : class, IDaprDataVersioner - => builder.WithDaprOperation(serviceLifetime); + /// The builder to register the type on. + /// The lifetime the service should be registered for. + /// + /// Thrown when an invalid service lifetime is provided. + private static IDaprDataProcessingBuilder WithDaprOperation(this IDaprDataProcessingBuilder builder, + ServiceLifetime lifetime) + where TService : class, IDaprDataOperation + { + switch (lifetime) + { + case ServiceLifetime.Singleton: + builder.Services.TryAddSingleton(); + break; + case ServiceLifetime.Scoped: + builder.Services.TryAddScoped(); + break; + case ServiceLifetime.Transient: + builder.Services.TryAddTransient(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, null); + } + + return builder; + } /// - /// Adds a data versioning operation. + /// Registers the specified /// - public static IDaprDataProcessingBuilder WithVersioning(this IDaprDataProcessingBuilder builder, - Func versionerFactory, ServiceLifetime serviceLifetime) - => builder.WithDaprOperation(versionerFactory, serviceLifetime); + /// The builder to register the type on. + /// The data operation factory used to register the data operation service. + /// The lifetime the service should be registered for. + /// The type of service being registered. + /// The input type provided to the operation. + /// The output type provided by the operation. + /// + /// Thrown when an invalid service lifetime is provided. + private static IDaprDataProcessingBuilder WithDaprOperation(this IDaprDataProcessingBuilder builder, + Func operationFactory, ServiceLifetime lifetime) + where TService : class, IDaprDataOperation + { + switch (lifetime) + { + case ServiceLifetime.Singleton: + builder.Services.TryAddSingleton(operationFactory); + break; + case ServiceLifetime.Scoped: + builder.Services.TryAddScoped(operationFactory); + break; + case ServiceLifetime.Transient: + builder.Services.TryAddTransient(operationFactory); + break; + default: + throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, null); + } + + return builder; + } } diff --git a/src/Dapr.Common/Data/Extensions/DictionaryExtensions.cs b/src/Dapr.Common/Data/Extensions/DictionaryExtensions.cs new file mode 100644 index 000000000..5e516127a --- /dev/null +++ b/src/Dapr.Common/Data/Extensions/DictionaryExtensions.cs @@ -0,0 +1,24 @@ +namespace Dapr.Common.Data.Extensions; + +/// +/// Provides extension methods for use with a . +/// +internal static class DictionaryExtensions +{ + /// + /// Merges the keys and values of the provided dictionary in mergeFrom with the + /// dictionary provided in mergeTo. + /// + /// The dictionary the values are being merged into. + /// The dictionary the values are being merged from. + /// The type of the key for either dictionary. + /// The type of the value for either dictionary. + internal static void MergeFrom(this Dictionary mergeTo, + Dictionary mergeFrom) where TKey : notnull + { + foreach (var kvp in mergeFrom) + { + mergeTo[kvp.Key] = kvp.Value; + } + } +} diff --git a/src/Dapr.Common/Data/Operations/IDaprDataProcessingBuilder.cs b/src/Dapr.Common/Data/Extensions/IDaprDataProcessingBuilder.cs similarity index 92% rename from src/Dapr.Common/Data/Operations/IDaprDataProcessingBuilder.cs rename to src/Dapr.Common/Data/Extensions/IDaprDataProcessingBuilder.cs index 98ba7b02d..62432542f 100644 --- a/src/Dapr.Common/Data/Operations/IDaprDataProcessingBuilder.cs +++ b/src/Dapr.Common/Data/Extensions/IDaprDataProcessingBuilder.cs @@ -11,9 +11,7 @@ // limitations under the License. // ------------------------------------------------------------------------ -using Dapr.Common.Data.Extensions; - -namespace Dapr.Common.Data.Operations; +namespace Dapr.Common.Data.Extensions; /// /// Provides a root builder for the Dapr processing functionality facilitating a more fluent-style registration. diff --git a/src/Dapr.Common/Data/Operations/DaprDataOperationPayload.cs b/src/Dapr.Common/Data/Operations/DaprOperationPayload.cs similarity index 69% rename from src/Dapr.Common/Data/Operations/DaprDataOperationPayload.cs rename to src/Dapr.Common/Data/Operations/DaprOperationPayload.cs index eac575e13..93ca62228 100644 --- a/src/Dapr.Common/Data/Operations/DaprDataOperationPayload.cs +++ b/src/Dapr.Common/Data/Operations/DaprOperationPayload.cs @@ -17,27 +17,16 @@ namespace Dapr.Common.Data.Operations; /// Contains the result of a Dapr data operation. /// /// The type of the payload. -public record DaprDataOperationPayload +public record DaprOperationPayload { /// - /// Initializes a . + /// Initializes a . /// /// The resulting payload following the operation. - public DaprDataOperationPayload(T payload) + public DaprOperationPayload(T payload) { Payload = payload; } - - /// - /// Initializes a . - /// - /// The resulting payload following the operation. - /// The name of the operation that produced this as a result. - public DaprDataOperationPayload(T payload, string operationName) - { - Payload = payload; - Metadata["Ops"] = operationName; - } /// /// The result of the operation. diff --git a/src/Dapr.Common/Data/Operations/Providers/Versioning/IDaprDataVersioner.cs b/src/Dapr.Common/Data/Operations/IDaprByteBasedOperation.cs similarity index 52% rename from src/Dapr.Common/Data/Operations/Providers/Versioning/IDaprDataVersioner.cs rename to src/Dapr.Common/Data/Operations/IDaprByteBasedOperation.cs index 1148d1f1a..feaf60372 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Versioning/IDaprDataVersioner.cs +++ b/src/Dapr.Common/Data/Operations/IDaprByteBasedOperation.cs @@ -11,23 +11,11 @@ // limitations under the License. // ------------------------------------------------------------------------ -namespace Dapr.Common.Data.Operations.Providers.Versioning; +namespace Dapr.Common.Data.Operations; /// -/// Identifies an operation that provides a data versioning capability. +/// Represents a Dapr pipeline operation performed against byte values. /// -public interface IDaprDataVersioner : IDaprDataOperation +public interface IDaprByteBasedOperation : IDaprDataOperation, ReadOnlyMemory> { - /// - /// The current version of the data. - /// - int CurrentVersion { get; } - - /// - /// Registers an upgrade function for a specific version. - /// - /// The version to upgrade from. - /// The function to upgrade the data. - /// The type of data to upgrade. - void RegisterUpgrade(int fromVersion, Func upgradeFunc); } diff --git a/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs b/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs index 0cdde9eb8..872ae6a1d 100644 --- a/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs +++ b/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs @@ -37,7 +37,7 @@ public interface IDaprDataOperation : IDaprDataOperation /// The input data. /// Cancellation token. /// The output data and metadata for the operation. - Task> ExecuteAsync(TInput input, CancellationToken cancellationToken = default); + Task> ExecuteAsync(TInput? input, CancellationToken cancellationToken = default); /// /// Reverses the data operation. @@ -45,6 +45,6 @@ public interface IDaprDataOperation : IDaprDataOperation /// The processed input data being reversed. /// Cancellation token. /// The reversed output data and metadata for the operation. - Task> ReverseAsync(DaprDataOperationPayload input, + Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken = default); } diff --git a/src/Dapr.Common/Data/Operations/IDaprStringBasedOperation.cs b/src/Dapr.Common/Data/Operations/IDaprStringBasedOperation.cs new file mode 100644 index 000000000..29be721e2 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/IDaprStringBasedOperation.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations; + +/// +/// Represents a Dapr pipeline operation performed against string values. +/// +public interface IDaprStringBasedOperation : IDaprDataOperation +{ +} diff --git a/src/Dapr.Common/Data/Operations/IDaprStringByteTransitionOperation.cs b/src/Dapr.Common/Data/Operations/IDaprStringByteTransitionOperation.cs new file mode 100644 index 000000000..31a346216 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/IDaprStringByteTransitionOperation.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations; + +/// +/// Represents a Dapr pipeline operation performed against string values that output as byte arrays. +/// +public interface IDaprStringByteTransitionOperation : IDaprDataOperation> +{ +} diff --git a/src/Dapr.Common/Data/Operations/IDaprTStringTransitionOperation.cs b/src/Dapr.Common/Data/Operations/IDaprTStringTransitionOperation.cs new file mode 100644 index 000000000..fb0c73848 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/IDaprTStringTransitionOperation.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations; + +/// +/// Identifies an operation that provides a transition from a generic type to a string. +/// +public interface IDaprTStringTransitionOperation : IDaprDataOperation +{ +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Compression/Gzip/DaprGzipCompressor.cs b/src/Dapr.Common/Data/Operations/Providers/Compression/GzipCompressor.cs similarity index 76% rename from src/Dapr.Common/Data/Operations/Providers/Compression/Gzip/DaprGzipCompressor.cs rename to src/Dapr.Common/Data/Operations/Providers/Compression/GzipCompressor.cs index cca523dd9..d065406d0 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Compression/Gzip/DaprGzipCompressor.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Compression/GzipCompressor.cs @@ -13,10 +13,10 @@ using System.IO.Compression; -namespace Dapr.Common.Data.Operations.Providers.Compression.Gzip; +namespace Dapr.Common.Data.Operations.Providers.Compression; /// -public class DaprGzipCompressor : IDaprDataCompressor +public sealed class GzipCompressor : IDaprDataCompressor { /// /// The name of the operation. @@ -29,7 +29,7 @@ public class DaprGzipCompressor : IDaprDataCompressor /// The input data. /// Cancellation token. /// The output data and metadata for the operation. - public async Task>> ExecuteAsync(ReadOnlyMemory input, CancellationToken cancellationToken = default) + public async Task>> ExecuteAsync(ReadOnlyMemory input, CancellationToken cancellationToken = default) { using var outputStream = new MemoryStream(); await using (var gzipStream = new GZipStream(outputStream, CompressionMode.Compress)) @@ -38,7 +38,7 @@ public async Task>> ExecuteAsync(R } //Replace the existing payload with the compressed payload - return new DaprDataOperationPayload>(outputStream.ToArray(), Name); + return new DaprOperationPayload>(outputStream.ToArray()); } /// @@ -47,12 +47,12 @@ public async Task>> ExecuteAsync(R /// The processed input data being reversed. /// Cancellation token. /// The reversed output data and metadata for the operation. - public async Task>> ReverseAsync(DaprDataOperationPayload> input, CancellationToken cancellationToken) + public async Task>> ReverseAsync(DaprOperationPayload> input, CancellationToken cancellationToken) { using var inputStream = new MemoryStream(input.Payload.ToArray()); await using var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress); using var outputStream = new MemoryStream(); await gzipStream.CopyToAsync(outputStream, cancellationToken); - return new DaprDataOperationPayload>(outputStream.ToArray()); + return new DaprOperationPayload>(outputStream.ToArray()); } } diff --git a/src/Dapr.Common/Data/Operations/Providers/Compression/IDaprDataCompressor.cs b/src/Dapr.Common/Data/Operations/Providers/Compression/IDaprDataCompressor.cs index 6206e79cb..bf1678e15 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Compression/IDaprDataCompressor.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Compression/IDaprDataCompressor.cs @@ -14,8 +14,8 @@ namespace Dapr.Common.Data.Operations.Providers.Compression; /// - /// Identifies an operation that provides a data compression capability. - /// -public interface IDaprDataCompressor : IDaprDataOperation, ReadOnlyMemory> +/// Identifies an operation that provides a data compression capability. +/// +public interface IDaprDataCompressor : IDaprByteBasedOperation { } diff --git a/src/Dapr.Common/Data/Operations/Providers/Encoding/IDaprDataEncoder.cs b/src/Dapr.Common/Data/Operations/Providers/Encoding/IDaprDataEncoder.cs new file mode 100644 index 000000000..f39fa6767 --- /dev/null +++ b/src/Dapr.Common/Data/Operations/Providers/Encoding/IDaprDataEncoder.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Operations.Providers.Encoding; + +/// +/// Identifies an operation that encodes strings into byte arrays. +/// +public interface IDaprDataEncoder : IDaprStringByteTransitionOperation +{ +} diff --git a/src/Dapr.Common/Data/Operations/Providers/Versioning/DaprVersioner.cs b/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs similarity index 51% rename from src/Dapr.Common/Data/Operations/Providers/Versioning/DaprVersioner.cs rename to src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs index 6af8607ee..3a0a36689 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Versioning/DaprVersioner.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs @@ -11,19 +11,17 @@ // limitations under the License. // ------------------------------------------------------------------------ -namespace Dapr.Common.Data.Operations.Providers.Versioning; +namespace Dapr.Common.Data.Operations.Providers.Encoding; /// -/// Default implementation of an for handling data versioning operations. +/// Responsible for encoding a string to a byte array. /// -public class DaprVersioner : IDaprDataVersioner +public sealed class Utf8Encoder : IDaprDataEncoder { - private readonly Dictionary> _upgraders = new(); - /// /// The name of the operation. /// - public string Name => "Dapr.Versioning.DaprVersioner"; + public string Name => "Dapr.Encoding.Utf8Encoder"; /// /// Executes the data processing operation. @@ -31,9 +29,13 @@ public class DaprVersioner : IDaprDataVersioner /// The input data. /// Cancellation token. /// The output data and metadata for the operation. - public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) + public Task>> ExecuteAsync(string? input, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + ArgumentNullException.ThrowIfNull(input, nameof(input)); + + var bytes = System.Text.Encoding.UTF8.GetBytes(input); + var result = new DaprOperationPayload>(bytes); + return Task.FromResult(result); } /// @@ -42,27 +44,10 @@ public Task> ExecuteAsync(string input, Cancell /// The processed input data being reversed. /// Cancellation token. /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) + public Task> ReverseAsync(DaprOperationPayload> input, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + var strValue = System.Text.Encoding.UTF8.GetString(input.Payload.Span); + var result = new DaprOperationPayload(strValue); + return Task.FromResult(result); } - - /// - /// The current version of the data. - /// - public int CurrentVersion { get; } - - - /// - /// Registers an upgrade function for a specific version. - /// - /// The version to upgrade from. - /// The function to upgrade the data. - /// The type of data to upgrade. - public void RegisterUpgrade(int fromVersion, Func upgradeFunc) - { - _upgraders[fromVersion] = upgradeFunc; - } - - private record VersionedData(int Version, T Data); } diff --git a/src/Dapr.Common/Data/Operations/Providers/Integrity/Checksum/DaprSha256Validator.cs b/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs similarity index 84% rename from src/Dapr.Common/Data/Operations/Providers/Integrity/Checksum/DaprSha256Validator.cs rename to src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs index 835877e24..acc4ecbfe 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Integrity/Checksum/DaprSha256Validator.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs @@ -13,12 +13,12 @@ using System.Security.Cryptography; -namespace Dapr.Common.Data.Operations.Providers.Integrity.Checksum; +namespace Dapr.Common.Data.Operations.Providers.Integrity; /// /// Provides a data integrity validation service using an SHA256 hash. /// -public class DaprSha256Validator : IDaprDataValidator +public class Sha256Validator : IDaprDataValidator { /// /// The name of the operation. @@ -31,10 +31,10 @@ public class DaprSha256Validator : IDaprDataValidator /// The input data. /// Cancellation token. /// The output data and metadata for the operation. - public async Task>> ExecuteAsync(ReadOnlyMemory input, CancellationToken cancellationToken = default) + public async Task>> ExecuteAsync(ReadOnlyMemory input, CancellationToken cancellationToken = default) { var checksum = await CalculateChecksumAsync(input, cancellationToken); - var result = new DaprDataOperationPayload>(input, Name); + var result = new DaprOperationPayload>(input); result.Metadata.Add(GetChecksumKey(), checksum); return result; } @@ -45,7 +45,7 @@ public async Task>> ExecuteAsync(R /// The processed input data being reversed. /// Cancellation token. /// The reversed output data and metadata for the operation. - public async Task>> ReverseAsync(DaprDataOperationPayload> input, CancellationToken cancellationToken) + public async Task>> ReverseAsync(DaprOperationPayload> input, CancellationToken cancellationToken) { var checksumKey = GetChecksumKey(); if (input.Metadata.TryGetValue(checksumKey, out var checksum)) @@ -58,7 +58,7 @@ public async Task>> ReverseAsync(D } //If there's no checksum metadata or it matches, just continue with the next operation - return new DaprDataOperationPayload>(input.Payload); + return new DaprOperationPayload>(input.Payload); } /// diff --git a/src/Dapr.Common/Data/Operations/Providers/Integrity/IDaprDataValidator.cs b/src/Dapr.Common/Data/Operations/Providers/Integrity/IDaprDataValidator.cs index 5e59f8614..d455643b2 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Integrity/IDaprDataValidator.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Integrity/IDaprDataValidator.cs @@ -16,6 +16,6 @@ namespace Dapr.Common.Data.Operations.Providers.Integrity; /// /// Identifies an operation that provides data integrity validation. /// -public interface IDaprDataValidator : IDaprDataOperation, ReadOnlyMemory> +public interface IDaprDataValidator : IDaprByteBasedOperation { } diff --git a/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs b/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs index bef6366c2..a8b093494 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs @@ -11,19 +11,11 @@ // limitations under the License. // ------------------------------------------------------------------------ -using System.Text.RegularExpressions; - namespace Dapr.Common.Data.Operations.Providers.Masking; /// /// Identifies an operation that provides a data masking capability. /// -public interface IDaprDataMasker : IDaprDataOperation +public interface IDaprDataMasker : IDaprStringBasedOperation { - /// - /// Registers a pattern to match against. - /// - /// The regular expression to match to. - /// The string to place the matching value with. - void RegisterMatch(Regex pattern, string replacement); } diff --git a/src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs b/src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs similarity index 83% rename from src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs rename to src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs index 44799bc3e..1bbb3eb69 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Masking/DaprRegularExpressionMasker.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs @@ -18,7 +18,7 @@ namespace Dapr.Common.Data.Operations.Providers.Masking; /// /// Performs a masking operation on the provided input. /// -public class DaprRegularExpressionMasker : IDaprDataMasker +public class RegularExpressionMasker : IDaprDataMasker { private readonly Dictionary patterns = new(); @@ -33,9 +33,12 @@ public class DaprRegularExpressionMasker : IDaprDataMasker /// The input data. /// Cancellation token. /// The output data and metadata for the operation. - public Task> ExecuteAsync(string input, + public Task> ExecuteAsync(string? input, CancellationToken cancellationToken = default) { + if (input is null) + return Task.FromResult(new DaprOperationPayload(null)); + var updatedValue = input; foreach (var pattern in patterns) { @@ -43,7 +46,7 @@ public Task> ExecuteAsync(string input, updatedValue = pattern.Key.Replace(input, pattern.Value); } - var payloadResult = new DaprDataOperationPayload(updatedValue, Name); + var payloadResult = new DaprOperationPayload(updatedValue); return Task.FromResult(payloadResult); } @@ -53,9 +56,9 @@ public Task> ExecuteAsync(string input, /// The processed input data being reversed. /// Cancellation token. /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, + public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken = default) => - Task.FromResult(new DaprDataOperationPayload(input.Payload)); + Task.FromResult(new DaprOperationPayload(input.Payload)); /// /// Registers a pattern to match against. diff --git a/src/Dapr.Common/Data/Operations/Providers/Serialization/IDaprDataSerializer.cs b/src/Dapr.Common/Data/Operations/Providers/Serialization/IDaprDataSerializer.cs index b2e0f143a..2f3490fc0 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Serialization/IDaprDataSerializer.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Serialization/IDaprDataSerializer.cs @@ -16,6 +16,6 @@ namespace Dapr.Common.Data.Operations.Providers.Serialization; /// /// Identifies an operation that provides a data serialization capability. /// -public interface IDaprDataSerializer : IDaprDataOperation +public interface IDaprDataSerializer : IDaprTStringTransitionOperation { } diff --git a/src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs b/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs similarity index 83% rename from src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs rename to src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs index 678d1a595..3905ed9c2 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Serialization/DaprSystemTextJsonSerializer.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs @@ -18,7 +18,7 @@ namespace Dapr.Common.Data.Operations.Providers.Serialization; /// /// Provides serialization capabilities using System.Text.Json. /// -public class DaprSystemTextJsonSerializer : IDaprDataSerializer +public sealed class SystemTextJsonSerializer : IDaprDataSerializer { /// /// Optionally provided . @@ -36,10 +36,10 @@ public class DaprSystemTextJsonSerializer : IDaprDataSerializer /// The input data. /// Cancellation token. /// The output data and metadata for the operation. - public Task> ExecuteAsync(T input, CancellationToken cancellationToken = default) + public Task> ExecuteAsync(T? input, CancellationToken cancellationToken = default) { var jsonResult = JsonSerializer.Serialize(input, options); - var result = new DaprDataOperationPayload(jsonResult, Name); + var result = new DaprOperationPayload(jsonResult); return Task.FromResult(result); } @@ -50,11 +50,13 @@ public Task> ExecuteAsync(T input, Cancellation /// The processed input data being reversed. /// Cancellation token. /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, + public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken = default) { + ArgumentNullException.ThrowIfNull(input.Payload, nameof(input)); + var value = JsonSerializer.Deserialize(input.Payload, options); - var result = new DaprDataOperationPayload(value); + var result = new DaprOperationPayload(value); return Task.FromResult(result); } diff --git a/test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs b/test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs index 54f22fae2..0b9d73b26 100644 --- a/test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs +++ b/test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs @@ -34,9 +34,9 @@ private class InvalidOperation private class MockOperation1 : IDaprDataOperation { - public Task> ExecuteAsync(string input, CancellationToken cancellationToken) + public Task> ExecuteAsync(string input, CancellationToken cancellationToken) { - var result = new DaprDataOperationPayload($"{input}-processed1") + var result = new DaprOperationPayload($"{input}-processed1") { Metadata = new Dictionary { { "Operation1", "Processed" } } }; @@ -49,10 +49,10 @@ public Task> ExecuteAsync(string input, Cancell /// The processed input data being reversed. /// Cancellation token. /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, + public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) { - var result = new DaprDataOperationPayload(input.Payload.Replace("-processed1", "")) + var result = new DaprOperationPayload(input.Payload.Replace("-processed1", "")) { Metadata = new Dictionary { { "Operation1", "Reversed" } } }; @@ -78,10 +78,10 @@ private class MockOperation2 : IDaprDataOperation /// The input data. /// Cancellation token. /// The output data and metadata for the operation. - public Task> ExecuteAsync(string input, + public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) { - var result = new DaprDataOperationPayload($"{input}-processed2") + var result = new DaprOperationPayload($"{input}-processed2") { Metadata = new Dictionary { { "Operation2", "Processed" } } }; @@ -94,10 +94,10 @@ public Task> ExecuteAsync(string input, /// The processed input data being reversed. /// Cancellation token. /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, + public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) { - var result = new DaprDataOperationPayload(input.Payload.Replace("-processed2", "")) + var result = new DaprOperationPayload(input.Payload.Replace("-processed2", "")) { Metadata = new Dictionary { { "Operation2", "Reversed" } } }; diff --git a/test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs b/test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs new file mode 100644 index 000000000..aaab5e3c8 --- /dev/null +++ b/test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs @@ -0,0 +1,121 @@ +// using System.Collections.Generic; +// using System.Threading; +// using System.Threading.Tasks; +// using Dapr.Common.Data; +// using Dapr.Common.Data.Operations; +// using Xunit; +// +// namespace Dapr.Common.Test.Data; +// +// public class DaprDataPipelineTests +// { +// [Fact] +// public async Task ProcessAsync_ShouldProcessOperationsInOrder() +// { +// // Arrange +// var operations = new List +// { +// new MockOperation1(), +// new MockOperation2() +// }; +// var pipeline = new DaprDataPipeline<>(operations); +// +// // Act +// var result = await pipeline.ProcessAsync("input"); +// +// // Assert +// Assert.Equal("input-processed1-processed2", result.Payload); +// Assert.Contains("Operation1", result.Metadata); +// Assert.Contains("Operation2", result.Metadata); +// } +// +// [Fact] +// public async Task ReverseAsync_ShouldReverseOperationsInOrder() +// { +// // Arrange +// var operations = new List +// { +// new MockOperation1(), +// new MockOperation2() +// }; +// var pipeline = new DaprDataPipeline<>(operations); +// +// // Act +// var result = await pipeline.ReverseProcessAsync("input-processed1-processed2"); +// +// // Assert +// Assert.Equal("input", result.Payload); +// Assert.Contains("Operation1", result.Metadata); +// Assert.Contains("Operation2", result.Metadata); +// } +// +// private class MockOperation1 : IDaprDataOperation +// { +// public Task> ExecuteAsync(string input, CancellationToken cancellationToken) +// { +// var result = new DaprOperationPayload($"{input}-processed1") +// { +// Metadata = new Dictionary { { "Operation1", "Processed" } } +// }; +// return Task.FromResult(result); +// } +// +// /// +// /// Reverses the data operation. +// /// +// /// The processed input data being reversed. +// /// Cancellation token. +// /// The reversed output data and metadata for the operation. +// public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) +// { +// var result = new DaprOperationPayload(input.Payload.Replace("-processed1", "")) +// { +// Metadata = new Dictionary { { "Operation1", "Reversed" } } +// }; +// return Task.FromResult(result); +// } +// +// /// +// /// The name of the operation. +// /// +// public string Name => "Test"; +// } +// +// private class MockOperation2 : IDaprDataOperation +// { +// /// +// /// The name of the operation. +// /// +// public string Name => "Test"; +// +// /// +// /// Executes the data processing operation. +// /// +// /// The input data. +// /// Cancellation token. +// /// The output data and metadata for the operation. +// public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) +// { +// var result = new DaprOperationPayload($"{input}-processed2") +// { +// Metadata = new Dictionary { { "Operation2", "Processed" } } +// }; +// return Task.FromResult(result); +// } +// +// /// +// /// Reverses the data operation. +// /// +// /// The processed input data being reversed. +// /// Cancellation token. +// /// The reversed output data and metadata for the operation. +// public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) +// { +// var result = new DaprOperationPayload(input.Payload.Replace("-processed2", "")) +// { +// Metadata = new Dictionary { { "Operation2", "Reversed" } } +// }; +// return Task.FromResult(result); +// } +// } +// } diff --git a/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs index 1021bd76d..bab6e0e6f 100644 --- a/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs +++ b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs @@ -1,106 +1,108 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Dapr.Common.Data; -using Dapr.Common.Data.Attributes; -using Dapr.Common.Data.Operations; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Dapr.Common.Test.Data; - -public class DataPipelineFactoryTests -{ - [Fact] - public void CreatePipeline_ShouldCreatePipelineWithCorrectOperations() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - var serviceProvider = services.BuildServiceProvider(); - var factory = serviceProvider.GetRequiredService(); - - // Act - var pipeline = factory.CreatePipeline(); - - // Assert - Assert.NotNull(pipeline); - } - - [DataOperation(typeof(MockOperation1), typeof(MockOperation2))] - private class TestDataProcessor - { - } - -private class MockOperation1 : IDaprDataOperation - { - public Task> ExecuteAsync(string input, CancellationToken cancellationToken) - { - var result = new DaprDataOperationPayload($"{input}-processed1") - { - Metadata = new Dictionary { { "Operation1", "Processed" } } - }; - return Task.FromResult(result); - } - - /// - /// Reverses the data operation. - /// - /// The processed input data being reversed. - /// Cancellation token. - /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) - { - var result = new DaprDataOperationPayload(input.Payload.Replace("-processed1", "")) - { - Metadata = new Dictionary { { "Operation1", "Reversed" } } - }; - return Task.FromResult(result); - } - - /// - /// The name of the operation. - /// - public string Name => "Test"; - } - - private class MockOperation2 : IDaprDataOperation - { - /// - /// The name of the operation. - /// - public string Name => "Test"; - - /// - /// Executes the data processing operation. - /// - /// The input data. - /// Cancellation token. - /// The output data and metadata for the operation. - public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) - { - var result = new DaprDataOperationPayload($"{input}-processed2") - { - Metadata = new Dictionary { { "Operation2", "Processed" } } - }; - return Task.FromResult(result); - } - - /// - /// Reverses the data operation. - /// - /// The processed input data being reversed. - /// Cancellation token. - /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) - { - var result = new DaprDataOperationPayload(input.Payload.Replace("-processed2", "")) - { - Metadata = new Dictionary { { "Operation2", "Reversed" } } - }; - return Task.FromResult(result); - } - } -} +// using System.Collections.Generic; +// using System.Threading; +// using System.Threading.Tasks; +// using Dapr.Common.Data; +// using Dapr.Common.Data.Attributes; +// using Dapr.Common.Data.Operations; +// using Microsoft.Extensions.DependencyInjection; +// using Xunit; +// +// namespace Dapr.Common.Test.Data; +// +// public class DataPipelineFactoryTests +// { +// [Fact] +// public void CreatePipeline_ShouldCreatePipelineWithCorrectOperations() +// { +// // Arrange +// var services = new ServiceCollection(); +// +// +// services.AddSingleton(); +// services.AddSingleton(); +// services.AddSingleton(); +// var serviceProvider = services.BuildServiceProvider(); +// var factory = serviceProvider.GetRequiredService(); +// +// // Act +// var pipeline = factory.CreatePipeline(); +// +// // Assert +// Assert.NotNull(pipeline); +// } +// +// [DataOperation(typeof(MockOperation1), typeof(MockOperation2))] +// private class TestDataProcessor +// { +// } +// +// private class MockOperation1 : IDaprDataOperation +// { +// public Task> ExecuteAsync(string input, CancellationToken cancellationToken) +// { +// var result = new DaprOperationPayload($"{input}-processed1") +// { +// Metadata = new Dictionary { { "Operation1", "Processed" } } +// }; +// return Task.FromResult(result); +// } +// +// /// +// /// Reverses the data operation. +// /// +// /// The processed input data being reversed. +// /// Cancellation token. +// /// The reversed output data and metadata for the operation. +// public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) +// { +// var result = new DaprOperationPayload(input.Payload.Replace("-processed1", "")) +// { +// Metadata = new Dictionary { { "Operation1", "Reversed" } } +// }; +// return Task.FromResult(result); +// } +// +// /// +// /// The name of the operation. +// /// +// public string Name => "Test"; +// } +// +// private class MockOperation2 : IDaprDataOperation +// { +// /// +// /// The name of the operation. +// /// +// public string Name => "Test"; +// +// /// +// /// Executes the data processing operation. +// /// +// /// The input data. +// /// Cancellation token. +// /// The output data and metadata for the operation. +// public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) +// { +// var result = new DaprOperationPayload($"{input}-processed2") +// { +// Metadata = new Dictionary { { "Operation2", "Processed" } } +// }; +// return Task.FromResult(result); +// } +// +// /// +// /// Reverses the data operation. +// /// +// /// The processed input data being reversed. +// /// Cancellation token. +// /// The reversed output data and metadata for the operation. +// public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) +// { +// var result = new DaprOperationPayload(input.Payload.Replace("-processed2", "")) +// { +// Metadata = new Dictionary { { "Operation2", "Reversed" } } +// }; +// return Task.FromResult(result); +// } +// } +// } diff --git a/test/Dapr.Common.Test/Data/DataPipelineTests.cs b/test/Dapr.Common.Test/Data/DataPipelineTests.cs deleted file mode 100644 index 241a52e4d..000000000 --- a/test/Dapr.Common.Test/Data/DataPipelineTests.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Dapr.Common.Data; -using Dapr.Common.Data.Operations; -using Xunit; - -namespace Dapr.Common.Test.Data; - -public class DataPipelineTests -{ - [Fact] - public async Task ProcessAsync_ShouldProcessOperationsInOrder() - { - // Arrange - var operations = new List - { - new MockOperation1(), - new MockOperation2() - }; - var pipeline = new DataPipeline(operations); - - // Act - var result = await pipeline.ProcessAsync("input"); - - // Assert - Assert.Equal("input-processed1-processed2", result.Payload); - Assert.Contains("Operation1", result.Metadata); - Assert.Contains("Operation2", result.Metadata); - } - - [Fact] - public async Task ReverseAsync_ShouldReverseOperationsInOrder() - { - // Arrange - var operations = new List - { - new MockOperation1(), - new MockOperation2() - }; - var pipeline = new DataPipeline(operations); - - // Act - var result = await pipeline.ReverseProcessAsync("input-processed1-processed2"); - - // Assert - Assert.Equal("input", result.Payload); - Assert.Contains("Operation1", result.Metadata); - Assert.Contains("Operation2", result.Metadata); - } - - private class MockOperation1 : IDaprDataOperation - { - public Task> ExecuteAsync(string input, CancellationToken cancellationToken) - { - var result = new DaprDataOperationPayload($"{input}-processed1") - { - Metadata = new Dictionary { { "Operation1", "Processed" } } - }; - return Task.FromResult(result); - } - - /// - /// Reverses the data operation. - /// - /// The processed input data being reversed. - /// Cancellation token. - /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) - { - var result = new DaprDataOperationPayload(input.Payload.Replace("-processed1", "")) - { - Metadata = new Dictionary { { "Operation1", "Reversed" } } - }; - return Task.FromResult(result); - } - - /// - /// The name of the operation. - /// - public string Name => "Test"; - } - - private class MockOperation2 : IDaprDataOperation - { - /// - /// The name of the operation. - /// - public string Name => "Test"; - - /// - /// Executes the data processing operation. - /// - /// The input data. - /// Cancellation token. - /// The output data and metadata for the operation. - public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) - { - var result = new DaprDataOperationPayload($"{input}-processed2") - { - Metadata = new Dictionary { { "Operation2", "Processed" } } - }; - return Task.FromResult(result); - } - - /// - /// Reverses the data operation. - /// - /// The processed input data being reversed. - /// Cancellation token. - /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprDataOperationPayload input, CancellationToken cancellationToken) - { - var result = new DaprDataOperationPayload(input.Payload.Replace("-processed2", "")) - { - Metadata = new Dictionary { { "Operation2", "Reversed" } } - }; - return Task.FromResult(result); - } - } -} diff --git a/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs b/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs index 6404a0560..f9b2d9acc 100644 --- a/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs +++ b/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs @@ -1,5 +1,9 @@ using Dapr.Common.Data.Extensions; using Dapr.Common.Data.Operations; +using Dapr.Common.Data.Operations.Providers.Compression; +using Dapr.Common.Data.Operations.Providers.Integrity; +using Dapr.Common.Data.Operations.Providers.Masking; +using Dapr.Common.Data.Operations.Providers.Serialization; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -23,58 +27,178 @@ public void AddDaprDataProcessingPipeline_ShouldReturnDaprDataProcessingPipeline } [Fact] - public void WithDaprOperation_ShouldRegisterSingletonService() + public void WithSerializer_ShouldRegisterSerializationService() { // Arrange var services = new ServiceCollection(); var builder = new DaprDataProcessingPipelineBuilder(services); // Act - builder.WithDaprOperation(ServiceLifetime.Singleton); + builder.WithSerializer>(); // Assert var serviceProvider = services.BuildServiceProvider(); - var service = serviceProvider.GetService(); + var service = serviceProvider.GetService(); Assert.NotNull(service); + Assert.True(service is SystemTextJsonSerializer); } [Fact] - public void WithDaprOperation_ShouldRegisterScopedService() + public void WithSerializer_ShouldRegisterSerializationFactory() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithSerializer(_ => new SystemTextJsonSerializer()); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var service = serviceProvider.GetService(); + Assert.NotNull(service); + Assert.True(service is SystemTextJsonSerializer); + } + + [Fact] + public void WithCompressor_ShouldRegisterType() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithCompressor(); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var service = serviceProvider.GetService(); + Assert.NotNull(service); + Assert.True(service is GzipCompressor); + } + + [Fact] + public void WithCompressor_ShouldRegisterFactory() { // Arrange var services = new ServiceCollection(); var builder = new DaprDataProcessingPipelineBuilder(services); // Act - builder.WithDaprOperation(ServiceLifetime.Scoped); + builder.WithCompressor(_ => new GzipCompressor()); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var service = serviceProvider.GetService(); + Assert.NotNull(service); + Assert.True(service is GzipCompressor); + } + [Fact] + public void WithIntegrity_ShouldRegisterType() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithIntegrity(); + // Assert var serviceProvider = services.BuildServiceProvider(); - using (var scope = serviceProvider.CreateScope()) - { - var service = scope.ServiceProvider.GetService(); - Assert.NotNull(service); - } + var service = serviceProvider.GetService(); + Assert.NotNull(service); + Assert.True(service is Sha256Validator); } [Fact] - public void WithDaprOperation_ShouldRegisterTransientService() + public void WithIntegrity_ShouldRegisterFactory() { // Arrange var services = new ServiceCollection(); var builder = new DaprDataProcessingPipelineBuilder(services); // Act - builder.WithDaprOperation(ServiceLifetime.Transient); + builder.WithIntegrity(_ => new Sha256Validator()); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var service = serviceProvider.GetService(); + Assert.NotNull(service); + Assert.True(service is Sha256Validator); + } + [Fact] + public void WithMasking_ShouldRegisterType() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithMasking(); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var service = serviceProvider.GetService(); + Assert.NotNull(service); + Assert.True(service is RegularExpressionMasker); + } + + [Fact] + public void WithMasking_ShouldRegisterFactory() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithMasking(_ => new RegularExpressionMasker()); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var service = serviceProvider.GetService(); + Assert.NotNull(service); + Assert.True(service is RegularExpressionMasker); + } + + [Fact] + public void WithDaprOperation_ShouldRegisterScopedService() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithSerializer>(ServiceLifetime.Scoped); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + using var scope = serviceProvider.CreateScope(); + var service = scope.ServiceProvider.GetService(); + Assert.NotNull(service); + } + + [Fact] + public void WithDaprOperation_ShouldRegisterTransientService() + { + // Arrange + var services = new ServiceCollection(); + var builder = new DaprDataProcessingPipelineBuilder(services); + + // Act + builder.WithSerializer(_ => new SystemTextJsonSerializer(), ServiceLifetime.Transient); + // Assert var serviceProvider = services.BuildServiceProvider(); - var service1 = serviceProvider.GetService(); - var service2 = serviceProvider.GetService(); + var service1 = serviceProvider.GetService(); + var service2 = serviceProvider.GetService(); Assert.NotNull(service1); Assert.NotNull(service2); Assert.NotSame(service1, service2); } + + private record SampleRecord(string Name, int Count); private class MockOperation : IDaprDataOperation { diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs index 6c0231e9f..76ecc9868 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs @@ -1,4 +1,4 @@ -using Dapr.Common.Data.Operations.Providers.Compression.Gzip; +using Dapr.Common.Data.Operations.Providers.Compression; namespace Dapr.Common.Test.Data.Operators.Providers.Compression; @@ -7,13 +7,13 @@ namespace Dapr.Common.Test.Data.Operators.Providers.Compression; using System.Threading.Tasks; using Xunit; -public class DaprGzipCompressorTests +public class GzipCompressorTests { [Fact] public async Task ExecuteAsync_ShouldCompressData() { // Arrange - var compressor = new DaprGzipCompressor(); + var compressor = new GzipCompressor(); var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); // Act @@ -22,15 +22,13 @@ public async Task ExecuteAsync_ShouldCompressData() // Assert Assert.NotNull(result); Assert.NotEqual(input, result.Payload); - Assert.True(result.Metadata.ContainsKey("Ops")); - Assert.Equal(compressor.Name, result.Metadata["Ops"]); } [Fact] public async Task ReverseAsync_ShouldDecompressData() { // Arrange - var compressor = new DaprGzipCompressor(); + var compressor = new GzipCompressor(); var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); var compressedResult = await compressor.ExecuteAsync(input); diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs index d3f36e856..9dc1be357 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs @@ -2,18 +2,18 @@ using System.Threading; using System.Threading.Tasks; using Dapr.Common.Data.Operations; -using Dapr.Common.Data.Operations.Providers.Integrity.Checksum; +using Dapr.Common.Data.Operations.Providers.Integrity; using Xunit; namespace Dapr.Common.Test.Data.Operators.Providers.Integrity; -public class DaprSha256ValidatorTests +public class Sha256ValidatorTests { [Fact] public async Task ExecuteAsync_ShouldCalculateChecksum() { // Arrange - var validator = new DaprSha256Validator(); + var validator = new Sha256Validator(); var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); // Act @@ -22,17 +22,15 @@ public async Task ExecuteAsync_ShouldCalculateChecksum() // Assert Assert.NotNull(result); Assert.True(result.Metadata.ContainsKey($"{validator.Name}-hash")); - Assert.True(result.Metadata.ContainsKey("Ops")); - Assert.Equal(validator.Name, result.Metadata["Ops"]); } [Fact] public async Task ReverseAsync_ShouldValidateChecksumWithoutMetadataHeader() { // Arrange - var validator = new DaprSha256Validator(); + var validator = new Sha256Validator(); var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); - var result = new DaprDataOperationPayload>(input); + var result = new DaprOperationPayload>(input); await validator.ReverseAsync(result, CancellationToken.None); } @@ -41,7 +39,7 @@ public async Task ReverseAsync_ShouldValidateChecksumWithoutMetadataHeader() public async Task ReverseAsync_ShouldValidateChecksum() { // Arrange - var validator = new DaprSha256Validator(); + var validator = new Sha256Validator(); var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); var result = await validator.ExecuteAsync(input); @@ -53,7 +51,7 @@ public async Task ReverseAsync_ShouldValidateChecksum() public async Task ReverseAsync_ShouldThrowExceptionForInvalidChecksum() { // Arrange - var validator = new DaprSha256Validator(); + var validator = new Sha256Validator(); var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); var result = await validator.ExecuteAsync(input); result = result with { Payload = new ReadOnlyMemory(new byte[] { 6, 7, 8, 9, 0 }) }; diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs index 6e3e4d6c7..e555903a6 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs @@ -13,7 +13,7 @@ public class DaprRegularExpressionMapperTest public async Task ExecuteAsync_ShouldRunMapperCorrectly() { //Arrange - var mapper = new DaprRegularExpressionMasker(); + var mapper = new RegularExpressionMasker(); const string input = "This is not a real social security number: 012-34-5678"; mapper.RegisterMatch(new Regex(@"\d{3}-\d{2}-\d{4}"), "***-**-****"); @@ -22,15 +22,13 @@ public async Task ExecuteAsync_ShouldRunMapperCorrectly() //Assert Assert.Equal("This is not a real social security number: ***-**-****", result.Payload); - Assert.True(result.Metadata.ContainsKey("Ops")); - Assert.Equal(mapper.Name, result.Metadata["Ops"]); } [Fact] public async Task ExecuteAsync_ThrowIsCancellationTokenCancelled() { //Arrange - var mapper = new DaprRegularExpressionMasker(); + var mapper = new RegularExpressionMasker(); const string input = "This is not a real social security number: 012-34-5678"; mapper.RegisterMatch(new Regex(@"\d{3}-\d{2}-\d{4}"), "***-**-****"); @@ -46,7 +44,7 @@ public async Task ExecuteAsync_ThrowIsCancellationTokenCancelled() public async Task ReverseAsync_ShouldNotDoAnythingToPayload() { //Arrange - var mapper = new DaprRegularExpressionMasker(); + var mapper = new RegularExpressionMasker(); const string input = "This is a date: 2/18/2026"; mapper.RegisterMatch(new Regex(@"\d{3}-\d{2}-\d{4}"), "***-**-****"); diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/DaprSystemTextJsonSerializerTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs similarity index 73% rename from test/Dapr.Common.Test/Data/Operators/Providers/Serialization/DaprSystemTextJsonSerializerTest.cs rename to test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs index ced74793d..4fb925685 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/DaprSystemTextJsonSerializerTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs @@ -5,13 +5,13 @@ namespace Dapr.Common.Test.Data.Operators.Providers.Serialization; -public class DaprSystemTextJsonSerializerTest +public class SystemTextJsonSerializerTest { [Fact] public async Task ExecuteAsync_ShouldSerialize() { //Arrange - var validator = new DaprSystemTextJsonSerializer(); + var validator = new SystemTextJsonSerializer(); var input = new TestObject("Test", 15); //Act @@ -20,17 +20,15 @@ public async Task ExecuteAsync_ShouldSerialize() //Assert Assert.NotNull(result); Assert.Equal("{\"name\":\"Test\",\"count\":15}", result.Payload); - Assert.True(result.Metadata.ContainsKey("Ops")); - Assert.Equal(validator.Name, result.Metadata["Ops"]); } [Fact] public async Task ReverseAsync_ShouldDeserialize() { //Arrange - var validator = new DaprSystemTextJsonSerializer(); + var validator = new SystemTextJsonSerializer(); const string input = "{\"name\":\"Test\",\"count\":15}"; - var payload = new DaprDataOperationPayload(input); + var payload = new DaprOperationPayload(input); //Act var result = await validator.ReverseAsync(payload); From 701f3260ba971274357903cc8c33329e7a3da160 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 22 Oct 2024 14:24:40 -0500 Subject: [PATCH 05/12] Shortened operator names, made more consistent Signed-off-by: Whit Waldo --- .../Data/Operations/Providers/Encoding/Utf8Encoder.cs | 2 +- .../Data/Operations/Providers/Integrity/DaprSha256Validator.cs | 2 +- .../Operations/Providers/Masking/RegularExpressionMasker.cs | 2 +- .../Providers/Serialization/SystemTextJsonSerializer.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs b/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs index 3a0a36689..758ab7997 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs @@ -21,7 +21,7 @@ public sealed class Utf8Encoder : IDaprDataEncoder /// /// The name of the operation. /// - public string Name => "Dapr.Encoding.Utf8Encoder"; + public string Name => "Dapr.Encoding.Utf8"; /// /// Executes the data processing operation. diff --git a/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs b/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs index acc4ecbfe..cd22e3331 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs @@ -23,7 +23,7 @@ public class Sha256Validator : IDaprDataValidator /// /// The name of the operation. /// - public string Name => "Dapr.Integrity.Checksum"; + public string Name => "Dapr.Integrity.Sha256"; /// /// Executes the data processing operation. diff --git a/src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs b/src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs index 1bbb3eb69..2a8041c12 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs @@ -25,7 +25,7 @@ public class RegularExpressionMasker : IDaprDataMasker /// /// The name of the operation. /// - public string Name => "Dapr.Masking.RegularExpression"; + public string Name => "Dapr.Masking.Regexp"; /// /// Executes the data processing operation. diff --git a/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs b/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs index 3905ed9c2..070fcbce7 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs @@ -28,7 +28,7 @@ public sealed class SystemTextJsonSerializer : IDaprDataSerializer /// /// The name of the operation. /// - public string Name => "Dapr.Serialization.SystemTextJsonSerializer"; + public string Name => "Dapr.Serialization.SystemTextJson"; /// /// Executes the data processing operation. From 7f558a60c30eb42fb0bb49466450d43e2f446850 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 22 Oct 2024 20:08:29 -0500 Subject: [PATCH 06/12] Fixed unit tests, removed mapping operations Signed-off-by: Whit Waldo --- ...nAttribute.cs => DataPipelineAttribute.cs} | 6 +- src/Dapr.Common/Data/DataPipelineFactory.cs | 11 +- .../Extensions/DaprDataPipelineBuilder.cs | 16 +- ...taPipelineRegistrationBuilderExtensions.cs | 54 ++-- .../Providers/Masking/IDaprDataMasker.cs | 21 -- .../Masking/RegularExpressionMasker.cs | 72 ----- test/Dapr.Common.Test/DaprDefaultTest.cs | 15 +- ...Tests.cs => DataPipelineAttributeTests.cs} | 21 +- .../Data/DaprDataPipelineTests.cs | 226 ++++++++-------- .../Data/DataPipelineFactoryTests.cs | 249 ++++++++++-------- ...taPipelineRegistrationBuilderExtensions.cs | 95 +++---- .../Compression/GzipCompressorTest.cs | 15 +- .../Providers/Encoding/Utf8EncoderTest.cs | 41 +++ .../Integrity/DaprSha256ValidatorTest.cs | 15 +- .../DaprRegularExpressionMapperTest.cs | 58 ---- .../SystemTextJsonSerializerTest.cs | 15 +- 16 files changed, 433 insertions(+), 497 deletions(-) rename src/Dapr.Common/Data/Attributes/{DataOperationAttribute.cs => DataPipelineAttribute.cs} (90%) delete mode 100644 src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs delete mode 100644 src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs rename test/Dapr.Common.Test/Data/Attributes/{DataOperationAttributeTests.cs => DataPipelineAttributeTests.cs} (78%) create mode 100644 test/Dapr.Common.Test/Data/Operators/Providers/Encoding/Utf8EncoderTest.cs delete mode 100644 test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs diff --git a/src/Dapr.Common/Data/Attributes/DataOperationAttribute.cs b/src/Dapr.Common/Data/Attributes/DataPipelineAttribute.cs similarity index 90% rename from src/Dapr.Common/Data/Attributes/DataOperationAttribute.cs rename to src/Dapr.Common/Data/Attributes/DataPipelineAttribute.cs index 432997d4a..1fa54b8bd 100644 --- a/src/Dapr.Common/Data/Attributes/DataOperationAttribute.cs +++ b/src/Dapr.Common/Data/Attributes/DataPipelineAttribute.cs @@ -19,7 +19,7 @@ namespace Dapr.Common.Data.Attributes; /// Attribute-based approach for indicating which data operations should be performed on a type and in what order. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] -public sealed class DataOperationAttribute : Attribute +public sealed class DataPipelineAttribute : Attribute { /// /// Contains the various data operation types available and the order in which to apply them. @@ -27,11 +27,11 @@ public sealed class DataOperationAttribute : Attribute public readonly IReadOnlyList DataOperationTypes; /// - /// Initializes a new . + /// Initializes a new . /// /// /// - public DataOperationAttribute(params Type[] dataOperationTypes) + public DataPipelineAttribute(params Type[] dataOperationTypes) { var registeredTypes = new List(); diff --git a/src/Dapr.Common/Data/DataPipelineFactory.cs b/src/Dapr.Common/Data/DataPipelineFactory.cs index 2d8a5697a..7ba0ba3f2 100644 --- a/src/Dapr.Common/Data/DataPipelineFactory.cs +++ b/src/Dapr.Common/Data/DataPipelineFactory.cs @@ -20,7 +20,7 @@ namespace Dapr.Common.Data; /// /// Used to create a data pipeline specific to a given type using the ordered operation services indicated in the -/// attribute on that type. +/// attribute on that type. /// internal sealed class DataPipelineFactory { @@ -44,18 +44,17 @@ public DataPipelineFactory(IServiceProvider serviceProvider) /// public DaprDataPipeline CreateEncodingPipeline() { - var attribute = typeof(T).GetCustomAttribute(); + var attribute = typeof(T).GetCustomAttribute(); if (attribute == null) { return new DaprDataPipeline(new List>()); } + var allRegisteredOperations = serviceProvider.GetServices().ToList(); var operations = attribute.DataOperationTypes - .Select(type => serviceProvider.GetRequiredService(type) as IDaprDataOperation) - .Where(op => op is not null) - .Cast() + .SelectMany(type => allRegisteredOperations.Where(op => op.GetType() == type)) .ToList(); - + return new DaprDataPipeline(operations); } diff --git a/src/Dapr.Common/Data/Extensions/DaprDataPipelineBuilder.cs b/src/Dapr.Common/Data/Extensions/DaprDataPipelineBuilder.cs index bbb2a7bd0..c310910eb 100644 --- a/src/Dapr.Common/Data/Extensions/DaprDataPipelineBuilder.cs +++ b/src/Dapr.Common/Data/Extensions/DaprDataPipelineBuilder.cs @@ -11,7 +11,6 @@ // limitations under the License. // ------------------------------------------------------------------------ -using Dapr.Common.Data.Operations; using Microsoft.Extensions.DependencyInjection; namespace Dapr.Common.Data.Extensions; @@ -19,7 +18,7 @@ namespace Dapr.Common.Data.Extensions; /// /// Used by the fluent registration builder to configure a Dapr data pipeline. /// -public class DaprDataPipelineBuilder : IDaprDataPipelineBuilder +public sealed class DaprDataPipelineBuilder : IDaprDataPipelineBuilder { /// /// The registered services on the builder. @@ -34,16 +33,3 @@ public DaprDataPipelineBuilder(IServiceCollection services) Services = services; } } - -/// -/// Used to perform fluent registrations on the Dapr data processing pipeline. -/// -public class DaprDataProcessingPipelineBuilder : DaprDataPipelineBuilder, IDaprDataProcessingBuilder -{ - /// - /// Used to initialize a new . - /// - public DaprDataProcessingPipelineBuilder(IServiceCollection services) : base(services) - { - } -} diff --git a/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs b/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs index 9ea199190..f42d60fa1 100644 --- a/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs +++ b/src/Dapr.Common/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs @@ -13,8 +13,8 @@ using Dapr.Common.Data.Operations; using Dapr.Common.Data.Operations.Providers.Compression; +using Dapr.Common.Data.Operations.Providers.Encoding; using Dapr.Common.Data.Operations.Providers.Integrity; -using Dapr.Common.Data.Operations.Providers.Masking; using Dapr.Common.Data.Operations.Providers.Serialization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -29,17 +29,18 @@ public static class DaprDataPipelineRegistrationBuilderExtensions /// /// Registers a Dapr data processing pipeline. /// - /// + /// /// - public static IDaprDataProcessingBuilder AddDaprDataProcessingPipeline(this IDaprServiceBuilder builder) + public static IDaprDataPipelineBuilder AddDaprDataProcessingPipeline(this IServiceCollection services) { - return new DaprDataProcessingPipelineBuilder(builder.Services); + services.AddSingleton(); + return new DaprDataPipelineBuilder(services); } /// /// Adds a serializer data operation. /// - public static IDaprDataProcessingBuilder WithSerializer(this IDaprDataProcessingBuilder builder, + public static IDaprDataPipelineBuilder WithSerializer(this IDaprDataPipelineBuilder builder, ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : class, IDaprDataOperation => builder.WithDaprOperation(lifetime); @@ -47,7 +48,7 @@ public static IDaprDataProcessingBuilder WithSerializer(this IDaprData /// /// Adds a serializer data operation. /// - public static IDaprDataProcessingBuilder WithSerializer(this IDaprDataProcessingBuilder builder, + public static IDaprDataPipelineBuilder WithSerializer(this IDaprDataPipelineBuilder builder, Func> serializerFactory, ServiceLifetime lifetime = ServiceLifetime.Singleton) => builder.WithDaprOperation, TInput, string>(serializerFactory, lifetime); @@ -55,7 +56,7 @@ public static IDaprDataProcessingBuilder WithSerializer(this IDaprDataPr /// /// Adds a compression data operation. /// - public static IDaprDataProcessingBuilder WithCompressor(this IDaprDataProcessingBuilder builder, + public static IDaprDataPipelineBuilder WithCompressor(this IDaprDataPipelineBuilder builder, ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : class, IDaprDataCompressor => builder.WithDaprOperation(lifetime); @@ -63,7 +64,7 @@ public static IDaprDataProcessingBuilder WithCompressor(this IDaprData /// /// Adds a compressor data operation. /// - public static IDaprDataProcessingBuilder WithCompressor(this IDaprDataProcessingBuilder builder, + public static IDaprDataPipelineBuilder WithCompressor(this IDaprDataPipelineBuilder builder, Func compressorFactory, ServiceLifetime lifetime = ServiceLifetime.Singleton) => builder.WithDaprOperation, ReadOnlyMemory>(compressorFactory, @@ -72,7 +73,7 @@ public static IDaprDataProcessingBuilder WithCompressor(this IDaprDataProcessing /// /// Adds a data integrity operation. /// - public static IDaprDataProcessingBuilder WithIntegrity(this IDaprDataProcessingBuilder builder, + public static IDaprDataPipelineBuilder WithIntegrity(this IDaprDataPipelineBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) where TService : class, IDaprDataValidator => builder.WithDaprOperation(serviceLifetime); @@ -80,27 +81,28 @@ public static IDaprDataProcessingBuilder WithIntegrity(this IDaprDataP /// /// Adds a data integrity operation using a factory that provides an . /// - public static IDaprDataProcessingBuilder WithIntegrity(this IDaprDataProcessingBuilder builder, + public static IDaprDataPipelineBuilder WithIntegrity(this IDaprDataPipelineBuilder builder, Func validatorFactory, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) => builder.WithDaprOperation, ReadOnlyMemory>(validatorFactory, serviceLifetime); - + /// - /// Adds a data masking operation. + /// Adds an encoder operation. /// - public static IDaprDataProcessingBuilder WithMasking(this IDaprDataProcessingBuilder builder, + public static IDaprDataPipelineBuilder WithEncoder(this IDaprDataPipelineBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) - where TService : class, IDaprDataMasker + where TService : class, IDaprDataEncoder => builder.WithDaprOperation(serviceLifetime); /// - /// Adds a data masking operation using a factory that provides an . + /// Adds an encoder operation using a factory that provides an . /// - public static IDaprDataProcessingBuilder WithMasking(this IDaprDataProcessingBuilder builder, - Func maskerFactory, + public static IDaprDataPipelineBuilder WithEncoder(this IDaprDataPipelineBuilder builder, + Func encoderFactory, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) => - builder.WithDaprOperation(maskerFactory, serviceLifetime); + builder.WithDaprOperation>(encoderFactory, + serviceLifetime); /// /// Registers the specified Dapr operation services. @@ -109,20 +111,20 @@ public static IDaprDataProcessingBuilder WithMasking(this IDaprDataProcessingBui /// The lifetime the service should be registered for. /// /// Thrown when an invalid service lifetime is provided. - private static IDaprDataProcessingBuilder WithDaprOperation(this IDaprDataProcessingBuilder builder, + private static IDaprDataPipelineBuilder WithDaprOperation(this IDaprDataPipelineBuilder builder, ServiceLifetime lifetime) where TService : class, IDaprDataOperation { switch (lifetime) { case ServiceLifetime.Singleton: - builder.Services.TryAddSingleton(); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); break; case ServiceLifetime.Scoped: - builder.Services.TryAddScoped(); + builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped()); break; case ServiceLifetime.Transient: - builder.Services.TryAddTransient(); + builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); break; default: throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, null); @@ -142,20 +144,20 @@ private static IDaprDataProcessingBuilder WithDaprOperation(this IDapr /// The output type provided by the operation. /// /// Thrown when an invalid service lifetime is provided. - private static IDaprDataProcessingBuilder WithDaprOperation(this IDaprDataProcessingBuilder builder, + private static IDaprDataPipelineBuilder WithDaprOperation(this IDaprDataPipelineBuilder builder, Func operationFactory, ServiceLifetime lifetime) where TService : class, IDaprDataOperation { switch (lifetime) { case ServiceLifetime.Singleton: - builder.Services.TryAddSingleton(operationFactory); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(operationFactory)); break; case ServiceLifetime.Scoped: - builder.Services.TryAddScoped(operationFactory); + builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped(operationFactory)); break; case ServiceLifetime.Transient: - builder.Services.TryAddTransient(operationFactory); + builder.Services.TryAddEnumerable(ServiceDescriptor.Transient(operationFactory)); break; default: throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, null); diff --git a/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs b/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs deleted file mode 100644 index a8b093494..000000000 --- a/src/Dapr.Common/Data/Operations/Providers/Masking/IDaprDataMasker.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ------------------------------------------------------------------------ -// Copyright 2024 The Dapr Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ------------------------------------------------------------------------ - -namespace Dapr.Common.Data.Operations.Providers.Masking; - -/// -/// Identifies an operation that provides a data masking capability. -/// -public interface IDaprDataMasker : IDaprStringBasedOperation -{ -} diff --git a/src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs b/src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs deleted file mode 100644 index 2a8041c12..000000000 --- a/src/Dapr.Common/Data/Operations/Providers/Masking/RegularExpressionMasker.cs +++ /dev/null @@ -1,72 +0,0 @@ -// ------------------------------------------------------------------------ -// Copyright 2024 The Dapr Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ------------------------------------------------------------------------ - -using System.Text.RegularExpressions; - -namespace Dapr.Common.Data.Operations.Providers.Masking; - -/// -/// Performs a masking operation on the provided input. -/// -public class RegularExpressionMasker : IDaprDataMasker -{ - private readonly Dictionary patterns = new(); - - /// - /// The name of the operation. - /// - public string Name => "Dapr.Masking.Regexp"; - - /// - /// Executes the data processing operation. - /// - /// The input data. - /// Cancellation token. - /// The output data and metadata for the operation. - public Task> ExecuteAsync(string? input, - CancellationToken cancellationToken = default) - { - if (input is null) - return Task.FromResult(new DaprOperationPayload(null)); - - var updatedValue = input; - foreach (var pattern in patterns) - { - cancellationToken.ThrowIfCancellationRequested(); - updatedValue = pattern.Key.Replace(input, pattern.Value); - } - - var payloadResult = new DaprOperationPayload(updatedValue); - return Task.FromResult(payloadResult); - } - - /// - /// Reverses the data operation. - /// - /// The processed input data being reversed. - /// Cancellation token. - /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprOperationPayload input, - CancellationToken cancellationToken = default) => - Task.FromResult(new DaprOperationPayload(input.Payload)); - - /// - /// Registers a pattern to match against. - /// - /// The regular expression to match to. - /// The string to place the matching value with. - public void RegisterMatch(Regex pattern, string replacement) - { - patterns.Add(pattern, replacement); - } -} diff --git a/test/Dapr.Common.Test/DaprDefaultTest.cs b/test/Dapr.Common.Test/DaprDefaultTest.cs index ef4d0da3c..42e4318ba 100644 --- a/test/Dapr.Common.Test/DaprDefaultTest.cs +++ b/test/Dapr.Common.Test/DaprDefaultTest.cs @@ -1,4 +1,17 @@ -using System; +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System; using Microsoft.Extensions.Configuration; using Xunit; diff --git a/test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs b/test/Dapr.Common.Test/Data/Attributes/DataPipelineAttributeTests.cs similarity index 78% rename from test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs rename to test/Dapr.Common.Test/Data/Attributes/DataPipelineAttributeTests.cs index 0b9d73b26..77bc45a2d 100644 --- a/test/Dapr.Common.Test/Data/Attributes/DataOperationAttributeTests.cs +++ b/test/Dapr.Common.Test/Data/Attributes/DataPipelineAttributeTests.cs @@ -1,4 +1,17 @@ -using System.Collections.Generic; +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Dapr.Common.Data.Attributes; @@ -7,20 +20,20 @@ namespace Dapr.Common.Test.Data.Attributes; -public class DataOperationAttributeTests +public class DataPipelineAttributeTests { [Fact] public void DataOperationAttribute_ShouldThrowExceptionForInvalidTypes() { // Arrange & Act & Assert - Assert.Throws(() => new DataOperationAttribute(typeof(InvalidOperation))); + Assert.Throws(() => new DataPipelineAttribute(typeof(InvalidOperation))); } [Fact] public void DataOperationAttribute_ShouldRegisterValidTypes() { // Arrange & Act - var attribute = new DataOperationAttribute(typeof(MockOperation1), typeof(MockOperation2)); + var attribute = new DataPipelineAttribute(typeof(MockOperation1), typeof(MockOperation2)); // Assert Assert.Equal(2, attribute.DataOperationTypes.Count); diff --git a/test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs b/test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs index aaab5e3c8..157a9ce74 100644 --- a/test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs +++ b/test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs @@ -1,121 +1,105 @@ -// using System.Collections.Generic; -// using System.Threading; -// using System.Threading.Tasks; -// using Dapr.Common.Data; -// using Dapr.Common.Data.Operations; -// using Xunit; -// -// namespace Dapr.Common.Test.Data; -// -// public class DaprDataPipelineTests -// { -// [Fact] -// public async Task ProcessAsync_ShouldProcessOperationsInOrder() -// { -// // Arrange -// var operations = new List -// { -// new MockOperation1(), -// new MockOperation2() -// }; -// var pipeline = new DaprDataPipeline<>(operations); -// -// // Act -// var result = await pipeline.ProcessAsync("input"); -// -// // Assert -// Assert.Equal("input-processed1-processed2", result.Payload); -// Assert.Contains("Operation1", result.Metadata); -// Assert.Contains("Operation2", result.Metadata); -// } -// -// [Fact] -// public async Task ReverseAsync_ShouldReverseOperationsInOrder() -// { -// // Arrange -// var operations = new List -// { -// new MockOperation1(), -// new MockOperation2() -// }; -// var pipeline = new DaprDataPipeline<>(operations); -// -// // Act -// var result = await pipeline.ReverseProcessAsync("input-processed1-processed2"); -// -// // Assert -// Assert.Equal("input", result.Payload); -// Assert.Contains("Operation1", result.Metadata); -// Assert.Contains("Operation2", result.Metadata); -// } -// -// private class MockOperation1 : IDaprDataOperation -// { -// public Task> ExecuteAsync(string input, CancellationToken cancellationToken) -// { -// var result = new DaprOperationPayload($"{input}-processed1") -// { -// Metadata = new Dictionary { { "Operation1", "Processed" } } -// }; -// return Task.FromResult(result); -// } -// -// /// -// /// Reverses the data operation. -// /// -// /// The processed input data being reversed. -// /// Cancellation token. -// /// The reversed output data and metadata for the operation. -// public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) -// { -// var result = new DaprOperationPayload(input.Payload.Replace("-processed1", "")) -// { -// Metadata = new Dictionary { { "Operation1", "Reversed" } } -// }; -// return Task.FromResult(result); -// } -// -// /// -// /// The name of the operation. -// /// -// public string Name => "Test"; -// } -// -// private class MockOperation2 : IDaprDataOperation -// { -// /// -// /// The name of the operation. -// /// -// public string Name => "Test"; -// -// /// -// /// Executes the data processing operation. -// /// -// /// The input data. -// /// Cancellation token. -// /// The output data and metadata for the operation. -// public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) -// { -// var result = new DaprOperationPayload($"{input}-processed2") -// { -// Metadata = new Dictionary { { "Operation2", "Processed" } } -// }; -// return Task.FromResult(result); -// } -// -// /// -// /// Reverses the data operation. -// /// -// /// The processed input data being reversed. -// /// Cancellation token. -// /// The reversed output data and metadata for the operation. -// public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) -// { -// var result = new DaprOperationPayload(input.Payload.Replace("-processed2", "")) -// { -// Metadata = new Dictionary { { "Operation2", "Reversed" } } -// }; -// return Task.FromResult(result); -// } -// } -// } +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Dapr.Common.Data; +using Dapr.Common.Data.Operations; +using Dapr.Common.Data.Operations.Providers.Compression; +using Dapr.Common.Data.Operations.Providers.Encoding; +using Dapr.Common.Data.Operations.Providers.Integrity; +using Dapr.Common.Data.Operations.Providers.Serialization; +using Xunit; + +namespace Dapr.Common.Test.Data; + +public class DaprDataPipelineTests +{ + [Fact] + public async Task ProcessAsync_ShouldProcessBasicOperations() + { + // Arrange + var operations = new List + { + new SystemTextJsonSerializer(), + new Utf8Encoder() + }; + var pipeline = new DaprDataPipeline(operations); + + // Act + var result = await pipeline.ProcessAsync(new SampleRecord("Sample", 15)); + + // Assert + Assert.Equal("eyJuYW1lIjoiU2FtcGxlIiwidmFsdWUiOjE1fQ==", Convert.ToBase64String(result.Payload.Span)); + Assert.True(result.Metadata.ContainsKey("ops")); + Assert.Equal("Dapr.Serialization.SystemTextJson,Dapr.Encoding.Utf8", result.Metadata["ops"]); + } + + [Fact] + public async Task ProcessAsync_ShouldProcessOptionalOperations() + { + // Arrange + var operations = new List + { + new GzipCompressor(), + new SystemTextJsonSerializer(), + new Utf8Encoder(), + new Sha256Validator() + }; + var pipeline = new DaprDataPipeline(operations); + + // Act + var result = await pipeline.ProcessAsync(new SampleRecord("Sample", 15)); + + var base64 = Convert.ToBase64String(result.Payload.Span); + + Assert.Equal("H4sIAAAAAAAACqtWykvMTVWyUgpOzC3ISVXSUSpLzCkFChia1gIAotvhPBwAAAA=", Convert.ToBase64String(result.Payload.Span)); + Assert.Equal(2, result.Metadata.Keys.Count); + Assert.True(result.Metadata.ContainsKey("Dapr.Integrity.Sha256-hash")); + Assert.Equal("x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=", result.Metadata["Dapr.Integrity.Sha256-hash"]); + Assert.True(result.Metadata.ContainsKey("ops")); + Assert.Equal("Dapr.Serialization.SystemTextJson,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256", result.Metadata["ops"]); + } + + [Fact] + public async Task ReverseAsync_ShouldReverseOperationsInMetadataOrder() + { + // Arrange + var operations = new List + { + new GzipCompressor(), + new SystemTextJsonSerializer(), + new Utf8Encoder(), + new Sha256Validator() + }; + var pipeline = new DaprDataPipeline(operations); + + // Act + var payload = Convert.FromBase64String("H4sIAAAAAAAACqtWykvMTVWyUgpOzC3ISVXSUSpLzCkFChia1gIAotvhPBwAAAA="); + var metadata = new Dictionary + { + { "Dapr.Integrity.Sha256-hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" }, + { + "ops", + "Dapr.Serialization.SystemTextJson,Dapr.Masking.Regexp,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256" + } + }; + var result = await pipeline.ReverseProcessAsync(payload, metadata); + + Assert.Equal("Sample", result.Payload.Name); + Assert.Equal(15, result.Payload.Value); + } + + private record SampleRecord(string Name, int Value); +} diff --git a/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs index bab6e0e6f..5e57ad688 100644 --- a/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs +++ b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs @@ -1,108 +1,141 @@ -// using System.Collections.Generic; -// using System.Threading; -// using System.Threading.Tasks; -// using Dapr.Common.Data; -// using Dapr.Common.Data.Attributes; -// using Dapr.Common.Data.Operations; -// using Microsoft.Extensions.DependencyInjection; -// using Xunit; -// -// namespace Dapr.Common.Test.Data; -// -// public class DataPipelineFactoryTests -// { -// [Fact] -// public void CreatePipeline_ShouldCreatePipelineWithCorrectOperations() -// { -// // Arrange -// var services = new ServiceCollection(); -// -// -// services.AddSingleton(); -// services.AddSingleton(); -// services.AddSingleton(); -// var serviceProvider = services.BuildServiceProvider(); -// var factory = serviceProvider.GetRequiredService(); -// -// // Act -// var pipeline = factory.CreatePipeline(); -// -// // Assert -// Assert.NotNull(pipeline); -// } -// -// [DataOperation(typeof(MockOperation1), typeof(MockOperation2))] -// private class TestDataProcessor -// { -// } -// -// private class MockOperation1 : IDaprDataOperation -// { -// public Task> ExecuteAsync(string input, CancellationToken cancellationToken) -// { -// var result = new DaprOperationPayload($"{input}-processed1") -// { -// Metadata = new Dictionary { { "Operation1", "Processed" } } -// }; -// return Task.FromResult(result); -// } -// -// /// -// /// Reverses the data operation. -// /// -// /// The processed input data being reversed. -// /// Cancellation token. -// /// The reversed output data and metadata for the operation. -// public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) -// { -// var result = new DaprOperationPayload(input.Payload.Replace("-processed1", "")) -// { -// Metadata = new Dictionary { { "Operation1", "Reversed" } } -// }; -// return Task.FromResult(result); -// } -// -// /// -// /// The name of the operation. -// /// -// public string Name => "Test"; -// } -// -// private class MockOperation2 : IDaprDataOperation -// { -// /// -// /// The name of the operation. -// /// -// public string Name => "Test"; -// -// /// -// /// Executes the data processing operation. -// /// -// /// The input data. -// /// Cancellation token. -// /// The output data and metadata for the operation. -// public Task> ExecuteAsync(string input, CancellationToken cancellationToken = default) -// { -// var result = new DaprOperationPayload($"{input}-processed2") -// { -// Metadata = new Dictionary { { "Operation2", "Processed" } } -// }; -// return Task.FromResult(result); -// } -// -// /// -// /// Reverses the data operation. -// /// -// /// The processed input data being reversed. -// /// Cancellation token. -// /// The reversed output data and metadata for the operation. -// public Task> ReverseAsync(DaprOperationPayload input, CancellationToken cancellationToken) -// { -// var result = new DaprOperationPayload(input.Payload.Replace("-processed2", "")) -// { -// Metadata = new Dictionary { { "Operation2", "Reversed" } } -// }; -// return Task.FromResult(result); -// } -// } -// } +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Collections.Generic; +using Dapr.Common.Data; +using Dapr.Common.Data.Attributes; +using Dapr.Common.Data.Extensions; +using Dapr.Common.Data.Operations.Providers.Compression; +using Dapr.Common.Data.Operations.Providers.Encoding; +using Dapr.Common.Data.Operations.Providers.Integrity; +using Dapr.Common.Data.Operations.Providers.Serialization; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + + namespace Dapr.Common.Test.Data; + +public class DataPipelineFactoryTests +{ + [Fact] + public void CreatePipeline_ShouldCreateProcessingPipelineWithCorrectOperations() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithSerializer>() + .WithIntegrity(_ => new Sha256Validator()) + .WithEncoder(c => new Utf8Encoder()); + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + + // Act + var pipeline = factory.CreateEncodingPipeline(); + + // Assert + Assert.NotNull(pipeline); + } + + [Fact] + public void CreatePipeline_ShouldThrowIfSerializationTypeNotRegisteredForProcessingPipeline() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithIntegrity(_ => new Sha256Validator()) + .WithEncoder(c => new Utf8Encoder()); + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + + // Act & Assert + Assert.Throws(() => factory.CreateEncodingPipeline()); + } + + [Fact] + public void CreatePipeline_ShouldThrowIEncodingTypeNotRegisteredForProcessingPipeline() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithSerializer>() + .WithIntegrity(_ => new Sha256Validator()); + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + + // Act & Assert + Assert.Throws(() => factory.CreateEncodingPipeline()); + } + + [Fact] + public void CreatePipeline_ShouldThrowIfSerializationTypeNotRegisteredForReverserocessingPipeline() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithSerializer>() + .WithIntegrity(_ => new Sha256Validator()) + .WithEncoder(c => new Utf8Encoder()); + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + var metadata = new Dictionary + { + { "Dapr.Integrity.Sha256-hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" }, + { + "ops", + "Dapr.Serialization.SystemTextJson,Dapr.Masking.Regexp,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256" + } + }; + + // Act & Assert + Assert.Throws(() => factory.CreateDecodingPipeline(metadata)); + } + + [Fact] + public void CreatePipeline_ShouldCreateReversePipelineWithCorrectOperations() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithSerializer>() + .WithIntegrity(_ => new Sha256Validator()) + .WithEncoder(c => new Utf8Encoder()); + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + var metadata = new Dictionary + { + { "Dapr.Integrity.Sha256-hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" }, + { + "ops", + "Dapr.Serialization.SystemTextJson,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256" + } + }; + + // Act + var pipeline = factory.CreateDecodingPipeline(metadata); + + // Assert + Assert.NotNull(pipeline); + } + + [DataPipeline(typeof(GzipCompressor), typeof(Utf8Encoder), typeof(SystemTextJsonSerializer))] + private sealed record SampleRecord(string Name, int Value, bool Flag); +} diff --git a/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs b/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs index f9b2d9acc..133402173 100644 --- a/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs +++ b/test/Dapr.Common.Test/Data/Extensions/DaprDataPipelineRegistrationBuilderExtensions.cs @@ -1,8 +1,20 @@ -using Dapr.Common.Data.Extensions; +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common.Data.Extensions; using Dapr.Common.Data.Operations; using Dapr.Common.Data.Operations.Providers.Compression; using Dapr.Common.Data.Operations.Providers.Integrity; -using Dapr.Common.Data.Operations.Providers.Masking; using Dapr.Common.Data.Operations.Providers.Serialization; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -16,14 +28,13 @@ public void AddDaprDataProcessingPipeline_ShouldReturnDaprDataProcessingPipeline { // Arrange var services = new ServiceCollection(); - var builder = new DaprDataPipelineBuilder(services); // Act - var result = builder.AddDaprDataProcessingPipeline(); + var result = services.AddDaprDataProcessingPipeline(); // Assert Assert.NotNull(result); - Assert.IsType(result); + Assert.IsType(result); } [Fact] @@ -31,10 +42,10 @@ public void WithSerializer_ShouldRegisterSerializationService() { // Arrange var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); - + // Act - builder.WithSerializer>(); + services.AddDaprDataProcessingPipeline() + .WithSerializer>(); // Assert var serviceProvider = services.BuildServiceProvider(); @@ -48,10 +59,10 @@ public void WithSerializer_ShouldRegisterSerializationFactory() { // Arrange var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); // Act - builder.WithSerializer(_ => new SystemTextJsonSerializer()); + services.AddDaprDataProcessingPipeline() + .WithSerializer(_ => new SystemTextJsonSerializer()); // Assert var serviceProvider = services.BuildServiceProvider(); @@ -65,10 +76,10 @@ public void WithCompressor_ShouldRegisterType() { // Arrange var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); // Act - builder.WithCompressor(); + services.AddDaprDataProcessingPipeline() + .WithCompressor(); // Assert var serviceProvider = services.BuildServiceProvider(); @@ -82,10 +93,10 @@ public void WithCompressor_ShouldRegisterFactory() { // Arrange var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); // Act - builder.WithCompressor(_ => new GzipCompressor()); + services.AddDaprDataProcessingPipeline() + .WithCompressor(_ => new GzipCompressor()); // Assert var serviceProvider = services.BuildServiceProvider(); @@ -99,10 +110,10 @@ public void WithIntegrity_ShouldRegisterType() { // Arrange var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); - + // Act - builder.WithIntegrity(); + services.AddDaprDataProcessingPipeline() + .WithIntegrity(); // Assert var serviceProvider = services.BuildServiceProvider(); @@ -116,10 +127,10 @@ public void WithIntegrity_ShouldRegisterFactory() { // Arrange var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); // Act - builder.WithIntegrity(_ => new Sha256Validator()); + services.AddDaprDataProcessingPipeline() + .WithIntegrity(_ => new Sha256Validator()); // Assert var serviceProvider = services.BuildServiceProvider(); @@ -127,50 +138,16 @@ public void WithIntegrity_ShouldRegisterFactory() Assert.NotNull(service); Assert.True(service is Sha256Validator); } - - [Fact] - public void WithMasking_ShouldRegisterType() - { - // Arrange - var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); - - // Act - builder.WithMasking(); - - // Assert - var serviceProvider = services.BuildServiceProvider(); - var service = serviceProvider.GetService(); - Assert.NotNull(service); - Assert.True(service is RegularExpressionMasker); - } - - [Fact] - public void WithMasking_ShouldRegisterFactory() - { - // Arrange - var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); - - // Act - builder.WithMasking(_ => new RegularExpressionMasker()); - - // Assert - var serviceProvider = services.BuildServiceProvider(); - var service = serviceProvider.GetService(); - Assert.NotNull(service); - Assert.True(service is RegularExpressionMasker); - } [Fact] public void WithDaprOperation_ShouldRegisterScopedService() { // Arrange var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); - + // Act - builder.WithSerializer>(ServiceLifetime.Scoped); + services.AddDaprDataProcessingPipeline() + .WithSerializer>(ServiceLifetime.Scoped); // Assert var serviceProvider = services.BuildServiceProvider(); @@ -184,10 +161,10 @@ public void WithDaprOperation_ShouldRegisterTransientService() { // Arrange var services = new ServiceCollection(); - var builder = new DaprDataProcessingPipelineBuilder(services); // Act - builder.WithSerializer(_ => new SystemTextJsonSerializer(), ServiceLifetime.Transient); + services.AddDaprDataProcessingPipeline() + .WithSerializer(_ => new SystemTextJsonSerializer(), ServiceLifetime.Transient); // Assert var serviceProvider = services.BuildServiceProvider(); @@ -198,7 +175,7 @@ public void WithDaprOperation_ShouldRegisterTransientService() Assert.NotSame(service1, service2); } - private record SampleRecord(string Name, int Count); + private sealed record SampleRecord(string Name, int Count); private class MockOperation : IDaprDataOperation { diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs index 76ecc9868..5e6f49728 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs @@ -1,4 +1,17 @@ -using Dapr.Common.Data.Operations.Providers.Compression; +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common.Data.Operations.Providers.Compression; namespace Dapr.Common.Test.Data.Operators.Providers.Compression; diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Encoding/Utf8EncoderTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Encoding/Utf8EncoderTest.cs new file mode 100644 index 000000000..d6d7b33f4 --- /dev/null +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Encoding/Utf8EncoderTest.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Dapr.Common.Data.Operations.Providers.Encoding; +using Xunit; + +namespace Dapr.Common.Test.Data.Operators.Providers.Encoding; + +public class Utf8EncoderTest +{ + [Fact] + public async Task ExecuteAsync_ShouldEncodeData() + { + // Arrange + var encoder = new Utf8Encoder(); + const string input = "This is a test value!"; + + // Act + var encodedResult = await encoder.ExecuteAsync(input); + + // Assert + Assert.NotNull(encodedResult); + Assert.Equal("VGhpcyBpcyBhIHRlc3QgdmFsdWUh", Convert.ToBase64String(encodedResult.Payload.Span)); + } + + [Fact] + public async Task ReverseAsync_ShouldDecodeData() + { + // Arrange + var encoder = new Utf8Encoder(); + const string input = "This is a test value!"; + var encodedResult = await encoder.ExecuteAsync(input); + + // Act + var reverseResult = await encoder.ReverseAsync(encodedResult, CancellationToken.None); + + // Assert + Assert.NotNull(reverseResult); + Assert.Equal(input, reverseResult.Payload); + } +} diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs index 9dc1be357..8586a1e93 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs @@ -1,4 +1,17 @@ -using System; +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System; using System.Threading; using System.Threading.Tasks; using Dapr.Common.Data.Operations; diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs deleted file mode 100644 index e555903a6..000000000 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Masking/DaprRegularExpressionMapperTest.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Dapr.Common.Data.Operations.Providers.Masking; -using Xunit; - -namespace Dapr.Common.Test.Data.Operators.Providers.Masking; - -public class DaprRegularExpressionMapperTest -{ - [Fact] - public async Task ExecuteAsync_ShouldRunMapperCorrectly() - { - //Arrange - var mapper = new RegularExpressionMasker(); - const string input = "This is not a real social security number: 012-34-5678"; - mapper.RegisterMatch(new Regex(@"\d{3}-\d{2}-\d{4}"), "***-**-****"); - - //Act - var result = await mapper.ExecuteAsync(input); - - //Assert - Assert.Equal("This is not a real social security number: ***-**-****", result.Payload); - } - - [Fact] - public async Task ExecuteAsync_ThrowIsCancellationTokenCancelled() - { - //Arrange - var mapper = new RegularExpressionMasker(); - const string input = "This is not a real social security number: 012-34-5678"; - mapper.RegisterMatch(new Regex(@"\d{3}-\d{2}-\d{4}"), "***-**-****"); - - //Act - var cancellationTokenSource = new CancellationTokenSource(); - cancellationTokenSource.Cancel(); //CancelAsync is only in .NET 8 and up - - //Assert - await Assert.ThrowsAsync(() => mapper.ExecuteAsync(input, cancellationTokenSource.Token)); - } - - [Fact] - public async Task ReverseAsync_ShouldNotDoAnythingToPayload() - { - //Arrange - var mapper = new RegularExpressionMasker(); - const string input = "This is a date: 2/18/2026"; - mapper.RegisterMatch(new Regex(@"\d{3}-\d{2}-\d{4}"), "***-**-****"); - - //Act - var result = await mapper.ExecuteAsync(input); - var reverseResult = await mapper.ReverseAsync(result); - - //Assert - Assert.Equal(input, reverseResult.Payload); - } -} diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs index 4fb925685..0018483ac 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs @@ -1,4 +1,17 @@ -using System.Threading.Tasks; +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Threading.Tasks; using Dapr.Common.Data.Operations; using Dapr.Common.Data.Operations.Providers.Serialization; using Xunit; From 39821649c0cf4618db046f93accdf2a7daeaa2cf Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 22 Oct 2024 21:09:13 -0500 Subject: [PATCH 07/12] Added another unit test Signed-off-by: Whit Waldo --- .../Data/DataPipelineFactoryTests.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs index 5e57ad688..4f4af0970 100644 --- a/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs +++ b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs @@ -82,13 +82,12 @@ public void CreatePipeline_ShouldThrowIEncodingTypeNotRegisteredForProcessingPip } [Fact] - public void CreatePipeline_ShouldThrowIfSerializationTypeNotRegisteredForReverserocessingPipeline() + public void CreatePipeline_ShouldThrowIfSerializationTypeNotRegisteredForReverseProcessingPipeline() { // Arrange var services = new ServiceCollection(); services.AddDaprDataProcessingPipeline() .WithCompressor() - .WithSerializer>() .WithIntegrity(_ => new Sha256Validator()) .WithEncoder(c => new Utf8Encoder()); @@ -106,6 +105,31 @@ public void CreatePipeline_ShouldThrowIfSerializationTypeNotRegisteredForReverse // Act & Assert Assert.Throws(() => factory.CreateDecodingPipeline(metadata)); } + + [Fact] + public void CreatePipeline_ShouldThrowIfEncodingTypeNotRegisteredForReverseProcessingPipeline() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithIntegrity(_ => new Sha256Validator()) + .WithSerializer>(); + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + var metadata = new Dictionary + { + { "Dapr.Integrity.Sha256-hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" }, + { + "ops", + "Dapr.Serialization.SystemTextJson,Dapr.Masking.Regexp,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256" + } + }; + + // Act & Assert + Assert.Throws(() => factory.CreateDecodingPipeline(metadata)); + } [Fact] public void CreatePipeline_ShouldCreateReversePipelineWithCorrectOperations() From 1420a399b030117dbd8d62321e92144accaadee7 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 23 Oct 2024 00:56:21 -0500 Subject: [PATCH 08/12] Updated to support duplicate operations with appropriate metadata retention for each operation. Added more unit tests, including E2E of standard and duplicated operations. Signed-off-by: Whit Waldo --- src/Dapr.Common/Data/DaprDecoderPipeline.cs | 145 +++++++++++++ ...DataPipeline.cs => DaprEncoderPipeline.cs} | 104 +++------- src/Dapr.Common/Data/DataPipelineFactory.cs | 36 +++- .../Data/Extensions/DictionaryExtensions.cs | 24 ++- .../Data/Operations/IDaprDataOperation.cs | 3 +- .../Providers/Compression/GzipCompressor.cs | 3 +- .../Providers/Encoding/Utf8Encoder.cs | 3 +- .../Integrity/DaprSha256Validator.cs | 16 +- .../Serialization/SystemTextJsonSerializer.cs | 3 +- .../Attributes/DataPipelineAttributeTests.cs | 92 +------- .../Data/DaprDecoderPipelineTests.cs | 196 ++++++++++++++++++ ...neTests.cs => DaprEncoderPipelineTests.cs} | 47 ++--- .../Data/DataPipelineFactoryTests.cs | 29 ++- .../Compression/GzipCompressorTest.cs | 2 +- .../Providers/Encoding/Utf8EncoderTest.cs | 2 +- .../SystemTextJsonSerializerTest.cs | 2 +- 16 files changed, 493 insertions(+), 214 deletions(-) create mode 100644 src/Dapr.Common/Data/DaprDecoderPipeline.cs rename src/Dapr.Common/Data/{DaprDataPipeline.cs => DaprEncoderPipeline.cs} (58%) create mode 100644 test/Dapr.Common.Test/Data/DaprDecoderPipelineTests.cs rename test/Dapr.Common.Test/Data/{DaprDataPipelineTests.cs => DaprEncoderPipelineTests.cs} (65%) diff --git a/src/Dapr.Common/Data/DaprDecoderPipeline.cs b/src/Dapr.Common/Data/DaprDecoderPipeline.cs new file mode 100644 index 000000000..c3eaf5b1d --- /dev/null +++ b/src/Dapr.Common/Data/DaprDecoderPipeline.cs @@ -0,0 +1,145 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common.Data.Operations; + +namespace Dapr.Common.Data; + +/// +/// Processes the data using the provided providers. +/// +internal sealed class DaprDecoderPipeline +{ + private readonly Stack prefixKeys; + private readonly IDaprTStringTransitionOperation? genericToStringOp; + private readonly List stringOps = new(); + private readonly IDaprStringByteTransitionOperation? stringToByteOp; + private readonly List byteOps = new(); + + /// + /// Used to initialize a new . + /// + public DaprDecoderPipeline(IEnumerable operations, Stack prefixKeys) + { + this.prefixKeys = prefixKeys; + + foreach (var op in operations) + { + switch (op) + { + case IDaprTStringTransitionOperation genToStrOp when genericToStringOp is not null: + throw new DaprException( + $"Multiple types are declared for the conversion of the data to a string in the data pipeline for {typeof(TOutput)} - only one is allowed"); + case IDaprTStringTransitionOperation genToStrOp: + genericToStringOp = genToStrOp; + break; + case IDaprStringBasedOperation strOp: + stringOps.Add(strOp); + break; + case IDaprStringByteTransitionOperation strToByte when stringToByteOp is not null: + throw new DaprException( + $"Multiple types are declared for the pipeline conversion from a string to a byte array for {typeof(TOutput)} - only one is allowed"); + case IDaprStringByteTransitionOperation strToByte: + stringToByteOp = strToByte; + break; + case IDaprByteBasedOperation byteOp: + byteOps.Add(byteOp); + break; + } + } + + if (genericToStringOp is null) + { + throw new DaprException( + $"A pipeline operation must be specified to convert a {typeof(TOutput)} into a serializable string"); + } + + if (stringToByteOp is null) + { + throw new DaprException( + $"A pipeline operation must be specified to convert a {typeof(TOutput)} into a byte array"); + } + } + + /// + /// Processes the reverse of the data in the order of the provided list of . + /// + /// The data to process in reverse. + /// The metadata providing the mechanism(s) used to encode the data. + /// Cancellation token. + /// The evaluated data. + public async Task> ReverseProcessAsync(ReadOnlyMemory payload, + Dictionary metadata, CancellationToken cancellationToken = default) + { + var metadataPrefixes = new Stack(prefixKeys); + + //First, perform byte-based operations + var inboundPayload = new DaprOperationPayload>(payload) { Metadata = metadata }; + var byteBasedResult = await ReverseByteOperationsAsync(inboundPayload, metadataPrefixes, cancellationToken); + + //Convert this back to a string from a byte array + var currentPrefix = metadataPrefixes.Pop(); + var stringResult = await stringToByteOp!.ReverseAsync(byteBasedResult, currentPrefix, cancellationToken); + + //Perform the string-based operations + var stringBasedResult = await ReverseStringOperationsAsync(stringResult, metadataPrefixes, cancellationToken); + + //Convert from a string back into its generic type + currentPrefix = metadataPrefixes.Pop(); + var genericResult = await genericToStringOp!.ReverseAsync(stringBasedResult, currentPrefix, cancellationToken); + + return genericResult; + } + + /// + /// Performs a reversal operation for the string-based operations. + /// + /// The payload to run the reverse operation against. + /// The prefix values for retrieving data from the metadata for this operation. + /// Cancellation token. + /// + private async Task> ReverseStringOperationsAsync( + DaprOperationPayload payload, + Stack metadataPrefixes, CancellationToken cancellationToken) + { + stringOps.Reverse(); + foreach (var op in stringOps) + { + var currentPrefix = metadataPrefixes.Pop(); + payload = await op.ReverseAsync(payload, currentPrefix, cancellationToken); + } + + return payload; + } + + /// + /// Performs a reversal operation for the byte-based operations. + /// + /// The current state of the payload. + /// The prefix values for retrieving data from the metadata for this operation. + /// Cancellation token. + /// The most up-to-date payload. + private async Task>> + ReverseByteOperationsAsync(DaprOperationPayload> payload, Stack metadataPrefixes, + CancellationToken cancellationToken) + { + byteOps.Reverse(); + foreach (var op in byteOps) + { + var currentPrefix = metadataPrefixes.Pop(); + payload = await op.ReverseAsync(payload, currentPrefix, cancellationToken); + } + + return payload; + } +} diff --git a/src/Dapr.Common/Data/DaprDataPipeline.cs b/src/Dapr.Common/Data/DaprEncoderPipeline.cs similarity index 58% rename from src/Dapr.Common/Data/DaprDataPipeline.cs rename to src/Dapr.Common/Data/DaprEncoderPipeline.cs index 239f662fa..54003769a 100644 --- a/src/Dapr.Common/Data/DaprDataPipeline.cs +++ b/src/Dapr.Common/Data/DaprEncoderPipeline.cs @@ -11,7 +11,6 @@ // limitations under the License. // ------------------------------------------------------------------------ -using System.Text; using Dapr.Common.Data.Extensions; using Dapr.Common.Data.Operations; @@ -20,23 +19,25 @@ namespace Dapr.Common.Data; /// /// Processes the data using the provided providers. /// -internal sealed class DaprDataPipeline +internal sealed class DaprEncoderPipeline { /// /// The metadata key containing the operations. /// private const string OperationKey = "ops"; + + private readonly List operationNames = new(); + private readonly Dictionary operationInvocations = new(); - private readonly StringBuilder operationNameBuilder = new(); private readonly IDaprTStringTransitionOperation? genericToStringOp; private readonly List stringOps = new(); private readonly IDaprStringByteTransitionOperation? stringToByteOp; private readonly List byteOps = new(); /// - /// Used to initialize a new . + /// Used to initialize a new . /// - public DaprDataPipeline(IEnumerable operations) + public DaprEncoderPipeline(IEnumerable operations) { foreach (var op in operations) { @@ -89,34 +90,30 @@ public async Task>> ProcessAsync(TInpu //Start by serializing the input to a string var serializationPayload = await genericToStringOp!.ExecuteAsync(input, cancellationToken); - combinedMetadata.MergeFrom(serializationPayload.Metadata); - AppendOperationName(genericToStringOp.Name); + combinedMetadata.MergeFrom(serializationPayload.Metadata, RegisterOperationInvocation(genericToStringOp.Name)); //Run through any provided string-based operations var stringPayload = new DaprOperationPayload(serializationPayload.Payload); foreach (var strOp in stringOps) { stringPayload = await strOp.ExecuteAsync(stringPayload.Payload, cancellationToken); - combinedMetadata.MergeFrom(stringPayload.Metadata); - AppendOperationName(strOp.Name); + combinedMetadata.MergeFrom(stringPayload.Metadata, RegisterOperationInvocation(strOp.Name)); } //Encode the string payload to a byte array var encodedPayload = await stringToByteOp!.ExecuteAsync(stringPayload.Payload, cancellationToken); - combinedMetadata.MergeFrom(encodedPayload.Metadata); - AppendOperationName(stringToByteOp.Name); + combinedMetadata.MergeFrom(encodedPayload.Metadata, RegisterOperationInvocation(stringToByteOp.Name)); //Run through any provided byte-based operations var bytePayload = new DaprOperationPayload>(encodedPayload.Payload); foreach (var byteOp in byteOps) { bytePayload = await byteOp.ExecuteAsync(bytePayload.Payload, cancellationToken); - combinedMetadata.MergeFrom(bytePayload.Metadata); - AppendOperationName(byteOp.Name); + combinedMetadata.MergeFrom(bytePayload.Metadata, RegisterOperationInvocation(byteOp.Name)); } //Persist the op names to the metadata - combinedMetadata[OperationKey] = operationNameBuilder.ToString(); + combinedMetadata[OperationKey] = string.Join(',', operationNames); //Create a payload that combines the payload and metadata var resultPayload = new DaprOperationPayload>(bytePayload.Payload) @@ -127,75 +124,32 @@ public async Task>> ProcessAsync(TInpu } /// - /// Processes the reverse of the data in the order of the provided list of . + /// Gets the formatted operation name with its zero-based invocation count. /// - /// The data to process in reverse. - /// The metadata providing the mechanism(s) used to encode the data. - /// Cancellation token. - /// The evaluated data. - public async Task> ReverseProcessAsync(ReadOnlyMemory payload, Dictionary metadata, CancellationToken cancellationToken = default) + /// The name of the operation. + /// A string value containing the operation name and its zero-based invocation count. + private string RegisterOperationInvocation(string operationName) { - //First, perform byte-based operations - var inboundPayload = new DaprOperationPayload>(payload) { Metadata = metadata }; - var byteBasedResult = await ReverseByteOperationsAsync(inboundPayload, cancellationToken); + //Add to the operation names + var result = $"{operationName}[{GetAndAddOperationInvocation(operationName)}]"; + operationNames.Add(result); - //Convert this back to a string from a byte array - var stringResult = await stringToByteOp!.ReverseAsync(byteBasedResult, cancellationToken); - - //Perform the string-based operations - var stringBasedResult = await ReverseStringOperationsAsync(stringResult, cancellationToken); - - //Convert from a string back into its generic type - var genericResult = await genericToStringOp!.ReverseAsync(stringBasedResult, cancellationToken); - - return genericResult; + //Return to be used in the metadata key + return result; } /// - /// Performs a reversal operation for the string-based operations. + /// Registers another operation invocation. /// - /// The payload to run the reverse operation against. - /// Cancellation token. - /// - private async Task> ReverseStringOperationsAsync(DaprOperationPayload payload, - CancellationToken cancellationToken) + /// The name of the operation. + /// The zero-based count of the operational invocation. + private int GetAndAddOperationInvocation(string operationName) { - stringOps.Reverse(); - foreach (var op in stringOps) - { - payload = await op.ReverseAsync(payload, cancellationToken); - } + if (!operationInvocations.TryGetValue(operationName, out var invocationCount)) + operationInvocations[operationName] = 1; + else + operationInvocations[operationName]++; - return payload; - } - - /// - /// Performs a reversal operation for the byte-based operations. - /// - /// The current state of the payload. - /// Cancellation token. - /// The most up-to-date payload. - private async Task>> - ReverseByteOperationsAsync(DaprOperationPayload> payload, - CancellationToken cancellationToken) - { - byteOps.Reverse(); - foreach (var op in byteOps) - { - payload = await op.ReverseAsync(payload, cancellationToken); - } - - return payload; - } - - /// - /// Appends the operation name to the string. - /// - /// The name of the operation to append. - private void AppendOperationName(string name) - { - if (operationNameBuilder.Length > 0) - operationNameBuilder.Append(','); - operationNameBuilder.Append(name); + return invocationCount; } } diff --git a/src/Dapr.Common/Data/DataPipelineFactory.cs b/src/Dapr.Common/Data/DataPipelineFactory.cs index 7ba0ba3f2..662a0963c 100644 --- a/src/Dapr.Common/Data/DataPipelineFactory.cs +++ b/src/Dapr.Common/Data/DataPipelineFactory.cs @@ -12,6 +12,7 @@ // ------------------------------------------------------------------------ using System.Reflection; +using System.Text.RegularExpressions; using Dapr.Common.Data.Attributes; using Dapr.Common.Data.Operations; using Microsoft.Extensions.DependencyInjection; @@ -42,12 +43,12 @@ public DataPipelineFactory(IServiceProvider serviceProvider) /// /// The type to create the pipeline for. /// - public DaprDataPipeline CreateEncodingPipeline() + public DaprEncoderPipeline CreateEncodingPipeline() { var attribute = typeof(T).GetCustomAttribute(); if (attribute == null) { - return new DaprDataPipeline(new List>()); + return new DaprEncoderPipeline(new List>()); } var allRegisteredOperations = serviceProvider.GetServices().ToList(); @@ -55,7 +56,7 @@ public DaprDataPipeline CreateEncodingPipeline() .SelectMany(type => allRegisteredOperations.Where(op => op.GetType() == type)) .ToList(); - return new DaprDataPipeline(operations); + return new DaprEncoderPipeline(operations); } /// @@ -66,7 +67,7 @@ public DaprDataPipeline CreateEncodingPipeline() /// The type to deserialize to. /// A pipeline configured for reverse processing. /// - public DaprDataPipeline CreateDecodingPipeline(Dictionary metadata) + public DaprDecoderPipeline CreateDecodingPipeline(Dictionary metadata) { const string operationKey = "ops"; if (!metadata.TryGetValue(operationKey, out var opNames)) @@ -76,16 +77,29 @@ public DaprDataPipeline CreateDecodingPipeline(Dictionary m } //Run through the names backwards in the order of the operations as named in the metadata - var operations = new List(opNames.Split(',').Reverse()); + var operations = new List(opNames.Split(',').Reverse()).ToList(); + + var services = serviceProvider.GetServices().ToList(); + var metadataPrefixes = new Stack(); + var operationalServices = new List(); - var matchingDataOperations = serviceProvider.GetServices() - .Where(op => operations.Contains(op.Name)) - .ToList(); + for (var a = 0; a < operations.Count; a++) + { + var operation = operations[a]; + var plainName = Regex.Replace(operation, @"\[\d+\]$", string.Empty); + + var matchingService = services.FirstOrDefault(op => string.Equals(op.Name, plainName)); + if (matchingService is null) + throw new DaprException($"Unable to locate service matching {plainName} in service registry"); + + operationalServices.Add(matchingService); + metadataPrefixes.Push(operation); + } - if (matchingDataOperations.Count != operations.Count) + if (operationalServices.Count != operations.Count) { //Identify which names are missing - foreach (var op in matchingDataOperations) + foreach (var op in operationalServices) { operations.Remove(op.Name); } @@ -94,6 +108,6 @@ public DaprDataPipeline CreateDecodingPipeline(Dictionary m $"Registered services were not located for the following operation names present in the metadata: {String.Join(',', operations)}"); } - return new DaprDataPipeline(matchingDataOperations); + return new DaprDecoderPipeline(operationalServices, metadataPrefixes); } } diff --git a/src/Dapr.Common/Data/Extensions/DictionaryExtensions.cs b/src/Dapr.Common/Data/Extensions/DictionaryExtensions.cs index 5e516127a..090daf22f 100644 --- a/src/Dapr.Common/Data/Extensions/DictionaryExtensions.cs +++ b/src/Dapr.Common/Data/Extensions/DictionaryExtensions.cs @@ -1,4 +1,17 @@ -namespace Dapr.Common.Data.Extensions; +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common.Data.Extensions; /// /// Provides extension methods for use with a . @@ -11,14 +24,15 @@ internal static class DictionaryExtensions /// /// The dictionary the values are being merged into. /// The dictionary the values are being merged from. - /// The type of the key for either dictionary. + /// The prefix to prepend to the key of the merged values. /// The type of the value for either dictionary. - internal static void MergeFrom(this Dictionary mergeTo, - Dictionary mergeFrom) where TKey : notnull + internal static void MergeFrom(this Dictionary mergeTo, + Dictionary mergeFrom, string prefix) { foreach (var kvp in mergeFrom) { - mergeTo[kvp.Key] = kvp.Value; + var newKey = $"{prefix}{kvp.Key}"; + mergeTo[newKey] = kvp.Value; } } } diff --git a/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs b/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs index 872ae6a1d..337d9be9b 100644 --- a/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs +++ b/src/Dapr.Common/Data/Operations/IDaprDataOperation.cs @@ -43,8 +43,9 @@ public interface IDaprDataOperation : IDaprDataOperation /// Reverses the data operation. /// /// The processed input data being reversed. + /// The prefix value of the keys containing the operation metadata. /// Cancellation token. /// The reversed output data and metadata for the operation. - Task> ReverseAsync(DaprOperationPayload input, + Task> ReverseAsync(DaprOperationPayload input, string metadataPrefix, CancellationToken cancellationToken = default); } diff --git a/src/Dapr.Common/Data/Operations/Providers/Compression/GzipCompressor.cs b/src/Dapr.Common/Data/Operations/Providers/Compression/GzipCompressor.cs index d065406d0..bb5573765 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Compression/GzipCompressor.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Compression/GzipCompressor.cs @@ -45,9 +45,10 @@ public async Task>> ExecuteAsync(ReadO /// Reverses the data operation. /// /// The processed input data being reversed. + /// The prefix value of the keys containing the operation metadata. /// Cancellation token. /// The reversed output data and metadata for the operation. - public async Task>> ReverseAsync(DaprOperationPayload> input, CancellationToken cancellationToken) + public async Task>> ReverseAsync(DaprOperationPayload> input, string metadataPrefix, CancellationToken cancellationToken) { using var inputStream = new MemoryStream(input.Payload.ToArray()); await using var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress); diff --git a/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs b/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs index 758ab7997..fd973306f 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Encoding/Utf8Encoder.cs @@ -42,9 +42,10 @@ public Task>> ExecuteAsync(string? inp /// Reverses the data operation. /// /// The processed input data being reversed. + /// The prefix value of the keys containing the operation metadata. /// Cancellation token. /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprOperationPayload> input, CancellationToken cancellationToken = default) + public Task> ReverseAsync(DaprOperationPayload> input, string metadataPrefix, CancellationToken cancellationToken = default) { var strValue = System.Text.Encoding.UTF8.GetString(input.Payload.Span); var result = new DaprOperationPayload(strValue); diff --git a/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs b/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs index cd22e3331..cd0f39b17 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs @@ -24,6 +24,11 @@ public class Sha256Validator : IDaprDataValidator /// The name of the operation. /// public string Name => "Dapr.Integrity.Sha256"; + + /// + /// The key containing the hash value in the metadata. + /// + private const string HashKey = "hash"; /// /// Executes the data processing operation. @@ -35,7 +40,7 @@ public async Task>> ExecuteAsync(ReadO { var checksum = await CalculateChecksumAsync(input, cancellationToken); var result = new DaprOperationPayload>(input); - result.Metadata.Add(GetChecksumKey(), checksum); + result.Metadata.Add(HashKey, checksum); return result; } @@ -43,11 +48,12 @@ public async Task>> ExecuteAsync(ReadO /// Reverses the data operation. /// /// The processed input data being reversed. + /// The prefix value of the keys containing the operation metadata. /// Cancellation token. /// The reversed output data and metadata for the operation. - public async Task>> ReverseAsync(DaprOperationPayload> input, CancellationToken cancellationToken) + public async Task>> ReverseAsync(DaprOperationPayload> input, string metadataPrefix, CancellationToken cancellationToken) { - var checksumKey = GetChecksumKey(); + var checksumKey = GetChecksumKey(metadataPrefix); if (input.Metadata.TryGetValue(checksumKey, out var checksum)) { var newChecksum = await CalculateChecksumAsync(input.Payload, cancellationToken); @@ -57,7 +63,7 @@ public async Task>> ReverseAsync(DaprO } } - //If there's no checksum metadata or it matches, just continue with the next operation + //If there's no checksum metadata of if it matches, just continue with the next operation return new DaprOperationPayload>(input.Payload); } @@ -81,5 +87,5 @@ private async static Task CalculateChecksumAsync(ReadOnlyMemory da /// Get the key used to store the hash in the metadata. /// /// The key value. - private string GetChecksumKey() => $"{Name}-hash"; + private static string GetChecksumKey(string keyPrefix) => $"{keyPrefix}{HashKey}"; } diff --git a/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs b/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs index 070fcbce7..02ae6d650 100644 --- a/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs +++ b/src/Dapr.Common/Data/Operations/Providers/Serialization/SystemTextJsonSerializer.cs @@ -48,9 +48,10 @@ public sealed class SystemTextJsonSerializer : IDaprDataSerializer /// Reverses the data operation. /// /// The processed input data being reversed. + /// The prefix value of the keys containing the operation metadata. /// Cancellation token. /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprOperationPayload input, + public Task> ReverseAsync(DaprOperationPayload input, string metadataPrefix, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(input.Payload, nameof(input)); diff --git a/test/Dapr.Common.Test/Data/Attributes/DataPipelineAttributeTests.cs b/test/Dapr.Common.Test/Data/Attributes/DataPipelineAttributeTests.cs index 77bc45a2d..0b7bd130f 100644 --- a/test/Dapr.Common.Test/Data/Attributes/DataPipelineAttributeTests.cs +++ b/test/Dapr.Common.Test/Data/Attributes/DataPipelineAttributeTests.cs @@ -11,11 +11,11 @@ // limitations under the License. // ------------------------------------------------------------------------ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; +using System.Text.Unicode; using Dapr.Common.Data.Attributes; -using Dapr.Common.Data.Operations; +using Dapr.Common.Data.Operations.Providers.Compression; +using Dapr.Common.Data.Operations.Providers.Encoding; +using Dapr.Common.Data.Operations.Providers.Serialization; using Xunit; namespace Dapr.Common.Test.Data.Attributes; @@ -33,88 +33,18 @@ public void DataOperationAttribute_ShouldThrowExceptionForInvalidTypes() public void DataOperationAttribute_ShouldRegisterValidTypes() { // Arrange & Act - var attribute = new DataPipelineAttribute(typeof(MockOperation1), typeof(MockOperation2)); + var attribute = new DataPipelineAttribute(typeof(GzipCompressor), typeof(Utf8Encoder), typeof(SystemTextJsonSerializer)); // Assert - Assert.Equal(2, attribute.DataOperationTypes.Count); - Assert.Contains(typeof(MockOperation1), attribute.DataOperationTypes); - Assert.Contains(typeof(MockOperation2), attribute.DataOperationTypes); + Assert.Equal(3, attribute.DataOperationTypes.Count); + Assert.Contains(typeof(GzipCompressor), attribute.DataOperationTypes); + Assert.Contains(typeof(Utf8Encoder), attribute.DataOperationTypes); + Assert.Contains(typeof(SystemTextJsonSerializer), attribute.DataOperationTypes); } - private class InvalidOperation + private sealed class InvalidOperation { } - private class MockOperation1 : IDaprDataOperation - { - public Task> ExecuteAsync(string input, CancellationToken cancellationToken) - { - var result = new DaprOperationPayload($"{input}-processed1") - { - Metadata = new Dictionary { { "Operation1", "Processed" } } - }; - return Task.FromResult(result); - } - - /// - /// Reverses the data operation. - /// - /// The processed input data being reversed. - /// Cancellation token. - /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprOperationPayload input, - CancellationToken cancellationToken) - { - var result = new DaprOperationPayload(input.Payload.Replace("-processed1", "")) - { - Metadata = new Dictionary { { "Operation1", "Reversed" } } - }; - return Task.FromResult(result); - } - - /// - /// The name of the operation. - /// - public string Name => "Test"; - } - - private class MockOperation2 : IDaprDataOperation - { - /// - /// The name of the operation. - /// - public string Name => "Test"; - - /// - /// Executes the data processing operation. - /// - /// The input data. - /// Cancellation token. - /// The output data and metadata for the operation. - public Task> ExecuteAsync(string input, - CancellationToken cancellationToken = default) - { - var result = new DaprOperationPayload($"{input}-processed2") - { - Metadata = new Dictionary { { "Operation2", "Processed" } } - }; - return Task.FromResult(result); - } - - /// - /// Reverses the data operation. - /// - /// The processed input data being reversed. - /// Cancellation token. - /// The reversed output data and metadata for the operation. - public Task> ReverseAsync(DaprOperationPayload input, - CancellationToken cancellationToken) - { - var result = new DaprOperationPayload(input.Payload.Replace("-processed2", "")) - { - Metadata = new Dictionary { { "Operation2", "Reversed" } } - }; - return Task.FromResult(result); - } - } + private sealed record MyRecord(string Name); } diff --git a/test/Dapr.Common.Test/Data/DaprDecoderPipelineTests.cs b/test/Dapr.Common.Test/Data/DaprDecoderPipelineTests.cs new file mode 100644 index 000000000..6c0b391fc --- /dev/null +++ b/test/Dapr.Common.Test/Data/DaprDecoderPipelineTests.cs @@ -0,0 +1,196 @@ +// ------------------------------------------------------------------------ +// Copyright 2024 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Unicode; +using System.Threading; +using System.Threading.Tasks; +using Dapr.Common.Data; +using Dapr.Common.Data.Attributes; +using Dapr.Common.Data.Extensions; +using Dapr.Common.Data.Operations; +using Dapr.Common.Data.Operations.Providers.Compression; +using Dapr.Common.Data.Operations.Providers.Encoding; +using Dapr.Common.Data.Operations.Providers.Integrity; +using Dapr.Common.Data.Operations.Providers.Serialization; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Dapr.Common.Test.Data; + +public class DaprDecoderPipelineTests +{ + [Fact] + public async Task ReverseAsync_ShouldReverseOperationsInMetadataOrder() + { + // Arrange + var operations = new List + { + new GzipCompressor(), + new SystemTextJsonSerializer(), + new Utf8Encoder(), + new Sha256Validator() + }; + var opNames = new Stack(); + opNames.Push("Dapr.Serialization.SystemTextJson[0]"); + opNames.Push("Dapr.Encoding.Utf8[0]"); + opNames.Push("Dapr.Compression.Gzip[0]"); + opNames.Push("Dapr.Integrity.Sha256[0]"); + + var pipeline = new DaprDecoderPipeline(operations, opNames); + + // Act + var payload = Convert.FromBase64String("H4sIAAAAAAAACqtWykvMTVWyUgpOzC3ISVXSUSpLzCkFChia1gIAotvhPBwAAAA="); + var metadata = new Dictionary + { + { "Dapr.Integrity.Sha256-hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" }, + { + "ops", + "Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0],Dapr.Compression.Gzip[0],Dapr.Integrity.Sha256[0]" + } + }; + var result = await pipeline.ReverseProcessAsync(payload, metadata); + + Assert.Equal("Sample", result.Payload.Name); + Assert.Equal(15, result.Payload.Value); + } + + [Fact] + public async Task EndToEndTest() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithSerializer>() + .WithIntegrity(_ => new Sha256Validator()) + .WithEncoder(c => new Utf8Encoder()); + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + + var encoderPipeline = factory.CreateEncodingPipeline(); + var record = new SimpleRegistration("This is merely a test!"); + + // Act + var encodedPayload = await encoderPipeline.ProcessAsync(record, CancellationToken.None); + + // Assert + Assert.NotNull(encodedPayload); + Assert.True(encodedPayload.Payload.Length > 0); + Assert.Equal("H4sIAAAAAAAACqtWykvMTVWyUgrJyCxWAKLc1KLUnEqFRIWS1OISRaVaAF3KYX0hAAAA", + Convert.ToBase64String(encodedPayload.Payload.Span)); + Assert.NotNull(encodedPayload.Metadata); + Assert.True(encodedPayload.Metadata.ContainsKey("ops")); + Assert.Equal("Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0],Dapr.Compression.Gzip[0],Dapr.Integrity.Sha256[0]", encodedPayload.Metadata["ops"]); + Assert.True(encodedPayload.Metadata.ContainsKey("Dapr.Integrity.Sha256[0]hash")); + Assert.Equal("Ehr18bGgwtfe/uq8MbfnIQkbsUYOAHt7xWNAecRo2DI=", encodedPayload.Metadata["Dapr.Integrity.Sha256[0]hash"]); + + // Act #2 + var decoderPipeline = factory.CreateDecodingPipeline(encodedPayload.Metadata); + var decodedPayload = await decoderPipeline.ReverseProcessAsync(encodedPayload.Payload, encodedPayload.Metadata); + + // Assert #2 + Assert.Equal(record, decodedPayload.Payload); + } + + [Fact] + public async Task EndToEndTest_ShouldFailValidationWithBadHashValue() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithSerializer>() + .WithIntegrity(_ => new Sha256Validator()) + .WithEncoder(c => new Utf8Encoder()); + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + + var encoderPipeline = factory.CreateEncodingPipeline(); + var record = new SimpleRegistration("This is merely a test!"); + + // Act + var encodedPayload = await encoderPipeline.ProcessAsync(record, CancellationToken.None); + + // Assert + Assert.NotNull(encodedPayload); + Assert.True(encodedPayload.Payload.Length > 0); + Assert.Equal("H4sIAAAAAAAACqtWykvMTVWyUgrJyCxWAKLc1KLUnEqFRIWS1OISRaVaAF3KYX0hAAAA", + Convert.ToBase64String(encodedPayload.Payload.Span)); + Assert.NotNull(encodedPayload.Metadata); + Assert.True(encodedPayload.Metadata.ContainsKey("ops")); + Assert.Equal("Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0],Dapr.Compression.Gzip[0],Dapr.Integrity.Sha256[0]", encodedPayload.Metadata["ops"]); + Assert.True(encodedPayload.Metadata.ContainsKey("Dapr.Integrity.Sha256[0]hash")); + Assert.Equal("Ehr18bGgwtfe/uq8MbfnIQkbsUYOAHt7xWNAecRo2DI=", encodedPayload.Metadata["Dapr.Integrity.Sha256[0]hash"]); + + encodedPayload.Metadata["Dapr.Integrity.Sha256[0]hash"] = "abc123"; + + // Act & Assert #2 + var decoderPipeline = factory.CreateDecodingPipeline(encodedPayload.Metadata); + await Assert.ThrowsAsync(async () => + await decoderPipeline.ReverseProcessAsync(encodedPayload.Payload, encodedPayload.Metadata)); + } + + [Fact] + public async Task EndToEndWithDuplicateOperations() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithSerializer>() + .WithIntegrity(_ => new Sha256Validator()) + .WithEncoder(c => new Utf8Encoder()); + + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + + var encoderPipeline = factory.CreateEncodingPipeline(); + var record = new DuplicateRegistration("Don't worry - this is only a test!"); + + // Act + var encodedPayload = await encoderPipeline.ProcessAsync(record, CancellationToken.None); + + // Assert + Assert.NotNull(encodedPayload); + Assert.True(encodedPayload.Payload.Length > 0); + Assert.Equal("H4sIAAAAAAAACpPv5mAAA67VYae8z/iGbgoqOnm+W9PUwMBIP1DjvL7WqpALoRonT+iEMSz6s2eOV6tL66Qrj4Rcl0YxiFw4c8oIqBUAdhx5/UQAAAA=", + Convert.ToBase64String(encodedPayload.Payload.Span)); + Assert.NotNull(encodedPayload.Metadata); + Assert.True(encodedPayload.Metadata.ContainsKey("ops")); + Assert.Equal("Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0],Dapr.Compression.Gzip[0],Dapr.Integrity.Sha256[0],Dapr.Compression.Gzip[1],Dapr.Integrity.Sha256[1]", encodedPayload.Metadata["ops"]); + Assert.True(encodedPayload.Metadata.ContainsKey("Dapr.Integrity.Sha256[0]hash")); + Assert.Equal("9+H+ngzx1fru8VdywlpoT0E20JqBXm1k81Un/o7z0ZM=", encodedPayload.Metadata["Dapr.Integrity.Sha256[0]hash"]); + Assert.True(encodedPayload.Metadata.ContainsKey("Dapr.Integrity.Sha256[1]hash")); + Assert.Equal("r9EkN6xWpuB9saAWGy92aGvU0T8dkLt2Kur5/ItSf2s=", encodedPayload.Metadata["Dapr.Integrity.Sha256[1]hash"]); + + // Act #2 + var decoderPipeline = factory.CreateDecodingPipeline(encodedPayload.Metadata); + var decodedPayload = await decoderPipeline.ReverseProcessAsync(encodedPayload.Payload, encodedPayload.Metadata); + + // Assert #2 + Assert.Equal(record, decodedPayload.Payload); + } + + private record SampleRecord(string Name, int Value); + + [DataPipeline(typeof(GzipCompressor), typeof(SystemTextJsonSerializer), typeof(Utf8Encoder), typeof(Sha256Validator))] + private record SimpleRegistration(string Name); + + [DataPipeline(typeof(SystemTextJsonSerializer), typeof(GzipCompressor), typeof(Utf8Encoder), typeof(Sha256Validator), typeof(GzipCompressor), typeof(Sha256Validator))] + private record DuplicateRegistration(string Name); +} diff --git a/test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs b/test/Dapr.Common.Test/Data/DaprEncoderPipelineTests.cs similarity index 65% rename from test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs rename to test/Dapr.Common.Test/Data/DaprEncoderPipelineTests.cs index 157a9ce74..a563d65d9 100644 --- a/test/Dapr.Common.Test/Data/DaprDataPipelineTests.cs +++ b/test/Dapr.Common.Test/Data/DaprEncoderPipelineTests.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Dapr.Common.Data; +using Dapr.Common.Data.Attributes; using Dapr.Common.Data.Operations; using Dapr.Common.Data.Operations.Providers.Compression; using Dapr.Common.Data.Operations.Providers.Encoding; @@ -24,7 +25,7 @@ namespace Dapr.Common.Test.Data; -public class DaprDataPipelineTests +public class DaprEncoderPipelineTests { [Fact] public async Task ProcessAsync_ShouldProcessBasicOperations() @@ -35,7 +36,7 @@ public async Task ProcessAsync_ShouldProcessBasicOperations() new SystemTextJsonSerializer(), new Utf8Encoder() }; - var pipeline = new DaprDataPipeline(operations); + var pipeline = new DaprEncoderPipeline(operations); // Act var result = await pipeline.ProcessAsync(new SampleRecord("Sample", 15)); @@ -43,7 +44,7 @@ public async Task ProcessAsync_ShouldProcessBasicOperations() // Assert Assert.Equal("eyJuYW1lIjoiU2FtcGxlIiwidmFsdWUiOjE1fQ==", Convert.ToBase64String(result.Payload.Span)); Assert.True(result.Metadata.ContainsKey("ops")); - Assert.Equal("Dapr.Serialization.SystemTextJson,Dapr.Encoding.Utf8", result.Metadata["ops"]); + Assert.Equal("Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0]", result.Metadata["ops"]); } [Fact] @@ -57,23 +58,21 @@ public async Task ProcessAsync_ShouldProcessOptionalOperations() new Utf8Encoder(), new Sha256Validator() }; - var pipeline = new DaprDataPipeline(operations); + var pipeline = new DaprEncoderPipeline(operations); // Act var result = await pipeline.ProcessAsync(new SampleRecord("Sample", 15)); - var base64 = Convert.ToBase64String(result.Payload.Span); - Assert.Equal("H4sIAAAAAAAACqtWykvMTVWyUgpOzC3ISVXSUSpLzCkFChia1gIAotvhPBwAAAA=", Convert.ToBase64String(result.Payload.Span)); Assert.Equal(2, result.Metadata.Keys.Count); - Assert.True(result.Metadata.ContainsKey("Dapr.Integrity.Sha256-hash")); - Assert.Equal("x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=", result.Metadata["Dapr.Integrity.Sha256-hash"]); + Assert.True(result.Metadata.ContainsKey("Dapr.Integrity.Sha256[0]hash")); + Assert.Equal("x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=", result.Metadata["Dapr.Integrity.Sha256[0]hash"]); Assert.True(result.Metadata.ContainsKey("ops")); - Assert.Equal("Dapr.Serialization.SystemTextJson,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256", result.Metadata["ops"]); + Assert.Equal("Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0],Dapr.Compression.Gzip[0],Dapr.Integrity.Sha256[0]", result.Metadata["ops"]); } - + [Fact] - public async Task ReverseAsync_ShouldReverseOperationsInMetadataOrder() + public async Task ProcessAsync_ShouldProcessDuplicateOperations() { // Arrange var operations = new List @@ -81,24 +80,20 @@ public async Task ReverseAsync_ShouldReverseOperationsInMetadataOrder() new GzipCompressor(), new SystemTextJsonSerializer(), new Utf8Encoder(), - new Sha256Validator() + new Sha256Validator(), + new GzipCompressor() }; - var pipeline = new DaprDataPipeline(operations); + var pipeline = new DaprEncoderPipeline(operations); // Act - var payload = Convert.FromBase64String("H4sIAAAAAAAACqtWykvMTVWyUgpOzC3ISVXSUSpLzCkFChia1gIAotvhPBwAAAA="); - var metadata = new Dictionary - { - { "Dapr.Integrity.Sha256-hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" }, - { - "ops", - "Dapr.Serialization.SystemTextJson,Dapr.Masking.Regexp,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256" - } - }; - var result = await pipeline.ReverseProcessAsync(payload, metadata); - - Assert.Equal("Sample", result.Payload.Name); - Assert.Equal(15, result.Payload.Value); + var result = await pipeline.ProcessAsync(new SampleRecord("Sample", 15)); + + Assert.Equal("H4sIAAAAAAAACpPv5mAAA67VYae8z/iGbgri8juje8Iz9FKglvcZTVYuiVnXmBgW3X5oIwNUBQAguNy9LwAAAA==", Convert.ToBase64String(result.Payload.Span)); + Assert.Equal(2, result.Metadata.Keys.Count); + Assert.True(result.Metadata.ContainsKey("Dapr.Integrity.Sha256[0]hash")); + Assert.Equal("x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=", result.Metadata["Dapr.Integrity.Sha256[0]hash"]); + Assert.True(result.Metadata.ContainsKey("ops")); + Assert.Equal("Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0],Dapr.Compression.Gzip[0],Dapr.Integrity.Sha256[0],Dapr.Compression.Gzip[1]", result.Metadata["ops"]); } private record SampleRecord(string Name, int Value); diff --git a/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs index 4f4af0970..5723f3201 100644 --- a/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs +++ b/test/Dapr.Common.Test/Data/DataPipelineFactoryTests.cs @@ -46,7 +46,7 @@ public void CreatePipeline_ShouldCreateProcessingPipelineWithCorrectOperations() // Assert Assert.NotNull(pipeline); } - + [Fact] public void CreatePipeline_ShouldThrowIfSerializationTypeNotRegisteredForProcessingPipeline() { @@ -98,7 +98,7 @@ public void CreatePipeline_ShouldThrowIfSerializationTypeNotRegisteredForReverse { "Dapr.Integrity.Sha256-hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" }, { "ops", - "Dapr.Serialization.SystemTextJson,Dapr.Masking.Regexp,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256" + "Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0],Dapr.Compression.Gzip[0],Dapr.Integrity.Sha256[0]" } }; @@ -123,14 +123,35 @@ public void CreatePipeline_ShouldThrowIfEncodingTypeNotRegisteredForReverseProce { "Dapr.Integrity.Sha256-hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" }, { "ops", - "Dapr.Serialization.SystemTextJson,Dapr.Masking.Regexp,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256" + "Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0],Dapr.Compression.Gzip[0],Dapr.Integrity.Sha256[0]" } }; // Act & Assert Assert.Throws(() => factory.CreateDecodingPipeline(metadata)); } + + [Fact] + public void CreatePipeline_ShouldThrowIfOpsNotDefinedInMetadata() + { + // Arrange + var services = new ServiceCollection(); + services.AddDaprDataProcessingPipeline() + .WithCompressor() + .WithIntegrity(_ => new Sha256Validator()) + .WithSerializer>(); + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetRequiredService(); + var metadata = new Dictionary + { + { "Dapr.Integrity.Sha256[0]hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" } + }; + + // Act & Assert + Assert.Throws(() => factory.CreateDecodingPipeline(metadata)); + } + [Fact] public void CreatePipeline_ShouldCreateReversePipelineWithCorrectOperations() { @@ -149,7 +170,7 @@ public void CreatePipeline_ShouldCreateReversePipelineWithCorrectOperations() { "Dapr.Integrity.Sha256-hash", "x9yYvPm6j9Xd7X1Iwz08iQFKidQQXR9giprO3SBZg7Y=" }, { "ops", - "Dapr.Serialization.SystemTextJson,Dapr.Encoding.Utf8,Dapr.Compression.Gzip,Dapr.Integrity.Sha256" + "Dapr.Serialization.SystemTextJson[0],Dapr.Encoding.Utf8[0],Dapr.Compression.Gzip[0],Dapr.Integrity.Sha256[0]" } }; diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs index 5e6f49728..be15e63e3 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Compression/GzipCompressorTest.cs @@ -46,7 +46,7 @@ public async Task ReverseAsync_ShouldDecompressData() var compressedResult = await compressor.ExecuteAsync(input); // Act - var result = await compressor.ReverseAsync(compressedResult, CancellationToken.None); + var result = await compressor.ReverseAsync(compressedResult, string.Empty, CancellationToken.None); // Assert Assert.NotNull(result); diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Encoding/Utf8EncoderTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Encoding/Utf8EncoderTest.cs index d6d7b33f4..57eaf95c6 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Encoding/Utf8EncoderTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Encoding/Utf8EncoderTest.cs @@ -32,7 +32,7 @@ public async Task ReverseAsync_ShouldDecodeData() var encodedResult = await encoder.ExecuteAsync(input); // Act - var reverseResult = await encoder.ReverseAsync(encodedResult, CancellationToken.None); + var reverseResult = await encoder.ReverseAsync(encodedResult, string.Empty, CancellationToken.None); // Assert Assert.NotNull(reverseResult); diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs index 0018483ac..d49a5377d 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Serialization/SystemTextJsonSerializerTest.cs @@ -44,7 +44,7 @@ public async Task ReverseAsync_ShouldDeserialize() var payload = new DaprOperationPayload(input); //Act - var result = await validator.ReverseAsync(payload); + var result = await validator.ReverseAsync(payload, string.Empty); //Assert Assert.NotNull(result); From 1d57fd5d06d2cc9bc8f958297148dfe69870b369 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 23 Oct 2024 00:56:44 -0500 Subject: [PATCH 09/12] Updated unit tests + unit test class name Signed-off-by: Whit Waldo --- ...256ValidatorTest.cs => Sha256ValidatorTest.cs} | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) rename test/Dapr.Common.Test/Data/Operators/Providers/Integrity/{DaprSha256ValidatorTest.cs => Sha256ValidatorTest.cs} (84%) diff --git a/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/Sha256ValidatorTest.cs similarity index 84% rename from test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs rename to test/Dapr.Common.Test/Data/Operators/Providers/Integrity/Sha256ValidatorTest.cs index 8586a1e93..859643f22 100644 --- a/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/DaprSha256ValidatorTest.cs +++ b/test/Dapr.Common.Test/Data/Operators/Providers/Integrity/Sha256ValidatorTest.cs @@ -34,7 +34,7 @@ public async Task ExecuteAsync_ShouldCalculateChecksum() // Assert Assert.NotNull(result); - Assert.True(result.Metadata.ContainsKey($"{validator.Name}-hash")); + Assert.True(result.Metadata.ContainsKey("hash")); } [Fact] @@ -45,7 +45,7 @@ public async Task ReverseAsync_ShouldValidateChecksumWithoutMetadataHeader() var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); var result = new DaprOperationPayload>(input); - await validator.ReverseAsync(result, CancellationToken.None); + await validator.ReverseAsync(result, $"{validator.Name}[0]", CancellationToken.None); } [Fact] @@ -57,7 +57,7 @@ public async Task ReverseAsync_ShouldValidateChecksum() var result = await validator.ExecuteAsync(input); // Act & Assert - await validator.ReverseAsync(result, CancellationToken.None); + await validator.ReverseAsync(result, $"{validator.Name}[0]", CancellationToken.None); } [Fact] @@ -67,9 +67,12 @@ public async Task ReverseAsync_ShouldThrowExceptionForInvalidChecksum() var validator = new Sha256Validator(); var input = new ReadOnlyMemory(new byte[] { 1, 2, 3, 4, 5 }); var result = await validator.ExecuteAsync(input); - result = result with { Payload = new ReadOnlyMemory(new byte[] { 6, 7, 8, 9, 0 }) }; - + result = result with + { + Payload = new ReadOnlyMemory(new byte[] { 6, 7, 8, 9, 0 }) + }; + // Act & Assert - await Assert.ThrowsAsync(() => validator.ReverseAsync(result, CancellationToken.None)); + await Assert.ThrowsAsync(() => validator.ReverseAsync(result,string.Empty, CancellationToken.None)); } } From 0cd258693be2d32fd6c112ea911f8b0d8aa83b37 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 23 Oct 2024 01:52:52 -0500 Subject: [PATCH 10/12] Minor file rename Signed-off-by: Whit Waldo --- .../Integrity/{DaprSha256Validator.cs => Sha256Validator.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Dapr.Common/Data/Operations/Providers/Integrity/{DaprSha256Validator.cs => Sha256Validator.cs} (100%) diff --git a/src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs b/src/Dapr.Common/Data/Operations/Providers/Integrity/Sha256Validator.cs similarity index 100% rename from src/Dapr.Common/Data/Operations/Providers/Integrity/DaprSha256Validator.cs rename to src/Dapr.Common/Data/Operations/Providers/Integrity/Sha256Validator.cs From 7ca1b6bf1d9a49141720cb97f305ed124447da86 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 23 Oct 2024 02:13:33 -0500 Subject: [PATCH 11/12] Removed outdated project reference Signed-off-by: Whit Waldo --- src/Dapr.Common/Dapr.Common.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Dapr.Common/Dapr.Common.csproj b/src/Dapr.Common/Dapr.Common.csproj index 767cf1b41..31af3952c 100644 --- a/src/Dapr.Common/Dapr.Common.csproj +++ b/src/Dapr.Common/Dapr.Common.csproj @@ -13,8 +13,4 @@ - - - - From fcf02b02e80ec3a84950ce3b015a07ebf549fa35 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 23 Oct 2024 02:14:30 -0500 Subject: [PATCH 12/12] Removed unnecessary usings that were inadvertently added Signed-off-by: Whit Waldo --- src/Dapr.Client/BindingRequest.cs | 1 - src/Dapr.Client/BindingResponse.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Dapr.Client/BindingRequest.cs b/src/Dapr.Client/BindingRequest.cs index 3056e4cc6..5448588ac 100644 --- a/src/Dapr.Client/BindingRequest.cs +++ b/src/Dapr.Client/BindingRequest.cs @@ -13,7 +13,6 @@ using System; using System.Collections.Generic; -using Dapr.Common; namespace Dapr.Client { diff --git a/src/Dapr.Client/BindingResponse.cs b/src/Dapr.Client/BindingResponse.cs index f097b7352..f362533e4 100644 --- a/src/Dapr.Client/BindingResponse.cs +++ b/src/Dapr.Client/BindingResponse.cs @@ -13,7 +13,6 @@ using System; using System.Collections.Generic; -using Dapr.Common; namespace Dapr.Client {