From b9dc28ed32d9f466c734cde1223eb2e4910771af Mon Sep 17 00:00:00 2001 From: Vincent van Proosdij Date: Wed, 23 Oct 2024 17:36:24 +0000 Subject: [PATCH] Merged PR 8150: Added new Filter Feature + More repository Result extensions + nuget updates --- BridgingIT.DevKit.sln | 12 + Directory.Packages.props | 10 +- README.md | 25 +- docs/features-commands.md | 18 + docs/features-documentstorage.md | 18 + docs/features-domain-models.md | 24 + docs/features-domain-repositories.md | 18 + docs/features-filtering.md | 1607 +++++++++++++++++ docs/features-jobscheduling.md | 18 + docs/features-messaging.md | 18 + docs/features-modules.md | 18 + docs/features-queries.md | 18 + docs/features-results.md | 1002 ++++++++++ docs/features-startuptasks.md | 18 + .../Core/Core.Application/packages.lock.json | 32 +- .../Core/Core.Domain/packages.lock.json | 24 +- .../Core.EndToEndTests/packages.lock.json | 62 +- .../Core.Infrastructure/packages.lock.json | 26 +- .../Core.IntegrationTests/packages.lock.json | 62 +- .../DinnerFiesta.Core.Presentation.csproj | 19 +- .../Web/Controllers/CoreController.cs | 17 + .../Core/Core.Presentation/packages.lock.json | 26 +- .../Core/Core.UnitTests/packages.lock.json | 28 +- .../Marketing.Application/packages.lock.json | 32 +- .../Marketing.Domain/packages.lock.json | 24 +- .../packages.lock.json | 62 +- .../packages.lock.json | 26 +- .../packages.lock.json | 38 +- ...DinnerFiesta.Marketing.Presentation.csproj | 19 +- .../Marketing.Presentation/packages.lock.json | 26 +- .../Marketing.UnitTests/packages.lock.json | 28 +- .../packages.lock.json | 6 +- ...innerFiesta.Presentation.Web.Server.csproj | 22 +- .../Presentation.Web.Server/Program.cs | 1 + .../packages.lock.json | 36 +- .../packages.lock.json | 32 +- .../packages.lock.json | 24 +- .../packages.lock.json | 26 +- .../packages.lock.json | 26 +- .../packages.lock.json | 28 +- .../WeatherForecast/WeatherForecast-REST.http | 23 +- .../Modules/Core/Queries/CityFindAllQuery.cs | 5 +- .../Core/Queries/CityFindAllQueryHandler.cs | 6 +- .../Core/Queries/ForecastFindAllQuery.cs | 6 +- .../Queries/ForecastFindAllQueryHandler.cs | 4 +- .../packages.lock.json | 32 +- .../WeatherForecast.Domain/packages.lock.json | 24 +- .../packages.lock.json | 64 +- .../packages.lock.json | 26 +- .../packages.lock.json | 40 +- .../packages.lock.json | 6 +- .../Core/Controllers/CityController.cs | 15 +- .../Core/Controllers/ForecastController.cs | 18 +- .../Modules/Core/CoreModule.cs | 6 +- .../Program.cs | 1 + .../packages.lock.json | 36 +- .../packages.lock.json | 32 +- .../packages.lock.json | 32 +- src/Application.Commands/packages.lock.json | 32 +- .../packages.lock.json | 32 +- src/Application.Entities/packages.lock.json | 32 +- .../packages.lock.json | 23 +- src/Application.Messaging/packages.lock.json | 23 +- src/Application.Queries/packages.lock.json | 23 +- src/Application.Storage/packages.lock.json | 15 +- src/Application.Utilities/packages.lock.json | 23 +- .../Filtering/FilterModel.cs | 223 +++ src/Common.Abstractions/OrderDirection.cs | 13 + .../Result/Errors/ExceptionError.cs | 43 + src/Common.Caching/packages.lock.json | 15 +- src/Common.Extensions/DateTimeExtension .cs | 120 -- src/Common.Extensions/DateTimeExtensions.cs | 224 +++ src/Common.Extensions/ExceptionExtensions.cs | 7 + src/Common.Extensions/TimeSpanExtensions.cs | 123 ++ src/Common.Extensions/TypeExtensions.cs | 20 + src/Common.Mapping/packages.lock.json | 15 +- src/Common.Modules/packages.lock.json | 21 +- .../Common.Serialization.csproj | 4 + .../Converters/EnumConverter.cs | 36 + .../Converters/FilterCriteriaJsonConverter.cs | 133 ++ .../FilterSpecificationNodeConverter.cs | 58 + .../Converters/JsonTypeConverter.cs | 3 +- .../Converters/NewtonSoftConverters.cs | 79 + .../DefaultJsonNetSerializerSettings.cs | 5 +- .../DefaultSystemTextJsonSerializerOptions.cs | 15 +- src/Common.Serialization/packages.lock.json | 18 +- src/Common.Utilities.Xunit/packages.lock.json | 23 +- src/Common.Utilities/packages.lock.json | 15 +- .../packages.lock.json | 24 +- .../packages.lock.json | 24 +- src/Domain.EventSourcing/packages.lock.json | 24 +- src/Domain.Mediator/MediatRInspector.cs | 2 + src/Domain.Mediator/packages.lock.json | 24 +- src/Domain.Outbox/packages.lock.json | 24 +- src/Domain/Domain.csproj | 1 + .../Filtering/CustomSpecificationBuilder.cs | 746 ++++++++ src/Domain/Filtering/IncludeOptionBuilder.cs | 35 + src/Domain/Filtering/OrderOptionBuilder.cs | 36 + src/Domain/Filtering/SpecificationBuilder.cs | 365 ++++ src/Domain/Filtering/SpecificationResolver.cs | 72 + .../Behaviors/RepositoryLoggingBehavior.cs | 20 +- src/Domain/Repositories/IGenericRepository.cs | 4 +- src/Domain/Repositories/OrderDirection.cs | 4 +- .../Repositories/RepositoryExtensions.cs | 88 + .../RepositoryResultExtensions.cs | 281 --- ...nericReadOnlyRepositoryResultExtensions.cs | 835 +++++++++ .../GenericRepositoryResultExtensions.cs | 142 ++ src/Domain/Specifications/Specification.cs | 90 +- src/Domain/packages.lock.json | 21 +- .../packages.lock.json | 24 +- .../packages.lock.json | 24 +- .../packages.lock.json | 24 +- .../packages.lock.json | 23 +- .../packages.lock.json | 15 +- .../packages.lock.json | 26 +- .../packages.lock.json | 26 +- .../packages.lock.json | 26 +- .../packages.lock.json | 26 +- .../packages.lock.json | 26 +- .../packages.lock.json | 26 +- .../packages.lock.json | 26 +- .../packages.lock.json | 26 +- .../packages.lock.json | 26 +- src/Infrastructure.LiteDB/packages.lock.json | 18 +- src/Infrastructure.Mapping/packages.lock.json | 24 +- src/Infrastructure.Pulsar/packages.lock.json | 23 +- .../packages.lock.json | 23 +- .../packages.lock.json | 23 +- src/Presentation.Serilog/packages.lock.json | 6 +- .../packages.lock.json | 32 +- .../Filtering/FromBodyFilterAttribute.cs | 11 + .../Filtering/FromBodyFilterModelBinder.cs | 39 + .../Filtering/FromQueryFilterAttribute.cs | 11 + .../Filtering/FromQueryFilterModelBinder.cs | 42 + .../Filtering/HttpContextExtensions.cs | 103 ++ .../Host/Configure.ProblemDetails.cs | 3 +- src/Presentation.Web/packages.lock.json | 32 +- .../packages.lock.json | 28 +- .../Application.UnitTests/packages.lock.json | 34 +- .../Extensions/DateTimeExtensionsTests.cs | 220 +++ .../Extensions/TimeSpanExtensionsTests.cs | 463 ++++- .../FilterModelSystemTextJsonTests.cs | 473 +++++ .../Serialization/SerializerTestsBase.cs | 2 +- .../Tracing/TraceActivityDecoratorTests.cs | 2 +- tests/Common.UnitTests/packages.lock.json | 25 +- .../packages.lock.json | 34 +- .../Filtering/IncludeOptionBuilderTests.cs | 75 + .../Filtering/OrderOptionBuilderTests.cs | 82 + .../Filtering/SpecificationBuilderTests.cs | 988 ++++++++++ .../Filtering/SpecificationResolverTests.cs | 135 ++ .../Domain/Model/EnumerationTests.cs | 2 +- ...ReadOnlyRepositoryResultExtensionsTests.cs | 848 +++++++++ .../RepositoryResultFilterExtensionsTests.cs | 193 ++ .../Specifications/SpecificationTests.cs | 154 ++ tests/Domain.UnitTests/_shared/Stubs.cs | 108 ++ tests/Domain.UnitTests/packages.lock.json | 34 +- ...tityFrameworkGenericRepositoryTestsBase.cs | 109 +- ...tityFrameworkGenericRepositoryTestsBase.cs | 751 ++++++++ ...rkSqlServerGenericRepositoryResultTests.cs | 164 ++ .../_shared/Stubs.cs | 4 +- .../packages.lock.json | 28 +- .../packages.lock.json | 28 +- .../Filtering/HttpContextExtensionsTests.cs | 191 ++ .../Presentation.UnitTests/packages.lock.json | 34 +- 164 files changed, 12624 insertions(+), 1549 deletions(-) create mode 100644 docs/features-commands.md create mode 100644 docs/features-documentstorage.md create mode 100644 docs/features-domain-models.md create mode 100644 docs/features-domain-repositories.md create mode 100644 docs/features-filtering.md create mode 100644 docs/features-jobscheduling.md create mode 100644 docs/features-messaging.md create mode 100644 docs/features-modules.md create mode 100644 docs/features-queries.md create mode 100644 docs/features-results.md create mode 100644 docs/features-startuptasks.md create mode 100644 src/Common.Abstractions/Filtering/FilterModel.cs create mode 100644 src/Common.Abstractions/OrderDirection.cs create mode 100644 src/Common.Abstractions/Result/Errors/ExceptionError.cs delete mode 100644 src/Common.Extensions/DateTimeExtension .cs create mode 100644 src/Common.Extensions/DateTimeExtensions.cs create mode 100644 src/Common.Serialization/Converters/EnumConverter.cs create mode 100644 src/Common.Serialization/Converters/FilterCriteriaJsonConverter.cs create mode 100644 src/Common.Serialization/Converters/FilterSpecificationNodeConverter.cs create mode 100644 src/Common.Serialization/Converters/NewtonSoftConverters.cs create mode 100644 src/Domain/Filtering/CustomSpecificationBuilder.cs create mode 100644 src/Domain/Filtering/IncludeOptionBuilder.cs create mode 100644 src/Domain/Filtering/OrderOptionBuilder.cs create mode 100644 src/Domain/Filtering/SpecificationBuilder.cs create mode 100644 src/Domain/Filtering/SpecificationResolver.cs delete mode 100644 src/Domain/Repositories/RepositoryResultExtensions.cs create mode 100644 src/Domain/Repositories/Result/GenericReadOnlyRepositoryResultExtensions.cs create mode 100644 src/Domain/Repositories/Result/GenericRepositoryResultExtensions.cs create mode 100644 src/Presentation.Web/Filtering/FromBodyFilterAttribute.cs create mode 100644 src/Presentation.Web/Filtering/FromBodyFilterModelBinder.cs create mode 100644 src/Presentation.Web/Filtering/FromQueryFilterAttribute.cs create mode 100644 src/Presentation.Web/Filtering/FromQueryFilterModelBinder.cs create mode 100644 src/Presentation.Web/Filtering/HttpContextExtensions.cs create mode 100644 tests/Common.UnitTests/Serialization/Filtering/FilterModelSystemTextJsonTests.cs create mode 100644 tests/Domain.UnitTests/Domain/Filtering/IncludeOptionBuilderTests.cs create mode 100644 tests/Domain.UnitTests/Domain/Filtering/OrderOptionBuilderTests.cs create mode 100644 tests/Domain.UnitTests/Domain/Filtering/SpecificationBuilderTests.cs create mode 100644 tests/Domain.UnitTests/Domain/Filtering/SpecificationResolverTests.cs create mode 100644 tests/Domain.UnitTests/Repositories/GenericReadOnlyRepositoryResultExtensionsTests.cs create mode 100644 tests/Domain.UnitTests/Repositories/RepositoryResultFilterExtensionsTests.cs create mode 100644 tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/Result/EntityFrameworkGenericRepositoryTestsBase.cs create mode 100644 tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/Result/EntityFrameworkSqlServerGenericRepositoryResultTests.cs create mode 100644 tests/Presentation.UnitTests/Web/Filtering/HttpContextExtensionsTests.cs diff --git a/BridgingIT.DevKit.sln b/BridgingIT.DevKit.sln index 88c17064..36a45a53 100644 --- a/BridgingIT.DevKit.sln +++ b/BridgingIT.DevKit.sln @@ -57,6 +57,18 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure.AutoMapper", "src\Infrastructure.AutoMapper\Infrastructure.AutoMapper.csproj", "{B4D8B3F4-A9EA-4F74-A9F8-D7F15F45946C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{16A3992B-4AAB-482C-AF16-A3434D9716A8}" + ProjectSection(SolutionItems) = preProject + docs\features-filtering.md = docs\features-filtering.md + docs\features-commands.md = docs\features-commands.md + docs\features-messaging.md = docs\features-messaging.md + docs\features-queries.md = docs\features-queries.md + docs\features-modules.md = docs\features-modules.md + docs\features-jobscheduling.md = docs\features-jobscheduling.md + docs\features-startuptasks.md = docs\features-startuptasks.md + docs\features-documentstorage.md = docs\features-documentstorage.md + docs\features-domain-repositories.md = docs\features-domain-repositories.md + docs\features-results.md = docs\features-results.md + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure.EntityFramework.EventSourcing.SqlServer", "src\Infrastructure.EntityFramework.EventSourcing.SqlServer\Infrastructure.EntityFramework.EventSourcing.SqlServer.csproj", "{F8BD56DD-C6F9-4853-9E09-F761F3CD2051}" EndProject diff --git a/Directory.Packages.props b/Directory.Packages.props index 254bd089..fcacfe82 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -37,7 +37,7 @@ - + @@ -78,10 +78,10 @@ - - + + - + @@ -105,7 +105,7 @@ - + diff --git a/README.md b/README.md index 8849b7a0..4da3e9c5 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,13 @@ the [RELEASES](https://raw.githubusercontent.com/bridgingIT/bITdevKit/main/RELEA Join us in advancing the world of software development with the bITDevKit! + +* [Supported patterns, elements:](#supported-patterns-elements) +* [Features (excerpt):](#features-excerpt) +* [Libraries used (excerpt):](#libraries-used-excerpt) +* [Example projects](#example-projects) +* [Collaboration](#collaboration) + Supported patterns, elements: -------------------------------- @@ -31,7 +38,7 @@ Supported patterns, elements: - ValueObjects - TypedId - DomainEvents -- BusinesRules, Check +- DomainRules/Policies - Repository - Specifications - Commands/Queries @@ -43,15 +50,19 @@ Supported patterns, elements: Features (excerpt): ------------------------------------- +- [Results](./docs/features-results.md) +- [Commands](./docs/features-commands.md) & [Queries](./docs/features-queries.md) +- [Domain Model](./docs/features-domain-models.md) +- [Domain Repositories](./docs/features-domain-repositories.md) +- [Modules](./docs/features-modules.md) +- [Filtering](./docs/features-filtering.md) +- [Messaging](./docs/features-messaging.md) +- [JobScheduling](./docs/features-jobscheduling.md) +- [StartupTasks](./docs/features-startuptasks.md) +- [DocumentStorage](./docs/features-documentstorage.md) - EventStore (CQRS) -- Job Scheduling -- Storage - - Documents - - Files (TODO) - Caching -- Messaging - Queuing (TODO) -- Modules Libraries used (excerpt): ------------------------------------- diff --git a/docs/features-commands.md b/docs/features-commands.md new file mode 100644 index 00000000..2f8f2b31 --- /dev/null +++ b/docs/features-commands.md @@ -0,0 +1,18 @@ +# Commands Feature Documentation + + + +* [Overview](#overview) +* [Usage](#usage) + + + +## Overview + +### Challenges + +### Solution + +### Use Cases + +## Usage \ No newline at end of file diff --git a/docs/features-documentstorage.md b/docs/features-documentstorage.md new file mode 100644 index 00000000..d8d0d006 --- /dev/null +++ b/docs/features-documentstorage.md @@ -0,0 +1,18 @@ +# DocumentStorage Feature Documentation + + + +* [Overview](#overview) +* [Usage](#usage) + + + +## Overview + +### Challenges + +### Solution + +### Use Cases + +## Usage \ No newline at end of file diff --git a/docs/features-domain-models.md b/docs/features-domain-models.md new file mode 100644 index 00000000..b48a42d4 --- /dev/null +++ b/docs/features-domain-models.md @@ -0,0 +1,24 @@ +# Domain Models Feature Documentation + + + +* [Overview](#overview) +* [Aggregates](#aggregates) +* [Specifications](#specifications) +* [Entities](#entities) +* [Events](#events) +* [ValueObjects](#valueobjects) +* [TypedIds](#typedids) +* [Rules/Policies](#rules-policies) + + + +## Overview + +### Challenges + +### Solution + +### Use Cases + +outbox \ No newline at end of file diff --git a/docs/features-domain-repositories.md b/docs/features-domain-repositories.md new file mode 100644 index 00000000..4a3dace9 --- /dev/null +++ b/docs/features-domain-repositories.md @@ -0,0 +1,18 @@ +# Domain Repositories Feature Documentation + + + +* [Overview](#overview) +* [Usage](#usage) + + + +## Overview + +### Challenges + +### Solution + +### Use Cases + +## Usage \ No newline at end of file diff --git a/docs/features-filtering.md b/docs/features-filtering.md new file mode 100644 index 00000000..998fcd8e --- /dev/null +++ b/docs/features-filtering.md @@ -0,0 +1,1607 @@ +# Filtering Feature Documentation + + + +* [Overview](#overview) +* [Request Flow Diagram](#request-flow-diagram) +* [Filter Model Structure](#filter-model-structure) +* [API Implementation](#api-implementation) +* [HTTP Request Examples](#http-request-examples) +* [HTTP Response Format](#http-response-format) +* [Best Practices](#best-practices) +* [Standard Filter Operators](#standard-filter-operators) +* [Custom Filter Types](#custom-filter-types) +* [Complex Filter Examples](#complex-filter-examples) +* [Appendix A: Angular Usage Guide](#appendix-a-angular-usage-guide) +* [Appendix B: Flow Diagram](#appendix-b-flow-diagram) +* [Appendix C: Disclaimer](#appendix-c-disclaimer) + + + +## Overview + +> The Filtering feature provides a flexible and powerful way to filter, sort, and paginate data +> through API requests. It allows clients to construct complex queries using a JSON-based filter +> model that gets translated into domain specifications on the server side. The translated filter +> model can easily be handled by the DevKit repositories. + +```mermaid +graph LR + R[Client Request]-->|filter|E[Endpoint]-->|filter|Q[QueryHandler]-->|filter|P[Repository] + P-->|query|D[(Database)] + P-.->|PagedResult|R +``` + +### Challenges + +> Modern applications require complex data querying capabilities where clients need to: + +- Filter data based on multiple conditions +- Combine different filter types (equality, ranges, text search, etc.) +- Sort results by multiple fields +- Include related entities (eager loading) +- Paginate results for better performance +- Handle nested entity relationships +- Support dynamic query building + +> Traditional REST APIs often struggle with these requirements, leading to: + +- Multiple specialized endpoints for different query scenarios +- Complex URL parameters that are hard to maintain +- Limited query capabilities +- Poor reusability across different entity types + +### Solution + +> The Filtering feature solves these challenges by providing: + +1. **Unified Query Interface** + - Single, consistent way to express complex queries + - Works across different entity types + - Supports both simple and complex filtering scenarios + - No need to create custom endpoints for each query scenario╬ + +2. **Type-Safe Implementation** + - Strongly-typed models for both client and server (Swagger) + - Compile-time validation of filter structures + - Clear contract between frontend and backend (FilterModel) + +3. **Flexible Architecture** + - Extensible design for more custom filter types [TODO] + - Support for additional domain-specific specifications + - Easy integration with existing repositories (FindOptions) + +4. **Performance Optimization** + - Built-in pagination support + - Efficient query building (Expressions) + - Optimized database access through specifications + +### Use Cases + +1. **Data Grids, Tables and Lists** + - Dynamic column filtering + - Multi-column sorting + - Server-side pagination + +2. **Search Interfaces** + - Full-text search across multiple fields + - Combined filters (date ranges, categories, status) + - Related entity filtering + +3. **Lookup lists** + - Dynamic data loading for select components + - Type-ahead/autocomplete requests + +## Request Flow Diagram + +```mermaid +sequenceDiagram + participant C as Client + participant A as API Controller + participant H as Query Handler + participant R as Repository + participant S as SpecificationBuilder + participant O as OrderOptionBuilder + participant I as IncludeOptionBuilder + participant D as Database + + C->>+A: HTTP Request with FilterModel + A->>+H: Send Query(FilterModel) + H->>+R: FindAllAsync(FilterModel) + + par Build FindOptions + R->>+S: Build + S-->>-R: Specifications + R->>+O: Build + O-->>-R: OrderOptions + R->>+I: Build + I-->>-R: IncludeOptions + end + + R->>+D: Execute Query (FindOptions) + D-->>-R: Raw Results + R-->>-H: PagedResult + H-->>-A: Response + A-->>-C: HTTP Response (PagedResult) +``` + +The following sections detail the implementation and usage of the Filtering feature, providing +comprehensive examples and best practices for common scenarios. + +## Filter Model Structure + +```json +{ + "page": 1, + "pageSize": 10, + "filters": [ + { + "field": "name", + "operator": "eq|neq|isnull|isnotnull|isempty|isnotempty|gt|gte|lt|lte|contains|doesnotcontain|startswith|doesnotstartwith|endswith|doesnotendwith|any|all|none", + "value": "any", + "logic": "and|or", + "customType": "none|fulltextsearch|daterange|daterelative|timerange|timerelative|numericrange|isnull|isnotnull|enumvalues|textin|textnotin|numericin|numericnotin|namedspecification|compositespecification", + "customParameters": { + "key": "value" + }, + "specificationName": "name", + "specificationArguments": [], + "compositeSpecification": { + "nodes": [] + } + } + ], + "orderings": [ + { + "field": "name", + "direction": "asc|desc" + } + ], + "includes": [ + "name" + ] +} +``` + +## API Implementation + +### ASP.NET Controller Example + +```csharp +[ApiController] +[Route("api/[controller]")] +public class UsersController : ControllerBase +{ + ... + [HttpGet] + public async Task>> GetAll( + [FromQueryFilter] FilterModel filter) + { + // or: var filter await this.HttpContext.FromQueryFilterAsync(); + var response = await mediator.Send(new UserFindAllQuery(filter)); // handler calls repository.FindAllPagedResultAsync(filter) + + return Ok(mapper.Map(response)); + } + + [HttpPost("search")] + public async Task>> Search( + [FromBodyFilter] FilterModel filter) + { + // or: var filter await this.HttpContext.FromBodyFilterAsync(); + var response = await mediator.Send(new UserSearchQuery(filter)); // handler calls repository.FindAllPagedResultAsync(filter) + + return Ok(mapper.Map(response)); + } +} +``` + +### ASP.NET Minimal API Example + +```csharp +app.MapGet("/api/users/search", async (HttpContext context, IMediator mediator, CancellationToken cancellationToken) => +{ + var filter = await context.FromQueryFilterAsync(); + var response = await mediator.Send( + new UserSearchQuery(filter), cancellationToken); // handler calls repository.FindAllPagedResultAsync(filter) + + return Results.Ok(response); +}); +``` + +### Repository Usage (QueryHandler) + +```csharp +public class UserQueryHandler : IRequestHandler> +{ + private readonly IGenericReadOnlyRepository repository; + + public UserQueryHandler(IGenericReadOnlyRepository repository) + { + this.repository = repository; + } + + public async Task> Handle( + UserFindAllQuery query, + CancellationToken cancellationToken) + { + return await repository.FindAllResultAsync( + query.Filter, + cancellationToken: cancellationToken); + } +} +``` + +## HTTP Request Examples + +### GET Request + +Simple filter as URL parameters: + +```http +GET /api/core/cities?filter={"page":1,"pageSize":10,"filters":[{"field":"Name","operator":"eq","value":"Berlin"}]} HTTP/1.1 +Accept: application/json +``` + +URL-encoded for more complex filters: + +[URL-encode](https://en.wikipedia.org/wiki/Percent-encoding) the filter JSON and put it into a +single query string parameter named `filter`.: + +```json +{ + "page": 1, + "pageSize": 10, + "filters": [ + { + "field": "name", + "operator": "eq", + "value": "John" + } + ] +} // encoded to %7B%22page%22%3A1%2C%22pageSize%2.... +``` + +```http +GET /api/users?filter=api/users?filter=%7B%22page%22%3A1%2C%22pageSize%22%3A10%2C%22filters%22%3A%5B%7B%22field%22%3A%22name%22%2C%22operator%22%3A%22eq%22%2C%22value%22%3A%22John%22%7D%5D%7D HTTP/1.1 +Accept: application/json +``` + +The following considerations apply to HTTP GET requests: + +- HTTP GET requests should be URL-encoded to prevent issues with special characters. +- HTTP GET requests size limits may apply, consider using POST for large filter models. +- HTTP GET requests parameters are visible in logs and browser history. +- HTTP GET requests should be kept short and readable for maintainability. + +### POST Request + +```http +POST /api/users/search HTTP/1.1 +Host: api.example.com +Content-Type: application/json +Accept: application/json + +{ + "page": 1, + "pageSize": 20, + "filters": [ + { + "customType": "daterange", + "customParameters": { + "field": "createdAt", + "startDate": "2024-01-01T00:00:00Z", + "endDate": "2024-12-31T23:59:59Z", + "inclusive": true + } + }, + { + "field": "department.name", + "operator": "eq", + "value": "Engineering", + "logic": "and" + } + ], + "orderings": [ + { + "field": "lastName", + "direction": "asc" + } + ], + "includes": [ + "department", + "assignments" + ] +} +``` + +The following considerations apply to HTTP POST requests: + +- HTTP POST requests can handle larger payloads than GET requests. +- HTTP POST requests are more secure for sensitive data. +- HTTP POST requests can be used for complex filter models. +- HTTP POST requests are not cached by browsers. + +## HTTP Response Format + +### Successful Response + +```json +{ + "success": true, + "messages": [ + "Data retrieved successfully" + ], + "errors": [], + "value": [ + { + "id": 1, + "firstName": "John", + "lastName": "Doe", + "email": "john.doe@example.com", + "department": { + "id": 1, + "name": "Engineering" + } + } + ], + "currentPage": 1, + "totalPages": 5, + "totalCount": 100, + "pageSize": 20, + "hasPreviousPage": false, + "hasNextPage": true +} +``` + +### Error Response + +```json +{ + "success": false, + "messages": [ + "Failed to retrieve data" + ], + "errors": [ + { + "code": "INVALID_FILTER", + "message": "Invalid filter parameters provided" + } + ], + "value": null, + "currentPage": 0, + "totalPages": 0, + "totalCount": 0, + "pageSize": 0, + "hasPreviousPage": false, + "hasNextPage": false +} +``` + +### Response Properties + +#### Base Result Properties + +- `success`: Indicates if the request was successful +- `messages`: Array of informational or error messages +- `errors`: Array of structured error objects when success is false +- `value`: Collection of items for the current page + +#### Pagination Metadata + +- `currentPage`: Current page number (1-based) +- `totalPages`: Total number of pages available +- `totalCount`: Total number of items across all pages +- `pageSize`: Number of items per page +- `hasPreviousPage`: Indicates if a previous page exists +- `hasNextPage`: Indicates if a next page exists + +## Best Practices + +1. **Request Method Selection** + - Use GET for simple queries and basic filtering + - Use POST for complex filters or when URL length might be an issue + - Consider using POST when sending sensitive filter data + +2. **Performance Considerations** + - Keep page sizes reasonable (recommended: 10-50 items) + - Use includes selectively to prevent excessive data loading + - Consider adding indexes for commonly filtered fields + +3. **Error Handling** + - Always check the `success` property in responses + - Handle error messages appropriately in your client application + - Log error details for debugging purposes + +4. **Security** + - Validate all filter inputs server-side + - Implement appropriate rate limiting + - Consider adding pagination limits to prevent DOS attacks + +# Standard Filter Operators + +## Comparison Operators + +### Equal (eq) + +Matches exact values + +```json +{ + "field": "status", + "operator": "eq", + "value": "active" +} +``` + +### Not Equal (neq) + +Matches values that are not equal + +```json +{ + "field": "status", + "operator": "neq", + "value": "deleted" +} +``` + +### Greater Than (gt) + +```json +{ + "field": "age", + "operator": "gt", + "value": 18 +} +``` + +### Greater Than or Equal (gte) + +```json +{ + "field": "price", + "operator": "gte", + "value": 100.00 +} +``` + +### Less Than (lt) + +```json +{ + "field": "stock", + "operator": "lt", + "value": 10 +} +``` + +### Less Than or Equal (lte) + +```json +{ + "field": "temperature", + "operator": "lte", + "value": 25.5 +} +``` + +## String Operators + +### Contains + +```json +{ + "field": "description", + "operator": "contains", + "value": "premium" +} +``` + +### Does Not Contain + +```json +{ + "field": "title", + "operator": "doesnotcontain", + "value": "test" +} +``` + +### Starts With + +```json +{ + "field": "email", + "operator": "startswith", + "value": "admin" +} +``` + +### Does Not Start With + +```json +{ + "field": "code", + "operator": "doesnotstartwith", + "value": "TMP" +} +``` + +### Ends With + +```json +{ + "field": "filename", + "operator": "endswith", + "value": ".pdf" +} +``` + +### Does Not End With + +```json +{ + "field": "url", + "operator": "doesnotendwith", + "value": "/temp" +} +``` + +## Null Checks + +### Is Null + +```json +{ + "field": "deletedAt", + "operator": "isnull" +} +``` + +### Is Not Null + +```json +{ + "field": "email", + "operator": "isnotnull" +} +``` + +## Empty Checks + +### Is Empty + +```json +{ + "field": "notes", + "operator": "isempty" +} +``` + +### Is Not Empty + +```json +{ + "field": "phoneNumber", + "operator": "isnotempty" +} +``` + +## Collection Operators + +### Any + +Matches if any child element satisfies the condition + +```json +{ + "field": "orders", + "operator": "any", + "value": { + "field": "total", + "operator": "gt", + "value": 1000 + } +} +``` + +### All + +Matches if all child elements satisfy the condition + +```json +{ + "field": "orderItems", + "operator": "all", + "value": { + "field": "quantity", + "operator": "gt", + "value": 0 + } +} +``` + +### None + +Matches if no child elements satisfy the condition + +```json +{ + "field": "reviews", + "operator": "none", + "value": { + "field": "rating", + "operator": "lt", + "value": 3 + } +} +``` + +# Custom Filter Types + +> Custom filter types provide more specialized filtering capabilities. They are used by setting the +`customType` property instead of using the standard `operator`. + +## Date and Time Filters + +### Date Range + +Filter entries within a specific date range + +```json +{ + "customType": "daterange", + "customParameters": { + "field": "createdAt", + "startDate": "2024-01-01T00:00:00Z", + "endDate": "2024-12-31T23:59:59Z", + "inclusive": true + } +} +``` + +### Date Relative + +Filter based on relative date periods + +```json +{ + "customType": "daterelative", + "customParameters": { + "field": "lastLogin", + "unit": "day", + "amount": 7, + "direction": "past" + } +} +``` + +### Time Range + +Filter entries within a specific time range + +```json +{ + "customType": "timerange", + "customParameters": { + "field": "shiftStart", + "startTime": "09:00:00", + "endTime": "17:00:00", + "inclusive": true + } +} +``` + +### Time Relative + +Filter based on relative time periods + +```json +{ + "customType": "timerelative", + "customParameters": { + "field": "lastActivity", + "unit": "hour", + "amount": 2, + "direction": "past" + } +} +``` + +## Text Search Filters + +### Full Text Search + +Search across multiple fields + +```json +{ + "customType": "fulltextsearch", + "customParameters": { + "searchTerm": "important document", + "fields": [ + "title", + "description", + "content" + ] + } +} +``` + +### Text In + +Match against a list of possible values + +```json +{ + "customType": "textin", + "customParameters": { + "field": "status", + "values": "active;pending;review" + } +} +``` + +### Text Not In + +Exclude matches from a list of values + +```json +{ + "customType": "textnotin", + "customParameters": { + "field": "category", + "values": "archived;deleted;draft" + } +} +``` + +## Numeric Filters + +### Numeric Range + +Filter numbers within a range + +```json +{ + "customType": "numericrange", + "customParameters": { + "field": "price", + "min": 10.00, + "max": 50.00 + } +} +``` + +### Numeric In + +Match against a list of numeric values + +```json +{ + "customType": "numericin", + "customParameters": { + "field": "priority", + "values": "1;2;3" + } +} +``` + +### Numeric Not In + +Exclude specific numeric values + +```json +{ + "customType": "numericnotin", + "customParameters": { + "field": "errorCode", + "values": "404;500;503" + } +} +``` + +## Enum Filters + +### Enum Values + +Filter by enum values using names or integers + +```json +{ + "customType": "enumvalues", + "customParameters": { + "field": "status", + "values": "Active;Pending" + } +} +``` + +## Null Check Filters + +### Is Null + +Explicit null check filter + +```json +{ + "customType": "isnull", + "customParameters": { + "field": "canceledAt" + } +} +``` + +### Is Not Null + +Explicit non-null check filter + +```json +{ + "customType": "isnotnull", + "customParameters": { + "field": "completedAt" + } +} +``` + +## Specification Filters + +### Named Specification + +Use pre-registered domain specifications + +```json +{ + "customType": "namedspecification", + "specificationName": "IsActive", + "specificationArguments": [] +} +``` + +### Composite Specification + +Combine multiple specifications with logical operators + +```json +{ + "customType": "compositespecification", + "compositeSpecification": { + "nodes": [ + { + "name": "IsActive", + "arguments": [] + }, + { + "logic": "and", + "nodes": [ + { + "name": "HasValidLicense", + "arguments": [] + }, + { + "name": "IsInRegion", + "arguments": [ + "EU" + ] + } + ] + } + ] + } +} +``` + +# Complex Filter Examples + +## Overview + +> Complex filters allow you to create sophisticated queries by combining different filter types, +> using nested conditions, and applying custom filter types. They are particularly useful when +> simple +> equality or comparison filters aren't sufficient. + +## Use Cases and Examples + +### 1. Date Range with Status Filter + +Useful for finding records within a specific date range that match certain status criteria. + +```json +{ + "page": 1, + "pageSize": 20, + "filters": [ + { + "customType": "daterange", + "customParameters": { + "field": "createdAt", + "startDate": "2024-01-01T00:00:00Z", + "endDate": "2024-12-31T23:59:59Z", + "inclusive": true + } + }, + { + "field": "status", + "operator": "eq", + "value": "active", + "logic": "and" + } + ] +} +``` + +**Use Case:** Finding all active users who registered during 2024. + +### 2. Full-Text Search with Multiple Fields + +Perfect for implementing search functionality across multiple text fields. + +```json +{ + "filters": [ + { + "customType": "fulltextsearch", + "customParameters": { + "searchTerm": "project management", + "fields": [ + "title", + "description", + "skills", + "notes" + ] + } + } + ] +} +``` + +**Use Case:** Searching for employees with specific skills or experience across their profile data. + +### 3. Nested Entity Filtering + +Useful when you need to filter based on related entity properties. + +```json +{ + "filters": [ + { + "field": "department.name", + "operator": "eq", + "value": "Engineering", + "logic": "and" + }, + { + "field": "projects", + "operator": "any", + "value": { + "field": "status", + "operator": "eq", + "value": "Active" + } + } + ], + "includes": [ + "department", + "projects" + ] +} +``` + +**Use Case:** Finding engineers who are assigned to active projects. + +### 4. Multiple Date-Related Conditions + +Combines multiple date-based filters for temporal analysis. + +```json +{ + "filters": [ + { + "customType": "daterange", + "customParameters": { + "field": "hireDate", + "startDate": "2023-01-01T00:00:00Z", + "endDate": "2023-12-31T23:59:59Z", + "inclusive": true + } + }, + { + "customType": "daterelative", + "customParameters": { + "field": "lastActivity", + "unit": "day", + "amount": 30, + "direction": "past" + }, + "logic": "and" + } + ] +} +``` + +**Use Case:** Finding employees hired in 2023 who have been active in the last 30 days. + +### 5. Complex Numeric Conditions + +Useful for financial or metric-based filtering. + +```json +{ + "filters": [ + { + "customType": "numericrange", + "customParameters": { + "field": "salary", + "min": 50000, + "max": 100000 + } + }, + { + "field": "performance.rating", + "operator": "gte", + "value": 4, + "logic": "and" + }, + { + "field": "projects", + "operator": "any", + "value": { + "field": "budget", + "operator": "gt", + "value": 100000 + }, + "logic": "and" + } + ] +} +``` + +**Use Case:** Finding high-performing employees within a specific salary range working on +high-budget projects. + +### 6. Time-Based Working Hours Filter + +Useful for scheduling and availability queries. + +```json +{ + "filters": [ + { + "customType": "timerange", + "customParameters": { + "field": "workingHours.start", + "startTime": "09:00:00", + "endTime": "17:00:00", + "inclusive": true + } + }, + { + "field": "timezone", + "operator": "eq", + "value": "UTC+1", + "logic": "and" + } + ] +} +``` + +**Use Case:** Finding employees working during specific hours in a particular timezone. + +### 7. Enum and Collection Filtering + +Combines enum values with collection checks. + +```json +{ + "filters": [ + { + "customType": "enumvalues", + "customParameters": { + "field": "employmentType", + "values": "FullTime;PartTime" + } + }, + { + "field": "skills", + "operator": "all", + "value": { + "customType": "enumvalues", + "customParameters": { + "field": "level", + "values": "Expert;Advanced" + } + }, + "logic": "and" + } + ] +} +``` + +**Use Case:** Finding full-time or part-time employees who are experts in all their listed skills. + +### 8. Complex Text Pattern Matching + +Useful for advanced text search scenarios. + +```json +{ + "filters": [ + { + "field": "email", + "operator": "endswith", + "value": "@company.com" + }, + { + "customType": "textin", + "customParameters": { + "field": "department", + "values": "Engineering;Research;Development" + }, + "logic": "and" + }, + { + "field": "notes", + "operator": "contains", + "value": "leadership", + "logic": "and" + } + ] +} +``` + +**Use Case:** Finding internal employees from specific departments with leadership mentions in their +notes. + +## Advanced Combinations + +### Combined Project and Team Filter + +```json +{ + "filters": [ + { + "field": "teams", + "operator": "any", + "value": { + "field": "members", + "operator": "all", + "value": { + "field": "skills", + "operator": "any", + "value": { + "field": "level", + "operator": "gte", + "value": 3 + } + } + } + }, + { + "customType": "daterange", + "customParameters": { + "field": "projects.deadline", + "startDate": "2024-01-01T00:00:00Z", + "endDate": "2024-12-31T23:59:59Z", + "inclusive": true + }, + "logic": "and" + } + ], + "includes": [ + "teams", + "teams.members", + "teams.members.skills", + "projects" + ] +} +``` + +**Use Case:** Finding teams where all members have advanced skills (level ≥ 3) and are working on +projects due in 2024. + +# Appendix A: Angular Usage Guide + +> This appendix provides detailed information about using the Filtering feature in an +> Angular application. + +This implementation provides a complete Angular solution including: + +- Type-safe interfaces +- Reusable service layer +- Component implementation with pagination +- Error handling +- HTTP parameter building + +## Type Definitions + +### Core Filter Model Interfaces + +```typescript +// models/filter.model.ts +export interface FilterCriteria { + field: string; + operator: string; + value?: any; + logic?: 'and' | 'or'; + customType?: string; + customParameters?: Record; +} + +export interface FilterModel { + page: number; + pageSize: number; + filters: FilterCriteria[]; + orderings?: Array<{ + field: string; + direction: 'asc' | 'desc'; + }>; + includes?: string[]; +} + +export interface PagedResult { + items: T[]; + totalCount: number; + pageNumber: number; + pageSize: number; + totalPages: number; +} +``` + +## Service Implementation + +### API Service + +```typescript +// services/api.service.ts +import {Injectable} from '@angular/core'; +import {HttpClient, HttpParams} from '@angular/common/http'; +import {Observable} from 'rxjs'; +import {environment} from '../environments/environment'; +import {FilterModel, PagedResult} from '../models'; + +@Injectable({ + providedIn: 'root' +}) +export class ApiService { + constructor( + private http: HttpClient, + private baseUrl: string + ) { + } + + getFiltered(filterModel: FilterModel): Observable> { + let params = new HttpParams() + .set('page', filterModel.page.toString()) + .set('pageSize', filterModel.pageSize.toString()); + + filterModel.filters.forEach((filter, index) => { + params = params + .set(`filters[${index}].field`, filter.field) + .set(`filters[${index}].operator`, filter.operator); + + if (filter.value !== undefined) { + params = params.set(`filters[${index}].value`, filter.value.toString()); + } + + if (filter.logic) { + params = params.set(`filters[${index}].logic`, filter.logic); + } + + if (filter.customType) { + params = params.set(`filters[${index}].customType`, filter.customType); + if (filter.customParameters) { + Object.entries(filter.customParameters).forEach(([key, value]) => { + params = params.set( + `filters[${index}].customParameters.${key}`, + value.toString() + ); + }); + } + } + }); + + filterModel.orderings?.forEach((order, index) => { + params = params + .set(`orderings[${index}].field`, order.field) + .set(`orderings[${index}].direction`, order.direction); + }); + + filterModel.includes?.forEach((include, index) => { + params = params.set(`includes[${index}]`, include); + }); + + return this.http.get>(this.baseUrl, {params}); + } +} +``` + +### Entity-Specific Service + +```typescript +// services/user.service.ts +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {environment} from '../environments/environment'; +import {User} from '../models'; +import {ApiService} from './api.service'; + +@Injectable({ + providedIn: 'root' +}) +export class UserService extends ApiService { + constructor(http: HttpClient) { + super(http, `${environment.apiBaseUrl}/api/users`); + } +} +``` + +## Component Implementation + +### List Component Example + +```typescript +// components/user-list/user-list.component.ts +import {Component, OnInit} from '@angular/core'; +import {UserService} from '../../services/user.service'; +import {User, FilterModel, PagedResult} from '../../models'; +import {finalize} from 'rxjs/operators'; + +@Component({ + selector: 'app-user-list', + template: ` +
+ + +
+ +
Loading...
+ +
+ {{ error }} +
+ + + + + + + + + + + + + + + + +
NameEmailDepartment
{{ user.firstName }} {{ user.lastName }}{{ user.email }}{{ user.department }}
+ + + ` +}) +export class UserListComponent implements OnInit { + users: PagedResult | null = null; + loading = false; + error: string | null = null; + + private currentFilter: FilterModel = { + page: 1, + pageSize: 10, + filters: [] + }; + + constructor(private userService: UserService) { + } + + ngOnInit() { + this.loadUsers(); + } + + loadUsers() { + this.loading = true; + this.error = null; + + this.userService.getFiltered(this.currentFilter) + .pipe( + finalize(() => this.loading = false) + ) + .subscribe({ + next: (result) => { + this.users = result; + }, + error: (error) => { + this.error = 'Failed to load users. Please try again.'; + console.error('Error loading users:', error); + } + }); + } + + applyDepartmentFilter(department: string) { + this.currentFilter = { + ...this.currentFilter, + filters: [ + { + field: 'department', + operator: 'eq', + value: department + } + ] + }; + this.loadUsers(); + } + + applyDateRangeFilter() { + const endDate = new Date(); + const startDate = new Date(); + startDate.setDate(startDate.getDate() - 30); + + this.currentFilter = { + ...this.currentFilter, + filters: [ + { + customType: 'daterange', + customParameters: { + field: 'createdAt', + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + inclusive: true + } + } + ] + }; + this.loadUsers(); + } + + nextPage() { + if (this.users && this.currentFilter.page < this.users.totalPages) { + this.currentFilter.page++; + this.loadUsers(); + } + } + + previousPage() { + if (this.currentFilter.page > 1) { + this.currentFilter.page--; + this.loadUsers(); + } + } +} +``` + +## Error Handling + +### HTTP Interceptor + +```typescript +// interceptors/error.interceptor.ts +import {Injectable} from '@angular/core'; +import { + HttpInterceptor, + HttpRequest, + HttpHandler, + HttpEvent, + HttpErrorResponse +} from '@angular/common/http'; +import {Observable, throwError} from 'rxjs'; +import {catchError} from 'rxjs/operators'; + +@Injectable() +export class ErrorInterceptor implements HttpInterceptor { + intercept( + request: HttpRequest, + next: HttpHandler + ): Observable> { + return next.handle(request).pipe( + catchError((error: HttpErrorResponse) => { + let errorMessage = 'An unknown error occurred!'; + + if (error.error instanceof ErrorEvent) { + // Client-side error + errorMessage = error.error.message; + } else { + // Server-side error + switch (error.status) { + case 400: + errorMessage = 'Invalid filter parameters'; + break; + case 401: + errorMessage = 'Unauthorized access'; + break; + case 403: + errorMessage = 'Forbidden access'; + break; + case 404: + errorMessage = 'Resource not found'; + break; + case 500: + errorMessage = 'Internal server error'; + break; + } + } + + return throwError(() => new Error(errorMessage)); + }) + ); + } +} +``` + +## Registration in App Module + +```typescript +// app.module.ts +import {NgModule} from '@angular/core'; +import {HTTP_INTERCEPTORS} from '@angular/common/http'; +import {ErrorInterceptor} from './interceptors/error.interceptor'; + +@NgModule({ + // ...other module configuration + providers: [ + { + provide: HTTP_INTERCEPTORS, + useClass: ErrorInterceptor, + multi: true + } + ] +}) +export class AppModule { +} +``` + +# Appendix B: Flow Diagram + +```mermaid +graph TD + A[Client Request] -->|FilterModel JSON| B[API Controller] + B -->|FilterModel| C[Query Handler] + C -->|FilterModel| D[Repository FindAllAsync] + D -->|Build| E[SpecificationBuilder] + D -->|Build| F[OrderOptionBuilder] + D -->|Build| G[IncludeOptionBuilder] + E -->|Specifications| FO[FindOptions] + F -->|OrderOptions| FO + G -->|IncludeOptions| FO + FO -->|-| H[(Database Query)] + H -->|PagedResult| I[Response] +``` + +# Appendix C: Disclaimer + +> This Filtering feature described here is designed to provide a pragmatic, flexible filtering +> solution for +> REST APIs. + +It is not intended to replace or compete with comprehensive query technologies like: + +- **GraphQL**: A complete query language that provides a type system and allows clients to specify + exactly what data they need +- **OData**: A standardized protocol for building and consuming RESTful APIs with rich query + capabilities + +The Filtering feature is best suited for: + +- When already using the DevKit ecosystem, providing seamless integration with its repository + and specification patterns +- For REST APIs needing structured filtering +- When requiring a balance between flexibility and simplicity +- Need for a typed, maintainable filtering solution without the overhead of implementing larger + query frameworks + +If the application requires complex schema definitions, introspection, or full query language +capabilities, consider using GraphQL or OData instead. The Filtering feature focuses on providing a +straightforward, typed approach to common filtering scenarios while maintaining REST principles and +leveraging DevKit features. \ No newline at end of file diff --git a/docs/features-jobscheduling.md b/docs/features-jobscheduling.md new file mode 100644 index 00000000..f10b73d9 --- /dev/null +++ b/docs/features-jobscheduling.md @@ -0,0 +1,18 @@ +# JobScheduling Feature Documentation + + + +* [Overview](#overview) +* [Usage](#usage) + + + +## Overview + +### Challenges + +### Solution + +### Use Cases + +## Usage \ No newline at end of file diff --git a/docs/features-messaging.md b/docs/features-messaging.md new file mode 100644 index 00000000..e3c12dae --- /dev/null +++ b/docs/features-messaging.md @@ -0,0 +1,18 @@ +# Messaging Feature Documentation + + + +* [Overview](#overview) +* [Usage](#usage) + + + +## Overview + +### Challenges + +### Solution + +### Use Cases + +## Usage \ No newline at end of file diff --git a/docs/features-modules.md b/docs/features-modules.md new file mode 100644 index 00000000..e08c0899 --- /dev/null +++ b/docs/features-modules.md @@ -0,0 +1,18 @@ +# Modules Feature Documentation + + + +* [Overview](#overview) +* [Usage](#usage) + + + +## Overview + +### Challenges + +### Solution + +### Use Cases + +## Usage \ No newline at end of file diff --git a/docs/features-queries.md b/docs/features-queries.md new file mode 100644 index 00000000..a8f45455 --- /dev/null +++ b/docs/features-queries.md @@ -0,0 +1,18 @@ +# Queries Feature Documentation + + + +* [Overview](#overview) +* [Usage](#usage) + + + +## Overview + +### Challenges + +### Solution + +### Use Cases + +## Usage \ No newline at end of file diff --git a/docs/features-results.md b/docs/features-results.md new file mode 100644 index 00000000..b201efdb --- /dev/null +++ b/docs/features-results.md @@ -0,0 +1,1002 @@ +# Results Feature Documentation + + + +* [Overview](#overview) + * [Challenges](#challenges) + * [Solution](#solution) + * [Architecture](#architecture) + * [Use Cases](#use-cases) +* [Usage](#usage) + * [Basic Result Operations](#basic-result-operations) + * [Result with Values](#result-with-values) + * [Error Handling](#error-handling) + * [Working with Messages](#working-with-messages) + * [Paged Results](#paged-results) + * [Custom Error Types](#custom-error-types) + * [Exception Handling](#exception-handling) + * [Best Practices](#best-practices) +* [Examples](#examples) + * [Repository Pattern Example](#repository-pattern-example) + * [Service Layer Example](#service-layer-example) + * [API Controller Example](#api-controller-example) + * [Complex Workflow Example](#complex-workflow-example) +* [Appendix A: Repository Extensions](#appendix-a-repository-extensions) + + + +## Overview + +### Challenges + +When developing modern applications, handling operation outcomes effectively presents several +challenges: + +1. **Inconsistent Error Handling**: Different parts of the application may handle errors in + different ways, leading to inconsistent error reporting and handling. +2. **Context Loss**: Important error context and details can be lost when exceptions are caught and + rethrown up the call stack. +3. **Mixed Concerns**: Business logic errors often get mixed with technical exceptions, making it + harder to handle each appropriately. +4. **Pagination Complexity**: Managing paginated data with associated metadata adds complexity to + result handling. +5. **Type Safety**: Maintaining type safety while handling both successful and failed operations can + be challenging. +6. **Error Propagation**: Propagating errors through multiple layers of the application while + preserving context. + +### Solution + +The Result pattern implementation provides a comprehensive solution by: + +1. Providing a standardized way to handle operation outcomes +2. Encapsulating success/failure status, messages, and errors in a single object +3. Supporting generic result types for operations that return values +4. Offering specialized support for paginated results +5. Enabling strongly-typed error handling +6. Maintaining immutability with a fluent interface design + +### Architecture + +```mermaid +classDiagram + class IResult { + +Messages: IReadOnlyList~string~ + +Errors: IReadOnlyList~IResultError~ + +IsSuccess: bool + +IsFailure: bool + +WithMessage(message: string): Result + +WithError(error: IResultError): Result + +WithError~TError~(): Result + +HasError(): bool + } + + class Result { + -success: bool + -messages: List~string~ + -errors: List~IResultError~ + +static Success(): Result + +static Failure(): Result + +WithMessage(message: string): Result + +WithError(error: IResultError): Result + } + + class Result~T~ { + +Value: T + +static Success(value: T): Result~T~ + +static Failure(): Result~T~ + } + + class PagedResult~T~ { + +CurrentPage: int + +TotalPages: int + +TotalCount: long + +PageSize: int + +HasPreviousPage: bool + +HasNextPage: bool + } + + class IResultError { + +Message: string + } + + class ResultErrorBase { + +Message: string + } + + class ExceptionError { + -exception: Exception + +ExceptionType: string + +StackTrace: string + +OriginalException: Exception + } + + IResult <|.. Result + Result <|-- Result~T~ + Result~T~ <|-- PagedResult~T~ + IResultError <|.. ResultErrorBase + IResultError <|.. ExceptionError +``` + +### Use Cases + +The Result pattern is particularly useful in the following scenarios: + +1. **Service Layer Operations** + +- Handling business rule validations +- Processing complex operations with multiple potential failure points +- Returning domain-specific errors + +2. **Data Access Operations** + +- Managing database operations +- Handling entity not found scenarios +- Dealing with validation errors + +3. **API Endpoints** + +- Returning paginated data +- Handling complex operation outcomes +- Providing detailed error information + +4. **Complex Workflows** + +- Managing multi-step processes +- Handling conditional operations +- Aggregating errors from multiple sources + +## Usage + +### Basic Result Operations + +```csharp +public class ResultDemo +{ + public void BasicOperations() + { + // Creating success results + var success = Result.Success(); + var successWithMessage = Result.Success("Operation completed successfully"); + + // Creating failure results + var failure = Result.Failure(); + var failureWithMessage = Result.Failure("Operation failed"); + + // Checking result status + if (success.IsSuccess) + { + // Handle success + } + + if (failure.IsFailure) + { + // Handle failure + } + } +} +``` + +### Result with Values + +```csharp +public class UserService +{ + private readonly IUserRepository repository; + + public UserService(IUserRepository repository) + { + this.repository = repository; + } + + public Result GetUserById(int id) + { + var user = this.repository.FindById(id); + if (user == null) + { + return Result.Failure() + .WithError() + .WithMessage($"User {id} not found"); + } + + return Result.Success(user); + } +} +``` + +### Error Handling + +```csharp +public class ValidationError : ResultErrorBase +{ + public ValidationError(string message) : base(message) + { + } +} + +public class ValidationService +{ + public Result ValidateData(DataModel model) + { + var result = Result.Success(); + + if (!this.IsValid(model)) + { + result = Result.Failure() + .WithError(new ValidationError("Invalid input")) + .WithMessage("Validation failed"); + } + + // Check for specific errors + if (result.HasError()) + { + // Handle validation error + } + + if (result.HasError(out var validationErrors)) + { + foreach (var error in validationErrors) + { + Console.WriteLine(error.Message); + } + } + + return result; + } + + private bool IsValid(DataModel model) + { + // Validation logic + return true; + } +} +``` + +### Working with Messages + +```csharp +public class WorkflowService +{ + public Result ProcessWorkflow() + { + var result = Result.Success() + .WithMessage("Step 1 completed") + .WithMessage("Step 2 completed"); + + var messages = new[] { "Process started", "Process completed" }; + result = Result.Success() + .WithMessages(messages); + + foreach (var message in result.Messages) + { + Console.WriteLine(message); + } + + return result; + } +} +``` + +### Paged Results + +```csharp +public class ProductService +{ + private readonly IProductRepository repository; + private readonly ILogger logger; + + public ProductService( + IProductRepository repository, + ILogger logger) + { + this.repository = repository; + this.logger = logger; + } + + public async Task> GetProductsAsync(int page = 1, int pageSize = 10) + { + try + { + var totalCount = await this.repository.CountAsync(); + var products = await this.repository.GetPageAsync(page, pageSize); + + return PagedResult.Success( + products, + totalCount, + page, + pageSize); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Failed to get products"); + return PagedResult.Failure() + .WithError(new ExceptionError(ex)); + } + } +} +``` + +### Custom Error Types + +```csharp +public class ValidationResultError : ResultErrorBase +{ + public ValidationResultError(string field, string message) + : base($"Validation failed for {field}: {message}") + { + } +} + +public class UnauthorizedResultError : ResultErrorBase +{ + public UnauthorizedResultError() + : base("User is not authorized to perform this action") + { + } +} + +public class OrderService +{ + private readonly IAuthService authService; + private readonly IOrderRepository orderRepository; + + public OrderService( + IAuthService authService, + IOrderRepository orderRepository) + { + this.authService = authService; + this.orderRepository = orderRepository; + } + + public Result CreateOrder(OrderRequest request) + { + if (!this.authService.CanCreateOrders()) + { + return Result.Failure(); + } + + if (request.Quantity <= 0) + { + return Result.Failure() + .WithError(new ValidationResultError("Quantity", "Must be greater than zero")); + } + + var order = this.orderRepository.Create(request); + return Result.Success(order, "Order created successfully"); + } +} +``` + +### Exception Handling + +```csharp +public class DataService +{ + private readonly IDataRepository repository; + private readonly ILogger logger; + + public DataService( + IDataRepository repository, + ILogger logger) + { + this.repository = repository; + this.logger = logger; + } + + public Result GetData() + { + try + { + var data = this.repository.FetchData(); + return Result.Success(data); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Failed to fetch data"); + return Result.Failure() + .WithError(new ExceptionError(ex)) + .WithMessage("Failed to fetch data"); + } + } +} +``` + +### Best Practices + +1. **Early Returns**: Return failures as soon as possible to avoid unnecessary processing. + +```csharp +public class OrderProcessor +{ + public Result ProcessOrder(OrderRequest request) + { + if (request == null) + { + return Result.Failure("Request cannot be null"); + } + + if (!request.IsValid) + { + return Result.Failure("Invalid request"); + } + + // Continue processing + return Result.Success(new Order(request)); + } +} +``` + +2. **Meaningful Messages**: Include context in error messages. + +```csharp +public Result ProcessOrderById(int orderId, string status) +{ + return Result.Failure($"Failed to process order {orderId}: Invalid status {status}"); +} +``` + +3. **Type-Safe Errors**: Use strongly-typed errors for better error handling. + +```csharp +public class OrderNotFoundError : ResultErrorBase +{ + public OrderNotFoundError(int orderId) + : base($"Order {orderId} not found") + { + } +} +``` + +## Examples + +### Repository Pattern Example + +```csharp +public class UserRepository +{ + private readonly DbContext dbContext; + + public UserRepository(DbContext dbContext) + { + this.dbContext = dbContext; + } + + public Result GetById(int id) + { + try + { + var user = this.dbContext.Users.FindById(id); + if (user == null) + { + return Result.Failure(); + } + + return Result.Success(user); + } + catch (Exception ex) + { + return Result.Failure() + .WithError(new ExceptionError(ex)) + .WithMessage($"Failed to retrieve user {id}"); + } + } +} +``` + +### Service Layer Example + +```csharp +public class UserService +{ + private readonly IUserRepository repository; + private readonly IValidator validator; + private readonly IMapper mapper; + + public UserService( + IUserRepository repository, + IValidator validator, + IMapper mapper) + { + this.repository = repository; + this.validator = validator; + this.mapper = mapper; + } + + public Result UpdateUser(int id, UpdateUserRequest request) + { + var getUserResult = this.repository.GetById(id); + if (getUserResult.IsFailure) + { + return Result.Failure() + .WithErrors(getUserResult.Errors) + .WithMessages(getUserResult.Messages); + } + + var user = getUserResult.Value; + user.Update(request); + + var validationResult = this.validator.Validate(user); + if (!validationResult.IsValid) + { + return Result.Failure() + .WithError(new ValidationResultError("User", validationResult.Error)); + } + + var savedUser = this.repository.Save(user); + return Result.Success(this.mapper.ToDto(savedUser)); + } +} +``` + +### API Controller Example + +```csharp +[ApiController] +[Route("api/[controller]")] +public class ProductsController : ControllerBase +{ + private readonly IProductService productService; + + public ProductsController(IProductService productService) + { + this.productService = productService; + } + + [HttpGet] + public async Task GetProducts([FromQuery] int page = 1, [FromQuery] int pageSize = 10) + { + var result = await this.productService.GetProductsAsync(page, pageSize); + + if (result.IsFailure) + { + return this.BadRequest(new + { + Errors = result.Errors.Select(e => e.Message), + Messages = result.Messages + }); + } + + return this.Ok(new + { + Data = result.Value, + Pagination = new + { + result.CurrentPage, + result.TotalPages, + result.TotalCount, + result.HasNextPage, + result.HasPreviousPage + } + }); + } +} +``` + +### Complex Workflow Example + +```csharp +public class OrderProcessor +{ + private readonly IOrderRepository orderRepository; + private readonly IInventoryService inventoryService; + private readonly IPaymentService paymentService; + private readonly INotificationService notificationService; + private readonly IValidator validator; + private readonly ILogger logger; + + public OrderProcessor( + IOrderRepository orderRepository, + IInventoryService inventoryService, + IPaymentService paymentService, + INotificationService notificationService, + IValidator validator, + ILogger logger) + { + this.orderRepository = orderRepository; + this.inventoryService = inventoryService; + this.paymentService = paymentService; + this.notificationService = notificationService; + this.validator = validator; + this.logger = logger; + } + + public async Task> ProcessOrderAsync(OrderRequest request) + { + // Validate request + var validationResult = await this.ValidateOrderRequestAsync(request); + if (validationResult.IsFailure) + { + return Result.Failure() + .WithErrors(validationResult.Errors) + .WithMessage("Order validation failed"); + } + + // Reserve inventory + var inventoryResult = await this.ReserveInventoryAsync(request.Items); + if (inventoryResult.IsFailure) + { + return Result.Failure() + .WithErrors(inventoryResult.Errors) + .WithMessage("Inventory reservation failed"); + } + + try + { + // Process payment + var paymentResult = await this.ProcessPaymentAsync(request.Payment); + if (paymentResult.IsFailure) + { + // Rollback inventory reservation + await this.ReleaseInventoryAsync(request.Items); + return Result.Failure() + .WithErrors(paymentResult.Errors) + .WithMessage("Payment processing failed"); + } + + // Create order + var order = await this.CreateOrderAsync(request, paymentResult.Value); + if (order.IsFailure) + { + // Rollback payment and inventory + await this.ReversePaymentAsync(paymentResult.Value); + await this.ReleaseInventoryAsync(request.Items); + return Result.Failure() + .WithErrors(order.Errors) + .WithMessage("Order creation failed"); + } + + // Send notifications + await this.notificationService.SendOrderConfirmationAsync(order.Value); + + return Result.Success(order.Value) + .WithMessage("Order processed successfully"); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Unexpected error during order processing"); + return Result.Failure() + .WithError(new ExceptionError(ex)) + .WithMessage("An unexpected error occurred while processing the order"); + } + } + + private async Task ValidateOrderRequestAsync(OrderRequest request) + { + try + { + var validationResult = await this.validator.ValidateAsync(request); + if (!validationResult.IsValid) + { + return Result.Failure() + .WithError(new ValidationResultError("Order", validationResult.Message)); + } + + return Result.Success(); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Validation error"); + return Result.Failure() + .WithError(new ExceptionError(ex)); + } + } + + private async Task> ReserveInventoryAsync(IEnumerable items) + { + try + { + return await this.inventoryService.ReserveItemsAsync(items); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Inventory reservation error"); + return Result.Failure() + .WithError(new ExceptionError(ex)); + } + } + + private async Task> ProcessPaymentAsync(PaymentDetails payment) + { + try + { + var transaction = await this.paymentService.ProcessAsync(payment); + if (transaction.IsFailure) + { + this.logger.LogWarning("Payment failed: {Message}", transaction.Messages.FirstOrDefault()); + } + + return transaction; + } + catch (Exception ex) + { + this.logger.LogError(ex, "Payment processing error"); + return Result.Failure() + .WithError(new ExceptionError(ex)); + } + } + + private async Task> CreateOrderAsync(OrderRequest request, PaymentTransaction transaction) + { + try + { + var order = new Order(request, transaction); + return await this.orderRepository.InsertResultAsync(order); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Order creation error"); + return Result.Failure() + .WithError(new ExceptionError(ex)); + } + } + + private async Task ReleaseInventoryAsync(IEnumerable items) + { + try + { + await this.inventoryService.ReleaseReservationAsync(items); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Error releasing inventory"); + } + } + + private async Task ReversePaymentAsync(PaymentTransaction transaction) + { + try + { + await this.paymentService.ReverseTransactionAsync(transaction); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Error reversing payment"); + } + } +} +``` + +This example demonstrates: + +1. **Proper Error Handling** + +- Each operation returns a `Result` or `Result` +- Errors are properly propagated and transformed +- Rollback operations are performed when needed + +2. **Clean Code Practices** + +- Follows the .editorconfig standards +- Proper use of dependency injection +- Clear separation of concerns +- Consistent error handling + +3. **Workflow Management** + +- Sequential processing with proper validation +- Rollback mechanisms for failed operations +- Proper logging at each step + +4. **Result Pattern Usage** + +- Consistent use of Result objects +- Proper error aggregation +- Clear success/failure paths + +5. **Resource Management** + +- Proper cleanup in case of failures +- Structured error handling +- Comprehensive logging + +# Appendix A: Repository Extensions + +> The DevKit provides extension methods for repositories that don't natively support the Result +> pattern. These extensions wrap standard repository operations in Result objects, providing +> consistent error handling and operation results across your application. + +### Available Extensions + +1. Read-Only Repository Extensions (`GenericReadOnlyRepositoryResultExtensions`): + +- Count operations +- Find operations +- Paged query operations + +2. Repository Extensions (`GenericRepositoryResultExtensions`): + +- Insert operations +- Update operations +- Upsert operations +- Delete operations + +## Usage Examples + +### Basic CRUD Operations + +```csharp +public class UserService +{ + private readonly IGenericRepository _repository; + + // Insert with Result + public async Task> CreateUserAsync(User user) + { + return await _repository.InsertResultAsync(user); + } + + // Update with Result + public async Task> UpdateUserAsync(User user) + { + return await _repository.UpdateResultAsync(user); + } + + // Delete with Result + public async Task> DeleteUserAsync(int id) + { + return await _repository.DeleteByIdResultAsync(id); + } +} +``` + +### Query Operations + +```csharp +public class ProductService +{ + private readonly IGenericReadOnlyRepository _repository; + + // Count with Result + public async Task> GetProductCountAsync() + { + return await _repository.CountResultAsync(); + } + + // Find One with Result + public async Task> GetProductByIdAsync(int id) + { + return await _repository.FindOneResultAsync(id); + } + + // Find All Paged with Result + public async Task> GetProductsPagedAsync( + int page = 1, + int pageSize = 10) + { + return await _repository.FindAllPagedResultAsync( + ordering: "Name ascending", + page: page, + pageSize: pageSize); + } +} +``` + +### Advanced Queries + +```csharp +public class OrderService +{ + private readonly IGenericReadOnlyRepository _repository; + + // Complex query with specifications + public async Task> GetOrdersAsync( + FilterModel filterModel, + IEnumerable> additionalSpecs = null) + { + return await _repository.FindAllPagedResultAsync( + filterModel, + additionalSpecs); + } + + // Query with includes + public async Task> GetOrderWithDetailsAsync(int id) + { + return await _repository.FindOneResultAsync( + id, + options: new FindOptions + { + Include = new IncludeOption(o => o.OrderItems) + }); + } +} +``` + +### Error Handling + +The extensions automatically handle exceptions and wrap them in Result objects: + +```csharp +public class InventoryService +{ + private readonly IGenericRepository _repository; + + public async Task> UpdateInventoryAsync(Inventory inventory) + { + var result = await _repository.UpdateResultAsync(inventory); + + if (result.IsFailure) + { + // Check for specific errors + if (result.HasError()) + { + // Handle database exception + _logger.LogError(result.Errors.First().Message); + } + } + + return result; + } +} +``` + +## Best Practices + +1. **Consistent Usage**: Use these extensions throughout the application for consistent error + handling: + +```csharp +// Instead of: +try { + var product = await _repository.FindOneAsync(id); + return product; +} +catch (Exception ex) { + // Handle error +} + +// Use: +var result = await _repository.FindOneResultAsync(id); +return result; +``` + +2. **Combining Operations**: Chain repository operations while maintaining error handling: + +```csharp +public async Task> ProcessOrderAsync(Order order) +{ + // Check inventory + var inventoryResult = await _inventoryRepository + .FindOneResultAsync(order.ProductId); + + if (inventoryResult.IsFailure) + return Result.Failure() + .WithErrors(inventoryResult.Errors); + + // Insert order + var orderResult = await _orderRepository + .InsertResultAsync(order); + + return orderResult; +} +``` + +3. **Paged Queries with Specifications**: + +```csharp +public async Task> SearchProductsAsync( + string searchTerm, + int page = 1, + int pageSize = 10) +{ + var specification = new Specification(p => p.Name.Contains(searchTerm)); + + return await _repository.FindAllPagedResultAsync(specification); +} +``` + +4. **Filtering with model**: + +```csharp +public async Task> GetOrdersAsync(FilterModel filterModel) +{ + var specifications = new List> + { + new Specification(o => o.Status == OrderStatus.Active) + }; + + return await _repository.FindAllPagedResultAsync(filterModel, specifications); +} +``` + +These extensions provide a seamless way to integrate the Result pattern with existing repository +implementations, ensuring consistent error handling and operation results across your application. \ No newline at end of file diff --git a/docs/features-startuptasks.md b/docs/features-startuptasks.md new file mode 100644 index 00000000..b4edc45c --- /dev/null +++ b/docs/features-startuptasks.md @@ -0,0 +1,18 @@ +# StartupTasks Feature Documentation + + + +* [Overview](#overview) +* [Usage](#usage) + + + +## Overview + +### Challenges + +### Solution + +### Use Cases + +## Usage~~~~ \ No newline at end of file diff --git a/examples/DinnerFiesta/Modules/Core/Core.Application/packages.lock.json b/examples/DinnerFiesta/Modules/Core/Core.Application/packages.lock.json index e82407fb..ce6c1e57 100644 --- a/examples/DinnerFiesta/Modules/Core/Core.Application/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Core/Core.Application/packages.lock.json @@ -33,8 +33,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -397,7 +397,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -411,8 +411,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -446,7 +447,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -523,11 +525,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -741,9 +743,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -751,6 +753,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/examples/DinnerFiesta/Modules/Core/Core.Domain/packages.lock.json b/examples/DinnerFiesta/Modules/Core/Core.Domain/packages.lock.json index 50a870f8..5d489502 100644 --- a/examples/DinnerFiesta/Modules/Core/Core.Domain/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Core/Core.Domain/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -91,8 +91,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -126,7 +127,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -168,11 +170,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -258,6 +260,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/examples/DinnerFiesta/Modules/Core/Core.EndToEndTests/packages.lock.json b/examples/DinnerFiesta/Modules/Core/Core.EndToEndTests/packages.lock.json index 7f7244c9..db8562f3 100644 --- a/examples/DinnerFiesta/Modules/Core/Core.EndToEndTests/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Core/Core.EndToEndTests/packages.lock.json @@ -26,24 +26,24 @@ }, "Microsoft.Playwright": { "type": "Direct", - "requested": "[1.47.0, )", - "resolved": "1.47.0", - "contentHash": "D9verOkoSO1vqqAe36jmuQlceEHd2leoYlLOXQkMuVDFTbhvblVk7LOm9LeS50u+5xNcIcJi1+vA2rZxN5tW4A==", + "requested": "[1.48.0, )", + "resolved": "1.48.0", + "contentHash": "u1/zX2/YzpNjkjKurOeLgXrqtQwDVdHMl1/kppxDst3NEg4htJHPVC2xs1dmV+f6P7t/BXlP14xbJ6E5I6tTXQ==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "6.0.0", "System.ComponentModel.Annotations": "5.0.0", - "System.Text.Json": "6.0.0" + "System.Text.Json": "6.0.10" } }, "Microsoft.Playwright.NUnit": { "type": "Direct", - "requested": "[1.47.0, )", - "resolved": "1.47.0", - "contentHash": "DOvKdrTiAX0S7kK9ppl5ZTa8lWvlChAiplxecNHrCbv3/g2CJCM3P3VFLHlU5/mvwuT7kT6VvblUPRGohLl70w==", + "requested": "[1.48.0, )", + "resolved": "1.48.0", + "contentHash": "LcioiiHttSFtqqEX9CivGTV0ATGXI6D5xj1HNGPXZFFug9VL0JjTqHVRQYmTqNwMv+mujA1zgiyFblQzAtyZ4Q==", "dependencies": { "Microsoft.NET.Test.Sdk": "16.11.0", - "Microsoft.Playwright": "1.47.0", - "Microsoft.Playwright.TestAdapter": "1.47.0", + "Microsoft.Playwright": "1.48.0", + "Microsoft.Playwright.TestAdapter": "1.48.0", "NUnit": "3.13.2", "NUnit3TestAdapter": "4.0.0" } @@ -241,8 +241,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -913,10 +913,10 @@ }, "Microsoft.Playwright.TestAdapter": { "type": "Transitive", - "resolved": "1.47.0", - "contentHash": "hKZPcPWknCYPSajPr16Xrwh0q3C7hx7GO2bmWLZ2uFMS3y+78UeBUBkMwMUJdtZRkHDtQGKRw7uPX8wEuOiDCw==", + "resolved": "1.48.0", + "contentHash": "ARFL+9yXFiln1U4twjM0XeSqwxRctOf/ABEjm3Lt0879UGK3dMEBck3H32EQ2jMOtRhdp5ovD1O9bh3h6E8JMw==", "dependencies": { - "Microsoft.Playwright": "1.47.0", + "Microsoft.Playwright": "1.48.0", "Microsoft.TestPlatform.ObjectModel": "17.3.0" } }, @@ -2108,7 +2108,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -2122,8 +2122,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -2162,7 +2163,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -2179,7 +2180,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -2319,7 +2321,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Http.Polly": "[8.0.10, )", "Microsoft.Extensions.Localization": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "System.ComponentModel.Annotations": "[5.0.0, )" } }, @@ -2445,7 +2447,7 @@ "dependencies": { "Ensure.That": "[10.1.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Expressions": "[5.0.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Settings.Configuration": "[8.0.4, )", @@ -2680,11 +2682,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -3099,9 +3101,9 @@ }, "MudBlazor": { "type": "CentralTransitive", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", @@ -3284,9 +3286,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.AspNetCore": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Modules/Core/Core.Infrastructure/packages.lock.json b/examples/DinnerFiesta/Modules/Core/Core.Infrastructure/packages.lock.json index 3b68fbdf..ae7c5b41 100644 --- a/examples/DinnerFiesta/Modules/Core/Core.Infrastructure/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Core/Core.Infrastructure/packages.lock.json @@ -68,8 +68,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -668,7 +668,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -682,8 +682,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -717,7 +718,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -899,11 +901,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1203,9 +1205,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Modules/Core/Core.IntegrationTests/packages.lock.json b/examples/DinnerFiesta/Modules/Core/Core.IntegrationTests/packages.lock.json index 801d1718..c62e94da 100644 --- a/examples/DinnerFiesta/Modules/Core/Core.IntegrationTests/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Core/Core.IntegrationTests/packages.lock.json @@ -126,24 +126,24 @@ }, "Microsoft.Playwright": { "type": "Direct", - "requested": "[1.47.0, )", - "resolved": "1.47.0", - "contentHash": "D9verOkoSO1vqqAe36jmuQlceEHd2leoYlLOXQkMuVDFTbhvblVk7LOm9LeS50u+5xNcIcJi1+vA2rZxN5tW4A==", + "requested": "[1.48.0, )", + "resolved": "1.48.0", + "contentHash": "u1/zX2/YzpNjkjKurOeLgXrqtQwDVdHMl1/kppxDst3NEg4htJHPVC2xs1dmV+f6P7t/BXlP14xbJ6E5I6tTXQ==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "6.0.0", "System.ComponentModel.Annotations": "5.0.0", - "System.Text.Json": "6.0.0" + "System.Text.Json": "6.0.10" } }, "Microsoft.Playwright.NUnit": { "type": "Direct", - "requested": "[1.47.0, )", - "resolved": "1.47.0", - "contentHash": "DOvKdrTiAX0S7kK9ppl5ZTa8lWvlChAiplxecNHrCbv3/g2CJCM3P3VFLHlU5/mvwuT7kT6VvblUPRGohLl70w==", + "requested": "[1.48.0, )", + "resolved": "1.48.0", + "contentHash": "LcioiiHttSFtqqEX9CivGTV0ATGXI6D5xj1HNGPXZFFug9VL0JjTqHVRQYmTqNwMv+mujA1zgiyFblQzAtyZ4Q==", "dependencies": { "Microsoft.NET.Test.Sdk": "16.11.0", - "Microsoft.Playwright": "1.47.0", - "Microsoft.Playwright.TestAdapter": "1.47.0", + "Microsoft.Playwright": "1.48.0", + "Microsoft.Playwright.TestAdapter": "1.48.0", "NUnit": "3.13.2", "NUnit3TestAdapter": "4.0.0" } @@ -440,8 +440,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -1163,10 +1163,10 @@ }, "Microsoft.Playwright.TestAdapter": { "type": "Transitive", - "resolved": "1.47.0", - "contentHash": "hKZPcPWknCYPSajPr16Xrwh0q3C7hx7GO2bmWLZ2uFMS3y+78UeBUBkMwMUJdtZRkHDtQGKRw7uPX8wEuOiDCw==", + "resolved": "1.48.0", + "contentHash": "ARFL+9yXFiln1U4twjM0XeSqwxRctOf/ABEjm3Lt0879UGK3dMEBck3H32EQ2jMOtRhdp5ovD1O9bh3h6E8JMw==", "dependencies": { - "Microsoft.Playwright": "1.47.0", + "Microsoft.Playwright": "1.48.0", "Microsoft.TestPlatform.ObjectModel": "17.3.0" } }, @@ -2439,7 +2439,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -2453,8 +2453,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -2493,7 +2494,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -2510,7 +2511,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -2668,7 +2670,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Http.Polly": "[8.0.10, )", "Microsoft.Extensions.Localization": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "System.ComponentModel.Annotations": "[5.0.0, )" } }, @@ -2794,7 +2796,7 @@ "dependencies": { "Ensure.That": "[10.1.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Expressions": "[5.0.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Settings.Configuration": "[8.0.4, )", @@ -3019,11 +3021,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -3414,9 +3416,9 @@ }, "MudBlazor": { "type": "CentralTransitive", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", @@ -3599,9 +3601,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.AspNetCore": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Modules/Core/Core.Presentation/DinnerFiesta.Core.Presentation.csproj b/examples/DinnerFiesta/Modules/Core/Core.Presentation/DinnerFiesta.Core.Presentation.csproj index a386df87..994970fd 100644 --- a/examples/DinnerFiesta/Modules/Core/Core.Presentation/DinnerFiesta.Core.Presentation.csproj +++ b/examples/DinnerFiesta/Modules/Core/Core.Presentation/DinnerFiesta.Core.Presentation.csproj @@ -49,15 +49,14 @@ - - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/examples/DinnerFiesta/Modules/Core/Core.Presentation/Web/Controllers/CoreController.cs b/examples/DinnerFiesta/Modules/Core/Core.Presentation/Web/Controllers/CoreController.cs index bdb5a042..08d43441 100644 --- a/examples/DinnerFiesta/Modules/Core/Core.Presentation/Web/Controllers/CoreController.cs +++ b/examples/DinnerFiesta/Modules/Core/Core.Presentation/Web/Controllers/CoreController.cs @@ -6,6 +6,7 @@ namespace BridgingIT.DevKit.Examples.DinnerFiesta.Modules.Core.Presentation.Web.Controllers; using Application; +using BridgingIT.DevKit.Presentation; using Common; using DevKit.Presentation.Web; using Domain; @@ -44,6 +45,22 @@ public override async Task>> HostFindAll(Can return result.ToOkActionResult(this.mapper); } + // [HttpGet, Route("api/core/hosts/filtered", Name = "Core_HostFindAllFilteredQuery")] + // public async Task>> HostFindAllFilteredQuery([FromQueryFilter] FilterModel filter, CancellationToken cancellationToken) + // { + // var result = (await this.mediator.Send(new HostFindAllQuery(), cancellationToken)).Result; + // + // return result.ToOkActionResult(this.mapper); + // } + // + // [HttpPost, Route("api/core/hosts/filtered", Name = "Core_HostFindAllFilteredBody")] + // public async Task>> HostFindAllFilteredBody([FromBodyFilter] FilterModel filter, CancellationToken cancellationToken) + // { + // var result = (await this.mediator.Send(new HostFindAllQuery(), cancellationToken)).Result; + // + // return result.ToOkActionResult(this.mapper); + // } + public override async Task> HostCreate( [FromBody] HostModel body, CancellationToken cancellationToken) diff --git a/examples/DinnerFiesta/Modules/Core/Core.Presentation/packages.lock.json b/examples/DinnerFiesta/Modules/Core/Core.Presentation/packages.lock.json index 95ce9fe8..168ced3a 100644 --- a/examples/DinnerFiesta/Modules/Core/Core.Presentation/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Core/Core.Presentation/packages.lock.json @@ -51,8 +51,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -1315,7 +1315,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1329,8 +1329,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1364,7 +1365,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1621,11 +1623,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1965,9 +1967,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Modules/Core/Core.UnitTests/packages.lock.json b/examples/DinnerFiesta/Modules/Core/Core.UnitTests/packages.lock.json index 03bc3033..ddd81314 100644 --- a/examples/DinnerFiesta/Modules/Core/Core.UnitTests/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Core/Core.UnitTests/packages.lock.json @@ -182,8 +182,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1631,7 +1631,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1645,8 +1645,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1678,7 +1679,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1695,7 +1696,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1965,11 +1967,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -2332,9 +2334,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Modules/Marketing/Marketing.Application/packages.lock.json b/examples/DinnerFiesta/Modules/Marketing/Marketing.Application/packages.lock.json index 70c01b51..8f1152e3 100644 --- a/examples/DinnerFiesta/Modules/Marketing/Marketing.Application/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Marketing/Marketing.Application/packages.lock.json @@ -33,8 +33,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -397,7 +397,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -411,8 +411,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -446,7 +447,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -523,11 +525,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -741,9 +743,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -751,6 +753,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/examples/DinnerFiesta/Modules/Marketing/Marketing.Domain/packages.lock.json b/examples/DinnerFiesta/Modules/Marketing/Marketing.Domain/packages.lock.json index 50a870f8..5d489502 100644 --- a/examples/DinnerFiesta/Modules/Marketing/Marketing.Domain/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Marketing/Marketing.Domain/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -91,8 +91,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -126,7 +127,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -168,11 +170,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -258,6 +260,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/examples/DinnerFiesta/Modules/Marketing/Marketing.EndToEndTests/packages.lock.json b/examples/DinnerFiesta/Modules/Marketing/Marketing.EndToEndTests/packages.lock.json index 7f7244c9..db8562f3 100644 --- a/examples/DinnerFiesta/Modules/Marketing/Marketing.EndToEndTests/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Marketing/Marketing.EndToEndTests/packages.lock.json @@ -26,24 +26,24 @@ }, "Microsoft.Playwright": { "type": "Direct", - "requested": "[1.47.0, )", - "resolved": "1.47.0", - "contentHash": "D9verOkoSO1vqqAe36jmuQlceEHd2leoYlLOXQkMuVDFTbhvblVk7LOm9LeS50u+5xNcIcJi1+vA2rZxN5tW4A==", + "requested": "[1.48.0, )", + "resolved": "1.48.0", + "contentHash": "u1/zX2/YzpNjkjKurOeLgXrqtQwDVdHMl1/kppxDst3NEg4htJHPVC2xs1dmV+f6P7t/BXlP14xbJ6E5I6tTXQ==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "6.0.0", "System.ComponentModel.Annotations": "5.0.0", - "System.Text.Json": "6.0.0" + "System.Text.Json": "6.0.10" } }, "Microsoft.Playwright.NUnit": { "type": "Direct", - "requested": "[1.47.0, )", - "resolved": "1.47.0", - "contentHash": "DOvKdrTiAX0S7kK9ppl5ZTa8lWvlChAiplxecNHrCbv3/g2CJCM3P3VFLHlU5/mvwuT7kT6VvblUPRGohLl70w==", + "requested": "[1.48.0, )", + "resolved": "1.48.0", + "contentHash": "LcioiiHttSFtqqEX9CivGTV0ATGXI6D5xj1HNGPXZFFug9VL0JjTqHVRQYmTqNwMv+mujA1zgiyFblQzAtyZ4Q==", "dependencies": { "Microsoft.NET.Test.Sdk": "16.11.0", - "Microsoft.Playwright": "1.47.0", - "Microsoft.Playwright.TestAdapter": "1.47.0", + "Microsoft.Playwright": "1.48.0", + "Microsoft.Playwright.TestAdapter": "1.48.0", "NUnit": "3.13.2", "NUnit3TestAdapter": "4.0.0" } @@ -241,8 +241,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -913,10 +913,10 @@ }, "Microsoft.Playwright.TestAdapter": { "type": "Transitive", - "resolved": "1.47.0", - "contentHash": "hKZPcPWknCYPSajPr16Xrwh0q3C7hx7GO2bmWLZ2uFMS3y+78UeBUBkMwMUJdtZRkHDtQGKRw7uPX8wEuOiDCw==", + "resolved": "1.48.0", + "contentHash": "ARFL+9yXFiln1U4twjM0XeSqwxRctOf/ABEjm3Lt0879UGK3dMEBck3H32EQ2jMOtRhdp5ovD1O9bh3h6E8JMw==", "dependencies": { - "Microsoft.Playwright": "1.47.0", + "Microsoft.Playwright": "1.48.0", "Microsoft.TestPlatform.ObjectModel": "17.3.0" } }, @@ -2108,7 +2108,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -2122,8 +2122,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -2162,7 +2163,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -2179,7 +2180,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -2319,7 +2321,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Http.Polly": "[8.0.10, )", "Microsoft.Extensions.Localization": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "System.ComponentModel.Annotations": "[5.0.0, )" } }, @@ -2445,7 +2447,7 @@ "dependencies": { "Ensure.That": "[10.1.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Expressions": "[5.0.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Settings.Configuration": "[8.0.4, )", @@ -2680,11 +2682,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -3099,9 +3101,9 @@ }, "MudBlazor": { "type": "CentralTransitive", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", @@ -3284,9 +3286,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.AspNetCore": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Modules/Marketing/Marketing.Infrastructure/packages.lock.json b/examples/DinnerFiesta/Modules/Marketing/Marketing.Infrastructure/packages.lock.json index d9279221..1c51402c 100644 --- a/examples/DinnerFiesta/Modules/Marketing/Marketing.Infrastructure/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Marketing/Marketing.Infrastructure/packages.lock.json @@ -68,8 +68,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -668,7 +668,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -682,8 +682,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -717,7 +718,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -899,11 +901,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1203,9 +1205,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Modules/Marketing/Marketing.IntegrationTests/packages.lock.json b/examples/DinnerFiesta/Modules/Marketing/Marketing.IntegrationTests/packages.lock.json index 034efa51..adc4314f 100644 --- a/examples/DinnerFiesta/Modules/Marketing/Marketing.IntegrationTests/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Marketing/Marketing.IntegrationTests/packages.lock.json @@ -407,8 +407,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -2368,7 +2368,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -2382,8 +2382,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -2422,7 +2423,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -2439,7 +2440,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -2597,7 +2599,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Http.Polly": "[8.0.10, )", "Microsoft.Extensions.Localization": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "System.ComponentModel.Annotations": "[5.0.0, )" } }, @@ -2723,7 +2725,7 @@ "dependencies": { "Ensure.That": "[10.1.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Expressions": "[5.0.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Settings.Configuration": "[8.0.4, )", @@ -2948,11 +2950,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -3343,9 +3345,9 @@ }, "MudBlazor": { "type": "CentralTransitive", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", @@ -3528,9 +3530,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.AspNetCore": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Modules/Marketing/Marketing.Presentation/DinnerFiesta.Marketing.Presentation.csproj b/examples/DinnerFiesta/Modules/Marketing/Marketing.Presentation/DinnerFiesta.Marketing.Presentation.csproj index 2506cba9..f5314078 100644 --- a/examples/DinnerFiesta/Modules/Marketing/Marketing.Presentation/DinnerFiesta.Marketing.Presentation.csproj +++ b/examples/DinnerFiesta/Modules/Marketing/Marketing.Presentation/DinnerFiesta.Marketing.Presentation.csproj @@ -47,15 +47,14 @@ - - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/examples/DinnerFiesta/Modules/Marketing/Marketing.Presentation/packages.lock.json b/examples/DinnerFiesta/Modules/Marketing/Marketing.Presentation/packages.lock.json index 106be5ce..4fc1574e 100644 --- a/examples/DinnerFiesta/Modules/Marketing/Marketing.Presentation/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Marketing/Marketing.Presentation/packages.lock.json @@ -51,8 +51,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -962,7 +962,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -976,8 +976,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1011,7 +1012,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1236,11 +1238,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1547,9 +1549,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Modules/Marketing/Marketing.UnitTests/packages.lock.json b/examples/DinnerFiesta/Modules/Marketing/Marketing.UnitTests/packages.lock.json index 2a10f65a..362b56a9 100644 --- a/examples/DinnerFiesta/Modules/Marketing/Marketing.UnitTests/packages.lock.json +++ b/examples/DinnerFiesta/Modules/Marketing/Marketing.UnitTests/packages.lock.json @@ -182,8 +182,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1278,7 +1278,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1292,8 +1292,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1325,7 +1326,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1342,7 +1343,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1578,11 +1580,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1912,9 +1914,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", diff --git a/examples/DinnerFiesta/Presentation.Web.Client/packages.lock.json b/examples/DinnerFiesta/Presentation.Web.Client/packages.lock.json index 0284312c..910f9f28 100644 --- a/examples/DinnerFiesta/Presentation.Web.Client/packages.lock.json +++ b/examples/DinnerFiesta/Presentation.Web.Client/packages.lock.json @@ -100,9 +100,9 @@ }, "MudBlazor": { "type": "Direct", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", diff --git a/examples/DinnerFiesta/Presentation.Web.Server/DinnerFiesta.Presentation.Web.Server.csproj b/examples/DinnerFiesta/Presentation.Web.Server/DinnerFiesta.Presentation.Web.Server.csproj index a0a9d394..b4e4f17f 100644 --- a/examples/DinnerFiesta/Presentation.Web.Server/DinnerFiesta.Presentation.Web.Server.csproj +++ b/examples/DinnerFiesta/Presentation.Web.Server/DinnerFiesta.Presentation.Web.Server.csproj @@ -70,16 +70,16 @@ - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/examples/DinnerFiesta/Presentation.Web.Server/Program.cs b/examples/DinnerFiesta/Presentation.Web.Server/Program.cs index ba6e7f29..c4fe4168 100644 --- a/examples/DinnerFiesta/Presentation.Web.Server/Program.cs +++ b/examples/DinnerFiesta/Presentation.Web.Server/Program.cs @@ -228,6 +228,7 @@ void ConfigureJsonOptions(JsonOptions options) options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); } void ConfigureHealth(IServiceCollection services) diff --git a/examples/DinnerFiesta/Presentation.Web.Server/packages.lock.json b/examples/DinnerFiesta/Presentation.Web.Server/packages.lock.json index bef09203..a8efdbfc 100644 --- a/examples/DinnerFiesta/Presentation.Web.Server/packages.lock.json +++ b/examples/DinnerFiesta/Presentation.Web.Server/packages.lock.json @@ -397,8 +397,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -2161,7 +2161,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -2175,8 +2175,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -2217,7 +2218,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -2357,7 +2359,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Http.Polly": "[8.0.10, )", "Microsoft.Extensions.Localization": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "System.ComponentModel.Annotations": "[5.0.0, )" } }, @@ -2442,7 +2444,7 @@ "dependencies": { "Ensure.That": "[10.1.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Expressions": "[5.0.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Settings.Configuration": "[8.0.4, )", @@ -2646,11 +2648,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -3016,9 +3018,9 @@ }, "MudBlazor": { "type": "CentralTransitive", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", @@ -3113,9 +3115,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Expressions": { "type": "CentralTransitive", diff --git a/examples/EventSourcingDemo/EventSourcingDemo.Application/packages.lock.json b/examples/EventSourcingDemo/EventSourcingDemo.Application/packages.lock.json index 2253e992..a4e9f26b 100644 --- a/examples/EventSourcingDemo/EventSourcingDemo.Application/packages.lock.json +++ b/examples/EventSourcingDemo/EventSourcingDemo.Application/packages.lock.json @@ -25,8 +25,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -324,7 +324,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -338,8 +338,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -373,7 +374,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -448,11 +450,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -640,9 +642,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -650,6 +652,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/examples/EventSourcingDemo/EventSourcingDemo.Domain/packages.lock.json b/examples/EventSourcingDemo/EventSourcingDemo.Domain/packages.lock.json index 858ce78e..9549bb60 100644 --- a/examples/EventSourcingDemo/EventSourcingDemo.Domain/packages.lock.json +++ b/examples/EventSourcingDemo/EventSourcingDemo.Domain/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -117,8 +117,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -152,7 +153,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -220,11 +222,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -323,6 +325,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/examples/EventSourcingDemo/EventSourcingDemo.Infrastructure/packages.lock.json b/examples/EventSourcingDemo/EventSourcingDemo.Infrastructure/packages.lock.json index a9f67e7a..3d10f753 100644 --- a/examples/EventSourcingDemo/EventSourcingDemo.Infrastructure/packages.lock.json +++ b/examples/EventSourcingDemo/EventSourcingDemo.Infrastructure/packages.lock.json @@ -36,8 +36,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -502,7 +502,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -516,8 +516,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -551,7 +552,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -765,11 +767,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1041,9 +1043,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/examples/EventSourcingDemo/EventSourcingDemo.Presentation.Web/packages.lock.json b/examples/EventSourcingDemo/EventSourcingDemo.Presentation.Web/packages.lock.json index c512fd33..a6253baf 100644 --- a/examples/EventSourcingDemo/EventSourcingDemo.Presentation.Web/packages.lock.json +++ b/examples/EventSourcingDemo/EventSourcingDemo.Presentation.Web/packages.lock.json @@ -86,8 +86,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -1067,7 +1067,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1081,8 +1081,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1116,7 +1117,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -1379,11 +1381,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1657,9 +1659,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/examples/EventSourcingDemo/tests/EventSourcingDemo.Integration.Tests/packages.lock.json b/examples/EventSourcingDemo/tests/EventSourcingDemo.Integration.Tests/packages.lock.json index 5911e07b..56115533 100644 --- a/examples/EventSourcingDemo/tests/EventSourcingDemo.Integration.Tests/packages.lock.json +++ b/examples/EventSourcingDemo/tests/EventSourcingDemo.Integration.Tests/packages.lock.json @@ -226,8 +226,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1445,7 +1445,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1459,8 +1459,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1492,7 +1493,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1509,7 +1510,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -1789,11 +1791,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -2121,9 +2123,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", diff --git a/examples/WeatherForecast/WeatherForecast-REST.http b/examples/WeatherForecast/WeatherForecast-REST.http index 12fce443..e170b44f 100644 --- a/examples/WeatherForecast/WeatherForecast-REST.http +++ b/examples/WeatherForecast/WeatherForecast-REST.http @@ -1,5 +1,4 @@ -@baseUrl = https://localhost:44352 -@city_id = ddf66783-5cd3-d02a-2d09-83e3cd9a2b94 +@baseUrl = https://localhost:5001 ################################################################################### ### API [GET] City FindAll # @@ -53,6 +52,22 @@ Content-Type: application/json } ################################################################################### -### API [GET] Forecast FindAll # -GET {{baseUrl}}/api/core/forecasts HTTP/1.1 +### API [GET] Cities FindAll FILTER # +GET {{baseUrl}}/api/core/cities?filter={"page":1,"pageSize":10,"filters":[{"field":"Name","operator":"eq","value":"Berlin"}]} HTTP/1.1 +Host: api.example.com +Accept: application/json + +################################################################################### +### API [GET] Cities FindAll FILTER # +GET {{baseUrl}}/api/core/cities?filter=%20%7B%0A%22page%22%3A%201%2C%0A%22pageSize%22%3A%2010%2C%0A%22filters%22%3A%20%5B%0A%7B%20%22field%22%3A%20%22Name%22%2C%20%22operator%22%3A%20%22eq%22%2C%20%22value%22%3A%20%22Berlin%22%20%7D%0A%5D%0A%7D HTTP/1.1 Content-Type: application/json + +################################################################################### +### API [GET] Forecast FindAll FILTER # +GET {{baseUrl}}/api/core/forecasts?filter={"page":0,"pageSize":0,"filters":[{"field":"type.name","operator":"isnotnull"},{"field":"type.name","operator":"eq","value":"AAA" },{"field":"temperatureMin","operator":"gte","value":16.1 },{ "field":"timestamp","operator": "gte","value":"2024-10-24T10:00:00"}]} HTTP/1.1 +Content-Type: application/json + +################################################################################### +### API [GET] Forecast FindAll FILTER # +GET {{baseUrl}}/api/core/forecasts?filter=%7B%0A%22page%22%3A%200%2C%0A%22pageSize%22%3A%200%2C%0A%22filters%22%3A%20%5B%0A%7B%20%22field%22%3A%20%22type.name%22%2C%20%22operator%22%3A%20%22isnotnull%22%20%7D%2C%0A%7B%20%22field%22%3A%20%22type.name%22%2C%20%22operator%22%3A%20%22eq%22%2C%20%22value%22%3A%20%22AAA%22%20%7D%2C%0A%7B%20%22field%22%3A%20%22temperatureMin%22%2C%20%22operator%22%3A%20%22gte%22%2C%20%22value%22%3A%2016.1%20%7D%2C%0A%7B%20%22field%22%3A%20%22timestamp%22%2C%20%22operator%22%3A%20%22gte%22%2C%20%22value%22%3A%20%222024-10-24T10%3A00%3A00%2B00%3A00%22%20%7D%0A%5D%0A%7D HTTP/1.1 +Content-Type: application/json \ No newline at end of file diff --git a/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/CityFindAllQuery.cs b/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/CityFindAllQuery.cs index 88de52f8..ade3a5bd 100644 --- a/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/CityFindAllQuery.cs +++ b/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/CityFindAllQuery.cs @@ -5,10 +5,13 @@ namespace BridgingIT.DevKit.Examples.WeatherForecast.Application.Modules.Core; +using BridgingIT.DevKit.Common; using DevKit.Application.Queries; -public class CityFindAllQuery : QueryRequestBase>, ICacheQuery +public class CityFindAllQuery(FilterModel filter = null) : QueryRequestBase>, ICacheQuery { + public FilterModel Filter { get; } = filter; + CacheQueryOptions ICacheQuery.Options => new() { Key = $"application_{nameof(CityFindAllQuery)}", SlidingExpiration = new TimeSpan(0, 0, 30) }; } \ No newline at end of file diff --git a/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/CityFindAllQueryHandler.cs b/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/CityFindAllQueryHandler.cs index c95b935e..c7fbe54d 100644 --- a/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/CityFindAllQueryHandler.cs +++ b/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/CityFindAllQueryHandler.cs @@ -27,10 +27,12 @@ public CityFindAllQueryHandler( } public override async Task>> Process( - CityFindAllQuery request, + CityFindAllQuery query, CancellationToken cancellationToken) { - var cities = await this.cityRepository.FindAllAsync(new CityIsNotDeletedSpecification(), + var cities = await this.cityRepository.FindAllAsync( + query.Filter, + [new CityIsNotDeletedSpecification()], cancellationToken: cancellationToken) .AnyContext(); diff --git a/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/ForecastFindAllQuery.cs b/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/ForecastFindAllQuery.cs index 40ce7613..2e1908fa 100644 --- a/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/ForecastFindAllQuery.cs +++ b/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/ForecastFindAllQuery.cs @@ -5,6 +5,10 @@ namespace BridgingIT.DevKit.Examples.WeatherForecast.Application.Modules.Core; +using BridgingIT.DevKit.Common; using DevKit.Application.Queries; -public class ForecastFindAllQuery : QueryRequestBase> { } \ No newline at end of file +public class ForecastFindAllQuery(FilterModel filter = null) : QueryRequestBase> +{ + public FilterModel Filter { get; } = filter; +} \ No newline at end of file diff --git a/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/ForecastFindAllQueryHandler.cs b/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/ForecastFindAllQueryHandler.cs index 590510ec..a3293af6 100644 --- a/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/ForecastFindAllQueryHandler.cs +++ b/examples/WeatherForecast/WeatherForecast.Application/Modules/Core/Queries/ForecastFindAllQueryHandler.cs @@ -30,8 +30,8 @@ public override async Task>> Pr CancellationToken cancellationToken) { var forecasts = await this.forecastRepository.FindAllAsync( - new FindOptions { Order = new OrderOption(e => e.Timestamp) }, - cancellationToken) + query.Filter, + cancellationToken: cancellationToken) // //new FindOptions { Order = new OrderOption(e => e.Timestamp) }, .AnyContext(); return new QueryResponse> diff --git a/examples/WeatherForecast/WeatherForecast.Application/packages.lock.json b/examples/WeatherForecast/WeatherForecast.Application/packages.lock.json index a1ae17fe..1041c7b7 100644 --- a/examples/WeatherForecast/WeatherForecast.Application/packages.lock.json +++ b/examples/WeatherForecast/WeatherForecast.Application/packages.lock.json @@ -47,8 +47,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -411,7 +411,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -425,8 +425,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -460,7 +461,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -539,11 +541,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -757,9 +759,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -767,6 +769,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/examples/WeatherForecast/WeatherForecast.Domain/packages.lock.json b/examples/WeatherForecast/WeatherForecast.Domain/packages.lock.json index 50a870f8..5d489502 100644 --- a/examples/WeatherForecast/WeatherForecast.Domain/packages.lock.json +++ b/examples/WeatherForecast/WeatherForecast.Domain/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -91,8 +91,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -126,7 +127,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -168,11 +170,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -258,6 +260,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/examples/WeatherForecast/WeatherForecast.EndToEndTests/packages.lock.json b/examples/WeatherForecast/WeatherForecast.EndToEndTests/packages.lock.json index 9af17972..b75932a7 100644 --- a/examples/WeatherForecast/WeatherForecast.EndToEndTests/packages.lock.json +++ b/examples/WeatherForecast/WeatherForecast.EndToEndTests/packages.lock.json @@ -26,24 +26,24 @@ }, "Microsoft.Playwright": { "type": "Direct", - "requested": "[1.47.0, )", - "resolved": "1.47.0", - "contentHash": "D9verOkoSO1vqqAe36jmuQlceEHd2leoYlLOXQkMuVDFTbhvblVk7LOm9LeS50u+5xNcIcJi1+vA2rZxN5tW4A==", + "requested": "[1.48.0, )", + "resolved": "1.48.0", + "contentHash": "u1/zX2/YzpNjkjKurOeLgXrqtQwDVdHMl1/kppxDst3NEg4htJHPVC2xs1dmV+f6P7t/BXlP14xbJ6E5I6tTXQ==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "6.0.0", "System.ComponentModel.Annotations": "5.0.0", - "System.Text.Json": "6.0.0" + "System.Text.Json": "6.0.10" } }, "Microsoft.Playwright.NUnit": { "type": "Direct", - "requested": "[1.47.0, )", - "resolved": "1.47.0", - "contentHash": "DOvKdrTiAX0S7kK9ppl5ZTa8lWvlChAiplxecNHrCbv3/g2CJCM3P3VFLHlU5/mvwuT7kT6VvblUPRGohLl70w==", + "requested": "[1.48.0, )", + "resolved": "1.48.0", + "contentHash": "LcioiiHttSFtqqEX9CivGTV0ATGXI6D5xj1HNGPXZFFug9VL0JjTqHVRQYmTqNwMv+mujA1zgiyFblQzAtyZ4Q==", "dependencies": { "Microsoft.NET.Test.Sdk": "16.11.0", - "Microsoft.Playwright": "1.47.0", - "Microsoft.Playwright.TestAdapter": "1.47.0", + "Microsoft.Playwright": "1.48.0", + "Microsoft.Playwright.TestAdapter": "1.48.0", "NUnit": "3.13.2", "NUnit3TestAdapter": "4.0.0" } @@ -232,8 +232,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -899,10 +899,10 @@ }, "Microsoft.Playwright.TestAdapter": { "type": "Transitive", - "resolved": "1.47.0", - "contentHash": "hKZPcPWknCYPSajPr16Xrwh0q3C7hx7GO2bmWLZ2uFMS3y+78UeBUBkMwMUJdtZRkHDtQGKRw7uPX8wEuOiDCw==", + "resolved": "1.48.0", + "contentHash": "ARFL+9yXFiln1U4twjM0XeSqwxRctOf/ABEjm3Lt0879UGK3dMEBck3H32EQ2jMOtRhdp5ovD1O9bh3h6E8JMw==", "dependencies": { - "Microsoft.Playwright": "1.47.0", + "Microsoft.Playwright": "1.48.0", "Microsoft.TestPlatform.ObjectModel": "17.3.0" } }, @@ -1754,7 +1754,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1768,8 +1768,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1801,7 +1802,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1818,7 +1819,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1890,7 +1892,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Http.Polly": "[8.0.10, )", "Microsoft.Extensions.Localization": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "Newtonsoft.Json": "[13.0.3, )", "System.ComponentModel.Annotations": "[5.0.0, )" } @@ -1917,7 +1919,7 @@ "Ensure.That": "[10.1.0, )", "Microsoft.ApplicationInsights.AspNetCore": "[2.22.0, )", "Microsoft.AspNetCore.Components.WebAssembly.Server": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "OpenTelemetry.Exporter.Console": "[1.9.0, )", "OpenTelemetry.Exporter.Jaeger": "[1.5.1, )", "OpenTelemetry.Exporter.Prometheus.AspNetCore": "[1.7.0-rc.1, )", @@ -1994,7 +1996,7 @@ "dependencies": { "Ensure.That": "[10.1.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Expressions": "[5.0.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Settings.Configuration": "[8.0.4, )", @@ -2214,11 +2216,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -2612,9 +2614,9 @@ }, "MudBlazor": { "type": "CentralTransitive", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", @@ -2787,9 +2789,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.AspNetCore": { "type": "CentralTransitive", diff --git a/examples/WeatherForecast/WeatherForecast.Infrastructure/packages.lock.json b/examples/WeatherForecast/WeatherForecast.Infrastructure/packages.lock.json index f5d2608a..c9a27e9a 100644 --- a/examples/WeatherForecast/WeatherForecast.Infrastructure/packages.lock.json +++ b/examples/WeatherForecast/WeatherForecast.Infrastructure/packages.lock.json @@ -77,8 +77,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -680,7 +680,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -694,8 +694,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -729,7 +730,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -885,11 +887,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1203,9 +1205,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/examples/WeatherForecast/WeatherForecast.IntegrationTests/packages.lock.json b/examples/WeatherForecast/WeatherForecast.IntegrationTests/packages.lock.json index 94c40a7b..576464cc 100644 --- a/examples/WeatherForecast/WeatherForecast.IntegrationTests/packages.lock.json +++ b/examples/WeatherForecast/WeatherForecast.IntegrationTests/packages.lock.json @@ -294,8 +294,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -1824,7 +1824,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1838,8 +1838,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1871,7 +1872,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1888,7 +1889,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1960,7 +1962,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Http.Polly": "[8.0.10, )", "Microsoft.Extensions.Localization": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "Newtonsoft.Json": "[13.0.3, )", "System.ComponentModel.Annotations": "[5.0.0, )" } @@ -1987,7 +1989,7 @@ "Ensure.That": "[10.1.0, )", "Microsoft.ApplicationInsights.AspNetCore": "[2.22.0, )", "Microsoft.AspNetCore.Components.WebAssembly.Server": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "OpenTelemetry.Exporter.Console": "[1.9.0, )", "OpenTelemetry.Exporter.Jaeger": "[1.5.1, )", "OpenTelemetry.Exporter.Prometheus.AspNetCore": "[1.7.0-rc.1, )", @@ -2064,7 +2066,7 @@ "dependencies": { "Ensure.That": "[10.1.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Expressions": "[5.0.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Settings.Configuration": "[8.0.4, )", @@ -2278,11 +2280,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -2676,9 +2678,9 @@ }, "MudBlazor": { "type": "CentralTransitive", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", @@ -2851,9 +2853,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.AspNetCore": { "type": "CentralTransitive", diff --git a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Client/packages.lock.json b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Client/packages.lock.json index 5efa7b25..6ff927b2 100644 --- a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Client/packages.lock.json +++ b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Client/packages.lock.json @@ -100,9 +100,9 @@ }, "MudBlazor": { "type": "Direct", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", diff --git a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/Controllers/CityController.cs b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/Controllers/CityController.cs index fa01f50a..73ad5c90 100644 --- a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/Controllers/CityController.cs +++ b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/Controllers/CityController.cs @@ -7,6 +7,7 @@ namespace BridgingIT.DevKit.Examples.WeatherForecast.Presentation.Web.Server.Mod using System.Net; using Application.Modules.Core; +using BridgingIT.DevKit.Presentation; using Common; using DevKit.Presentation.Web; using MediatR; @@ -37,9 +38,19 @@ public CityController( [HttpGet] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.InternalServerError)] - public async Task>> GetAll() + public async Task>> GetAll([FromQueryFilter] FilterModel filter) { - var response = await this.mediator.Send(new CityFindAllQuery()).AnyContext(); + // Example filter model: + // { + // "page": 1, + // "pageSize": 10, + // "filters": [ + // { "field": "Name", "operator": "eq", "value": "Berlin" } + // ] + // } + + var response = await this.mediator.Send( + new CityFindAllQuery(filter)).AnyContext(); return this.Ok(this.mapper.Map(response.Result)); } diff --git a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/Controllers/ForecastController.cs b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/Controllers/ForecastController.cs index 59228a75..bd1a30ee 100644 --- a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/Controllers/ForecastController.cs +++ b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/Controllers/ForecastController.cs @@ -7,6 +7,7 @@ namespace BridgingIT.DevKit.Examples.WeatherForecast.Presentation.Web.Server.Mod using System.Net; using Application.Modules.Core; +using BridgingIT.DevKit.Presentation; using Common; using Domain.Model; using MediatR; @@ -37,9 +38,22 @@ public ForecastController( [HttpGet] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.InternalServerError)] - public async Task>> GetAll() + public async Task>> GetAll([FromQueryFilter] FilterModel filter) { - var response = await this.mediator.Send(new ForecastFindAllQuery()).AnyContext(); + // Example filter model: + // { + // "page": 0, + // "pageSize": 0, + // "filters": [ + // { "field": "type.name", "operator": "isnotnull" }, + // { "field": "type.name", "operator": "eq", "value": "AAA" }, + // { "field": "temperatureMin", "operator": "gte", "value": 16.1 }, + // { "field": "timestamp", "operator": "gte", "value": "2024-10-24T10:00:00+00:00" } + // ] + // } + + var response = await this.mediator.Send( + new ForecastFindAllQuery(filter)).AnyContext(); return this.Ok(this.mapper.Map(response.Result)); } diff --git a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/CoreModule.cs b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/CoreModule.cs index 01018330..795eecbb 100644 --- a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/CoreModule.cs +++ b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Modules/Core/CoreModule.cs @@ -41,13 +41,11 @@ public override IServiceCollection Register( var validator = new InlineValidator(); validator .RuleFor(applicationOptions => applicationOptions.OpenWeatherUrl) - .NotNull() - .NotEmpty() + .NotNull().NotEmpty() .WithMessage("OpenWeatherUrl cannot be null or empty"); validator .RuleFor(applicationOptions => applicationOptions.OpenWeatherApiKey) - .NotNull() - .NotEmpty() + .NotNull().NotEmpty() .WithMessage("OpenWeatherApiKey cannot be null or empty"); return validator diff --git a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Program.cs b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Program.cs index 6bd1b84c..f0b79ba2 100644 --- a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Program.cs +++ b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/Program.cs @@ -215,6 +215,7 @@ void ConfigureJsonOptions(JsonOptions options) options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); } void ConfigureHealth(IServiceCollection services) diff --git a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/packages.lock.json b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/packages.lock.json index 33536e71..01f78bbc 100644 --- a/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/packages.lock.json +++ b/examples/WeatherForecast/WeatherForecast.Presentation.Web.Server/packages.lock.json @@ -85,9 +85,9 @@ }, "MudBlazor": { "type": "Direct", - "requested": "[7.12.1, )", - "resolved": "7.12.1", - "contentHash": "ga1LYOIIORpVCe0k8mw5ZhY0sgTGYfVY9y/z9cup5cdhrVGYLH9iAViovTvjEdHvjIOqmcyVO6OSAdKZUdgWHA==", + "requested": "[7.13.0, )", + "resolved": "7.13.0", + "contentHash": "FapnlYCCN84ah+5vwMjcLAvdwFUXhsNxroi7y1sRI0vqYtUR6IZu0CaTbI9zcFAjDHQ4acHVNN71rcRbOfX+Rg==", "dependencies": { "Microsoft.AspNetCore.Components": "8.0.8", "Microsoft.AspNetCore.Components.Web": "8.0.8", @@ -389,8 +389,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -1808,7 +1808,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1822,8 +1822,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1857,7 +1858,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1929,7 +1931,7 @@ "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Http.Polly": "[8.0.10, )", "Microsoft.Extensions.Localization": "[8.0.10, )", - "MudBlazor": "[7.12.1, )", + "MudBlazor": "[7.13.0, )", "Newtonsoft.Json": "[13.0.3, )", "System.ComponentModel.Annotations": "[5.0.0, )" } @@ -1992,7 +1994,7 @@ "dependencies": { "Ensure.That": "[10.1.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[8.0.0, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Expressions": "[5.0.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Settings.Configuration": "[8.0.4, )", @@ -2181,11 +2183,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -2616,9 +2618,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Expressions": { "type": "CentralTransitive", diff --git a/src/Application.Commands.EventSourcing/packages.lock.json b/src/Application.Commands.EventSourcing/packages.lock.json index 19c2f929..9b9bc613 100644 --- a/src/Application.Commands.EventSourcing/packages.lock.json +++ b/src/Application.Commands.EventSourcing/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -307,7 +307,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -321,8 +321,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -356,7 +357,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -424,11 +426,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -616,9 +618,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -626,6 +628,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Application.Commands.Outbox/packages.lock.json b/src/Application.Commands.Outbox/packages.lock.json index 88c8d454..258891c3 100644 --- a/src/Application.Commands.Outbox/packages.lock.json +++ b/src/Application.Commands.Outbox/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -307,7 +307,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -321,8 +321,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -356,7 +357,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -407,11 +409,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -599,9 +601,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -609,6 +611,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Application.Commands/packages.lock.json b/src/Application.Commands/packages.lock.json index 662d2b99..7469ef20 100644 --- a/src/Application.Commands/packages.lock.json +++ b/src/Application.Commands/packages.lock.json @@ -59,8 +59,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -334,7 +334,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -348,8 +348,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -383,7 +384,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -409,11 +411,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -574,9 +576,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -584,6 +586,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Application.Entities.Messaging/packages.lock.json b/src/Application.Entities.Messaging/packages.lock.json index 2181eaf9..6c7046ab 100644 --- a/src/Application.Entities.Messaging/packages.lock.json +++ b/src/Application.Entities.Messaging/packages.lock.json @@ -21,8 +21,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -366,7 +366,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -380,8 +380,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -415,7 +416,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -485,11 +487,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -677,9 +679,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -687,6 +689,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Application.Entities/packages.lock.json b/src/Application.Entities/packages.lock.json index cc746c51..08cf852d 100644 --- a/src/Application.Entities/packages.lock.json +++ b/src/Application.Entities/packages.lock.json @@ -27,8 +27,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -346,7 +346,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -360,8 +360,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -395,7 +396,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -465,11 +467,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -651,9 +653,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -661,6 +663,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Application.JobScheduling/packages.lock.json b/src/Application.JobScheduling/packages.lock.json index 3ff770a6..f8cc4a34 100644 --- a/src/Application.JobScheduling/packages.lock.json +++ b/src/Application.JobScheduling/packages.lock.json @@ -60,8 +60,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -325,7 +325,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -339,8 +339,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -372,11 +373,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -518,9 +519,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Application.Messaging/packages.lock.json b/src/Application.Messaging/packages.lock.json index 79351f46..96355da5 100644 --- a/src/Application.Messaging/packages.lock.json +++ b/src/Application.Messaging/packages.lock.json @@ -40,8 +40,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -305,7 +305,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -319,8 +319,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -352,11 +353,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -498,9 +499,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Application.Queries/packages.lock.json b/src/Application.Queries/packages.lock.json index 771aa734..b8b605fe 100644 --- a/src/Application.Queries/packages.lock.json +++ b/src/Application.Queries/packages.lock.json @@ -64,8 +64,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -351,7 +351,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -365,8 +365,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -432,11 +433,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -591,9 +592,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Application.Storage/packages.lock.json b/src/Application.Storage/packages.lock.json index 980da1f7..72e1f485 100644 --- a/src/Application.Storage/packages.lock.json +++ b/src/Application.Storage/packages.lock.json @@ -59,8 +59,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -153,8 +153,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -186,11 +187,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, diff --git a/src/Application.Utilities/packages.lock.json b/src/Application.Utilities/packages.lock.json index 536bb4c6..46fe19b3 100644 --- a/src/Application.Utilities/packages.lock.json +++ b/src/Application.Utilities/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -281,7 +281,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -295,8 +295,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -334,11 +335,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -498,9 +499,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Common.Abstractions/Filtering/FilterModel.cs b/src/Common.Abstractions/Filtering/FilterModel.cs new file mode 100644 index 00000000..cd90a8af --- /dev/null +++ b/src/Common.Abstractions/Filtering/FilterModel.cs @@ -0,0 +1,223 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Common; + +using System.Runtime.Serialization; + +public class FilterModel +{ + public int Page { get; set; } = 1; + + public int PageSize { get; set; } = 10; + + public List Orderings { get; set; } = []; + + public List Filters { get; set; } = []; + + public List Includes { get; set; } = []; +} + +public class FilterCriteria +{ + public FilterCriteria() { } + + public FilterCriteria(string field, FilterOperator @operator, object value) + { + this.Field = field; + this.Operator = @operator; + this.Value = value; + } + + public FilterCriteria(FilterCustomType customType, Dictionary customParameters = null) + { + this.CustomType = customType; + this.CustomParameters = customParameters; + } + + public FilterCriteria(string specificationName, object[] specificationArguments) + { + this.SpecificationName = specificationName; + this.SpecificationArguments = specificationArguments; + } + + public string Field { get; set; } + + public FilterOperator Operator { get; set; } + + public object Value { get; set; } + + public FilterLogicOperator Logic { get; set; } = FilterLogicOperator.And; // TODO: make optional + + public List Filters { get; set; } = []; + + public FilterCustomType CustomType { get; set; } = FilterCustomType.None; // TODO: make optional + + public Dictionary CustomParameters { get; set; } + + public string SpecificationName { get; set; } + + public object[] SpecificationArguments { get; set; } + + public CompositeSpecification CompositeSpecification { get; set; } +} + +public class FilterOrderCriteria +{ + public string Field { get; set; } // TODO: rename to Name + + public OrderDirection Direction { get; set; } = OrderDirection.Ascending; +} + +public enum FilterLogicOperator +{ + [EnumMember(Value = "and")] + And, + + [EnumMember(Value = "or")] + Or +} + +public enum FilterOperator +{ + [EnumMember(Value = "eq")] + Equal, + + [EnumMember(Value = "neq")] + NotEqual, + + [EnumMember(Value = "isnull")] + IsNull, + + [EnumMember(Value = "isnotnull")] + IsNotNull, + + [EnumMember(Value = "isempty")] + IsEmpty, + + [EnumMember(Value = "isnotempty")] + IsNotEmpty, + + [EnumMember(Value = "gt")] + GreaterThan, + + [EnumMember(Value = "gte")] + GreaterThanOrEqual, + + [EnumMember(Value = "lt")] + LessThan, + + [EnumMember(Value = "lte")] + LessThanOrEqual, + + [EnumMember(Value = "contains")] + Contains, + + [EnumMember(Value = "doesnotcontain")] // string only + DoesNotContain, + + [EnumMember(Value = "startswith")] + StartsWith, + + [EnumMember(Value = "doesnotstartwith")] // string only + DoesNotStartWith, + + [EnumMember(Value = "endswith")] + EndsWith, + + [EnumMember(Value = "doesnotendwith")] // string only + DoesNotEndWith, + + [EnumMember(Value = "any")] + Any, // children + + [EnumMember(Value = "all")] + All, // children + + [EnumMember(Value = "none")] + None, // children +} + +public enum FilterCustomType +{ + [EnumMember(Value = "none")] + None, + + [EnumMember(Value = "fulltextsearch")] // params: searchTerm, fields + FullTextSearch, + + [EnumMember(Value = "daterange")] // params: field, startDate, endDate, inclusive + DateRange, + + [EnumMember(Value = "daterelative")] // params: field, unit (day/week/month/year), amount, direction (past/future) + DateRelative, + + [EnumMember(Value = "timerange")] // params: field, startTime, endTime, inclusive + TimeRange, + + [EnumMember(Value = "timerelative")] // params: field, unit (minute/hour), amount, direction (past/future) + TimeRelative, + + [EnumMember(Value = "numericrange")] // params: field, min, max + NumericRange, + + [EnumMember(Value = "isnull")] // params: field + IsNull, + + [EnumMember(Value = "isnotnull")] // params:field + IsNotNull, + + [EnumMember(Value = "enumvalues")] // params: field, values + EnumValues, + + [EnumMember(Value = "textin")] // params: field, values + TextIn, + + [EnumMember(Value = "textnotin")] // params: field, values + TextNotIn, + + [EnumMember(Value = "numericin")] // params: field, values + NumericIn, + + [EnumMember(Value = "numericnotin")] // params: field, values + NumericNotIn, + + [EnumMember(Value = "namedspecification")] + NamedSpecification, + + [EnumMember(Value = "compositespecification")] + CompositeSpecification +} + +public class CompositeSpecification +{ + public List Nodes { get; set; } = []; +} + + +public abstract class SpecificationNode { } + +public class SpecificationLeaf : SpecificationNode +{ + public string Name { get; set; } // name of registered specification + + public object[] Arguments { get; set; } +} + +public class SpecificationGroup : SpecificationNode +{ + public FilterLogicOperator Logic { get; set; } + + public List Nodes { get; set; } = []; +} + +public enum OrderDirection +{ + [EnumMember(Value = "asc")] + Ascending, + + [EnumMember(Value = "desc")] + Descending +} \ No newline at end of file diff --git a/src/Common.Abstractions/OrderDirection.cs b/src/Common.Abstractions/OrderDirection.cs new file mode 100644 index 00000000..2581ada4 --- /dev/null +++ b/src/Common.Abstractions/OrderDirection.cs @@ -0,0 +1,13 @@ +// // MIT-License +// // Copyright BridgingIT GmbH - All Rights Reserved +// // Use of this source code is governed by an MIT-style license that can be +// // found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license +// +// namespace BridgingIT.DevKit.Common; +// +// public enum OrderDirection +// { +// Ascending, +// +// Descending +// } \ No newline at end of file diff --git a/src/Common.Abstractions/Result/Errors/ExceptionError.cs b/src/Common.Abstractions/Result/Errors/ExceptionError.cs new file mode 100644 index 00000000..9af4fe2b --- /dev/null +++ b/src/Common.Abstractions/Result/Errors/ExceptionError.cs @@ -0,0 +1,43 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Common; + +/// +/// Represents an error result that encapsulates an exception. +/// +public class ExceptionError : IResultError +{ + private readonly Exception exception; + + /// + /// Initializes a new instance of the class. + /// + /// The exception to encapsulate. + public ExceptionError(Exception exception) + { + this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); + } + + /// + /// Gets the error message associated with the encapsulated exception. + /// + public string Message => this.exception.Message; + + /// + /// Gets the type of the encapsulated exception. + /// + public string ExceptionType => this.exception.GetType().FullName; + + /// + /// Gets the stack trace of the encapsulated exception. + /// + public string StackTrace => this.exception.StackTrace; + + /// + /// Gets the original exception that was encapsulated. + /// + public Exception OriginalException => this.exception; +} \ No newline at end of file diff --git a/src/Common.Caching/packages.lock.json b/src/Common.Caching/packages.lock.json index 0aecc277..e6ed578f 100644 --- a/src/Common.Caching/packages.lock.json +++ b/src/Common.Caching/packages.lock.json @@ -47,8 +47,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Options": { "type": "Transitive", @@ -96,8 +96,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -135,11 +136,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, diff --git a/src/Common.Extensions/DateTimeExtension .cs b/src/Common.Extensions/DateTimeExtension .cs deleted file mode 100644 index ef2a3a1d..00000000 --- a/src/Common.Extensions/DateTimeExtension .cs +++ /dev/null @@ -1,120 +0,0 @@ -// MIT-License -// Copyright BridgingIT GmbH - All Rights Reserved -// Use of this source code is governed by an MIT-style license that can be -// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license - -namespace BridgingIT.DevKit.Common; - -using System.Diagnostics; -using System.Globalization; - -public static class DateTimeExtensions -{ - [DebuggerStepThrough] - public static DateTime StartOfDay(this DateTime source) - { - return new DateTime(source.Year, source.Month, source.Day, 0, 0, 0, 0, source.Kind); - } - - [DebuggerStepThrough] - public static DateTime EndOfDay(this DateTime source) - { - return source.StartOfDay().AddDays(1).AddSeconds(-1); - } - - [DebuggerStepThrough] - public static DateTime StartOfWeek(this DateTime source) - { - return StartOfWeek(source, CultureInfo.InvariantCulture.DateTimeFormat.FirstDayOfWeek); - } - - [DebuggerStepThrough] - public static DateTime StartOfWeek(this DateTime source, DayOfWeek day) - { - var offset = source.DayOfWeek - day; - if (offset < 0) - { - offset += 7; - } - - return source.AddDays(-1 * offset); - } - - [DebuggerStepThrough] - public static DateTime StartOfMonth(this DateTime source) - { - return new DateTime(source.Year, source.Month, 1, 0, 0, 0, 0, source.Kind); - } - - [DebuggerStepThrough] - public static DateTime EndOfMonth(this DateTime source) - { - return source.StartOfMonth().AddMonths(1).AddSeconds(-1); - } - - [DebuggerStepThrough] - public static DateTime StartOfYear(this DateTime source) - { - return new DateTime(source.Year, 1, 1, 0, 0, 0, 0, source.Kind); - } - - [DebuggerStepThrough] - public static DateTime EndOfYear(this DateTime source) - { - return source.StartOfYear().AddYears(1).AddSeconds(-1); - } - - [DebuggerStepThrough] - public static DateTimeOffset StartOfDay(this DateTimeOffset source) - { - return new DateTimeOffset(source.Year, source.Month, source.Day, 0, 0, 0, 0, source.Offset); - } - - [DebuggerStepThrough] - public static DateTimeOffset EndOfDay(this DateTimeOffset source) - { - return source.StartOfDay().AddDays(1).AddSeconds(-1); - } - - [DebuggerStepThrough] - public static DateTimeOffset StartOfWeek(this DateTimeOffset source) - { - return StartOfWeek(source, CultureInfo.InvariantCulture.DateTimeFormat.FirstDayOfWeek); - } - - [DebuggerStepThrough] - public static DateTimeOffset StartOfWeek(this DateTimeOffset source, DayOfWeek day) - { - var offset = source.DayOfWeek - day; - if (offset < 0) - { - offset += 7; - } - - return source.AddDays(-1 * offset); - } - - [DebuggerStepThrough] - public static DateTimeOffset StartOfMonth(this DateTimeOffset source) - { - return new DateTimeOffset(source.Year, source.Month, 1, 0, 0, 0, 0, source.Offset); - } - - [DebuggerStepThrough] - public static DateTimeOffset EndOfMonth(this DateTimeOffset source) - { - return source.StartOfMonth().AddMonths(1).AddSeconds(-1); - } - - [DebuggerStepThrough] - public static DateTimeOffset StartOfYear(this DateTimeOffset source) - { - return new DateTimeOffset(source.Year, 1, 1, 0, 0, 0, 0, source.Offset); - } - - [DebuggerStepThrough] - public static DateTimeOffset EndOfYear(this DateTimeOffset source) - { - return source.StartOfYear().AddYears(1).AddSeconds(-1); - } -} \ No newline at end of file diff --git a/src/Common.Extensions/DateTimeExtensions.cs b/src/Common.Extensions/DateTimeExtensions.cs new file mode 100644 index 00000000..72940f55 --- /dev/null +++ b/src/Common.Extensions/DateTimeExtensions.cs @@ -0,0 +1,224 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Common; + +using System.Diagnostics; +using System.Globalization; + +public static class DateTimeExtensions +{ + [DebuggerStepThrough] + public static DateTime StartOfDay(this DateTime source) + { + return new DateTime(source.Year, source.Month, source.Day, 0, 0, 0, 0, source.Kind); + } + + [DebuggerStepThrough] + public static DateTime EndOfDay(this DateTime source) + { + return source.StartOfDay().AddDays(1).AddSeconds(-1); + } + + [DebuggerStepThrough] + public static DateTime StartOfWeek(this DateTime source) + { + return StartOfWeek(source, CultureInfo.InvariantCulture.DateTimeFormat.FirstDayOfWeek); + } + + [DebuggerStepThrough] + public static DateTime StartOfWeek(this DateTime source, DayOfWeek day) + { + var offset = source.DayOfWeek - day; + if (offset < 0) + { + offset += 7; + } + + return source.AddDays(-1 * offset); + } + + [DebuggerStepThrough] + public static DateTime StartOfMonth(this DateTime source) + { + return new DateTime(source.Year, source.Month, 1, 0, 0, 0, 0, source.Kind); + } + + [DebuggerStepThrough] + public static DateTime EndOfMonth(this DateTime source) + { + return source.StartOfMonth().AddMonths(1).AddSeconds(-1); + } + + [DebuggerStepThrough] + public static DateTime StartOfYear(this DateTime source) + { + return new DateTime(source.Year, 1, 1, 0, 0, 0, 0, source.Kind); + } + + [DebuggerStepThrough] + public static DateTime EndOfYear(this DateTime source) + { + return source.StartOfYear().AddYears(1).AddSeconds(-1); + } + + [DebuggerStepThrough] + public static DateTimeOffset StartOfDay(this DateTimeOffset source) + { + return new DateTimeOffset(source.Year, source.Month, source.Day, 0, 0, 0, 0, source.Offset); + } + + [DebuggerStepThrough] + public static DateTimeOffset EndOfDay(this DateTimeOffset source) + { + return source.StartOfDay().AddDays(1).AddSeconds(-1); + } + + [DebuggerStepThrough] + public static DateTimeOffset StartOfWeek(this DateTimeOffset source) + { + return StartOfWeek(source, CultureInfo.InvariantCulture.DateTimeFormat.FirstDayOfWeek); + } + + [DebuggerStepThrough] + public static DateTimeOffset StartOfWeek(this DateTimeOffset source, DayOfWeek day) + { + var offset = source.DayOfWeek - day; + if (offset < 0) + { + offset += 7; + } + + return source.AddDays(-1 * offset); + } + + [DebuggerStepThrough] + public static DateTimeOffset StartOfMonth(this DateTimeOffset source) + { + return new DateTimeOffset(source.Year, source.Month, 1, 0, 0, 0, 0, source.Offset); + } + + [DebuggerStepThrough] + public static DateTimeOffset EndOfMonth(this DateTimeOffset source) + { + return source.StartOfMonth().AddMonths(1).AddSeconds(-1); + } + + [DebuggerStepThrough] + public static DateTimeOffset StartOfYear(this DateTimeOffset source) + { + return new DateTimeOffset(source.Year, 1, 1, 0, 0, 0, 0, source.Offset); + } + + [DebuggerStepThrough] + public static DateTimeOffset EndOfYear(this DateTimeOffset source) + { + return source.StartOfYear().AddYears(1).AddSeconds(-1); + } + + [DebuggerStepThrough] + public static DateTime? ParseDateOrEpoch(this string source) + { + if (string.IsNullOrEmpty(source)) + { + return null; + } + + // Try parsing as epoch (Unix timestamp) + if (long.TryParse(source, out var epoch) && epoch is > 99999999 or < 0) // don't clash with compact format + { + return DateTimeOffset.FromUnixTimeSeconds(epoch).UtcDateTime; + } + + var formats = new[] + { + "yyyy-MM-dd", // ISO 8601 (e.g., "2024-03-14") + "yyyy-MM-ddTHH:mm:ss", // ISO 8601 with time (e.g., "2024-03-14T13:45:30") + "yyyy-MM-ddTHH:mm:ssZ", // ISO 8601 with UTC (e.g., "2024-03-14T13:45:30Z") + "yyyy-MM-ddTHH:mm:ss.fffffff", // ISO 8601 with milliseconds (e.g., "2024-03-14T13:45:30.1234567") + "dd/MM/yyyy", // UK format (e.g., "14/03/2024") + "MM/dd/yyyy", // US format (e.g., "03/14/2024") + "dd-MM-yyyy", // Alternative format (e.g., "14-03-2024") + "dd.MM.yyyy", // European format (e.g., "14.03.2024") + "yyyyMMdd", // Compact format (e.g., "20240314") + "dd MMM yyyy", // Month name format (e.g., "14 Mar 2024") + "d MMMM yyyy" // Full month name format (e.g., "14 March 2024") + }; + + // Try parsing as ISO 8601 date string + if (DateTime.TryParseExact( + source, + formats, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var date)) + { + return date; + } + + throw new ArgumentException($"Invalid date format: {source}. Useany format or Unix epoch."); + } + + public static bool TryParseDateOrEpoch(this string source, out DateTime result) + { + result = DateTime.MinValue; + + if (string.IsNullOrWhiteSpace(source)) + { + return false; + } + + // Try parsing as epoch (Unix timestamp) + if (long.TryParse(source, out var epoch) && epoch is > 99999999 or < 0) // don't clash with compact format + { + try + { + result = DateTimeOffset.FromUnixTimeSeconds(epoch).UtcDateTime; + + return true; + } + catch + { + return false; + } + } + + // Define accepted formats + var formats = new[] + { + "yyyy-MM-dd", // ISO 8601 (e.g., "2024-03-14") + "yyyy-MM-ddTHH:mm:ss", // ISO 8601 with time (e.g., "2024-03-14T13:45:30") + "yyyy-MM-ddTHH:mm:ssZ", // ISO 8601 with UTC (e.g., "2024-03-14T13:45:30Z") + "yyyy-MM-ddTHH:mm:ss.fffffff", // ISO 8601 with milliseconds (e.g., "2024-03-14T13:45:30.1234567") + "dd/MM/yyyy", // UK format (e.g., "14/03/2024") + "MM/dd/yyyy", // US format (e.g., "03/14/2024") + "dd-MM-yyyy", // Alternative format (e.g., "14-03-2024") + "dd.MM.yyyy", // European format (e.g., "14.03.2024") + "yyyyMMdd", // Compact format (e.g., "20240314") + "dd MMM yyyy", // Month name format (e.g., "14 Mar 2024") + "d MMMM yyyy" // Full month name format (e.g., "14 March 2024") + }; + + // Try parsing with exact formats first + if (DateTime.TryParseExact( + source, + formats, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var exactDate)) + { + result = exactDate; + + return true; + } + + // Try general parsing as fallback + return DateTime.TryParse( + source, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out result); + } +} \ No newline at end of file diff --git a/src/Common.Extensions/ExceptionExtensions.cs b/src/Common.Extensions/ExceptionExtensions.cs index ad8d2175..2200699e 100644 --- a/src/Common.Extensions/ExceptionExtensions.cs +++ b/src/Common.Extensions/ExceptionExtensions.cs @@ -5,7 +5,10 @@ namespace BridgingIT.DevKit.Common; +using System.Data; +using System.Data.Common; using System.Diagnostics; +using System.Net.Sockets; public static class ExceptionExtensions { @@ -155,4 +158,8 @@ public static bool IsExpectedException(this Exception ex, Func return false; } + + public static bool IsTransientException(this Exception ex) => + ex is DbException or DBConcurrencyException or SocketException or HttpRequestException or TaskCanceledException or TimeoutException; + // || ex is SqlException || ex is RequestFailedException } \ No newline at end of file diff --git a/src/Common.Extensions/TimeSpanExtensions.cs b/src/Common.Extensions/TimeSpanExtensions.cs index d59f5ade..95bf4002 100644 --- a/src/Common.Extensions/TimeSpanExtensions.cs +++ b/src/Common.Extensions/TimeSpanExtensions.cs @@ -6,6 +6,7 @@ namespace BridgingIT.DevKit.Common; using System.Diagnostics; +using System.Globalization; public static class TimeSpanExtensions { @@ -245,4 +246,126 @@ public static TimeSpan Weeks(this short value) { return TimeSpan.FromDays(value * 7); } + + [DebuggerStepThrough] + public static TimeSpan TruncateToSeconds(this TimeSpan timeSpan) + { + return new TimeSpan(timeSpan.Days, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds); + } + + [DebuggerStepThrough] + public static TimeSpan ParseTime(this string source) + { + var result = TimeSpan.Zero; + if (string.IsNullOrWhiteSpace(source)) + { + return result; + } + + // Handle compact formats first + if (source.All(char.IsDigit)) + { + switch (source.Length) + { + case 4: // HHmm + source = $"{source[..2]}:{source[2..]}"; + break; + case 6: // HHmmss + source = $"{source[..2]}:{source[2..4]}:{source[4..]}"; + break; + } + } + + // Define accepted formats + var formats = new[] + { + "HH:mm:ss", // 24-hour with seconds (e.g., "14:30:00") + "HH:mm", // 24-hour without seconds (e.g., "14:30") + "hh:mm:ss tt", // 12-hour with seconds (e.g., "02:30:00 PM") + "hh:mm tt", // 12-hour without seconds (e.g., "02:30 PM") + "HHmmss", // 24-hour compact with seconds (e.g., "143000") + "HHmm", // 24-hour compact without seconds (e.g., "1430") + "hh:mm:ss", // 12-hour with seconds without meridiem (assumes AM) + "hh:mm" // 12-hour without seconds without meridiem (assumes AM) + }; + + // Try parsing as TimeSpan first + if (TimeSpan.TryParse(source, out result)) + { + return result; + } + + // Try parsing as DateTime with various formats + if (DateTime.TryParseExact( + source, + formats, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var dateTime)) + { + result = dateTime.TimeOfDay; + + return result; + } + + return result; + } + + [DebuggerStepThrough] + public static bool TryParseTime(this string source, out TimeSpan result) + { + result = TimeSpan.Zero; + if (string.IsNullOrWhiteSpace(source)) + { + return false; + } + + // Handle compact formats first + if (source.All(char.IsDigit)) + { + switch (source.Length) + { + case 4: // HHmm + source = $"{source[..2]}:{source[2..]}"; + break; + case 6: // HHmmss + source = $"{source[..2]}:{source[2..4]}:{source[4..]}"; + break; + } + } + + // Define accepted formats + var formats = new[] + { + "HH:mm:ss", // 24-hour with seconds (e.g., "14:30:00") + "HH:mm", // 24-hour without seconds (e.g., "14:30") + "hh:mm:ss tt", // 12-hour with seconds (e.g., "02:30:00 PM") + "hh:mm tt", // 12-hour without seconds (e.g., "02:30 PM") + "HHmmss", // 24-hour compact with seconds (e.g., "143000") + "HHmm", // 24-hour compact without seconds (e.g., "1430") + "hh:mm:ss", // 12-hour with seconds without meridiem (assumes AM) + "hh:mm" // 12-hour without seconds without meridiem (assumes AM) + }; + + // Try parsing as TimeSpan first + if (TimeSpan.TryParse(source, out result)) + { + return true; + } + + // Try parsing as DateTime with various formats + if (DateTime.TryParseExact( + source, + formats, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var dateTime)) + { + result = dateTime.TimeOfDay; + + return true; + } + + return false; + } } \ No newline at end of file diff --git a/src/Common.Extensions/TypeExtensions.cs b/src/Common.Extensions/TypeExtensions.cs index f19f6694..2509beba 100644 --- a/src/Common.Extensions/TypeExtensions.cs +++ b/src/Common.Extensions/TypeExtensions.cs @@ -32,6 +32,16 @@ public static bool IsNotOfType(this object source, Type targetType) return source.GetType() != targetType; } + public static bool IsNullableType(this Type source) + { + if (source is null) + { + return false; + } + + return source.IsGenericType && source.GetGenericTypeDefinition() == typeof(Nullable<>); + } + [DebuggerStepThrough] public static string PrettyName(this Type source, bool useAngleBrackets = true) { @@ -177,6 +187,16 @@ public static PropertyInfo GetPropertyUnambiguous( return null; } + /// + /// Determine if a type implements a specific (open) generic interface type + /// + /// the instance to check + [DebuggerStepThrough] + public static bool ImplementsInterface(this Type source) + { + return source.ImplementsInterface(typeof(T)); + } + /// /// Determine if a type implements a specific (open) generic interface type /// diff --git a/src/Common.Mapping/packages.lock.json b/src/Common.Mapping/packages.lock.json index e5b97c44..6623ef3d 100644 --- a/src/Common.Mapping/packages.lock.json +++ b/src/Common.Mapping/packages.lock.json @@ -67,8 +67,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Options": { "type": "Transitive", @@ -116,8 +116,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -155,11 +156,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, diff --git a/src/Common.Modules/packages.lock.json b/src/Common.Modules/packages.lock.json index 87a73f17..584ef054 100644 --- a/src/Common.Modules/packages.lock.json +++ b/src/Common.Modules/packages.lock.json @@ -103,14 +103,14 @@ }, "Serilog": { "type": "Direct", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -372,8 +372,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -405,11 +406,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, diff --git a/src/Common.Serialization/Common.Serialization.csproj b/src/Common.Serialization/Common.Serialization.csproj index 36b33c4f..7062d8fa 100644 --- a/src/Common.Serialization/Common.Serialization.csproj +++ b/src/Common.Serialization/Common.Serialization.csproj @@ -13,4 +13,8 @@ + + + + \ No newline at end of file diff --git a/src/Common.Serialization/Converters/EnumConverter.cs b/src/Common.Serialization/Converters/EnumConverter.cs new file mode 100644 index 00000000..604555e7 --- /dev/null +++ b/src/Common.Serialization/Converters/EnumConverter.cs @@ -0,0 +1,36 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Common; + +using System.Reflection; +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; + +public class EnumConverter : JsonConverter + where T : Enum +{ + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + foreach (var field in typeof(T).GetFields()) + { + if (field.GetCustomAttribute() is EnumMemberAttribute attribute && attribute.Value?.Equals(value, StringComparison.OrdinalIgnoreCase) == true) + { + return (T)field.GetValue(null); + } + } + + throw new JsonException($"Unable to parse {value} to {typeof(T)}"); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + var field = value.GetType().GetField(value.ToString()); + var attribute = field.GetCustomAttribute(); + writer.WriteStringValue(attribute?.Value ?? value.ToString()); + } +} \ No newline at end of file diff --git a/src/Common.Serialization/Converters/FilterCriteriaJsonConverter.cs b/src/Common.Serialization/Converters/FilterCriteriaJsonConverter.cs new file mode 100644 index 00000000..1cae4e0f --- /dev/null +++ b/src/Common.Serialization/Converters/FilterCriteriaJsonConverter.cs @@ -0,0 +1,133 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BridgingIT.DevKit.Common; + +public class FilterCriteriaJsonConverter : JsonConverter +{ + public override FilterCriteria Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + var filterCriteria = new FilterCriteria(); + var enumConverter = new EnumConverter(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return filterCriteria; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + var field = reader.GetString(); + var normalizedPropertyName = char.ToUpper(field[0]) + field[1..]; + reader.Read(); + + switch (normalizedPropertyName) + { + case nameof(FilterCriteria.Field): + filterCriteria.Field = reader.GetString(); + + break; + case nameof(FilterCriteria.Operator): + filterCriteria.Operator = enumConverter.Read(ref reader, typeof(FilterOperator), options); + + break; + case nameof(FilterCriteria.Value): + filterCriteria.Value = JsonSerializer.Deserialize(ref reader, options); + + break; + case nameof(FilterCriteria.Logic): + filterCriteria.Logic = JsonSerializer.Deserialize(ref reader, options); + + break; + case nameof(FilterCriteria.Filters): + filterCriteria.Filters = JsonSerializer.Deserialize>(ref reader, options); + + break; + case nameof(FilterCriteria.CustomType): + filterCriteria.CustomType = JsonSerializer.Deserialize(ref reader, options); + + break; + case nameof(FilterCriteria.CustomParameters): + filterCriteria.CustomParameters = JsonSerializer.Deserialize>(ref reader, options); + + break; + case nameof(FilterCriteria.SpecificationName): + filterCriteria.SpecificationName = reader.GetString(); + + break; + case nameof(FilterCriteria.SpecificationArguments): + filterCriteria.SpecificationArguments = JsonSerializer.Deserialize(ref reader, options); + + break; + case nameof(FilterCriteria.CompositeSpecification): + filterCriteria.CompositeSpecification = JsonSerializer.Deserialize(ref reader, options); + + break; + } + } + + throw new JsonException(); + } + + public override void Write(Utf8JsonWriter writer, FilterCriteria value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WriteString(nameof(FilterCriteria.Field), value.Field); + writer.WritePropertyName(nameof(FilterCriteria.Operator)); + JsonSerializer.Serialize(writer, value.Operator, options); + writer.WritePropertyName(nameof(FilterCriteria.Value)); + JsonSerializer.Serialize(writer, value.Value, options); + writer.WritePropertyName(nameof(FilterCriteria.Logic)); + JsonSerializer.Serialize(writer, value.Logic, options); + + if (value.Filters?.Any() == true) + { + writer.WritePropertyName(nameof(FilterCriteria.Filters)); + JsonSerializer.Serialize(writer, value.Filters, options); + } + + writer.WritePropertyName(nameof(FilterCriteria.CustomType)); + JsonSerializer.Serialize(writer, value.CustomType, options); + + if (value.CustomParameters?.Any() == true) + { + writer.WritePropertyName(nameof(FilterCriteria.CustomParameters)); + JsonSerializer.Serialize(writer, value.CustomParameters, options); + } + + if (!string.IsNullOrEmpty(value.SpecificationName)) + { + writer.WriteString(nameof(FilterCriteria.SpecificationName), value.SpecificationName); + } + + if (value.SpecificationArguments?.Any() == true) + { + writer.WritePropertyName(nameof(FilterCriteria.SpecificationArguments)); + JsonSerializer.Serialize(writer, value.SpecificationArguments, options); + } + + if (value.CompositeSpecification != null) + { + writer.WritePropertyName(nameof(FilterCriteria.CompositeSpecification)); + JsonSerializer.Serialize(writer, value.CompositeSpecification, options); + } + + writer.WriteEndObject(); + } +} \ No newline at end of file diff --git a/src/Common.Serialization/Converters/FilterSpecificationNodeConverter.cs b/src/Common.Serialization/Converters/FilterSpecificationNodeConverter.cs new file mode 100644 index 00000000..43c8d9ac --- /dev/null +++ b/src/Common.Serialization/Converters/FilterSpecificationNodeConverter.cs @@ -0,0 +1,58 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Common; + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +public class FilterSpecificationNodeConverter : JsonConverter +{ + public override SpecificationNode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var doc = JsonDocument.ParseValue(ref reader); + var root = doc.RootElement; + + // Determine the type of SpecificationNode (abstract) based on the properties + if (root.TryGetProperty("Name", out _) || root.TryGetProperty("name", out _)) + { + return JsonSerializer.Deserialize(root.GetRawText(), options); + } + else if (root.TryGetProperty("Logic", out _) || root.TryGetProperty("logic", out _)) + { + return JsonSerializer.Deserialize(root.GetRawText(), options); + } + + throw new NotSupportedException("Unknown SpecificationNode type."); + } + + public override void Write(Utf8JsonWriter writer, SpecificationNode value, JsonSerializerOptions options) + { + switch (value) + { + case SpecificationLeaf leaf: + writer.WriteStartObject(); + writer.WriteString("Type", nameof(SpecificationLeaf)); + writer.WriteString("Name", leaf.Name); + writer.WritePropertyName(nameof(SpecificationLeaf.Arguments)); + JsonSerializer.Serialize(writer, leaf.Arguments, options); + writer.WriteEndObject(); + break; + + case SpecificationGroup group: + writer.WriteStartObject(); + writer.WriteString("Type", nameof(SpecificationGroup)); + writer.WriteString("Logic", group.Logic.ToString()); + writer.WritePropertyName(nameof(SpecificationGroup.Nodes)); + JsonSerializer.Serialize(writer, group.Nodes, options); + writer.WriteEndObject(); + break; + + default: + throw new NotSupportedException($"Type '{value.GetType()}' is not supported."); + } + } +} \ No newline at end of file diff --git a/src/Common.Serialization/Converters/JsonTypeConverter.cs b/src/Common.Serialization/Converters/JsonTypeConverter.cs index 7a5b645b..f4ae7f2a 100644 --- a/src/Common.Serialization/Converters/JsonTypeConverter.cs +++ b/src/Common.Serialization/Converters/JsonTypeConverter.cs @@ -8,8 +8,7 @@ namespace BridgingIT.DevKit.Common; using System.Text.Json; using System.Text.Json.Serialization; -public class - JsonTypeConverter +public class JsonTypeConverter : JsonConverter // source: https://stackoverflow.com/questions/66919668/net-core-graphql-graphql-systemtextjson-serialization-and-deserialization-of { public override Type Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/Common.Serialization/Converters/NewtonSoftConverters.cs b/src/Common.Serialization/Converters/NewtonSoftConverters.cs new file mode 100644 index 00000000..d9099c13 --- /dev/null +++ b/src/Common.Serialization/Converters/NewtonSoftConverters.cs @@ -0,0 +1,79 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Common; + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Reflection; + +public class EnumConverter : JsonConverter +{ + public override bool CanConvert(Type objectType) + { + return objectType.IsEnum; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = reader.Value.ToString(); + foreach (var field in objectType.GetFields()) + { + var attribute = field.GetCustomAttribute(); + if (attribute != null && attribute.Value == value) + { + return field.GetValue(null); + } + } + + throw new JsonSerializationException($"Unable to parse {value} to {objectType}"); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var field = value.GetType().GetField(value.ToString()); + var attribute = field.GetCustomAttribute(); + writer.WriteValue(attribute?.Value ?? value.ToString()); + } +} + +public class FilterCriteriaConverter : JsonConverter +{ + public override FilterCriteria ReadJson(JsonReader reader, Type objectType, FilterCriteria existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var jObject = JObject.Load(reader); + var filterCriteria = new FilterCriteria(); + + foreach (var property in typeof(FilterCriteria).GetProperties()) + { + if (jObject.TryGetValue(property.Name, out var token)) + { + var value = token.ToObject(property.PropertyType, serializer); + property.SetValue(filterCriteria, value); + } + } + + return filterCriteria; + } + + public override void WriteJson(JsonWriter writer, FilterCriteria value, JsonSerializer serializer) + { + writer.WriteStartObject(); + + foreach (var property in typeof(FilterCriteria).GetProperties()) + { + var propertyValue = property.GetValue(value); + if (propertyValue != null) + { + writer.WritePropertyName(property.Name); + serializer.Serialize(writer, propertyValue); + } + } + + writer.WriteEndObject(); + } +} \ No newline at end of file diff --git a/src/Common.Serialization/DefaultJsonNetSerializerSettings.cs b/src/Common.Serialization/DefaultJsonNetSerializerSettings.cs index 120c798a..31187b55 100644 --- a/src/Common.Serialization/DefaultJsonNetSerializerSettings.cs +++ b/src/Common.Serialization/DefaultJsonNetSerializerSettings.cs @@ -22,13 +22,16 @@ public static JsonSerializerSettings Create() NullValueHandling = NullValueHandling.Ignore, TypeNameHandling = TypeNameHandling.Auto, DefaultValueHandling = DefaultValueHandling.Ignore, - DateFormatString = "o", + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + DateFormatString = "yyyy-MM-ddTHH:mm:ss.FFFFFFFZ", DateFormatHandling = DateFormatHandling.IsoDateFormat, //DateParseHandling = DateParseHandling.DateTimeOffset, //DateTimeZoneHandling = DateTimeZoneHandling.Utc, Converters = new List { //new GuidConverter(), + new EnumConverter(), + new FilterCriteriaConverter(), new StringEnumConverter { AllowIntegerValues = true }, new IsoDateTimeConverter { diff --git a/src/Common.Serialization/DefaultSystemTextJsonSerializerOptions.cs b/src/Common.Serialization/DefaultSystemTextJsonSerializerOptions.cs index 5852b962..c9ea19fb 100644 --- a/src/Common.Serialization/DefaultSystemTextJsonSerializerOptions.cs +++ b/src/Common.Serialization/DefaultSystemTextJsonSerializerOptions.cs @@ -12,16 +12,27 @@ public static class DefaultSystemTextJsonSerializerOptions { public static JsonSerializerOptions Create() { - return new JsonSerializerOptions + var options = new JsonSerializerOptions { WriteIndented = true, PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { new JsonStringEnumConverter() }, + Converters = + { + new EnumConverter(), + new EnumConverter(), + new EnumConverter(), + new EnumConverter(), + new FilterCriteriaJsonConverter(), + new FilterSpecificationNodeConverter(), + new JsonStringEnumConverter() + }, TypeInfoResolver = new PrivateConstructorContractResolver() // allow deserialization of types with only private constructors //IncludeFields = true, //PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate // TODO: .NET8 https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-8/#populate-read-only-members }; + + return options; } } \ No newline at end of file diff --git a/src/Common.Serialization/packages.lock.json b/src/Common.Serialization/packages.lock.json index 20904f4f..4f9794b0 100644 --- a/src/Common.Serialization/packages.lock.json +++ b/src/Common.Serialization/packages.lock.json @@ -10,11 +10,11 @@ }, "MessagePack": { "type": "Direct", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -38,13 +38,19 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.NET.StringTools": { "type": "Transitive", "resolved": "17.6.3", "contentHash": "N0ZIanl1QCgvUumEL1laasU0a7sOE5ZwLZVTn0pAePnfhq8P7SvTjF8Axq+CnavuQkmdQpGNXQ1efZtu5kDFbA==" + }, + "BridgingIT.DevKit.Common.Abstractions": { + "type": "Project", + "dependencies": { + "Ensure.That": "[10.1.0, )" + } } } } diff --git a/src/Common.Utilities.Xunit/packages.lock.json b/src/Common.Utilities.Xunit/packages.lock.json index c468fde3..6d4cac8b 100644 --- a/src/Common.Utilities.Xunit/packages.lock.json +++ b/src/Common.Utilities.Xunit/packages.lock.json @@ -27,9 +27,9 @@ }, "Serilog": { "type": "Direct", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "Direct", @@ -88,8 +88,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1211,7 +1211,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1225,8 +1225,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1264,11 +1265,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, diff --git a/src/Common.Utilities/packages.lock.json b/src/Common.Utilities/packages.lock.json index fe8cbcf1..04b673a2 100644 --- a/src/Common.Utilities/packages.lock.json +++ b/src/Common.Utilities/packages.lock.json @@ -82,8 +82,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.NET.StringTools": { "type": "Transitive", @@ -122,19 +122,20 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, diff --git a/src/Domain.EventSourcing.Mediator/packages.lock.json b/src/Domain.EventSourcing.Mediator/packages.lock.json index f286e394..a8adc841 100644 --- a/src/Domain.EventSourcing.Mediator/packages.lock.json +++ b/src/Domain.EventSourcing.Mediator/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -117,8 +117,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -152,7 +153,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -211,11 +213,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -314,6 +316,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Domain.EventSourcing.Outbox/packages.lock.json b/src/Domain.EventSourcing.Outbox/packages.lock.json index 858ce78e..9549bb60 100644 --- a/src/Domain.EventSourcing.Outbox/packages.lock.json +++ b/src/Domain.EventSourcing.Outbox/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -117,8 +117,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -152,7 +153,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -220,11 +222,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -323,6 +325,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Domain.EventSourcing/packages.lock.json b/src/Domain.EventSourcing/packages.lock.json index 02d0a5ba..3e67ca00 100644 --- a/src/Domain.EventSourcing/packages.lock.json +++ b/src/Domain.EventSourcing/packages.lock.json @@ -16,8 +16,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -91,8 +91,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -126,7 +127,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "FluentValidation": { @@ -149,11 +151,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -239,6 +241,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Domain.Mediator/MediatRInspector.cs b/src/Domain.Mediator/MediatRInspector.cs index 5aa05402..7ff8c65b 100644 --- a/src/Domain.Mediator/MediatRInspector.cs +++ b/src/Domain.Mediator/MediatRInspector.cs @@ -81,7 +81,9 @@ private static void Print(IServiceCollection services, Type handlerType) private static string FormatGenericType(Type type) { if (!type.IsGenericType) + { return type.Name; + } var genericArguments = string.Join(", ", type.GetGenericArguments().Select(t => t.Name)); return $"{type.Name.Split('`')[0]}<{genericArguments}>"; diff --git a/src/Domain.Mediator/packages.lock.json b/src/Domain.Mediator/packages.lock.json index 2581774f..a7821925 100644 --- a/src/Domain.Mediator/packages.lock.json +++ b/src/Domain.Mediator/packages.lock.json @@ -36,8 +36,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -111,8 +111,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -146,7 +147,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "FluentValidation": { @@ -169,11 +171,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -249,6 +251,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Domain.Outbox/packages.lock.json b/src/Domain.Outbox/packages.lock.json index 549b3210..77388748 100644 --- a/src/Domain.Outbox/packages.lock.json +++ b/src/Domain.Outbox/packages.lock.json @@ -29,8 +29,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -130,8 +130,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -165,7 +166,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "FluentValidation": { @@ -188,11 +190,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -278,6 +280,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Domain/Domain.csproj b/src/Domain/Domain.csproj index 7eb5c5e2..f9a8cde2 100644 --- a/src/Domain/Domain.csproj +++ b/src/Domain/Domain.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Domain/Filtering/CustomSpecificationBuilder.cs b/src/Domain/Filtering/CustomSpecificationBuilder.cs new file mode 100644 index 00000000..db1f51e1 --- /dev/null +++ b/src/Domain/Filtering/CustomSpecificationBuilder.cs @@ -0,0 +1,746 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain; + +using System.Globalization; + +public static class CustomSpecificationBuilder +{ + public static ISpecification Build(FilterCriteria filter) + where TEntity : class, IEntity + { + if (filter == null) + { + throw new ArgumentException("Filter criteria is required."); + } + + return filter.CustomType switch + { + FilterCustomType.IsNull => BuildIsNull(filter), + FilterCustomType.IsNotNull => BuildIsNotNull(filter), + FilterCustomType.FullTextSearch => BuildFullTextSearch(filter), + FilterCustomType.DateRange => BuildDateRange(filter), + FilterCustomType.DateRelative => BuildDateRelative(filter), + FilterCustomType.TimeRange => BuildTimeRange(filter), + FilterCustomType.NumericRange => BuildNumericRange(filter), + FilterCustomType.EnumValues => BuildEnumValues(filter), + FilterCustomType.TextIn => BuildTextIn(filter), + FilterCustomType.TextNotIn => BuildTextNotIn(filter), + FilterCustomType.NumericIn => BuildNumericIn(filter), + FilterCustomType.NumericNotIn => BuildNumericNotIn(filter), + _ => throw new NotSupportedException($"Custom filter type {filter.CustomType} is not supported.") + }; + } + + private static ISpecification BuildFullTextSearch(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("searchTerm", out var searchTermObj) || + !filter.CustomParameters.TryGetValue("fields", out var fieldsObj)) + { + throw new ArgumentException("SearchTerm and Fields must be provided for FullTextSearch."); + } + + var searchTerm = searchTermObj as string; + var fields = fieldsObj as IEnumerable; + + if (string.IsNullOrEmpty(searchTerm) || fields == null || !fields.Any()) + { + throw new ArgumentException("Invalid searchTerm or Fields for FullTextSearch."); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + Expression combinedExpression = Expression.Constant(false); + + foreach (var field in fields) + { + var property = Expression.Property(parameter, field); + var containsMethod = typeof(string).GetMethod("Contains", [typeof(string)]); + var searchTermExpression = Expression.Constant(searchTerm); + var containsExpression = Expression.Call(property, containsMethod, searchTermExpression); + combinedExpression = Expression.OrElse(combinedExpression, containsExpression); + } + + var lambda = Expression.Lambda>(combinedExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildDateRange(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("startDate", out var startDateObj) || + !filter.CustomParameters.TryGetValue("endDate", out var endDateObj)) + { + throw new ArgumentException("Field, StartDate, and EndDate must be provided for DateRange filter."); + } + + var field = fieldObj as string; + var startDateString = startDateObj as string; + var endDateString = endDateObj as string; + + // Get inclusive parameter with default true if not specified + var inclusive = !filter.CustomParameters.TryGetValue("inclusive", out var inclusiveObj) || + inclusiveObj is not bool inclusiveValue || + inclusiveValue; + + if (string.IsNullOrEmpty(field) || (string.IsNullOrEmpty(startDateString) && string.IsNullOrEmpty(endDateString))) + { + throw new ArgumentException("Invalid field or date range for DateRange filter."); + } + + var startDateParsed = startDateString.TryParseDateOrEpoch(out var startDate); + var endDateParsed = endDateString.TryParseDateOrEpoch(out var endDate); + + if (!startDateParsed) + { + throw new ArgumentException($"Invalid start date format: {startDateString}"); + } + + if (!endDateParsed) + { + throw new ArgumentException($"Invalid end date format: {endDateString}"); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + + Expression startComparison = inclusive + ? Expression.GreaterThanOrEqual(property, Expression.Constant(startDate)) + : Expression.GreaterThan(property, Expression.Constant(startDate)); + + Expression endComparison = inclusive + ? Expression.LessThanOrEqual(property, Expression.Constant(endDate)) + : Expression.LessThan(property, Expression.Constant(endDate)); + + Expression dateRangeExpression = Expression.AndAlso(startComparison, endComparison); + + var lambda = Expression.Lambda>(dateRangeExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildDateRelative(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("unit", out var unitObj) || + !filter.CustomParameters.TryGetValue("amount", out var amountObj) || + !filter.CustomParameters.TryGetValue("direction", out var directionObj)) + { + throw new ArgumentException("Field, Unit, Amount, and Direction must be provided for DateRelative filter."); + } + + var field = fieldObj as string; + var unit = (unitObj as string)?.ToLowerInvariant(); + var amount = Convert.ToInt32(amountObj); + var direction = (directionObj as string)?.ToLowerInvariant(); + + if (string.IsNullOrWhiteSpace(field) || + string.IsNullOrWhiteSpace(unit) || + string.IsNullOrWhiteSpace(direction)) + { + throw new ArgumentException("Invalid parameters for DateRelative filter."); + } + + if (!new[] { "day", "week", "month", "year" }.Contains(unit)) + { + throw new ArgumentException("Unit must be one of: day, week, month, year"); + } + + if (!new[] { "past", "future" }.Contains(direction)) + { + throw new ArgumentException("Direction must be either 'past' or 'future'"); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + var propertyType = property.Type; + + // Handle nullable DateTime properties + var isNullable = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>); + var dateProperty = isNullable ? Expression.Property(property, "Value") : property; + + // Calculate the reference date + var now = DateTime.UtcNow; + var referenceDate = direction == "past" + ? GetPastDate(now, unit, amount) + : GetFutureDate(now, unit, amount); + + // Create the date comparison expression + var dateConstant = Expression.Constant(referenceDate); + Expression dateComparisonExpression; + + if (direction == "past") + { + dateComparisonExpression = Expression.GreaterThanOrEqual(dateProperty, dateConstant); + } + else + { + dateComparisonExpression = Expression.LessThanOrEqual(dateProperty, dateConstant); + } + + // For nullable properties, add null check + if (isNullable) + { + var notNullCheck = Expression.NotEqual(property, Expression.Constant(null, propertyType)); + dateComparisonExpression = Expression.AndAlso(notNullCheck, dateComparisonExpression); + } + + var lambda = Expression.Lambda>(dateComparisonExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildNumericRange(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("min", out var minValueObj) || + !filter.CustomParameters.TryGetValue("max", out var maxValueObj)) + { + throw new ArgumentException("PropertyName, MinValue, and MaxValue must be provided for NumericRange filter."); + } + + var field = fieldObj as string; + var minValue = Convert.ToDouble(minValueObj); + var maxValue = Convert.ToDouble(maxValueObj); + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + var convertedProperty = Expression.Convert(property, typeof(double)); + + var greaterThanOrEqual = Expression.GreaterThanOrEqual(convertedProperty, Expression.Constant(minValue)); + var lessThanOrEqual = Expression.LessThanOrEqual(convertedProperty, Expression.Constant(maxValue)); + var rangeExpression = Expression.AndAlso(greaterThanOrEqual, lessThanOrEqual); + + var lambda = Expression.Lambda>(rangeExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildIsNull(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj)) + { + throw new ArgumentException("PropertyName must be provided for ExistenceCheck filter."); + } + + var field = fieldObj as string; + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + var notNullExpression = Expression.Equal(property, Expression.Constant(null)); + + var lambda = Expression.Lambda>(notNullExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildIsNotNull(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj)) + { + throw new ArgumentException("PropertyName must be provided for ExistenceCheck filter."); + } + + var field = fieldObj as string; + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + var notNullExpression = Expression.NotEqual(property, Expression.Constant(null)); + + var lambda = Expression.Lambda>(notNullExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildTimeRange(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("startTime", out var startTimeObj) || + !filter.CustomParameters.TryGetValue("endTime", out var endTimeObj)) + { + throw new ArgumentException("Field, StartTime, and EndTime must be provided for TimeRange filter."); + } + + var field = fieldObj as string; + var startTimeString = startTimeObj as string; + var endTimeString = endTimeObj as string; + + // Get inclusive parameter with default true if not specified + var inclusive = !filter.CustomParameters.TryGetValue("inclusive", out var inclusiveObj) || + inclusiveObj is not bool inclusiveValue || + inclusiveValue; + + if (!startTimeString.TryParseTime(out var startTime) || !endTimeString.TryParseTime(out var endTime)) + { + throw new ArgumentException("StartTime and EndTime must be valid time strings in either 24-hour format (HH:mm:ss, HH:mm) or 12-hour format (hh:mm:ss tt, hh:mm tt)"); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + + // Convert the property to TimeSpan if it's a DateTime + var convertedProperty = typeof(TEntity).GetProperty(field).PropertyType == typeof(DateTime) + ? Expression.Property(property, "TimeOfDay") + : property; + + Expression timeRangeExpression; + + if (startTime <= endTime) + { + // Normal range (e.g., 9:00 AM to 5:00 PM) + Expression startComparison = inclusive + ? Expression.GreaterThanOrEqual(convertedProperty, Expression.Constant(startTime)) + : Expression.GreaterThan(convertedProperty, Expression.Constant(startTime)); + + Expression endComparison = inclusive + ? Expression.LessThanOrEqual(convertedProperty, Expression.Constant(endTime)) + : Expression.LessThan(convertedProperty, Expression.Constant(endTime)); + + timeRangeExpression = Expression.AndAlso(startComparison, endComparison); + } + else + { + // Overnight range (e.g., 10:00 PM to 6:00 AM) + Expression startComparison = inclusive + ? Expression.GreaterThanOrEqual(convertedProperty, Expression.Constant(startTime)) + : Expression.GreaterThan(convertedProperty, Expression.Constant(startTime)); + + Expression endComparison = inclusive + ? Expression.LessThanOrEqual(convertedProperty, Expression.Constant(endTime)) + : Expression.LessThan(convertedProperty, Expression.Constant(endTime)); + + var lessThan = Expression.LessThan(convertedProperty, Expression.Constant(TimeSpan.FromHours(24))); + var greaterThanOrEqualZero = Expression.GreaterThanOrEqual(convertedProperty, Expression.Constant(TimeSpan.Zero)); + + var firstPart = Expression.AndAlso(startComparison, lessThan); + var secondPart = Expression.AndAlso(greaterThanOrEqualZero, endComparison); + timeRangeExpression = Expression.OrElse(firstPart, secondPart); + } + + var lambda = Expression.Lambda>(timeRangeExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildTimeRelative(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("unit", out var unitObj) || + !filter.CustomParameters.TryGetValue("amount", out var amountObj) || + !filter.CustomParameters.TryGetValue("direction", out var directionObj)) + { + throw new ArgumentException("Field, Unit, Amount, and Direction must be provided for TimeRelative filter."); + } + + var field = fieldObj as string; + var unit = (unitObj as string)?.ToLowerInvariant(); + var amount = Convert.ToInt32(amountObj); + var direction = (directionObj as string)?.ToLowerInvariant(); + + if (string.IsNullOrWhiteSpace(field) || + string.IsNullOrWhiteSpace(unit) || + string.IsNullOrWhiteSpace(direction)) + { + throw new ArgumentException("Invalid parameters for TimeRelative filter."); + } + + if (!new[] { "minute", "hour" }.Contains(unit)) + { + throw new ArgumentException("Unit must be either 'minute' or 'hour'"); + } + + if (!new[] { "past", "future" }.Contains(direction)) + { + throw new ArgumentException("Direction must be either 'past' or 'future'"); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + var propertyType = property.Type; + + // Check if the property is TimeSpan or DateTime + if (propertyType != typeof(TimeSpan) && + propertyType != typeof(TimeSpan?) && + propertyType != typeof(DateTime) && + propertyType != typeof(DateTime?)) + { + throw new ArgumentException($"Property type must be TimeSpan or DateTime, but was {propertyType.Name}"); + } + + var isNullable = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>); + var isDateTime = propertyType == typeof(DateTime) || propertyType == typeof(DateTime?); + var referenceTime = GetReferenceTime(DateTime.UtcNow, unit, amount, direction, isDateTime); + + Expression timeConstant; + Expression timeProperty = isNullable + ? Expression.Property(property, "Value") + : property; + if (isDateTime) // For DateTime, we need to compare the TimeOfDay part + { + timeProperty = Expression.Property(timeProperty, "TimeOfDay"); + timeConstant = Expression.Constant(((DateTime)referenceTime).TimeOfDay); + } + else // TimeSpan + { + timeConstant = Expression.Constant((TimeSpan)referenceTime); + } + + Expression timeComparisonExpression = direction == "past" + ? Expression.GreaterThanOrEqual(timeProperty, timeConstant) + : Expression.LessThanOrEqual(timeProperty, timeConstant); + + // For nullable properties, add null check + if (isNullable) + { + var notNullCheck = Expression.NotEqual(property, Expression.Constant(null, propertyType)); + timeComparisonExpression = Expression.AndAlso(notNullCheck, timeComparisonExpression); + } + + var lambda = Expression.Lambda>(timeComparisonExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildEnumValues(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("values", out var valuesObj)) + { + throw new ArgumentException("Field and Values must be provided for EnumFilter."); + } + + var field = fieldObj as string; + var valuesString = valuesObj as string; + + if (string.IsNullOrWhiteSpace(valuesString)) + { + throw new ArgumentException("EnumValues must be a non-empty string of semicolon-separated enum values or integers."); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + var enumType = property.Type; + var underlyingType = Enum.GetUnderlyingType(enumType); + + var enumValues = valuesString.Split(';', StringSplitOptions.RemoveEmptyEntries) + .Select(v => + { + v = v.Trim(); + if (int.TryParse(v, out var intValue)) + { + return Enum.ToObject(enumType, intValue); + } + + return Enum.Parse(enumType, v); + }) + .ToList(); + + if (!enumValues.Any()) + { + throw new ArgumentException("No valid enum values provided."); + } + + // Convert enum values to their underlying type (usually int) + var convertedEnumValues = enumValues.Select(e => Convert.ChangeType(e, underlyingType)).ToList(); + + // Create a typed list to match the expected input type of the Contains method + var listType = typeof(List<>).MakeGenericType(underlyingType); + var typedList = Activator.CreateInstance(listType); + var addMethod = listType.GetMethod("Add"); + foreach (var value in convertedEnumValues) + { + addMethod.Invoke(typedList, [value]); + } + + var valuesExpression = Expression.Constant(typedList); + + // Use the Contains method to check if the property value is in the list of enum values + var containsMethod = typeof(Enumerable).GetMethods() + .First(m => m.Name == "Contains" && m.GetParameters().Length == 2) + .MakeGenericMethod(underlyingType); + + var containsExpression = Expression.Call(null, containsMethod, valuesExpression, Expression.Convert(property, underlyingType)); + + var lambda = Expression.Lambda>(containsExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildTextIn(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("values", out var valuesObj)) + { + throw new ArgumentException("Field and Values must be provided for TextIn custom filter."); + } + + var field = fieldObj as string; + var valuesString = valuesObj as string; + + if (string.IsNullOrWhiteSpace(valuesString)) + { + throw new ArgumentException("Values must be a non-empty string of semicolon-separated text values."); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + + // Split the values and create a list + var values = valuesString.Split(';', StringSplitOptions.RemoveEmptyEntries) + .Select(v => v.Trim()) + .Where(v => !string.IsNullOrWhiteSpace(v)) + .ToList(); + + if (!values.Any()) + { + throw new ArgumentException("No valid text values provided."); + } + + // Create a typed list of strings + var valuesExpression = Expression.Constant(values); + + // Use the Contains method to check if the property value is in the list + var containsMethod = typeof(Enumerable).GetMethods() + .First(m => m.Name == "Contains" && m.GetParameters().Length == 2) + .MakeGenericMethod(typeof(string)); + + var containsExpression = Expression.Call(null, containsMethod, valuesExpression, property); + + var lambda = Expression.Lambda>(containsExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildTextNotIn(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("values", out var valuesObj)) + { + throw new ArgumentException("Field and Values must be provided for TextNotIn custom filter."); + } + + var field = fieldObj as string; + var valuesString = valuesObj as string; + + if (string.IsNullOrWhiteSpace(valuesString)) + { + throw new ArgumentException("Values must be a non-empty string of semicolon-separated text values."); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + + // Split the values and create a list + var values = valuesString.Split(';', StringSplitOptions.RemoveEmptyEntries) + .Select(v => v.Trim()) + .Where(v => !string.IsNullOrWhiteSpace(v)) + .ToList(); + + if (!values.Any()) + { + throw new ArgumentException("No valid text values provided."); + } + + // Create a typed list of strings + var valuesExpression = Expression.Constant(values); + + // Use the Contains method and negate it + var containsMethod = typeof(Enumerable).GetMethods() + .First(m => m.Name == "Contains" && m.GetParameters().Length == 2) + .MakeGenericMethod(typeof(string)); + + var containsExpression = Expression.Call(null, containsMethod, valuesExpression, property); + var notInExpression = Expression.Not(containsExpression); + + var lambda = Expression.Lambda>(notInExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildNumericIn(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("values", out var valuesObj)) + { + throw new ArgumentException("Field and Values must be provided for NumericIn custom filter."); + } + + var field = fieldObj as string; + var valuesString = valuesObj as string; + + if (string.IsNullOrWhiteSpace(valuesString)) + { + throw new ArgumentException("Values must be a non-empty string of semicolon-separated numeric values."); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + var propertyType = property.Type; + + // Handle nullable types + var isNullable = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>); + var underlyingType = isNullable ? Nullable.GetUnderlyingType(propertyType) : propertyType; + + // Parse the numeric values + var values = valuesString.Split(';', StringSplitOptions.RemoveEmptyEntries) + .Select(v => + { + v = v.Trim(); + + return Convert.ChangeType(v, underlyingType, CultureInfo.InvariantCulture); + }) + .ToList(); + + if (!values.Any()) + { + throw new ArgumentException("No valid numeric values provided."); + } + + // Create a typed list for the numeric values + var listType = typeof(List<>).MakeGenericType(underlyingType); + var typedList = Activator.CreateInstance(listType); + var addMethod = listType.GetMethod("Add"); + foreach (var value in values) + { + addMethod.Invoke(typedList, [value]); + } + + var valuesExpression = Expression.Constant(typedList); + + // Use the Contains method + var containsMethod = typeof(Enumerable).GetMethods() + .First(m => m.Name == "Contains" && m.GetParameters().Length == 2) + .MakeGenericMethod(underlyingType); + + var propertyValue = isNullable + ? Expression.Property(property, "Value") + : property; + + var containsExpression = Expression.Call(null, containsMethod, valuesExpression, propertyValue); + + var lambda = Expression.Lambda>(containsExpression, parameter); + + return new Specification(lambda); + } + + private static ISpecification BuildNumericNotIn(FilterCriteria filter) + where TEntity : class, IEntity + { + if (!filter.CustomParameters.TryGetValue("field", out var fieldObj) || + !filter.CustomParameters.TryGetValue("values", out var valuesObj)) + { + throw new ArgumentException("Field and Values must be provided for NumericNotIn custom filter."); + } + + var field = fieldObj as string; + var valuesString = valuesObj as string; + + if (string.IsNullOrWhiteSpace(valuesString)) + { + throw new ArgumentException("Values must be a non-empty string of semicolon-separated numeric values."); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = Expression.Property(parameter, field); + var propertyType = property.Type; + + // Handle nullable types + var isNullable = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>); + var underlyingType = isNullable ? Nullable.GetUnderlyingType(propertyType) : propertyType; + + // Parse the numeric values + var values = valuesString.Split(';', StringSplitOptions.RemoveEmptyEntries) + .Select(v => + { + v = v.Trim(); + + return Convert.ChangeType(v, underlyingType, CultureInfo.InvariantCulture); + }) + .ToList(); + + if (!values.Any()) + { + throw new ArgumentException("No valid numeric values provided."); + } + + // Create a typed list for the numeric values + var listType = typeof(List<>).MakeGenericType(underlyingType); + var typedList = Activator.CreateInstance(listType); + var addMethod = listType.GetMethod("Add"); + foreach (var value in values) + { + addMethod.Invoke(typedList, [value]); + } + + var valuesExpression = Expression.Constant(typedList); + + // Use the Contains method and negate it + var containsMethod = typeof(Enumerable).GetMethods() + .First(m => m.Name == "Contains" && m.GetParameters().Length == 2) + .MakeGenericMethod(underlyingType); + + var propertyValue = isNullable + ? Expression.Property(property, "Value") + : property; + + var containsExpression = Expression.Call(null, containsMethod, valuesExpression, propertyValue); + var notInExpression = Expression.Not(containsExpression); + + var lambda = Expression.Lambda>(notInExpression, parameter); + + return new Specification(lambda); + } + + private static DateTime GetPastDate(DateTime now, string unit, int amount) // TODO: move to DateTimeExtensions + { + return unit switch + { + "day" => now.AddDays(-amount), + "week" => now.AddDays(-amount * 7), + "month" => now.AddMonths(-amount), + "year" => now.AddYears(-amount), + _ => throw new ArgumentException("Invalid date unit") + }; + } + + private static DateTime GetFutureDate(DateTime now, string unit, int amount) // TODO: move to DateTimeExtensions + { + return unit switch + { + "day" => now.AddDays(amount), + "week" => now.AddDays(amount * 7), + "month" => now.AddMonths(amount), + "year" => now.AddYears(amount), + _ => throw new ArgumentException("Invalid date unit") + }; + } + + private static object GetReferenceTime(DateTime now, string unit, int amount, string direction, bool returnDateTime) + { + var multiplier = direction == "past" ? -1 : 1; + + var resultDateTime = unit switch + { + "minute" => now.AddMinutes(amount * multiplier), + "hour" => now.AddHours(amount * multiplier), + _ => throw new ArgumentException("Invalid time unit") + }; + + return returnDateTime ? resultDateTime : resultDateTime.TimeOfDay; + } +} \ No newline at end of file diff --git a/src/Domain/Filtering/IncludeOptionBuilder.cs b/src/Domain/Filtering/IncludeOptionBuilder.cs new file mode 100644 index 00000000..dd3758a7 --- /dev/null +++ b/src/Domain/Filtering/IncludeOptionBuilder.cs @@ -0,0 +1,35 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.Repositories; + +public static class IncludeOptionBuilder +{ + public static List> Build(IEnumerable includes) + where TEntity : class, IEntity + { + if (includes == null || !includes.Any()) + { + return []; + } + + return includes.Select(BuildIncludeOption).ToList(); + } + + private static IncludeOption BuildIncludeOption(string include) + where TEntity : class, IEntity + { + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = BuildPropertyExpression(parameter, include); + var lambda = Expression.Lambda>(property, parameter); + + return new IncludeOption(lambda); + } + + private static Expression BuildPropertyExpression(ParameterExpression parameter, string include) + { + return include.Split('.').Aggregate((Expression)parameter, Expression.Property); + } +} \ No newline at end of file diff --git a/src/Domain/Filtering/OrderOptionBuilder.cs b/src/Domain/Filtering/OrderOptionBuilder.cs new file mode 100644 index 00000000..c46af32b --- /dev/null +++ b/src/Domain/Filtering/OrderOptionBuilder.cs @@ -0,0 +1,36 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.Repositories; + +public static class OrderOptionBuilder +{ + public static List> Build(IEnumerable criterias) + where TEntity : class, IEntity + { + if (criterias == null || !criterias.Any()) + { + return []; + } + + return criterias.Select(criteria => BuildOrderOption(criteria)).ToList(); + } + + private static OrderOption BuildOrderOption(FilterOrderCriteria criteria) + where TEntity : class, IEntity + { + var parameter = Expression.Parameter(typeof(TEntity), "x"); + var property = BuildPropertyExpression(parameter, criteria.Field); + var conversion = Expression.Convert(property, typeof(object)); + var lambda = Expression.Lambda>(conversion, parameter); + + return new OrderOption(lambda, criteria.Direction); + } + + private static Expression BuildPropertyExpression(ParameterExpression parameter, string name) + { + return name.Split('.').Aggregate((Expression)parameter, Expression.Property); + } +} \ No newline at end of file diff --git a/src/Domain/Filtering/SpecificationBuilder.cs b/src/Domain/Filtering/SpecificationBuilder.cs new file mode 100644 index 00000000..7c79cb10 --- /dev/null +++ b/src/Domain/Filtering/SpecificationBuilder.cs @@ -0,0 +1,365 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain; + +using System.Globalization; +using System.Text.Json; + +public static class SpecificationBuilder +{ + // public static void Register(string name) + // where TEntity : class, IEntity + // where TSpecification : ISpecification + // { + // SpecificationResolver.Register(name); + // } + // + // public static void Register(string name, Type specificationType) + // where TEntity : class, IEntity + // { + // SpecificationResolver.Register(specificationType, name); + // } + + public static IEnumerable> Build(IEnumerable filters, IEnumerable> specifications = null) + where TEntity : class, IEntity + { + if (filters == null || !filters.Any()) + { + return []; + } + + var result = new List>(); + ISpecification currentSpeciification = null; + + for (var i = 0; i < filters.Count(); i++) + { + var filter = filters.ElementAt(i); + var isLastFilter = i == filters.Count() - 1; + var specification = BuildSpecification(filter); + + if (currentSpeciification == null) + { + currentSpeciification = specification; + } + else + { + currentSpeciification = currentSpeciification.Or(specification); + } + + if (isLastFilter || filter.Logic == FilterLogicOperator.And) + { + result.Add(currentSpeciification); + currentSpeciification = null; + } + } + + if (specifications != null) + { + result.AddRange(specifications); + } + + return result; + } + + private static ISpecification BuildSpecification(FilterCriteria filter) + where TEntity : class, IEntity + { + if (filter == null) + { + throw new ArgumentException("Filter criteria is required."); + } + + return filter.CustomType switch + { + FilterCustomType.NamedSpecification => SpecificationResolver.Resolve(filter.SpecificationName, filter.SpecificationArguments), + FilterCustomType.CompositeSpecification => BuildComposite(filter.CompositeSpecification), + FilterCustomType.None => new Specification(BuildExpression(filter)), + _ => CustomSpecificationBuilder.Build(filter) + }; + } + + private static Expression> BuildExpression(FilterCriteria filter) + where TEntity : class, IEntity + { + if (filter == null) + { + throw new ArgumentException("Filter criteria is required."); + } + + if (filter.Field.IsNullOrEmpty()) + { + throw new ArgumentException("Field is required for filter criteria."); + } + + var parameter = Expression.Parameter(typeof(TEntity), "x"); + + var body = filter.Operator switch + { + FilterOperator.Any => BuildAnyExpression(parameter, filter.Field, (FilterCriteria)filter.Value), + FilterOperator.All => BuildAllExpression(parameter, filter.Field, (FilterCriteria)filter.Value), + FilterOperator.None => BuildNoneExpression(parameter, filter.Field, (FilterCriteria)filter.Value), + _ => BuildSimpleExpression(parameter, filter) + }; + + return Expression.Lambda>(body, parameter); + } + + private static Expression BuildSimpleExpression(ParameterExpression parameter, FilterCriteria filter) + { + var property = BuildPropertyExpression(parameter, filter.Field); + var value = ConvertValueType(filter.Value, property.Type); + var constant = Expression.Constant(value); + + // For nullable properties, we need to ensure we're comparing the value part + if (property.Type.IsNullableType()) + { + return filter.Operator switch + { + FilterOperator.Equal => Expression.Equal(property, constant), + FilterOperator.NotEqual => Expression.NotEqual(property, constant), + FilterOperator.IsNull => Expression.Equal(property, Expression.Constant(null)), + FilterOperator.IsNotNull => Expression.NotEqual(property, Expression.Constant(null)), + FilterOperator.IsEmpty => Expression.OrElse( + Expression.Equal(property, Expression.Constant(null, property.Type)), + Expression.Equal(property, Expression.Constant(string.Empty, property.Type))), + FilterOperator.IsNotEmpty => Expression.AndAlso( + Expression.NotEqual(property, Expression.Constant(null, property.Type)), + Expression.NotEqual(property, Expression.Constant(string.Empty, property.Type))), + FilterOperator.GreaterThan => Expression.GreaterThan( + Expression.Property(property, "Value"), + Expression.Convert(constant, property.Type.GetGenericArguments()[0])), + FilterOperator.GreaterThanOrEqual => Expression.GreaterThanOrEqual(Expression.Property(property, "Value"), + Expression.Convert(constant, property.Type.GetGenericArguments()[0])), + FilterOperator.LessThan => Expression.LessThan( + Expression.Property(property, "Value"), + Expression.Convert(constant, property.Type.GetGenericArguments()[0])), + FilterOperator.LessThanOrEqual => Expression.LessThanOrEqual( + Expression.Property(property, "Value"), + Expression.Convert(constant, property.Type.GetGenericArguments()[0])), + FilterOperator.Contains => Expression.Call(property, typeof(string).GetMethod("Contains", [typeof(string)]), constant), + FilterOperator.DoesNotContain => Expression.Not(Expression.Call(property, typeof(string).GetMethod("Contains", [typeof(string)]), constant)), + FilterOperator.StartsWith => Expression.Call(property, typeof(string).GetMethod("StartsWith", [typeof(string)]), constant), + FilterOperator.DoesNotStartWith => Expression.Not(Expression.Call(property, typeof(string).GetMethod("StartsWith", [typeof(string)]), constant)), + FilterOperator.EndsWith => Expression.Call(property, typeof(string).GetMethod("EndsWith", [typeof(string)]), constant), + FilterOperator.DoesNotEndWith => Expression.Not(Expression.Call(property, typeof(string).GetMethod("EndsWith", [typeof(string)]), constant)), + _ => throw new NotSupportedException($"Operator {filter.Operator} is not supported.") + }; + } + + // For non-nullable properties, use the original logic + return filter.Operator switch + { + FilterOperator.Equal => Expression.Equal(property, constant), + FilterOperator.NotEqual => Expression.NotEqual(property, constant), + FilterOperator.IsNull => Expression.Equal(property, Expression.Constant(null)), + FilterOperator.IsNotNull => Expression.NotEqual(property, Expression.Constant(null)), + FilterOperator.IsEmpty => Expression.OrElse(Expression.Equal(property, Expression.Constant(null)), Expression.Equal(property, Expression.Constant(string.Empty))), + FilterOperator.IsNotEmpty => Expression.AndAlso(Expression.NotEqual(property, Expression.Constant(null)), Expression.NotEqual(property, Expression.Constant(string.Empty))), + FilterOperator.GreaterThan => Expression.GreaterThan(property, constant), + FilterOperator.GreaterThanOrEqual => Expression.GreaterThanOrEqual(property, constant), + FilterOperator.LessThan => Expression.LessThan(property, constant), + FilterOperator.LessThanOrEqual => Expression.LessThanOrEqual(property, constant), + FilterOperator.Contains => Expression.Call(property, typeof(string).GetMethod("Contains", [typeof(string)]), constant), + FilterOperator.DoesNotContain => Expression.Not(Expression.Call(property, typeof(string).GetMethod("Contains", [typeof(string)]), constant)), + FilterOperator.StartsWith => Expression.Call(property, typeof(string).GetMethod("StartsWith", [typeof(string)]), constant), + FilterOperator.DoesNotStartWith => Expression.Not(Expression.Call(property, typeof(string).GetMethod("StartsWith", [typeof(string)]), constant)), + FilterOperator.EndsWith => Expression.Call(property, typeof(string).GetMethod("EndsWith", [typeof(string)]), constant), + FilterOperator.DoesNotEndWith => Expression.Not(Expression.Call(property, typeof(string).GetMethod("EndsWith", [typeof(string)]), constant)), + _ => throw new NotSupportedException($"Operator {filter.Operator} is not supported.") + }; + } + + private static Expression BuildPropertyExpression(ParameterExpression parameter, string field) + { + return field.Split('.').Aggregate((Expression)parameter, Expression.Property); // WARN: throws when propertyname not correct (ArgumentException) + } + + private static Expression BuildAnyExpression(ParameterExpression parameter, string fieldPath, FilterCriteria innerFilter) + { + var collection = BuildPropertyExpression(parameter, fieldPath); + var elementType = collection.Type.GetGenericArguments()[0]; + var elementParameter = Expression.Parameter(elementType, "element"); + + var innerBody = BuildExpression(elementType, innerFilter, elementParameter); + var lambda = Expression.Lambda(innerBody, elementParameter); + + return Expression.Call(typeof(Enumerable), "Any", [elementType], collection, lambda); + } + + private static Expression BuildAllExpression(ParameterExpression parameter, string fieldPath, FilterCriteria innerFilter) + { + var collection = BuildPropertyExpression(parameter, fieldPath); + var elementType = collection.Type.GetGenericArguments()[0]; + var elementParameter = Expression.Parameter(elementType, "element"); + + var innerBody = BuildExpression(elementType, innerFilter, elementParameter); + var lambda = Expression.Lambda(innerBody, elementParameter); + + return Expression.Call(typeof(Enumerable), "All", [elementType], collection, lambda); + } + + private static Expression BuildNoneExpression(ParameterExpression parameter, string fieldPath, FilterCriteria innerFilter) + { + var anyExpression = BuildAnyExpression(parameter, fieldPath, innerFilter); + + return Expression.Not(anyExpression); + } + + private static Expression BuildExpression(Type entityType, FilterCriteria filter, ParameterExpression parameter) + { + var property = BuildPropertyExpression(parameter, filter.Field); + var value = ConvertValueType(filter.Value, property.Type); + var constant = Expression.Constant(value, property.Type); + + if (property.Type.IsNullableType()) + { + var underlyingType = property.Type.GetGenericArguments()[0]; + var propertyValue = Expression.Property(property, "Value"); + var convertedConstant = Expression.Convert(constant, underlyingType); + var notNullCheck = Expression.NotEqual(property, Expression.Constant(null, property.Type)); + + return filter.Operator switch + { + FilterOperator.Equal => Expression.Equal(property, constant), + FilterOperator.NotEqual => Expression.NotEqual(property, constant), + FilterOperator.IsNull => Expression.Equal(property, Expression.Constant(null)), + FilterOperator.IsNotNull => Expression.NotEqual(property, Expression.Constant(null)), + FilterOperator.IsEmpty => Expression.OrElse( + Expression.Equal(property, Expression.Constant(null, property.Type)), + Expression.Equal(property, Expression.Constant(string.Empty, property.Type))), + FilterOperator.IsNotEmpty => Expression.AndAlso( + Expression.NotEqual(property, Expression.Constant(null, property.Type)), + Expression.NotEqual(property, Expression.Constant(string.Empty, property.Type))), + + FilterOperator.GreaterThan => Expression.AndAlso(notNullCheck, Expression.GreaterThan(propertyValue, convertedConstant)), + FilterOperator.GreaterThanOrEqual => Expression.AndAlso(notNullCheck, Expression.GreaterThanOrEqual(propertyValue, convertedConstant)), + FilterOperator.LessThan => Expression.AndAlso(notNullCheck, Expression.LessThan(propertyValue, convertedConstant)), + FilterOperator.LessThanOrEqual => Expression.AndAlso(notNullCheck, Expression.LessThanOrEqual(propertyValue, convertedConstant)), + FilterOperator.Contains => Expression + .AndAlso(notNullCheck, Expression.Call(property, typeof(string).GetMethod("Contains", [typeof(string)]), constant)), + FilterOperator.DoesNotContain => Expression + .AndAlso(notNullCheck, Expression.Not(Expression.Call(property, typeof(string).GetMethod("Contains", [typeof(string)]), constant))), + FilterOperator.StartsWith => Expression + .AndAlso(notNullCheck, Expression.Call(property, typeof(string).GetMethod("StartsWith", [typeof(string)]), constant)), + FilterOperator.DoesNotStartWith => Expression + .AndAlso(notNullCheck, Expression.Not(Expression.Call(property, typeof(string).GetMethod("StartsWith", [typeof(string)]), constant))), + FilterOperator.EndsWith => Expression + .AndAlso(notNullCheck, Expression.Call(property, typeof(string).GetMethod("EndsWith", [typeof(string)]), constant)), + FilterOperator.DoesNotEndWith => Expression + .AndAlso(notNullCheck, Expression.Not(Expression.Call(property, typeof(string).GetMethod("EndsWith", [typeof(string)]), constant))), + _ => throw new NotSupportedException($"Operator {filter.Operator} is not supported.") + }; + } + + return filter.Operator switch + { + FilterOperator.Equal => Expression.Equal(property, constant), + FilterOperator.NotEqual => Expression.NotEqual(property, constant), + FilterOperator.IsNull => Expression.Equal(property, Expression.Constant(null)), + FilterOperator.IsNotNull => Expression.NotEqual(property, Expression.Constant(null)), + FilterOperator.IsEmpty => Expression.OrElse( + Expression.Equal(property, Expression.Constant(null)), + Expression.Equal(property, Expression.Constant(string.Empty))), + FilterOperator.IsNotEmpty => Expression.AndAlso( + Expression.NotEqual(property, Expression.Constant(null)), + Expression.NotEqual(property, Expression.Constant(string.Empty))), + FilterOperator.GreaterThan => Expression.GreaterThan(property, constant), + FilterOperator.GreaterThanOrEqual => Expression.GreaterThanOrEqual(property, constant), + FilterOperator.LessThan => Expression.LessThan(property, constant), + FilterOperator.LessThanOrEqual => Expression.LessThanOrEqual(property, constant), + FilterOperator.Contains => Expression.Call(property, typeof(string).GetMethod("Contains", [typeof(string)]), constant), + FilterOperator.DoesNotContain => Expression.Not(Expression.Call(property, typeof(string).GetMethod("Contains", [typeof(string)]), constant)), + FilterOperator.StartsWith => Expression.Call(property, typeof(string).GetMethod("StartsWith", [typeof(string)]), constant), + FilterOperator.DoesNotStartWith => Expression.Not(Expression.Call(property, typeof(string).GetMethod("StartsWith", [typeof(string)]), constant)), + FilterOperator.EndsWith => Expression.Call(property, typeof(string).GetMethod("EndsWith", [typeof(string)]), constant), + FilterOperator.DoesNotEndWith => Expression.Not(Expression.Call(property, typeof(string).GetMethod("EndsWith", [typeof(string)]), constant)), + _ => throw new NotSupportedException($"Operator {filter.Operator} is not supported.") + }; + } + + private static ISpecification BuildComposite(CompositeSpecification compositeSpec) + where TEntity : class, IEntity + { + return BuildFromNodes(compositeSpec.Nodes); + } + + private static ISpecification BuildFromNodes(List nodes) + where TEntity : class, IEntity + { + if (nodes.Count == 1) + { + return BuildFromNode(nodes[0]); + } + + var specs = nodes.Select(BuildFromNode).ToList(); + + return specs.Aggregate((current, next) => current.And(next)); + } + + private static ISpecification BuildFromNode(SpecificationNode node) + where TEntity : class, IEntity + { + if (node is SpecificationLeaf leaf) + { + return SpecificationResolver.Resolve(leaf.Name, leaf.Arguments); + } + else if (node is SpecificationGroup group) + { + var specs = group.Nodes.Select(n => BuildFromNode(n)).ToList(); + + return group.Logic switch + { + FilterLogicOperator.Or => specs.Aggregate((current, next) => current.Or(next)), + FilterLogicOperator.And => specs.Aggregate((current, next) => current.And(next)), + _ => throw new ArgumentException($"Unsupported logical operator: {group.Logic}") + }; + } + + throw new ArgumentException($"Unknown node type: {node.GetType().Name}"); + } + + private static object ConvertValueType(object value, Type targetType) + { + if (value == null) + { + return null; + } + + var nullableTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType; + + if (value is JsonElement jsonElement) + { + return jsonElement.ValueKind switch + { + JsonValueKind.String => ConvertStringValue(jsonElement.GetString()), + JsonValueKind.Number when nullableTargetType == typeof(int) => jsonElement.GetInt32(), + JsonValueKind.Number when nullableTargetType == typeof(long) => jsonElement.GetInt64(), + JsonValueKind.Number when nullableTargetType == typeof(double) => jsonElement.GetDouble(), + JsonValueKind.Number when nullableTargetType == typeof(decimal) => jsonElement.GetDecimal(), + JsonValueKind.True => true, + JsonValueKind.False => false, + JsonValueKind.Null => null, + JsonValueKind.Array => jsonElement.EnumerateArray().ToList(), + JsonValueKind.Object => jsonElement.Deserialize>(), + _ => throw new ArgumentException($"Unsupported JsonValueKind: {jsonElement.ValueKind}") + }; + } + + return value is string stringValue + ? ConvertStringValue(stringValue) + : Convert.ChangeType(value, nullableTargetType); + + object ConvertStringValue(string stringValue) => nullableTargetType switch + { + not null when nullableTargetType == typeof(DateTimeOffset) => DateTimeOffset.Parse(stringValue, CultureInfo.InvariantCulture), + not null when nullableTargetType == typeof(DateTime) => DateTime.Parse(stringValue, CultureInfo.InvariantCulture), + not null when nullableTargetType == typeof(DateOnly) => DateOnly.Parse(stringValue, CultureInfo.InvariantCulture), + not null when nullableTargetType == typeof(TimeOnly) => TimeOnly.Parse(stringValue, CultureInfo.InvariantCulture), + not null when nullableTargetType == typeof(Guid) => Guid.Parse(stringValue), + _ => Convert.ChangeType(stringValue, nullableTargetType) + }; + } +} \ No newline at end of file diff --git a/src/Domain/Filtering/SpecificationResolver.cs b/src/Domain/Filtering/SpecificationResolver.cs new file mode 100644 index 00000000..6a9597e1 --- /dev/null +++ b/src/Domain/Filtering/SpecificationResolver.cs @@ -0,0 +1,72 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain; + +using System.Collections.Concurrent; + +public static class SpecificationResolver +{ + private static readonly ConcurrentDictionary SpecificationTypes = new ConcurrentDictionary(); + + public static void Register(string name = null) + where TEntity : class, IEntity + where TSpecification : ISpecification + { + Register(typeof(TSpecification), name ?? typeof(TSpecification).Name); + } + + public static void Register(Type specificationType, string name = null) + where TEntity : class, IEntity + { + if (!typeof(ISpecification).IsAssignableFrom(specificationType)) + { + throw new ArgumentException($"Type {specificationType.Name} does not implement ISpecification<{typeof(TEntity).Name}>"); + } + + name ??= specificationType.Name; + if (!SpecificationTypes.TryAdd(name, specificationType)) + { + throw new InvalidOperationException($"A specification with the name '{name}' has already been registered."); + } + } + + public static ISpecification Resolve(object[] arguments) + where TEntity : class, IEntity + { + return Resolve(typeof(TEntity).Name, arguments); + } + + public static ISpecification Resolve(string name, object[] arguments) + where TEntity : class, IEntity + { + name ??= typeof(TEntity).Name; + if (!SpecificationTypes.TryGetValue(name, out var specificationType)) + { + throw new ArgumentException($"No specification registered with name '{name}'"); + } + + return (ISpecification)Activator.CreateInstance(specificationType, arguments); + } + + public static bool IsRegistered(string name) + { + return SpecificationTypes.ContainsKey(name); + } + + public static Type GetType(string name) + { + if (!SpecificationTypes.TryGetValue(name, out var specificationType)) + { + throw new ArgumentException($"No specification registered with name '{name}'"); + } + return specificationType; + } + + public static void Clear() + { + SpecificationTypes.Clear(); + } +} \ No newline at end of file diff --git a/src/Domain/Repositories/Behaviors/RepositoryLoggingBehavior.cs b/src/Domain/Repositories/Behaviors/RepositoryLoggingBehavior.cs index 898126f3..42b1c779 100644 --- a/src/Domain/Repositories/Behaviors/RepositoryLoggingBehavior.cs +++ b/src/Domain/Repositories/Behaviors/RepositoryLoggingBehavior.cs @@ -57,9 +57,10 @@ public async Task CountAsync( foreach (var specification in specifications.SafeNull()) { - this.Logger.LogDebug("{LogKey} repository specification: {Specification}", + this.Logger.LogDebug("{LogKey} repository specification: {Specification} -> {SpecificationExpression}", Constants.LogKey, - specification.GetType().PrettyName()); + specification.GetType().PrettyName(), + specification.ToString()); } return await this.Inner.CountAsync(specifications, cancellationToken).AnyContext(); @@ -106,9 +107,10 @@ public async Task> FindAllAsync( if (specification is not null) { - this.Logger.LogDebug("{LogKey} repository specification: {Specification}", + this.Logger.LogDebug("{LogKey} repository specification: {Specification} -> {SpecificationExpression}", Constants.LogKey, - specification.GetType().PrettyName()); + specification.GetType().PrettyName(), + specification.ToString()); } return await this.Inner.FindAllAsync(specification, options, cancellationToken).AnyContext(); @@ -124,9 +126,10 @@ public async Task> FindAllAsync( foreach (var specification in specifications.SafeNull()) { - this.Logger.LogDebug("{LogKey} repository specification: {Specification}", + this.Logger.LogDebug("{LogKey} repository specification: {Specification} -> {SpecificationExpression}", Constants.LogKey, - specification.GetType().PrettyName()); + specification.GetType().PrettyName(), + specification.ToString()); } return await this.Inner.FindAllAsync(specifications, options, cancellationToken).AnyContext(); @@ -154,9 +157,10 @@ public async Task> ProjectAllAsync( if (specification is not null) { - this.Logger.LogDebug("{LogKey} repository specification: {Specification}", + this.Logger.LogDebug("{LogKey} repository specification: {Specification} -> {SpecificationExpression}", Constants.LogKey, - specification.GetType().PrettyName()); + specification.GetType().PrettyName(), + specification.ToString()); } return await this.Inner.ProjectAllAsync(specification, projection, options, cancellationToken).AnyContext(); diff --git a/src/Domain/Repositories/IGenericRepository.cs b/src/Domain/Repositories/IGenericRepository.cs index 24ec23e8..f88aa076 100644 --- a/src/Domain/Repositories/IGenericRepository.cs +++ b/src/Domain/Repositories/IGenericRepository.cs @@ -42,9 +42,7 @@ public interface IGenericRepository : IGenericReadOnlyRepository that represents the asynchronous operation, containing a tuple with the entity and the /// action result. /// - Task<(TEntity entity, RepositoryActionResult action)> UpsertAsync( - TEntity entity, - CancellationToken cancellationToken = default); + Task<(TEntity entity, RepositoryActionResult action)> UpsertAsync(TEntity entity, CancellationToken cancellationToken = default); /// /// Deletes the entity for the provided id. diff --git a/src/Domain/Repositories/OrderDirection.cs b/src/Domain/Repositories/OrderDirection.cs index ba054b8e..278ffb1f 100644 --- a/src/Domain/Repositories/OrderDirection.cs +++ b/src/Domain/Repositories/OrderDirection.cs @@ -1,4 +1,4 @@ -// MIT-License +/*// MIT-License // Copyright BridgingIT GmbH - All Rights Reserved // Use of this source code is governed by an MIT-style license that can be // found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license @@ -25,4 +25,4 @@ public enum OrderDirection /// Specifies that the ordering should be in descending sequence. /// Descending = 20 -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/Domain/Repositories/RepositoryExtensions.cs b/src/Domain/Repositories/RepositoryExtensions.cs index 9bb5070c..9ade640a 100644 --- a/src/Domain/Repositories/RepositoryExtensions.cs +++ b/src/Domain/Repositories/RepositoryExtensions.cs @@ -10,6 +10,48 @@ namespace BridgingIT.DevKit.Domain.Repositories; /// public static class RepositoryExtensions { + /// + /// Asynchronously finds all entities that match the given expression. + /// + /// The type of the entity. + /// The source repository. + /// The expression used to filter entities. + /// Optional find options. + /// Token to monitor for cancellation requests. + /// A task representing the asynchronous operation, containing a the collection of found entities. + public static async Task> FindAllAsync( + this IGenericReadOnlyRepository source, + Expression> expression, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + return await source.FindAllAsync(new Specification(expression), options, cancellationToken).AnyContext(); + } + + public static async Task> FindAllAsync( + this IGenericReadOnlyRepository source, + FilterModel filterModel, + IEnumerable> specifications = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + filterModel ??= new FilterModel(); + specifications = SpecificationBuilder.Build(filterModel.Filters, specifications).ToArray(); + var orders = OrderOptionBuilder.Build(filterModel.Orderings).ToArray(); + var includes = IncludeOptionBuilder.Build(filterModel.Includes).ToArray(); + + return await source.FindAllAsync( + specifications, + new FindOptions + { + Orders = orders, + Includes = includes + }, + cancellationToken) + .AnyContext(); + } + /// /// Asynchronously finds all IDs that match the given specifications. /// @@ -29,6 +71,52 @@ public static async Task> FindAllIdsAsync( .Select(i => i.To()); } + /// + /// Asynchronously finds all IDs that match the given specification. + /// + /// The type of the entity. + /// The type of the ID. + /// The source repository. + /// The expression used to filter entities. + /// Optional find options to customize the query. + /// Cancellation token to cancel the operation. + /// A task that represents the asynchronous operation. The task result contains an enumerable collection of IDs. + public static async Task> FindAllIdsAsync( + this IGenericReadOnlyRepository source, + Expression> expression, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + return (await source.ProjectAllAsync( + new Specification(expression), e => e.Id, options, cancellationToken).AnyContext()) + .Select(i => i.To()); + } + + public static async Task> FindAllIdsAsync( + this IGenericReadOnlyRepository source, + FilterModel filterModel, + IEnumerable> specifications, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + filterModel ??= new FilterModel(); + specifications = SpecificationBuilder.Build(filterModel.Filters, specifications).ToArray(); + var orders = OrderOptionBuilder.Build(filterModel.Orderings).ToArray(); + var includes = IncludeOptionBuilder.Build(filterModel.Includes).ToArray(); + + return (await source.ProjectAllAsync( + specifications, + e => e.Id, + new FindOptions + { + Orders = orders, + Includes = includes + }, + cancellationToken).AnyContext()) + .Select(i => i.To()); + } + /// /// Asynchronously finds all IDs that match the given specification. /// diff --git a/src/Domain/Repositories/RepositoryResultExtensions.cs b/src/Domain/Repositories/RepositoryResultExtensions.cs deleted file mode 100644 index d4cc97a5..00000000 --- a/src/Domain/Repositories/RepositoryResultExtensions.cs +++ /dev/null @@ -1,281 +0,0 @@ -// MIT-License -// Copyright BridgingIT GmbH - All Rights Reserved -// Use of this source code is governed by an MIT-style license that can be -// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license - -namespace BridgingIT.DevKit.Domain.Repositories; - -public static class RepositoryResultExtensions -{ - public static async Task> FindOneResultAsync( - this IGenericReadOnlyRepository source, - object id, - IFindOptions options = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - var entity = await source.FindOneAsync(id, options, cancellationToken).AnyContext(); - - return entity is null ? Result.Failure() : Result.Success(entity); - } - - public static async Task> FindOneResultAsync( - this IGenericReadOnlyRepository source, - ISpecification specification, - IFindOptions options = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - var entity = await source.FindOneAsync(specification, options, cancellationToken).AnyContext(); - - return entity is null ? Result.Failure() : Result.Success(entity); - } - - public static async Task> FindOneResultAsync( - this IGenericReadOnlyRepository source, - IEnumerable> specifications, - IFindOptions options = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - var entity = await source.FindOneAsync(specifications, options, cancellationToken).AnyContext(); - - return entity is null ? Result.Failure() : Result.Success(entity); - } - - public static async Task>> FindAllResultAsync( - this IGenericReadOnlyRepository source, - IFindOptions options = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - return Result>.Success(await source.FindAllAsync(options, cancellationToken).AnyContext()); - } - - public static async Task>> FindAllResultAsync( - this IGenericReadOnlyRepository source, - ISpecification specification, - IFindOptions options = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - return Result>.Success(await source.FindAllAsync(specification, options, cancellationToken) - .AnyContext()); - } - - public static async Task>> FindAllResultAsync( - this IGenericReadOnlyRepository source, - IEnumerable> specifications, - IFindOptions options = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - return Result>.Success(await source.FindAllAsync(specifications, - options, - cancellationToken) - .AnyContext()); - } - - public static async Task>> FindAllIdsResultAsync( - this IGenericReadOnlyRepository source, - IFindOptions options = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - return Result>.Success(await source.FindAllIdsAsync(options, cancellationToken) - .AnyContext()); - } - - public static async Task>> FindAllIdsResultAsync( - this IGenericReadOnlyRepository source, - ISpecification specification, - IFindOptions options = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - return Result>.Success(await source.FindAllIdsAsync(specification, - options, - cancellationToken) - .AnyContext()); - } - - public static async Task>> FindAllIdsResultAsync( - this IGenericReadOnlyRepository source, - IEnumerable> specifications, - IFindOptions options = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - return Result>.Success(await source.FindAllIdsAsync(specifications, - options, - cancellationToken) - .AnyContext()); - } - - public static async Task> FindAllPagedResultAsync( - this IGenericReadOnlyRepository source, - string ordering, // of the form > fieldname [ascending|descending], ... - int page = 1, - int pageSize = 10, - Expression> includeExpression = null, - string includePath = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - var count = await source.CountAsync(cancellationToken).AnyContext(); - - var entities = await source.FindAllAsync(new FindOptions - { - Order = !ordering.IsNullOrEmpty() ? new OrderOption(ordering) : null, - Skip = (page - 1) * pageSize, - Take = pageSize, - Include = includeExpression is not null ? new IncludeOption(includeExpression) : - !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null - }, - cancellationToken) - .AnyContext(); - - return PagedResult.Success(entities, count, page, pageSize); - } - - public static async Task> FindAllPagedResultAsync( - this IGenericReadOnlyRepository source, - Expression> orderingExpression, - int page = 1, - int pageSize = 10, - OrderDirection orderDirection = OrderDirection.Ascending, - Expression> includeExpression = null, - string includePath = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - var count = await source.CountAsync(cancellationToken).AnyContext(); - - var entities = await source.FindAllAsync(new FindOptions - { - Order = new OrderOption(orderingExpression, orderDirection), - Skip = (page - 1) * pageSize, - Take = pageSize, - Include = includeExpression is not null ? new IncludeOption(includeExpression) : - !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null - }, - cancellationToken) - .AnyContext(); - - return PagedResult.Success(entities, count, page, pageSize); - } - - public static async Task> FindAllPagedResultAsync( - this IGenericReadOnlyRepository source, - ISpecification specification, - string ordering, // of the form > fieldname [ascending|descending], ... - int page = 1, - int pageSize = 10, - Expression> includeExpression = null, - string includePath = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - var count = await source.CountAsync(specification, cancellationToken).AnyContext(); - - var entities = await source.FindAllAsync(specification, - new FindOptions - { - Order = !ordering.IsNullOrEmpty() ? new OrderOption(ordering) : null, - Skip = (page - 1) * pageSize, - Take = pageSize, - Include = includeExpression is not null ? new IncludeOption(includeExpression) : - !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null - }, - cancellationToken) - .AnyContext(); - - return PagedResult.Success(entities, count, page, pageSize); - } - - public static async Task> FindAllPagedResultAsync( - this IGenericReadOnlyRepository source, - ISpecification specification, - Expression> orderingExpression, - int page = 1, - int pageSize = 10, - OrderDirection orderDirection = OrderDirection.Ascending, - Expression> includeExpression = null, - string includePath = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - var count = await source.CountAsync(specification, cancellationToken).AnyContext(); - - var entities = await source.FindAllAsync(specification, - new FindOptions - { - Order = new OrderOption(orderingExpression, orderDirection), - Skip = (page - 1) * pageSize, - Take = pageSize, - Include = includeExpression is not null ? new IncludeOption(includeExpression) : - !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null - }, - cancellationToken) - .AnyContext(); - - return PagedResult.Success(entities, count, page, pageSize); - } - - public static async Task> FindAllPagedResultAsync( - this IGenericReadOnlyRepository source, - IEnumerable> specifications, - string ordering, // of the form > fieldname [ascending|descending], ... - int page = 1, - int pageSize = 10, - Expression> includeExpression = null, - string includePath = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - var count = await source.CountAsync(specifications, cancellationToken).AnyContext(); - - var entities = await source.FindAllAsync(specifications, - new FindOptions - { - Order = !ordering.IsNullOrEmpty() ? new OrderOption(ordering) : null, - Skip = (page - 1) * pageSize, - Take = pageSize, - Include = includeExpression is not null ? new IncludeOption(includeExpression) : - !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null - }, - cancellationToken) - .AnyContext(); - - return PagedResult.Success(entities, count, page, pageSize); - } - - public static async Task> FindAllPagedResultAsync( - this IGenericReadOnlyRepository source, - IEnumerable> specifications, - Expression> orderingExpression, - int page = 1, - int pageSize = 10, - OrderDirection orderDirection = OrderDirection.Ascending, - Expression> includeExpression = null, - string includePath = null, - CancellationToken cancellationToken = default) - where TEntity : class, IEntity - { - var count = await source.CountAsync(specifications, cancellationToken).AnyContext(); - - var entities = await source.FindAllAsync(specifications, - new FindOptions - { - Order = new OrderOption(orderingExpression, orderDirection), - Skip = (page - 1) * pageSize, - Take = pageSize, - Include = includeExpression is not null ? new IncludeOption(includeExpression) : - !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null - }, - cancellationToken) - .AnyContext(); - - return PagedResult.Success(entities, count, page, pageSize); - } -} \ No newline at end of file diff --git a/src/Domain/Repositories/Result/GenericReadOnlyRepositoryResultExtensions.cs b/src/Domain/Repositories/Result/GenericReadOnlyRepositoryResultExtensions.cs new file mode 100644 index 00000000..e977d51d --- /dev/null +++ b/src/Domain/Repositories/Result/GenericReadOnlyRepositoryResultExtensions.cs @@ -0,0 +1,835 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.Repositories; + +using System.Data; +using System.Data.Common; +using System.Net.Sockets; + +/// +/// Provides extension methods for performing result-based queries on repositories. +/// +public static class GenericReadOnlyRepositoryResultExtensions +{ + /// + /// Asynchronously counts the total number of entities and returns the result as a Result object. + /// + /// The type of the entity. + /// The source repository. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the count of entities. + public static async Task> CountResultAsync( + this IGenericReadOnlyRepository source, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var count = await source.CountAsync(cancellationToken).AnyContext(); + + return Result.Success(count); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously counts the number of entities based on the given specification and returns the result as a Result object. + /// + /// The type of the entity. + /// The source repository. + /// The expression to filter the entity. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the count of entities that match the specification. + public static async Task> CountResultAsync( + this IGenericReadOnlyRepository source, + Expression> expression, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + return await source.CountResultAsync(new Specification(expression), cancellationToken).AnyContext(); + } + + /// + /// Asynchronously counts the number of entities that satisfy the specified criteria and returns the result as a Result object. + /// + /// The type of the entity. + /// The source repository. + /// The criteria that entities must satisfy to be counted. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the count of entities that satisfy the specified criteria. + public static async Task> CountResultAsync( + this IGenericReadOnlyRepository source, + ISpecification specification, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var count = await source.CountAsync(specification, cancellationToken).AnyContext(); + + return Result.Success(count); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously counts the number of entities that satisfy the provided specifications and returns the result as a Result object. + /// + /// The type of the entity. + /// The source repository. + /// A collection of specifications to filter the entities. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the count of entities that satisfy the provided specifications. + public static async Task> CountResultAsync( + this IGenericReadOnlyRepository source, + IEnumerable> specifications, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var count = await source.CountAsync(specifications, cancellationToken).AnyContext(); + + return Result.Success(count); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// Asynchronously finds a single entity by its identifier and returns a result object. + /// The repository source from which to find the entity. + /// The identifier of the entity to be found. + /// Optional find options to customize the query. + /// Optional cancellation token to cancel the operation. + /// The type of the entity to be found. + /// A task that represents the asynchronous operation. The task result contains a result object with the found entity or a failure result if the entity was not found. + public static async Task> FindOneResultAsync( + this IGenericReadOnlyRepository source, + object id, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var entity = await source.FindOneAsync(id, options, cancellationToken).AnyContext(); + + return entity is null ? Result.Failure() : Result.Success(entity); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously finds a single entity based on the provided expression. + /// + /// The type of the entity. + /// The repository from which to retrieve the entity. + /// The expression to filter the entity. + /// Optional find options to customize the query. + /// Token to observe for cancellation. + /// A task that represents the asynchronous find operation. The task result contains the find result. + /// + public static async Task> FindOneResultAsync( + this IGenericReadOnlyRepository source, + Expression> expression, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + return await source.FindOneResultAsync(new Specification(expression), options, cancellationToken).AnyContext(); + } + + /// Asynchronously finds and returns a single entity that matches the given specification. + /// The repository from which the entity is to be fetched. + /// The specification that defines the criteria for selecting the entity. + /// Optional settings for the entity retrieval process. + /// A token to cancel the asynchronous operation. + /// The type of the entity to be fetched. + /// A result object containing the fetched entity if found, or a failure result if not found. + public static async Task> FindOneResultAsync( + this IGenericReadOnlyRepository source, + ISpecification specification, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var entity = await source.FindOneAsync(specification, options, cancellationToken).AnyContext(); + + return entity is null ? Result.Failure() : Result.Success(entity); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously retrieves a single entity based on multiple specifications. + /// + /// The type of the entity. + /// The repository to fetch the entity from. + /// The specifications to filter the entity. + /// Optional find options for retrieval. + /// Token to monitor for cancellation requests. + /// A result containing the found entity if successful, or an error if not found. + public static async Task> FindOneResultAsync( + this IGenericReadOnlyRepository source, + IEnumerable> specifications, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var entity = await source.FindOneAsync(specifications, options, cancellationToken).AnyContext(); + + return entity is null ? Result.Failure() : Result.Success(entity); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously retrieves all entities from the repository and returns them wrapped in a success result. + /// + /// The type of the entity. + /// The generic read-only repository from which to retrieve entities. + /// Optional find options for the query. + /// Optional cancellation token to cancel the operation. + /// A task representing the asynchronous operation. The task result contains a success result wrapping the retrieved entities. + public static async Task>> FindAllResultAsync( + this IGenericReadOnlyRepository source, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + return Result>.Success( + await source.FindAllAsync(options, cancellationToken).AnyContext()); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result>.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously finds all entities that match the given expression. + /// + /// The type of the entity. + /// The source repository. + /// The expression used to filter entities. + /// Optional find options. + /// Token to monitor for cancellation requests. + /// A task representing the asynchronous operation, containing a result with the collection of found entities. + public static async Task>> FindAllResultAsync( + this IGenericReadOnlyRepository source, + Expression> expression, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + return await source.FindAllResultAsync(new Specification(expression), options, cancellationToken).AnyContext(); + } + + public static async Task> FindAllResultAsync( + this IGenericReadOnlyRepository source, + FilterModel filterModel, + IEnumerable> specifications = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + filterModel ??= new FilterModel(); + specifications = SpecificationBuilder.Build(filterModel.Filters, specifications).ToArray(); + var orders = OrderOptionBuilder.Build(filterModel.Orderings).ToArray(); + var includes = IncludeOptionBuilder.Build(filterModel.Includes).ToArray(); + + var count = await source.CountAsync(specifications, cancellationToken).AnyContext(); + var entities = await source.FindAllAsync( + specifications, + new FindOptions + { + Orders = orders, + Includes = includes + }, + cancellationToken) + .AnyContext(); + + return PagedResult.Success(entities, count, filterModel.Page, filterModel.PageSize); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return PagedResult.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously finds all entities of type that satisfy the specified + /// and returns them as a of . + /// + /// The type of the entity. + /// The repository from which to read the entities. + /// The specification defining the conditions that the entities must satisfy. + /// Optional find options that may affect the query. + /// A token to cancel the operation. + /// A task representing the asynchronous operation. The task result contains a + /// of of . + public static async Task>> FindAllResultAsync( + this IGenericReadOnlyRepository source, + ISpecification specification, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + return Result>.Success( + await source.FindAllAsync(specification, options, cancellationToken).AnyContext()); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result>.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Finds all entities that match the given specifications and returns the result asynchronously. + /// + /// The type of the entity. + /// The source repository. + /// A collection of specifications to filter the entities. + /// Optional find options for the query. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the entities found. + public static async Task>> FindAllResultAsync( + this IGenericReadOnlyRepository source, + IEnumerable> specifications, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + return Result>.Success( + await source.FindAllAsync(specifications, options, cancellationToken).AnyContext()); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result>.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Finds all entity IDs in the repository based on the given options and cancellation token. + /// + /// The type of entity. + /// The type of the identifier. + /// The repository source. + /// The options to be used for the find operation. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains a Result object with the collection of IDs. + public static async Task>> FindAllIdsResultAsync( + this IGenericReadOnlyRepository source, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + return Result>.Success( + await source.FindAllIdsAsync(options, cancellationToken).AnyContext()); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result>.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously retrieves a list of entity identifiers that match the specified criteria. + /// + /// The type of the entity. + /// The type of the identifier. + /// The generic read-only repository to extend. + /// The expression used to filter the entities. + /// Optional additional options for the find operation. + /// A cancellation token that can be used to cancel the asynchronous operation. + /// A task that represents the asynchronous operation. The task result contains a result object with a list of entity identifiers. + public static async Task>> FindAllIdsResultAsync( + this IGenericReadOnlyRepository source, + Expression> expression, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + return await source.FindAllIdsResultAsync(new Specification(expression), options, cancellationToken).AnyContext(); + } + + /// + /// Asynchronously finds all IDs of entities that match a given specification. + /// + /// The type of the entity. + /// The type of the entity's ID. + /// The source repository to query. + /// The specification to filter entities. + /// Optional parameters to customize the query. + /// A token to cancel the operation. + /// A task that represents the asynchronous operation. + /// The task result contains a result object with a collection of entity IDs. + public static async Task>> FindAllIdsResultAsync( + this IGenericReadOnlyRepository source, + ISpecification specification, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + return Result>.Success( + await source.FindAllIdsAsync(specification, options, cancellationToken).AnyContext()); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result>.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously finds all identifiers matching the given specifications and returns the result. + /// + /// The type of the entity. + /// The type of the identifier. + /// The source repository from which to retrieve the entities. + /// A collection of specifications to filter the entities. + /// Optional find options to customize the query. + /// A token to cancel the asynchronous operation. + /// A task that represents the asynchronous operation. The task result contains a object with the resulting identifiers. + public static async Task>> FindAllIdsResultAsync( + this IGenericReadOnlyRepository source, + IEnumerable> specifications, + IFindOptions options = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + return Result>.Success( + await source.FindAllIdsAsync(specifications, options, cancellationToken).AnyContext()); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result>.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Finds and returns a paged result of all entities from the repository. + /// + /// The type of the entity. + /// The source repository. + /// The ordering criteria. + /// The page number to retrieve. + /// The number of items per page. + /// The expression for including related entities. + /// The path for including related entities. + /// A token to notify if the operation should be canceled. + /// A task that represents the asynchronous operation. The task result contains the paged result of entities. + public static async Task> FindAllPagedResultAsync( + this IGenericReadOnlyRepository source, + string ordering, // of the form > fieldname [ascending|descending], ... + int page = 1, + int pageSize = 10, + Expression> includeExpression = null, + string includePath = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var count = await source.CountAsync(cancellationToken).AnyContext(); + var entities = await source.FindAllAsync(new FindOptions + { + Order = !ordering.IsNullOrEmpty() ? new OrderOption(ordering) : null, + Skip = (page - 1) * pageSize, + Take = pageSize, + Include = includeExpression is not null ? new IncludeOption(includeExpression) : + !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null + }, + cancellationToken) + .AnyContext(); + + return PagedResult.Success(entities, count, page, pageSize); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return PagedResult.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously retrieves a paginated collection of entities from the repository. + /// + /// The type of entity. + /// The source repository from which the entities are retrieved. + /// Expression used to order the entities. + /// The page number to retrieve (defaults to 1). + /// The number of entities per page (defaults to 10). + /// The direction to order the entities (ascending or descending). + /// Optional expression to include related entities. + /// Optional include path to related entities. + /// Optional token to cancel the async operation. + /// A task that represents the asynchronous operation. The task result contains a which includes the entities and pagination info. + public static async Task> FindAllPagedResultAsync( + this IGenericReadOnlyRepository source, + Expression> orderingExpression, + int page = 1, + int pageSize = 10, + OrderDirection orderDirection = OrderDirection.Ascending, + Expression> includeExpression = null, + string includePath = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var count = await source.CountAsync(cancellationToken).AnyContext(); + var entities = await source.FindAllAsync(new FindOptions + { + Order = new OrderOption(orderingExpression, orderDirection), + Skip = (page - 1) * pageSize, + Take = pageSize, + Include = includeExpression is not null ? new IncludeOption(includeExpression) : + !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null + }, + cancellationToken) + .AnyContext(); + + return PagedResult.Success(entities, count, page, pageSize); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return PagedResult.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously retrieves a paginated result set of entities based on a specified condition. + /// + /// The type of the entity. + /// The generic read-only repository source. + /// The expression to filter the entities. + /// The ordering criteria as a string. + /// The page number to retrieve. Defaults to 1. + /// The number of items per page. Defaults to 10. + /// The expression to include related entities. + /// The path to include related entities. + /// The cancellation token to cancel the operation. + /// A task that represents the asynchronous operation. The task result contains a paged result set of entities. + public static async Task> FindAllPagedResultAsync( + this IGenericReadOnlyRepository source, + Expression> expression, + string ordering, // of the form > fieldname [ascending|descending], ... + int page = 1, + int pageSize = 10, + Expression> includeExpression = null, + string includePath = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + return await source.FindAllPagedResultAsync( + new Specification(expression), + ordering, + page, + pageSize, + includeExpression, + includePath, + cancellationToken).AnyContext(); + } + + /// + /// Retrieves a paginated result set of entities that match the provided specification. + /// + /// The type of the entity. + /// The repository to query against. + /// The specification to filter the entities. + /// The ordering criteria as a string. + /// The page number to retrieve. Default is 1. + /// The number of entities per page. Default is 10. + /// Optional expression specifying related entities to include. + /// Optional path string specifying related entities to include. + /// Optional cancellation token for the async operation. + /// A task that represents the asynchronous operation. The task result contains the paginated result set of entities. + public static async Task> FindAllPagedResultAsync( + this IGenericReadOnlyRepository source, + ISpecification specification, + string ordering, // of the form > fieldname [ascending|descending], ... + int page = 1, + int pageSize = 10, + Expression> includeExpression = null, + string includePath = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var count = await source.CountAsync(specification, cancellationToken).AnyContext(); + var entities = await source.FindAllAsync(specification, + new FindOptions + { + Order = !ordering.IsNullOrEmpty() ? new OrderOption(ordering) : null, + Skip = (page - 1) * pageSize, + Take = pageSize, + Include = includeExpression is not null ? new IncludeOption(includeExpression) : + !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null + }, + cancellationToken) + .AnyContext(); + + return PagedResult.Success(entities, count, page, pageSize); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return PagedResult.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Retrieves a paged result of entities based on the provided criteria. + /// + /// The type of the entity. + /// The repository source to query. + /// The filter expression to apply to the query. + /// The expression to order the query results. + /// The page number to retrieve. Defaults to 1. + /// The number of items per page. Defaults to 10. + /// The direction of ordering (ascending or descending). Defaults to ascending. + /// An optional expression to include related entities. + /// An optional path to include related entities. + /// The cancellation token to cancel the operations. + /// A task that represents the asynchronous operation. The task result contains a paged result of entities. + public static async Task> FindAllPagedResultAsync( + this IGenericReadOnlyRepository source, + Expression> expression, + Expression> orderingExpression, + int page = 1, + int pageSize = 10, + OrderDirection orderDirection = OrderDirection.Ascending, + Expression> includeExpression = null, + string includePath = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + return await source.FindAllPagedResultAsync( + new Specification(expression), + orderingExpression, + page, + pageSize, + orderDirection, + includeExpression, + includePath, + cancellationToken).AnyContext(); + } + + public static async Task> FindAllPagedResultAsync( + this IGenericReadOnlyRepository source, + FilterModel filterModel, + IEnumerable> specifications = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + filterModel ??= new FilterModel(); + specifications = SpecificationBuilder.Build(filterModel.Filters, specifications).ToArray(); + var orders = OrderOptionBuilder.Build(filterModel.Orderings).ToArray(); + var includes = IncludeOptionBuilder.Build(filterModel.Includes).ToArray(); + + var count = await source.CountAsync(specifications, cancellationToken).AnyContext(); + var entities = await source.FindAllAsync( + specifications, + new FindOptions + { + Orders = orders, + Skip = (filterModel.Page - 1) * filterModel.PageSize, + Take = filterModel.PageSize, + Includes = includes + }, + cancellationToken) + .AnyContext(); + + return PagedResult.Success(entities, count, filterModel.Page, filterModel.PageSize); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return PagedResult.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Asynchronously retrieves a paged result of entities from the repository based on the provided specifications. + /// + /// The type of the entity. + /// The repository instance. + /// The specification to filter entities. + /// The expression used for ordering the results. + /// The page number to retrieve (default is 1). + /// The number of items per page (default is 10). + /// The direction to order the results (default is ascending). + /// The expression to include related entities (default is null). + /// The path to include related entities (default is null). + /// Cancellation token (default is none). + /// A task representing the asynchronous operation, with a result of the paged entities. + public static async Task> FindAllPagedResultAsync( + this IGenericReadOnlyRepository source, + ISpecification specification, + Expression> orderingExpression, + int page = 1, + int pageSize = 10, + OrderDirection orderDirection = OrderDirection.Ascending, + Expression> includeExpression = null, + string includePath = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var count = await source.CountAsync(specification, cancellationToken).AnyContext(); + var entities = await source.FindAllAsync(specification, + new FindOptions + { + Order = new OrderOption(orderingExpression, orderDirection), + Skip = (page - 1) * pageSize, + Take = pageSize, + Include = includeExpression is not null ? new IncludeOption(includeExpression) : + !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null + }, + cancellationToken) + .AnyContext(); + + return PagedResult.Success(entities, count, page, pageSize); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return PagedResult.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Retrieves a paged result set of entities that satisfy the given specifications, + /// with optional ordering, paging, and inclusion of related data. + /// + /// The type of the entity. + /// The repository to retrieve data from. + /// The collection of specifications entities must satisfy. + /// The ordering clause for sorting the results. + /// The page number to retrieve. Default is 1. + /// The number of items per page. Default is 10. + /// An optional expression to include related data. + /// An optional path to include related data. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the paged result set of entities. + public static async Task> FindAllPagedResultAsync( + this IGenericReadOnlyRepository source, + IEnumerable> specifications, // filters + string ordering, // of the form > fieldname [ascending|descending], ... + int page = 1, + int pageSize = 10, + Expression> includeExpression = null, + string includePath = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var count = await source.CountAsync(specifications, cancellationToken).AnyContext(); + var entities = await source.FindAllAsync( + specifications, + new FindOptions + { + Order = !ordering.IsNullOrEmpty() ? new OrderOption(ordering) : null, + Skip = (page - 1) * pageSize, + Take = pageSize, + Include = includeExpression is not null ? new IncludeOption(includeExpression) : + !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null + }, + cancellationToken) + .AnyContext(); + + return PagedResult.Success(entities, count, page, pageSize); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return PagedResult.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Retrieves a paged result of entities from the repository based on specified criteria and ordering. + /// + /// The type of entity. + /// The source repository. + /// The specifications to apply to filter the entities. + /// The expression to order the entities. + /// The page number to retrieve, starting from 1. + /// The number of entities per page. + /// The direction of the order (Ascending or Descending). + /// An optional expression to include related entities. + /// An optional path string to include related entities. + /// A token to cancel the asynchronous operation. + /// A task that represents the asynchronous operation. The task result contains the paged result of entities. + public static async Task> FindAllPagedResultAsync( + this IGenericReadOnlyRepository source, + IEnumerable> specifications, + Expression> orderingExpression, + int page = 1, + int pageSize = 10, + OrderDirection orderDirection = OrderDirection.Ascending, + Expression> includeExpression = null, + string includePath = null, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var count = await source.CountAsync(specifications, cancellationToken).AnyContext(); + var entities = await source.FindAllAsync(specifications, + new FindOptions + { + Order = new OrderOption(orderingExpression, orderDirection), + Skip = (page - 1) * pageSize, + Take = pageSize, + Include = includeExpression is not null ? new IncludeOption(includeExpression) : + !includePath.IsNullOrEmpty() ? new IncludeOption(includePath) : null + }, + cancellationToken) + .AnyContext(); + + return PagedResult.Success(entities, count, page, pageSize); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return PagedResult.Failure(ex.Message, new ExceptionError(ex)); + } + } +} \ No newline at end of file diff --git a/src/Domain/Repositories/Result/GenericRepositoryResultExtensions.cs b/src/Domain/Repositories/Result/GenericRepositoryResultExtensions.cs new file mode 100644 index 00000000..c041141b --- /dev/null +++ b/src/Domain/Repositories/Result/GenericRepositoryResultExtensions.cs @@ -0,0 +1,142 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.Repositories; + +using System; +using System.Threading; +using System.Threading.Tasks; +using BridgingIT.DevKit.Common; + +/// +/// Provides extension methods for performing result-based operations on repositories. +/// +public static class GenericRepositoryResultExtensions +{ + /// + /// Inserts an entity and returns the result as a Result object. + /// + /// The type of the entity. + /// The source repository. + /// The entity to insert. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the inserted entity. + public static async Task> InsertResultAsync( + this IGenericRepository source, + TEntity entity, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var insertedEntity = await source.InsertAsync(entity, cancellationToken).AnyContext(); + return Result.Success(insertedEntity); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Updates an entity and returns the result as a Result object. + /// + /// The type of the entity. + /// The source repository. + /// The entity to update. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the updated entity. + public static async Task> UpdateResultAsync( + this IGenericRepository source, + TEntity entity, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var updatedEntity = await source.UpdateAsync(entity, cancellationToken).AnyContext(); + return Result.Success(updatedEntity); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Upserts an entity and returns the result as a Result object. + /// + /// The type of the entity. + /// The source repository. + /// The entity to upsert. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the upserted entity and the action result. + public static async Task> UpsertResultAsync( + this IGenericRepository source, + TEntity entity, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var (upsertedEntity, action) = await source.UpsertAsync(entity, cancellationToken).AnyContext(); + return Result<(TEntity, RepositoryActionResult)>.Success((upsertedEntity, action)); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result<(TEntity, RepositoryActionResult)>.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Deletes an entity by its id and returns the result as a Result object. + /// + /// The type of the entity. + /// The source repository. + /// The id of the entity to delete. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the deletion action result. + public static async Task> DeleteByIdResultAsync( + this IGenericRepository source, + object id, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var result = await source.DeleteAsync(id, cancellationToken).AnyContext(); + return Result.Success(result); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } + + /// + /// Deletes an entity and returns the result as a Result object. + /// + /// The type of the entity. + /// The source repository. + /// The entity to delete. + /// A token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a Result object with the deletion action result. + public static async Task> DeleteResultAsync( + this IGenericRepository source, + TEntity entity, + CancellationToken cancellationToken = default) + where TEntity : class, IEntity + { + try + { + var result = await source.DeleteAsync(entity, cancellationToken).AnyContext(); + return Result.Success(result); + } + catch (Exception ex) when (!ex.IsTransientException()) + { + return Result.Failure(ex.Message, new ExceptionError(ex)); + } + } +} \ No newline at end of file diff --git a/src/Domain/Specifications/Specification.cs b/src/Domain/Specifications/Specification.cs index b5bf8557..ae56f9ac 100644 --- a/src/Domain/Specifications/Specification.cs +++ b/src/Domain/Specifications/Specification.cs @@ -5,27 +5,92 @@ namespace BridgingIT.DevKit.Domain; +using System.Linq.Dynamic.Core; + +/// +/// Represents a specification pattern used to encapsulate domain-specific criteria +/// that can be combined and evaluated against entities of type T. +/// +/// The type of the entity that this specification is applied to. public class Specification : ISpecification { private readonly Expression> expression; + private readonly string dynamicExpression; + + private readonly object[] dynamicExpressionValues; + + /// + /// Represents a specification pattern used to define a query criteria for entities of type . + /// + /// The type of entity to which the specification is applied. public Specification(Expression> expression) { this.expression = expression; } + /// + /// Represents a specification pattern that is used to check if an entity satisfies certain criteria. + /// + /// The type of entity that this specification will be applied to. + public Specification(string dynamicExpression, params object[] dynamicExpressionValues) + { + this.dynamicExpression = dynamicExpression; + this.dynamicExpressionValues = dynamicExpressionValues; + } + + /// + /// Represents a specification pattern that encapsulates an expression used to filter objects of type . + /// + /// The type of entity this specification applies to. protected Specification() { } + /// + /// Converts the specification into an expression that evaluates to a boolean value. + /// + /// An expression that represents the specification. public virtual Expression> ToExpression() { - return this.expression; + if (this.expression != null) + { + return this.expression; + } + + if (this.dynamicExpression != null) + { + return DynamicExpressionParser.ParseLambda(null, false, this.dynamicExpression, this.dynamicExpressionValues); + } + + return default; } + /// + /// Converts the specification's expression into a predicate function that can be used to + /// evaluate if an entity meets the specification criteria. + /// + /// A function that takes an entity of the specified type and returns a boolean indicating + /// whether the entity satisfies the specification. public Func ToPredicate() { return this.ToExpression()?.Compile(); } + /// + /// Converts the specification to a string representation. + /// + /// + /// A string representation of the specification's expression. + /// + public override string ToString() + { + return this.ToExpression()?.ToString(); + } + + /// + /// Determines whether the specified entity satisfies the specification. + /// + /// The entity to evaluate. + /// true if the entity satisfies the specification; otherwise, false. public bool IsSatisfiedBy(T entity) { if (entity is null) @@ -35,9 +100,21 @@ public bool IsSatisfiedBy(T entity) var predicate = this.ToPredicate(); - return predicate(entity); + try + { + return predicate(entity); + } + catch (NullReferenceException) + { + return false; + } } + /// + /// Combines the current specification with another specification using a logical AND operation. + /// + /// The specification to combine with the current specification. + /// A new specification that represents the combined condition of the current specification and the provided specification. public ISpecification And(ISpecification specification) { //if (this == All) @@ -53,6 +130,11 @@ public ISpecification And(ISpecification specification) return new AndSpecification(this, specification); } + /// + /// Creates a new specification that is the logical OR of the current specification and the given specification. + /// + /// The specification to combine with the current specification using a logical OR. + /// A new specification that is the logical OR of the current and given specifications. public ISpecification Or(ISpecification specification) { //if (this == All || specification == All) @@ -63,6 +145,10 @@ public ISpecification Or(ISpecification specification) return new OrSpecification(this, specification); } + /// + /// Creates a specification that negates the current specification. + /// + /// An representing the negation of the current specification. public ISpecification Not() { return new NotSpecification(this); diff --git a/src/Domain/packages.lock.json b/src/Domain/packages.lock.json index 44461b2e..f6ff3db2 100644 --- a/src/Domain/packages.lock.json +++ b/src/Domain/packages.lock.json @@ -36,10 +36,16 @@ "Microsoft.Extensions.DependencyModel": "6.0.0" } }, + "System.Linq.Dynamic.Core": { + "type": "Direct", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -113,8 +119,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -152,11 +159,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, diff --git a/src/Infrastructure.AutoMapper/packages.lock.json b/src/Infrastructure.AutoMapper/packages.lock.json index c3f33769..474a914a 100644 --- a/src/Infrastructure.AutoMapper/packages.lock.json +++ b/src/Infrastructure.AutoMapper/packages.lock.json @@ -30,8 +30,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -126,8 +126,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -161,7 +162,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Infrastructure.Mapping": { @@ -221,11 +223,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -320,6 +322,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Infrastructure.Azure.Cosmos/packages.lock.json b/src/Infrastructure.Azure.Cosmos/packages.lock.json index 60c9ec40..d04f9fda 100644 --- a/src/Infrastructure.Azure.Cosmos/packages.lock.json +++ b/src/Infrastructure.Azure.Cosmos/packages.lock.json @@ -61,8 +61,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -751,8 +751,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -786,7 +787,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "FluentValidation": { @@ -803,11 +805,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -933,6 +935,12 @@ "Microsoft.Win32.SystemEvents": "6.0.0" } }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Net.Http": { "type": "CentralTransitive", "requested": "[4.3.4, )", diff --git a/src/Infrastructure.Azure.HealthChecks/packages.lock.json b/src/Infrastructure.Azure.HealthChecks/packages.lock.json index ac375ec6..668c9a07 100644 --- a/src/Infrastructure.Azure.HealthChecks/packages.lock.json +++ b/src/Infrastructure.Azure.HealthChecks/packages.lock.json @@ -71,8 +71,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -791,8 +791,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -826,7 +827,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Infrastructure.Azure.Cosmos": { @@ -903,11 +905,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1087,6 +1089,12 @@ "Microsoft.Win32.SystemEvents": "6.0.0" } }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Net.Http": { "type": "CentralTransitive", "requested": "[4.3.4, )", diff --git a/src/Infrastructure.Azure.ServiceBus/packages.lock.json b/src/Infrastructure.Azure.ServiceBus/packages.lock.json index 35e9cf42..1ba281a4 100644 --- a/src/Infrastructure.Azure.ServiceBus/packages.lock.json +++ b/src/Infrastructure.Azure.ServiceBus/packages.lock.json @@ -68,8 +68,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Azure.Amqp": { "type": "Transitive", @@ -408,7 +408,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -422,8 +422,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -461,11 +462,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -625,9 +626,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.Azure.Storage/packages.lock.json b/src/Infrastructure.Azure.Storage/packages.lock.json index 96b71dad..2ed24f03 100644 --- a/src/Infrastructure.Azure.Storage/packages.lock.json +++ b/src/Infrastructure.Azure.Storage/packages.lock.json @@ -60,8 +60,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -200,8 +200,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -239,11 +240,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, diff --git a/src/Infrastructure.EntityFramework.Cosmos/packages.lock.json b/src/Infrastructure.EntityFramework.Cosmos/packages.lock.json index bef0274d..a62ae2c8 100644 --- a/src/Infrastructure.EntityFramework.Cosmos/packages.lock.json +++ b/src/Infrastructure.EntityFramework.Cosmos/packages.lock.json @@ -51,8 +51,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -995,7 +995,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1009,8 +1009,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1044,7 +1045,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1126,11 +1128,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1415,9 +1417,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.EntityFramework.EventSourcing.SqlServer/packages.lock.json b/src/Infrastructure.EntityFramework.EventSourcing.SqlServer/packages.lock.json index 0af5f0d8..69eddd99 100644 --- a/src/Infrastructure.EntityFramework.EventSourcing.SqlServer/packages.lock.json +++ b/src/Infrastructure.EntityFramework.EventSourcing.SqlServer/packages.lock.json @@ -36,8 +36,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -502,7 +502,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -516,8 +516,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -551,7 +552,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -758,11 +760,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1034,9 +1036,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.EntityFramework.EventSourcing/packages.lock.json b/src/Infrastructure.EntityFramework.EventSourcing/packages.lock.json index 42b8601f..9c9aa65a 100644 --- a/src/Infrastructure.EntityFramework.EventSourcing/packages.lock.json +++ b/src/Infrastructure.EntityFramework.EventSourcing/packages.lock.json @@ -45,8 +45,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -624,7 +624,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -638,8 +638,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -673,7 +674,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -849,11 +851,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1128,9 +1130,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.EntityFramework.Outbox.AutoMapper/packages.lock.json b/src/Infrastructure.EntityFramework.Outbox.AutoMapper/packages.lock.json index 211772f0..6616d315 100644 --- a/src/Infrastructure.EntityFramework.Outbox.AutoMapper/packages.lock.json +++ b/src/Infrastructure.EntityFramework.Outbox.AutoMapper/packages.lock.json @@ -36,8 +36,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -494,7 +494,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -508,8 +508,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -543,7 +544,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -690,11 +692,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -938,9 +940,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.EntityFramework.Outbox/packages.lock.json b/src/Infrastructure.EntityFramework.Outbox/packages.lock.json index 7b0a4789..23d96884 100644 --- a/src/Infrastructure.EntityFramework.Outbox/packages.lock.json +++ b/src/Infrastructure.EntityFramework.Outbox/packages.lock.json @@ -31,8 +31,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -477,7 +477,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -491,8 +491,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -526,7 +527,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -608,11 +610,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -856,9 +858,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.EntityFramework.SqlServer/packages.lock.json b/src/Infrastructure.EntityFramework.SqlServer/packages.lock.json index d726e5b9..309a873a 100644 --- a/src/Infrastructure.EntityFramework.SqlServer/packages.lock.json +++ b/src/Infrastructure.EntityFramework.SqlServer/packages.lock.json @@ -67,8 +67,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -489,7 +489,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -503,8 +503,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -538,7 +539,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -620,11 +622,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -870,9 +872,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.EntityFramework.Sqlite/packages.lock.json b/src/Infrastructure.EntityFramework.Sqlite/packages.lock.json index ed64ab73..37b9f904 100644 --- a/src/Infrastructure.EntityFramework.Sqlite/packages.lock.json +++ b/src/Infrastructure.EntityFramework.Sqlite/packages.lock.json @@ -73,8 +73,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -624,7 +624,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -638,8 +638,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -673,7 +674,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -755,11 +757,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1021,9 +1023,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.ComponentModel.Annotations": { "type": "CentralTransitive", diff --git a/src/Infrastructure.EntityFramework/packages.lock.json b/src/Infrastructure.EntityFramework/packages.lock.json index 750c9b48..b494827e 100644 --- a/src/Infrastructure.EntityFramework/packages.lock.json +++ b/src/Infrastructure.EntityFramework/packages.lock.json @@ -84,8 +84,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -506,7 +506,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -520,8 +520,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -555,7 +556,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -620,11 +622,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -821,9 +823,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.EventSourcing/packages.lock.json b/src/Infrastructure.EventSourcing/packages.lock.json index a0f83e49..ebfc8600 100644 --- a/src/Infrastructure.EventSourcing/packages.lock.json +++ b/src/Infrastructure.EventSourcing/packages.lock.json @@ -36,8 +36,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -502,7 +502,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -516,8 +516,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -551,7 +552,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -715,11 +717,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -963,9 +965,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.LiteDB/packages.lock.json b/src/Infrastructure.LiteDB/packages.lock.json index 6b87c798..8bf2d35f 100644 --- a/src/Infrastructure.LiteDB/packages.lock.json +++ b/src/Infrastructure.LiteDB/packages.lock.json @@ -31,8 +31,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -106,8 +106,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -141,7 +142,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "FluentValidation": { @@ -164,11 +166,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, diff --git a/src/Infrastructure.Mapping/packages.lock.json b/src/Infrastructure.Mapping/packages.lock.json index 93da8afc..06bbf5a7 100644 --- a/src/Infrastructure.Mapping/packages.lock.json +++ b/src/Infrastructure.Mapping/packages.lock.json @@ -30,8 +30,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", @@ -126,8 +126,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -161,7 +162,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "AutoMapper": { @@ -212,11 +214,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -311,6 +313,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Infrastructure.Pulsar/packages.lock.json b/src/Infrastructure.Pulsar/packages.lock.json index ae960b73..513958db 100644 --- a/src/Infrastructure.Pulsar/packages.lock.json +++ b/src/Infrastructure.Pulsar/packages.lock.json @@ -37,8 +37,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -367,7 +367,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -381,8 +381,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -420,11 +421,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -584,9 +585,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Infrastructure.RabbitMQ/packages.lock.json b/src/Infrastructure.RabbitMQ/packages.lock.json index f715d16a..c55e9a1a 100644 --- a/src/Infrastructure.RabbitMQ/packages.lock.json +++ b/src/Infrastructure.RabbitMQ/packages.lock.json @@ -26,8 +26,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", @@ -311,7 +311,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -325,8 +325,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -364,11 +365,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -528,9 +529,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Presentation.Configuration/packages.lock.json b/src/Presentation.Configuration/packages.lock.json index 83620f72..51e83332 100644 --- a/src/Presentation.Configuration/packages.lock.json +++ b/src/Presentation.Configuration/packages.lock.json @@ -104,8 +104,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -466,7 +466,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -480,8 +480,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -519,11 +520,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -683,9 +684,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", diff --git a/src/Presentation.Serilog/packages.lock.json b/src/Presentation.Serilog/packages.lock.json index 812ad81f..392b8625 100644 --- a/src/Presentation.Serilog/packages.lock.json +++ b/src/Presentation.Serilog/packages.lock.json @@ -25,9 +25,9 @@ }, "Serilog": { "type": "Direct", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Expressions": { "type": "Direct", diff --git a/src/Presentation.Web.JobScheduling/packages.lock.json b/src/Presentation.Web.JobScheduling/packages.lock.json index e5e96ba0..9625441a 100644 --- a/src/Presentation.Web.JobScheduling/packages.lock.json +++ b/src/Presentation.Web.JobScheduling/packages.lock.json @@ -21,8 +21,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.CSharp": { "type": "Transitive", @@ -762,7 +762,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -776,8 +776,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -811,7 +812,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Presentation": { @@ -903,11 +905,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1130,9 +1132,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", @@ -1153,6 +1155,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/src/Presentation.Web/Filtering/FromBodyFilterAttribute.cs b/src/Presentation.Web/Filtering/FromBodyFilterAttribute.cs new file mode 100644 index 00000000..90563ec2 --- /dev/null +++ b/src/Presentation.Web/Filtering/FromBodyFilterAttribute.cs @@ -0,0 +1,11 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Presentation; + +using Microsoft.AspNetCore.Mvc; + +public class FromBodyFilterAttribute() + : ModelBinderAttribute(typeof(FromBodyFilterModelBinder)); \ No newline at end of file diff --git a/src/Presentation.Web/Filtering/FromBodyFilterModelBinder.cs b/src/Presentation.Web/Filtering/FromBodyFilterModelBinder.cs new file mode 100644 index 00000000..f5af3718 --- /dev/null +++ b/src/Presentation.Web/Filtering/FromBodyFilterModelBinder.cs @@ -0,0 +1,39 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Presentation; + +using System.Text.Json; +using BridgingIT.DevKit.Common; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +public class FromBodyFilterModelBinder : IModelBinder +{ + private static readonly ISerializer Serializer = new SystemTextJsonSerializer(); + + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + using var reader = new StreamReader(bindingContext.HttpContext.Request.Body); + var json = await reader.ReadToEndAsync(); + + if (string.IsNullOrEmpty(json)) + { + bindingContext.Result = ModelBindingResult.Failed(); + + return; + } + + try + { + var filterModel = Serializer.Deserialize(json); // options include necessary converteres + bindingContext.Result = ModelBindingResult.Success(filterModel); + } + catch (JsonException) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid JSON format for filter model."); + bindingContext.Result = ModelBindingResult.Failed(); + } + } +} \ No newline at end of file diff --git a/src/Presentation.Web/Filtering/FromQueryFilterAttribute.cs b/src/Presentation.Web/Filtering/FromQueryFilterAttribute.cs new file mode 100644 index 00000000..c256c078 --- /dev/null +++ b/src/Presentation.Web/Filtering/FromQueryFilterAttribute.cs @@ -0,0 +1,11 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Presentation; + +using Microsoft.AspNetCore.Mvc; + +public class FromQueryFilterAttribute() + : ModelBinderAttribute(typeof(FromQueryFilterModelBinder)); \ No newline at end of file diff --git a/src/Presentation.Web/Filtering/FromQueryFilterModelBinder.cs b/src/Presentation.Web/Filtering/FromQueryFilterModelBinder.cs new file mode 100644 index 00000000..56a3f74a --- /dev/null +++ b/src/Presentation.Web/Filtering/FromQueryFilterModelBinder.cs @@ -0,0 +1,42 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Presentation; + +using System.Text.Json; +using BridgingIT.DevKit.Common; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using YamlDotNet.Serialization; +using ISerializer = BridgingIT.DevKit.Common.ISerializer; + +public class FromQueryFilterModelBinder : IModelBinder +{ + private static readonly ISerializer Serializer = new SystemTextJsonSerializer(); + + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var json = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).FirstValue; + + if (string.IsNullOrEmpty(json)) + { + bindingContext.Result = ModelBindingResult.Failed(); + + return Task.CompletedTask; + } + + try + { + var filterModel = Serializer.Deserialize(json); // options include necessary converteres + bindingContext.Result = ModelBindingResult.Success(filterModel); + } + catch (JsonException) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid JSON format for filter model."); + bindingContext.Result = ModelBindingResult.Failed(); + } + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Presentation.Web/Filtering/HttpContextExtensions.cs b/src/Presentation.Web/Filtering/HttpContextExtensions.cs new file mode 100644 index 00000000..40604388 --- /dev/null +++ b/src/Presentation.Web/Filtering/HttpContextExtensions.cs @@ -0,0 +1,103 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Presentation; + +using System.Text.Json; +using BridgingIT.DevKit.Common; +using Microsoft.AspNetCore.Http; + +public static class HttpContextExtensions +{ + private static readonly ISerializer Serializer = new SystemTextJsonSerializer(); + + /// + /// usage: + /// app.MapGet("/data", (HttpContext context) => + /// { + /// var filterModel = context.FromQueryFilter(); + /// if (filterModel == null) + /// { + /// return Results.BadRequest("Invalid or missing filter."); + /// } + /// + /// return Results.Ok(filterModel); + /// }); + /// + /// + /// + /// + public static async Task FromQueryFilterAsync(this HttpContext context, string queryParameter = "filter") + { + ArgumentNullException.ThrowIfNull(context); + + // Default maximum query string length: 2,048 characters + var query = context.Request.Query[queryParameter].FirstOrDefault(); + + try + { + return string.IsNullOrEmpty(query) ? default : Serializer.Deserialize(query); + } + catch (JsonException) + { + context.Response.StatusCode = 400; + await context.Response.WriteAsync("Invalid JSON format for filter model."); + + return default; + } + } + + /// + /// usage: + /// app.MapPost("/data/body", async (HttpContext context) => + /// { + /// var filterModel = await context.FromBodyFilterAsync(); + /// if (filterModel == null) + /// { + /// return Results.BadRequest("Invalid or missing filter in the body."); + /// } + /// + /// return Results.Ok(filterModel); + /// }); + /// + /// + /// + /// + public static async Task FromBodyFilterAsync(this HttpContext context, bool enableBuffering = false) + { + ArgumentNullException.ThrowIfNull(context); + + try + { + if (enableBuffering) // Enable request body buffering to allow multiple reads + { + context.Request.EnableBuffering(); + } + + // Read request body as string + using var reader = new StreamReader(context.Request.Body, leaveOpen: enableBuffering); + var body = await reader.ReadToEndAsync(); + + if (enableBuffering) // Reset the request body stream position so it can be read again + { + context.Request.Body.Position = 0; + } + + if (string.IsNullOrEmpty(body)) + { + return default; + } + + return Serializer.Deserialize(body); + } + catch (JsonException) + { + context.Response.StatusCode = 400; + await context.Response.WriteAsync("Invalid JSON format for filter model."); + + return default; + } + } +} \ No newline at end of file diff --git a/src/Presentation.Web/Host/Configure.ProblemDetails.cs b/src/Presentation.Web/Host/Configure.ProblemDetails.cs index f57eac33..ead683a2 100644 --- a/src/Presentation.Web/Host/Configure.ProblemDetails.cs +++ b/src/Presentation.Web/Host/Configure.ProblemDetails.cs @@ -62,8 +62,7 @@ public static void ProblemDetails( { Title = "Bad Request", Status = StatusCodes.Status400BadRequest, - Detail = - $"[{nameof(ValidationException)}] A model validation error has occurred while executing the request", + Detail = $"[{nameof(ValidationException)}] A model validation error has occurred while executing the request", Type = "https://httpstatuses.com/400", Errors = ex.Errors?.OrderBy(v => v.PropertyName) .GroupBy(v => v.PropertyName.Replace("Entity.", string.Empty, StringComparison.OrdinalIgnoreCase), diff --git a/src/Presentation.Web/packages.lock.json b/src/Presentation.Web/packages.lock.json index fa9336e1..e3e6b7aa 100644 --- a/src/Presentation.Web/packages.lock.json +++ b/src/Presentation.Web/packages.lock.json @@ -58,8 +58,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.CSharp": { "type": "Transitive", @@ -777,7 +777,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -791,8 +791,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -826,7 +827,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Presentation": { @@ -894,11 +896,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1086,9 +1088,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "System.Diagnostics.DiagnosticSource": { "type": "CentralTransitive", @@ -1096,6 +1098,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Text.Json": { "type": "CentralTransitive", "requested": "[8.0.5, )", diff --git a/tests/Application.IntegrationTests/packages.lock.json b/tests/Application.IntegrationTests/packages.lock.json index 9242e20a..ef92cfd8 100644 --- a/tests/Application.IntegrationTests/packages.lock.json +++ b/tests/Application.IntegrationTests/packages.lock.json @@ -222,8 +222,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1694,7 +1694,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1708,8 +1708,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1741,7 +1742,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1758,7 +1759,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1888,11 +1890,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -2165,9 +2167,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", diff --git a/tests/Application.UnitTests/packages.lock.json b/tests/Application.UnitTests/packages.lock.json index 21b2b96f..cf74e07e 100644 --- a/tests/Application.UnitTests/packages.lock.json +++ b/tests/Application.UnitTests/packages.lock.json @@ -166,8 +166,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1563,7 +1563,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1577,8 +1577,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1610,7 +1611,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1627,7 +1628,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1697,11 +1699,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1923,9 +1925,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", @@ -1958,6 +1960,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Net.Http": { "type": "CentralTransitive", "requested": "[4.3.4, )", diff --git a/tests/Common.UnitTests/Extensions/DateTimeExtensionsTests.cs b/tests/Common.UnitTests/Extensions/DateTimeExtensionsTests.cs index 7c7729d0..ac41f186 100644 --- a/tests/Common.UnitTests/Extensions/DateTimeExtensionsTests.cs +++ b/tests/Common.UnitTests/Extensions/DateTimeExtensionsTests.cs @@ -8,6 +8,8 @@ namespace BridgingIT.DevKit.Common.UnitTests; [UnitTest("Common")] public class DateTimeExtensionsTests { + private readonly Faker faker = new(); + [Fact] public void StartOfDay_GivenDateTime_ReturnsStartOfDay() { @@ -216,4 +218,222 @@ public void EndOfYear_GivenDateTimeOffset_ReturnsEndOfYear() // Assert endOfYear.ShouldBe(new DateTimeOffset(2022, 12, 31, 23, 59, 59, TimeSpan.FromHours(2))); } + + [Fact] + public void ParseDateOrEpoch_NullInput_ReturnsNull() + { + // Arrange + string input = null; + + // Act + var result = input.ParseDateOrEpoch(); + + // Assert + result.ShouldBeNull(); + } + + [Fact] + public void ParseDateOrEpoch_EmptyInput_ReturnsNull() + { + // Arrange + var input = string.Empty; + + // Act + var result = input.ParseDateOrEpoch(); + + // Assert + result.ShouldBeNull(); + } + + [Fact] + public void ParseDateOrEpoch_ValidEpoch_ReturnsCorrectDateTime() + { + // Arrange + var epochSeconds = this.faker.Date.Past().ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalSeconds; + var input = ((long)epochSeconds).ToString(); + + // Act + var result = input.ParseDateOrEpoch(); + + // Assert + result.ShouldNotBeNull(); + result.Value.ShouldBe(DateTimeOffset.FromUnixTimeSeconds((long)epochSeconds).UtcDateTime); + } + + [Fact] + public void ParseDateOrEpoch_ValidIso8601_ReturnsCorrectDateTime() + { + // Arrange + var dateTime = this.faker.Date.Past().ToUniversalTime(); + var input = dateTime.ToString("yyyy-MM-ddTHH:mm:ss"); + + // Act + var result = input.ParseDateOrEpoch(); + + // Assert + result.ShouldNotBeNull(); + result.Value.ShouldBe(dateTime.Date + dateTime.TimeOfDay.TruncateToSeconds()); + } + + [Fact] + public void ParseDateOrEpoch_ValidIso8601WithMilliseconds_NotIgnoresMilliseconds() + { + // Arrange + var dateTime = this.faker.Date.Past().ToUniversalTime(); + var input = dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffffff"); + + // Act + var result = input.ParseDateOrEpoch(); + + // Assert + result.ShouldNotBeNull(); + result.Value.ShouldBe(dateTime.Date + dateTime.TimeOfDay); + } + + [Fact] + public void ParseDateOrEpoch_InvalidFormat_ThrowsArgumentException() + { + // Arrange + var input = this.faker.Lorem.Word(); + + // Act & Assert + Should.Throw(() => input.ParseDateOrEpoch()) + .Message.ShouldContain($"Invalid date format: {input}"); + } + + [Fact] + public void ParseDateOrEpoch_EpochBeforeUnixEpoch_ReturnsCorrectDateTime() + { + // Arrange + var input = "-86400"; // One day before Unix epoch + + // Act + var result = input.ParseDateOrEpoch(); + + // Assert + result.ShouldNotBeNull(); + result.Value.ShouldBe(new DateTime(1969, 12, 31, 0, 0, 0, DateTimeKind.Utc)); + } + + [Fact] + public void ParseDateOrEpoch_LargeEpochValue_ReturnsCorrectDateTime() + { + // Arrange + var input = "253402300799"; // 9999-12-31T23:59:59Z + + // Act + var result = input.ParseDateOrEpoch(); + + // Assert + result.ShouldNotBeNull(); + result.Value.ShouldBe(new DateTime(9999, 12, 31, 23, 59, 59, DateTimeKind.Utc)); + } + + [Fact] + public void TryParseDateOrEpoch_NullInput_ReturnsFalse() + { + // Arrange + string source = null; + + // Act + var success = source.TryParseDateOrEpoch(out var result); + + // Assert + success.ShouldBeFalse(); + result.ShouldBe(DateTime.MinValue); + } + + [Fact] + public void TryParseDateOrEpoch_EmptyString_ReturnsFalse() + { + // Arrange + var source = string.Empty; + + // Act + var success = source.TryParseDateOrEpoch(out var result); + + // Assert + success.ShouldBeFalse(); + result.ShouldBe(DateTime.MinValue); + } + + [Fact] + public void TryParseDateOrEpoch_WhiteSpace_ReturnsFalse() + { + // Arrange + var source = " "; + + // Act + var success = source.TryParseDateOrEpoch(out var result); + + // Assert + success.ShouldBeFalse(); + result.ShouldBe(DateTime.MinValue); + } + + [Fact] + public void TryParseDateOrEpoch_ValidUnixTimestamp_ReturnsTrue() + { + // Arrange + var expectedDate = this.faker.Date.Past(); + var unixTimestamp = ((DateTimeOffset)expectedDate).ToUnixTimeSeconds(); + var source = unixTimestamp.ToString(); + + // Act + var success = source.TryParseDateOrEpoch(out var result); + + // Assert + success.ShouldBeTrue(); + result.ShouldBe(expectedDate.ToUniversalTime(), TimeSpan.FromSeconds(1)); + } + + [Theory] + [InlineData("99999")] // Too small + public void TryParseDateOrEpoch_InvalidUnixTimestamp_ReturnsFalse(string source) + { + // Act + var success = source.TryParseDateOrEpoch(out var result); + + // Assert + success.ShouldBeFalse(); + result.ShouldBe(DateTime.MinValue); + } + + [Theory] + [InlineData("2024-03-14")] // ISO 8601 + [InlineData("2024-03-14T13:45:30")] // ISO 8601 with time + [InlineData("2024-03-14T13:45:30Z")] // ISO 8601 with UTC + [InlineData("2024-03-14T13:45:30.1234567")] // ISO 8601 with milliseconds + [InlineData("14/03/2024")] // UK format + [InlineData("03/14/2024")] // US format + [InlineData("14-03-2024")] // Alternative format + [InlineData("14.03.2024")] // European format + [InlineData("20240314")] // Compact format + [InlineData("14 Mar 2024")] // Month name format + [InlineData("14 March 2024")] // Full month name format + public void TryParseDateOrEpoch_ValidDateFormats_ReturnsTrue(string source) + { + // Act + var success = source.TryParseDateOrEpoch(out var result); + + // Assert + success.ShouldBeTrue(); + result.ShouldNotBe(DateTime.MinValue); + } + + [Theory] + [InlineData("not-a-date")] + [InlineData("2024-13-14")] // Invalid month + [InlineData("2024-03-32")] // Invalid day + [InlineData("14/13/2024")] // Invalid month + [InlineData("32/03/2024")] // Invalid day + public void TryParseDateOrEpoch_InvalidDateFormats_ReturnsFalse(string source) + { + // Act + var success = source.TryParseDateOrEpoch(out var result); + + // Assert + success.ShouldBeFalse(); + result.ShouldBe(DateTime.MinValue); + } } \ No newline at end of file diff --git a/tests/Common.UnitTests/Extensions/TimeSpanExtensionsTests.cs b/tests/Common.UnitTests/Extensions/TimeSpanExtensionsTests.cs index f2c5e54d..d4f22d10 100644 --- a/tests/Common.UnitTests/Extensions/TimeSpanExtensionsTests.cs +++ b/tests/Common.UnitTests/Extensions/TimeSpanExtensionsTests.cs @@ -5,87 +5,454 @@ namespace BridgingIT.DevKit.Common.UnitTests; +using System; +using Bogus; +using Shouldly; +using Xunit; + [UnitTest("Common")] public class TimeSpanExtensionsTests { + private readonly Faker faker = new(); + [Fact] - public void Short_tests() + public void ToCancellationTokenSource_ZeroTimeSpan_ReturnsCancelledToken() { - const short Value = 2; + // Arrange + var timeSpan = TimeSpan.Zero; - var ticks = Value.Ticks(); - ticks.Ticks.ShouldBe(Value); + // Act + var result = timeSpan.ToCancellationTokenSource(); - var milliSeconds = Value.Milliseconds(); - milliSeconds.TotalMilliseconds.ShouldBe(Value); + // Assert + result.IsCancellationRequested.ShouldBeTrue(); + } + + [Fact] + public void ToCancellationTokenSource_PositiveTimeSpan_ReturnsUncancelledToken() + { + // Arrange + var timeSpan = TimeSpan.FromSeconds(this.faker.Random.Int(1, 10)); + + // Act + var result = timeSpan.ToCancellationTokenSource(); + + // Assert + result.IsCancellationRequested.ShouldBeFalse(); + } + + [Fact] + public void ToCancellationTokenSource_NegativeTimeSpan_ReturnsUncancelledToken() + { + // Arrange + var timeSpan = TimeSpan.FromSeconds(-this.faker.Random.Int(1, 10)); + + // Act + var result = timeSpan.ToCancellationTokenSource(); + + // Assert + result.IsCancellationRequested.ShouldBeFalse(); + } + + [Fact] + public void ToCancellationTokenSource_NullableTimeSpanWithValue_ReturnsExpectedToken() + { + // Arrange + TimeSpan? timeSpan = TimeSpan.FromSeconds(this.faker.Random.Int(1, 10)); - var seconds = Value.Seconds(); - seconds.TotalSeconds.ShouldBe(Value); + // Act + var result = timeSpan.ToCancellationTokenSource(); - var minutes = Value.Minutes(); - minutes.TotalMinutes.ShouldBe(Value); + // Assert + result.IsCancellationRequested.ShouldBeFalse(); + } - var hours = Value.Hours(); - hours.TotalHours.ShouldBe(Value); + [Fact] + public void ToCancellationTokenSource_NullTimeSpan_ReturnsUncancelledToken() + { + // Arrange + TimeSpan? timeSpan = null; - var days = Value.Days(); - days.TotalDays.ShouldBe(Value); + // Act + var result = timeSpan.ToCancellationTokenSource(); - var weeks = Value.Weeks(); - weeks.TotalDays.ShouldBe(Value * 7); + // Assert + result.IsCancellationRequested.ShouldBeFalse(); } [Fact] - public void Int_tests() + public void ToCancellationTokenSource_NullTimeSpanWithDefault_UsesDefaultTimeout() { - const int Value = 2; + // Arrange + TimeSpan? timeSpan = null; + var defaultTimeout = TimeSpan.Zero; - var ticks = Value.Ticks(); - ticks.Ticks.ShouldBe(Value); + // Act + var result = timeSpan.ToCancellationTokenSource(defaultTimeout); - var milliSeconds = Value.Milliseconds(); - milliSeconds.TotalMilliseconds.ShouldBe(Value); + // Assert + result.IsCancellationRequested.ShouldBeTrue(); + } - var seconds = Value.Seconds(); - seconds.TotalSeconds.ShouldBe(Value); + [Fact] + public void Min_FirstSmallerThanSecond_ReturnsFirst() + { + // Arrange + var first = TimeSpan.FromSeconds(1); + var second = TimeSpan.FromSeconds(2); - var minutes = Value.Minutes(); - minutes.TotalMinutes.ShouldBe(Value); + // Act + var result = first.Min(second); - var hours = Value.Hours(); - hours.TotalHours.ShouldBe(Value); + // Assert + result.ShouldBe(first); + } - var days = Value.Days(); - days.TotalDays.ShouldBe(Value); + [Fact] + public void Min_SecondSmallerThanFirst_ReturnsSecond() + { + // Arrange + var first = TimeSpan.FromSeconds(2); + var second = TimeSpan.FromSeconds(1); + + // Act + var result = first.Min(second); - var weeks = Value.Weeks(); - weeks.TotalDays.ShouldBe(Value * 7); + // Assert + result.ShouldBe(second); } [Fact] - public void Long_tests() + public void Max_FirstLargerThanSecond_ReturnsFirst() { - const long Value = 2; + // Arrange + var first = TimeSpan.FromSeconds(2); + var second = TimeSpan.FromSeconds(1); - var ticks = Value.Ticks(); - ticks.Ticks.ShouldBe(Value); + // Act + var result = first.Max(second); + + // Assert + result.ShouldBe(first); + } - var milliSeconds = Value.Milliseconds(); - milliSeconds.TotalMilliseconds.ShouldBe(Value); + [Fact] + public void Max_SecondLargerThanFirst_ReturnsSecond() + { + // Arrange + var first = TimeSpan.FromSeconds(1); + var second = TimeSpan.FromSeconds(2); - var seconds = Value.Seconds(); - seconds.TotalSeconds.ShouldBe(Value); + // Act + var result = first.Max(second); - var minutes = Value.Minutes(); - minutes.TotalMinutes.ShouldBe(Value); + // Assert + result.ShouldBe(second); + } - var hours = Value.Hours(); - hours.TotalHours.ShouldBe(Value); + [Theory] + [InlineData(1)] + [InlineData(1000)] + [InlineData(-1)] + public void Ticks_ValidValue_ReturnsCorrectTimeSpan(long value) + { + // Act + var result = value.Ticks(); - var days = Value.Days(); - days.TotalDays.ShouldBe(Value); + // Assert + result.Ticks.ShouldBe(value); + } + + [Theory] + [InlineData(1)] + [InlineData(1000)] + [InlineData(-1)] + public void Milliseconds_ValidValue_ReturnsCorrectTimeSpan(long value) + { + // Act + var result = value.Milliseconds(); + + // Assert + result.TotalMilliseconds.ShouldBe(value); + } + + [Theory] + [InlineData(1)] + [InlineData(60)] + [InlineData(-1)] + public void Seconds_ValidValue_ReturnsCorrectTimeSpan(long value) + { + // Act + var result = value.Seconds(); + + // Assert + result.TotalSeconds.ShouldBe(value); + } + + [Theory] + [InlineData(1)] + [InlineData(60)] + [InlineData(-1)] + public void Minutes_ValidValue_ReturnsCorrectTimeSpan(long value) + { + // Act + var result = value.Minutes(); + + // Assert + result.TotalMinutes.ShouldBe(value); + } + + [Theory] + [InlineData(1)] + [InlineData(24)] + [InlineData(-1)] + public void Hours_ValidValue_ReturnsCorrectTimeSpan(long value) + { + // Act + var result = value.Hours(); + + // Assert + result.TotalHours.ShouldBe(value); + } + + [Theory] + [InlineData(1)] + [InlineData(7)] + [InlineData(-1)] + public void Days_ValidValue_ReturnsCorrectTimeSpan(long value) + { + // Act + var result = value.Days(); + + // Assert + result.TotalDays.ShouldBe(value); + } + + [Theory] + [InlineData(1)] + [InlineData(4)] + [InlineData(-1)] + public void Weeks_ValidValue_ReturnsCorrectTimeSpan(long value) + { + // Act + var result = value.Weeks(); + + // Assert + result.TotalDays.ShouldBe(value * 7); + } + + [Fact] + public void TruncateToSeconds_TimeSpanWithMilliseconds_RemovesMilliseconds() + { + // Arrange + var timeSpan = TimeSpan.FromMilliseconds(this.faker.Random.Double(1000, 2000)); + + // Act + var result = timeSpan.TruncateToSeconds(); + + // Assert + result.Milliseconds.ShouldBe(0); + result.Seconds.ShouldBe(timeSpan.Seconds); + } + + [Fact] + public void TruncateToSeconds_ComplexTimeSpan_PreservesLargerUnits() + { + // Arrange + var original = new TimeSpan(1, 2, 3, 4, 500); + + // Act + var result = original.TruncateToSeconds(); + + // Assert + result.Days.ShouldBe(1); + result.Hours.ShouldBe(2); + result.Minutes.ShouldBe(3); + result.Seconds.ShouldBe(4); + result.Milliseconds.ShouldBe(0); + } + + [Fact] + public void ParseTime_NullInput_ReturnsZeroTimeSpan() + { + // Arrange + string source = null; + + // Act + var result = source.ParseTime(); + + // Assert + result.ShouldBe(TimeSpan.Zero); + } + + [Fact] + public void ParseTime_EmptyString_ReturnsZeroTimeSpan() + { + // Arrange + var source = string.Empty; + + // Act + var result = source.ParseTime(); + + // Assert + result.ShouldBe(TimeSpan.Zero); + } + + [Fact] + public void ParseTime_WhiteSpace_ReturnsZeroTimeSpan() + { + // Arrange + var source = " "; + + // Act + var result = source.ParseTime(); + + // Assert + result.ShouldBe(TimeSpan.Zero); + } + + [Theory] + [InlineData("14:30:00", 14, 30, 0)] // 24-hour with seconds + [InlineData("14:30", 14, 30, 0)] // 24-hour without seconds + [InlineData("02:30:00 PM", 14, 30, 0)] // 12-hour with seconds + [InlineData("02:30 PM", 14, 30, 0)] // 12-hour without seconds + [InlineData("143000", 14, 30, 0)] // 24-hour compact with seconds + [InlineData("1430", 14, 30, 0)] // 24-hour compact without seconds + [InlineData("02:30:00", 2, 30, 0)] // 12-hour with seconds (AM) + [InlineData("02:30", 2, 30, 0)] // 12-hour without seconds (AM) + public void ParseTime_ValidTimeFormats_ReturnsCorrectTimeSpan(string source, int expectedHours, int expectedMinutes, int expectedSeconds) + { + // Arrange + var expected = new TimeSpan(expectedHours, expectedMinutes, expectedSeconds); + + // Act + var result = source.ParseTime(); + + // Assert + result.ShouldBe(expected); + } + + [Theory] + [InlineData("02:30:45.500")] // Direct TimeSpan format + [InlineData("1.02:30:45")] // TimeSpan with days + public void ParseTime_ValidTimeSpanFormat_ReturnsCorrectTimeSpan(string source) + { + // Arrange + var expected = TimeSpan.Parse(source); + + // Act + var result = source.ParseTime(); + + // Assert + result.ShouldBe(expected); + } + + [Theory] + [InlineData("not-a-time")] + [InlineData("14:60:00")] // Invalid minutes + [InlineData("14:30:61")] // Invalid seconds + [InlineData("14:30:00 XM")] // Invalid meridiem + public void ParseTime_InvalidTimeFormat_ReturnsZeroTimeSpan(string source) + { + // Act + var result = source.ParseTime(); + + // Assert + result.ShouldBe(TimeSpan.Zero); + } + + [Fact] + public void TryParseTime_NullInput_ReturnsFalse() + { + // Arrange + string source = null; + + // Act + var success = source.TryParseTime(out var result); + + // Assert + success.ShouldBeFalse(); + result.ShouldBe(TimeSpan.Zero); + } + + [Fact] + public void TryParseTime_EmptyString_ReturnsFalse() + { + // Arrange + var source = string.Empty; + + // Act + var success = source.TryParseTime(out var result); + + // Assert + success.ShouldBeFalse(); + result.ShouldBe(TimeSpan.Zero); + } + + [Fact] + public void TryParseTime_WhiteSpace_ReturnsFalse() + { + // Arrange + var source = " "; + + // Act + var success = source.TryParseTime(out var result); + + // Assert + success.ShouldBeFalse(); + result.ShouldBe(TimeSpan.Zero); + } + + [Theory] + [InlineData("14:30:00", 14, 30, 0)] // 24-hour with seconds + [InlineData("14:30", 14, 30, 0)] // 24-hour without seconds + [InlineData("02:30:00 PM", 14, 30, 0)] // 12-hour with seconds + [InlineData("02:30 PM", 14, 30, 0)] // 12-hour without seconds + [InlineData("143000", 14, 30, 0)] // 24-hour compact with seconds + [InlineData("1430", 14, 30, 0)] // 24-hour compact without seconds + [InlineData("02:30:00", 2, 30, 0)] // 12-hour with seconds (AM) + [InlineData("02:30", 2, 30, 0)] // 12-hour without seconds (AM) + public void TryParseTime_ValidTimeFormats_ReturnsTrueAndCorrectTimeSpan(string source, int expectedHours, int expectedMinutes, int expectedSeconds) + { + // Arrange + var expected = new TimeSpan(expectedHours, expectedMinutes, expectedSeconds); + + // Act + var success = source.TryParseTime(out var result); + + // Assert + success.ShouldBeTrue(); + result.ShouldBe(expected); + } + + [Theory] + [InlineData("02:30:45.500")] // Direct TimeSpan format + [InlineData("1.02:30:45")] // TimeSpan with days + public void TryParseTime_ValidTimeSpanFormat_ReturnsTrueAndCorrectTimeSpan(string source) + { + // Arrange + var expected = TimeSpan.Parse(source); + + // Act + var success = source.TryParseTime(out var result); + + // Assert + success.ShouldBeTrue(); + result.ShouldBe(expected); + } + + [Theory] + [InlineData("not-a-time")] + [InlineData("14:60:00")] // Invalid minutes + [InlineData("14:30:61")] // Invalid seconds + [InlineData("14:30:00 XM")] // Invalid meridiem + public void TryParseTime_InvalidTimeFormat_ReturnsFalseAndZeroTimeSpan(string source) + { + // Act + var success = source.TryParseTime(out var result); - var weeks = Value.Weeks(); - weeks.TotalDays.ShouldBe(Value * 7); + // Assert + success.ShouldBeFalse(); + result.ShouldBe(TimeSpan.Zero); } } \ No newline at end of file diff --git a/tests/Common.UnitTests/Serialization/Filtering/FilterModelSystemTextJsonTests.cs b/tests/Common.UnitTests/Serialization/Filtering/FilterModelSystemTextJsonTests.cs new file mode 100644 index 00000000..050bff6c --- /dev/null +++ b/tests/Common.UnitTests/Serialization/Filtering/FilterModelSystemTextJsonTests.cs @@ -0,0 +1,473 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Common.UnitTests.Serialization; + +using System.Runtime.Serialization; +using System.Text.Json; +using Xunit; +using Shouldly; +using BridgingIT.DevKit.Common; + +public class FilterModelSystemTextJsonTests +{ + private readonly SystemTextJsonSerializer serializer = new(); + + [Fact] + public void SerializeAndDeserialize_FilterModel_ShouldMatchOriginal() + { + // Arrange + var filterModel = this.CreateSampleFilterModel(); + + // Act + var json = this.serializer.SerializeToString(filterModel); + var deserializedModel = this.serializer.Deserialize(json); + + // Assert + deserializedModel.ShouldNotBeNull(); + this.AssertFilterModelEquals(deserializedModel, filterModel); + } + + [Fact] + public void SerializeToJsonString_FilterModel_ShouldProduceValidJson() + { + // Arrange + var filterModel = this.CreateSampleFilterModel(); + + // Act + var jsonString = this.serializer.SerializeToString(filterModel); + + // Assert + this.AssertJsonStringContains(jsonString, filterModel); + } + + [Fact] + public void DeserializeFromJsonString_FilterModel_ShouldProduceValidObject() + { + // Arrange + var jsonString = this.CreateSampleFilterModelJson(); + + // Act + var filterModel = this.serializer.Deserialize(jsonString); + + // Assert + this.AssertFilterModelMatchesJson(filterModel); + } + + [Fact] + public void SerializeAndDeserialize_ComplexFilterCriteria_ShouldMatchOriginal() + { + // Arrange + var filterCriteria = this.CreateComplexFilterCriteria(); + + // Act + var json = this.serializer.SerializeToString(filterCriteria); + var deserializedCriteria = this.serializer.Deserialize(json); + + // Assert + deserializedCriteria.ShouldNotBeNull(); + this.AssertComplexFilterCriteriaEquals(deserializedCriteria, filterCriteria); + } + + [Fact] + public void SerializeToJsonString_ComplexFilterCriteria_ShouldProduceValidJson() + { + // Arrange + var filterCriteria = this.CreateComplexFilterCriteria(); + + // Act + var jsonString = this.serializer.SerializeToString(filterCriteria); + + // Assert + this.AssertJsonStringContainsComplexFilterCriteria(jsonString); + } + + [Fact] + public void DeserializeFromJsonString_ComplexFilterCriteria_ShouldProduceValidObject() + { + // Arrange + var jsonString = this.CreateComplexFilterCriteriaJson(); + + // Act + var filterCriteria = this.serializer.Deserialize(jsonString); + + // Assert + this.AssertComplexFilterCriteriaMatchesJson(filterCriteria); + } + + // Helper methods for creating sample data and assertions + + private FilterModel CreateSampleFilterModel() + { + return new FilterModel + { + Page = 2, + PageSize = 15, + Filters = + [ + new FilterCriteria + { + Field = "Age", + Operator = FilterOperator.GreaterThanOrEqual, + Value = 18 + }, + new FilterCriteria + { + Field = "Name", + Operator = FilterOperator.Contains, + Value = "John" + }, + new FilterCriteria(FilterCustomType.DateRange, + new Dictionary + { + ["Field"] = "BirthDate", + ["StartDate"] = "1900-01-01", + ["EndDate"] = "2030-12-31" + }) + ], + Orderings = + [ + new FilterOrderCriteria + { + Field = "LastName", + Direction = OrderDirection.Ascending + } + ], + Includes = ["Orders", "Address"] + }; + } + + private void AssertFilterModelEquals(FilterModel actual, FilterModel expected) + { + actual.Page.ShouldBe(expected.Page); + actual.PageSize.ShouldBe(expected.PageSize); + actual.Filters.Count.ShouldBe(expected.Filters.Count); + actual.Orderings.Count.ShouldBe(expected.Orderings.Count); + actual.Includes.ShouldBe(expected.Includes); + + for (var i = 0; i < expected.Filters.Count; i++) + { + actual.Filters[i].Field.ShouldBe(expected.Filters[i].Field); + actual.Filters[i].Operator.ShouldBe(expected.Filters[i].Operator); + //actual.Filters[i].Value.ShouldBe(expected.Filters[i].Value); + } + + for (var i = 0; i < expected.Orderings.Count; i++) + { + actual.Orderings[i].Field.ShouldBe(expected.Orderings[i].Field); + actual.Orderings[i].Direction.ShouldBe(expected.Orderings[i].Direction); + } + } + + private void AssertJsonStringContains(string jsonString, FilterModel model) + { + jsonString.ShouldContain($"page\": {model.Page}"); + jsonString.ShouldContain($"pageSize\": {model.PageSize}"); + foreach (var filter in model.Filters) + { + if (filter.Field != null) + { + jsonString.ShouldContain($"field\": \"{filter.Field}\""); + jsonString.ShouldContain($"operator\": \"gte"); + jsonString.ShouldContain($"Value\": \"John\""); + } + } + + foreach (var ordering in model.Orderings) + { + jsonString.ShouldContain($"field\": \"{ordering.Field}\""); + jsonString.ShouldContain($"direction\": \"asc"); + } + + foreach (var include in model.Includes) + { + jsonString.ShouldContain($"{include}\""); + } + } + + private string CreateSampleFilterModelJson() + { + return """ + { + "page": 2, + "pageSize": 15, + "filters": [ + { + "field": "Age", + "operator": "gte", + "value": 18 + }, + { + "field": "Name", + "operator": "contains", + "value": "John" + } + ], + "orderings": [ + { + "field": "LastName", + "direction": "asc" + } + ], + "includes": ["Orders", "Address"] + } + """; + } + + private void AssertFilterModelMatchesJson(FilterModel model) + { + model.ShouldNotBeNull(); + model.Page.ShouldBe(2); + model.PageSize.ShouldBe(15); + model.Filters.Count.ShouldBe(2); + model.Orderings.Count.ShouldBe(1); + model.Includes.Count.ShouldBe(2); + + model.Filters[0].Field.ShouldBe("Age"); + model.Filters[0].Operator.ShouldBe(FilterOperator.GreaterThanOrEqual); + //model.Filters[0].Value.ShouldBe(18); + + model.Filters[1].Field.ShouldBe("Name"); + model.Filters[1].Operator.ShouldBe(FilterOperator.Contains); + //model.Filters[1].Value.ShouldBe("John"); + + model.Orderings[0].Field.ShouldBe("LastName"); + model.Orderings[0].Direction.ShouldBe(OrderDirection.Ascending); + + model.Includes.ShouldContain("Orders"); + model.Includes.ShouldContain("Address"); + } + + private FilterCriteria CreateComplexFilterCriteria() + { + return new FilterCriteria + { + CustomType = FilterCustomType.CompositeSpecification, + CompositeSpecification = new CompositeSpecification + { + Nodes = + [ + new SpecificationLeaf + { + Name = "AdultSpecification", + Arguments = [18] + }, + new SpecificationGroup + { + Logic = FilterLogicOperator.Or, + Nodes = + [ + new SpecificationLeaf + { + Name = "HighValueCustomerSpecification", + Arguments = [1000.0m] + }, + + new SpecificationLeaf + { + Name = "LoyalCustomerSpecification", + Arguments = [5] + } + ] + } + ] + } + }; + } + + private void AssertComplexFilterCriteriaEquals(FilterCriteria actual, FilterCriteria expected) + { + actual.Field.ShouldBe(expected.Field); + actual.CustomType.ShouldBe(expected.CustomType); + actual.CompositeSpecification.ShouldNotBeNull(); + actual.CompositeSpecification.Nodes.Count.ShouldBe(expected.CompositeSpecification.Nodes.Count); + + for (var i = 0; i < expected.CompositeSpecification.Nodes.Count; i++) + { + var actualNode = actual.CompositeSpecification.Nodes[i]; + var expectedNode = expected.CompositeSpecification.Nodes[i]; + + if (expectedNode is SpecificationLeaf expectedLeaf) + { + var actualLeaf = actualNode as SpecificationLeaf; + actualLeaf.ShouldNotBeNull(); + actualLeaf.Name.ShouldBe(expectedLeaf.Name); + // actualLeaf.Arguments.ShouldBe(expectedLeaf.Arguments); + } + else if (expectedNode is SpecificationGroup expectedGroup) + { + var actualGroup = actualNode as SpecificationGroup; + actualGroup.ShouldNotBeNull(); + actualGroup.Logic.ShouldBe(expectedGroup.Logic); + actualGroup.Nodes.Count.ShouldBe(expectedGroup.Nodes.Count); + // Recursively check nested nodes if needed + } + } + } + + private void AssertJsonStringContainsComplexFilterCriteria(string jsonString) + { + jsonString.ShouldContain("customType\": \"compositespecification\""); + jsonString.ShouldContain("compositeSpecification\""); + jsonString.ShouldContain("name\": \"AdultSpecification\""); + //jsonString.ShouldContain("arguments\": [18]"); + jsonString.ShouldContain("logic\": \"or\""); + jsonString.ShouldContain("name\": \"HighValueCustomerSpecification\""); + //jsonString.ShouldContain("arguments\": [1000.0]"); + jsonString.ShouldContain("name\": \"LoyalCustomerSpecification\""); + //jsonString.ShouldContain("arguments\": [5]"); + } + + private string CreateComplexFilterCriteriaJson() + { + return """ + { + "name": "ComplexFilter", + "customType": "compositespecification", + "compositeSpecification": { + "nodes": [ + { + "name": "AdultSpecification", + "arguments": [18] + }, + { + "logic": "or", + "nodes": [ + { + "name": "HighValueCustomerSpecification", + "arguments": [1000.0] + }, + { + "name": "LoyalCustomerSpecification", + "arguments": [5] + } + ] + } + ] + } + } + """; + } + + private void AssertComplexFilterCriteriaMatchesJson(FilterCriteria criteria) + { + criteria.ShouldNotBeNull(); + //criteria.Field.ShouldBe("ComplexFilter"); + criteria.CustomType.ShouldBe(FilterCustomType.CompositeSpecification); + criteria.CompositeSpecification.ShouldNotBeNull(); + criteria.CompositeSpecification.Nodes.Count.ShouldBe(2); + + var leaf = criteria.CompositeSpecification.Nodes[0] as SpecificationLeaf; + leaf.ShouldNotBeNull(); + leaf.Name.ShouldBe("AdultSpecification"); + //leaf.Arguments.ShouldContain(18); + + var group = criteria.CompositeSpecification.Nodes[1] as SpecificationGroup; + group.ShouldNotBeNull(); + group.Logic.ShouldBe(FilterLogicOperator.Or); + group.Nodes.Count.ShouldBe(2); + + var highValueLeaf = group.Nodes[0] as SpecificationLeaf; + highValueLeaf.ShouldNotBeNull(); + highValueLeaf.Name.ShouldBe("HighValueCustomerSpecification"); + //highValueLeaf.Arguments.ShouldContain(1000.0m); + + var loyalLeaf = group.Nodes[1] as SpecificationLeaf; + loyalLeaf.ShouldNotBeNull(); + loyalLeaf.Name.ShouldBe("LoyalCustomerSpecification"); + //loyalLeaf.Arguments.ShouldContain(5); + } +} + +public class EnumConverterTests +{ + private readonly Faker faker; + private readonly JsonSerializerOptions options; + private readonly SystemTextJsonSerializer serializer; + + public EnumConverterTests() + { + this.faker = new Faker(); + this.options = DefaultSystemTextJsonSerializerOptions.Create(); + this.options.Converters.Insert(0, new EnumConverter()); + this.serializer = new SystemTextJsonSerializer(this.options); + } + + [Fact] + public void Read_ValidEnumValue_ReturnsCorrectEnum() + { + // Arrange + var json = "\"CustomValue1\""; + var expectedEnum = TestEnum.Value1; + + // Act + var result = this.serializer.Deserialize( + new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json))); + + // Assert + result.ShouldBe(expectedEnum); + } + + [Fact] + public void Read_InvalidEnumValue_ThrowsJsonException() + { + // Arrange + var json = "\"InvalidValue\""; + + // Act & Assert + Should.Throw(() => + this.serializer.Deserialize(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json))) + ); + } + + [Fact] + public void Write_EnumWithEnumMemberAttribute_WritesAttributeValue() + { + // Arrange + const TestEnum enumValue = TestEnum.Value2; + + // Act + var result = JsonSerializer.Serialize(enumValue, this.options); + + // Assert + result.ShouldBe("\"CustomValue2\""); + } + + [Fact] + public void Write_EnumWithoutEnumMemberAttribute_WritesEnumName() + { + // Arrange + var enumValue = TestEnum.Value1; + + // Act + var result = JsonSerializer.Serialize(enumValue, this.options); + + // Assert + result.ShouldBe("\"CustomValue1\""); + } + + [Fact] + public void ReadWrite_RoundTrip_PreservesEnumValue() + { + // Arrange + var originalEnum = TestEnum.Value2; + + // Act + var json = JsonSerializer.Serialize(originalEnum, this.options); + var deserializedEnum = JsonSerializer.Deserialize(json, this.options); + + // Assert + deserializedEnum.ShouldBe(originalEnum); + } + + private enum TestEnum + { + [EnumMember(Value = "CustomValue1")] + Value1, + + [EnumMember(Value = "CustomValue2")] + Value2 + } +} \ No newline at end of file diff --git a/tests/Common.UnitTests/Serialization/SerializerTestsBase.cs b/tests/Common.UnitTests/Serialization/SerializerTestsBase.cs index 535c70b0..dfd79150 100644 --- a/tests/Common.UnitTests/Serialization/SerializerTestsBase.cs +++ b/tests/Common.UnitTests/Serialization/SerializerTestsBase.cs @@ -208,7 +208,7 @@ public class StubModel public IReadOnlyList Items { get => this.items; - init => this.items = new List(value); // init needed for systemtextjson deserialization + init => this.items = [..value]; // init needed for systemtextjson deserialization } public object ObjectProperty { get; set; } diff --git a/tests/Common.UnitTests/Utilities/Tracing/TraceActivityDecoratorTests.cs b/tests/Common.UnitTests/Utilities/Tracing/TraceActivityDecoratorTests.cs index 88e52852..bed41bf7 100644 --- a/tests/Common.UnitTests/Utilities/Tracing/TraceActivityDecoratorTests.cs +++ b/tests/Common.UnitTests/Utilities/Tracing/TraceActivityDecoratorTests.cs @@ -90,7 +90,7 @@ public void Activity_Created_MethodJaggedAndMultiDimArraysParams() "Action|String&|Boolean[][][]|Int16[,,][,][,,,]|Int64[][,][][,,]"); }, out var strVal, - Array.Empty(), + [], new short[,,,][,][,,] { }, new long[,,][][,][] { }); } diff --git a/tests/Common.UnitTests/packages.lock.json b/tests/Common.UnitTests/packages.lock.json index 687bf05a..454ed9dc 100644 --- a/tests/Common.UnitTests/packages.lock.json +++ b/tests/Common.UnitTests/packages.lock.json @@ -166,8 +166,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -673,7 +673,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -687,8 +687,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -727,7 +728,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -776,11 +777,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -987,9 +988,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", diff --git a/tests/Domain.IntegrationTests/packages.lock.json b/tests/Domain.IntegrationTests/packages.lock.json index 1456ecdf..6e0470a3 100644 --- a/tests/Domain.IntegrationTests/packages.lock.json +++ b/tests/Domain.IntegrationTests/packages.lock.json @@ -119,8 +119,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1308,7 +1308,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1322,8 +1322,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1355,7 +1356,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1372,7 +1373,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.Mediator": { @@ -1460,11 +1462,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1675,9 +1677,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", @@ -1710,6 +1712,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Net.Http": { "type": "CentralTransitive", "requested": "[4.3.4, )", diff --git a/tests/Domain.UnitTests/Domain/Filtering/IncludeOptionBuilderTests.cs b/tests/Domain.UnitTests/Domain/Filtering/IncludeOptionBuilderTests.cs new file mode 100644 index 00000000..51bc4a7f --- /dev/null +++ b/tests/Domain.UnitTests/Domain/Filtering/IncludeOptionBuilderTests.cs @@ -0,0 +1,75 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.UnitTests.Domain; + +using System.Linq.Expressions; +using BridgingIT.DevKit.Domain.Repositories; + +public class IncludeOptionBuilderTests +{ + [Fact] + public void Build_WithValidPaths_ReturnsCorrectIncludeOptions() + { + // Arrange + var includePaths = new List { "Orders", "BillingAddress.City" }; + + // Act + var result = IncludeOptionBuilder.Build(includePaths); + + // Assert + result.ShouldNotBeNull(); + result.Count.ShouldBe(2); + + var firstInclude = result.First(); + firstInclude.ShouldBeOfType>(); + firstInclude.Expression.ShouldNotBeNull(); + // firstInclude.Expression.Body.ShouldBeOfType(); + ((MemberExpression)firstInclude.Expression.Body).Member.Name.ShouldBe("Orders"); + + var secondInclude = result.Last(); + secondInclude.ShouldBeOfType>(); + secondInclude.Expression.ShouldNotBeNull(); + // secondInclude.Expression.Body.ShouldBeOfType(); + var memberExp = (MemberExpression)secondInclude.Expression.Body; + ((MemberExpression)memberExp.Expression).Member.Name.ShouldBe("BillingAddress"); + memberExp.Member.Name.ShouldBe("City"); + } + + [Fact] + public void Build_WithEmptyInput_ReturnsEmptyList() + { + // Arrange + var includePaths = new List(); + + // Act + var result = IncludeOptionBuilder.Build(includePaths); + + // Assert + result.ShouldNotBeNull(); + result.ShouldBeEmpty(); + } + + [Fact] + public void Build_WithNullInput_ReturnsEmptyList() + { + // Act + var result = IncludeOptionBuilder.Build(null); + + // Assert + result.ShouldNotBeNull(); + result.ShouldBeEmpty(); + } + + [Fact] + public void Build_WithInvalidPropertyPath_ThrowsArgumentException() + { + // Arrange + var includes = new List { "InvalidProperty" }; + + // Act & Assert + Should.Throw(() => IncludeOptionBuilder.Build(includes)); + } +} \ No newline at end of file diff --git a/tests/Domain.UnitTests/Domain/Filtering/OrderOptionBuilderTests.cs b/tests/Domain.UnitTests/Domain/Filtering/OrderOptionBuilderTests.cs new file mode 100644 index 00000000..35277227 --- /dev/null +++ b/tests/Domain.UnitTests/Domain/Filtering/OrderOptionBuilderTests.cs @@ -0,0 +1,82 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.UnitTests.Domain; + +using System.Linq.Expressions; +using BridgingIT.DevKit.Domain.Repositories; + +public class OrderOptionBuilderTests +{ + [Fact] + public void Build_WithValidCriteria_ReturnsCorrectOrderOptions() + { + // Arrange + var orderCriteria = new List + { + new() { Field = "FirstName", Direction = OrderDirection.Ascending }, + new() { Field = "Age", Direction = OrderDirection.Descending } + }; + + // Act + var result = OrderOptionBuilder.Build(orderCriteria); + + // Assert + result.ShouldNotBeNull(); + result.Count.ShouldBe(2); + + var firstOrder = result.First(); + firstOrder.ShouldBeOfType>(); + firstOrder.Expression.ShouldNotBeNull(); + firstOrder.Expression.Body.ShouldBeOfType(); + ((MemberExpression)((UnaryExpression)firstOrder.Expression.Body).Operand).Member.Name.ShouldBe("FirstName"); + firstOrder.Direction.ShouldBe(OrderDirection.Ascending); + + var secondOrder = result.Last(); + secondOrder.ShouldBeOfType>(); + secondOrder.Expression.ShouldNotBeNull(); + secondOrder.Expression.Body.ShouldBeOfType(); + ((MemberExpression)((UnaryExpression)secondOrder.Expression.Body).Operand).Member.Name.ShouldBe("Age"); + secondOrder.Direction.ShouldBe(OrderDirection.Descending); + } + + [Fact] + public void Build_WithEmptyInput_ReturnsEmptyList() + { + // Arrange + var orderCriteria = new List(); + + // Act + var result = OrderOptionBuilder.Build(orderCriteria); + + // Assert + result.ShouldNotBeNull(); + result.ShouldBeEmpty(); + } + + [Fact] + public void Build_WithNullInput_ReturnsEmptyList() + { + // Act + var result = OrderOptionBuilder.Build(null); + + // Assert + result.ShouldNotBeNull(); + result.ShouldBeEmpty(); + } + + [Fact] + public void Build_WithInvalidPropertyName_ThrowsArgumentException() + { + // Arrange + var orderCriteria = new List + { + new() { Field = "InvalidProperty", Direction = OrderDirection.Ascending } + }; + + // Act & Assert + Should.Throw(() => OrderOptionBuilder.Build(orderCriteria)); + } +} \ No newline at end of file diff --git a/tests/Domain.UnitTests/Domain/Filtering/SpecificationBuilderTests.cs b/tests/Domain.UnitTests/Domain/Filtering/SpecificationBuilderTests.cs new file mode 100644 index 00000000..092f0ebb --- /dev/null +++ b/tests/Domain.UnitTests/Domain/Filtering/SpecificationBuilderTests.cs @@ -0,0 +1,988 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.UnitTests.Domain; + +using System; +using System.Linq; +using Xunit; + +public class SpecificationBuilderTests +{ + public SpecificationBuilderTests() + { + SpecificationResolver.Clear(); + SpecificationResolver.Register("IsAdult"); + SpecificationResolver.Register("NameStartsWith"); + } + + [Fact] + public void BuildSpecifications_WithValidFilters_ReturnsCorrectSpecifications() + { + // Arrange + var filters = new[] + { + new FilterCriteria { Field = "FirstName", Operator = FilterOperator.Equal, Value = "John" }, + new FilterCriteria { Field = "Age", Operator = FilterOperator.GreaterThanOrEqual, Value = 18 } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(2); + result.ShouldAllBe(spec => spec.GetType().ImplementsInterface>()); + } + + [Fact] + public void BuildSpecifications_WithChildFilter_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + Field = "BillingAddress.City", + Operator = FilterOperator.Equal, + Value = "Berlin" + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + + var matchingPerson = new PersonStub + { + BillingAddress = AddressStub.Create("Billing", "123 Bill St", "", "10115", "Berlin", "Germany") + }; + var nonMatchingPerson = new PersonStub + { + BillingAddress = AddressStub.Create("Billing", "456 Invoice Rd", "", "80331", "Munich", "Germany") + }; + + spec.IsSatisfiedBy(matchingPerson).ShouldBeTrue(); + spec.IsSatisfiedBy(nonMatchingPerson).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithEqualOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] { new FilterCriteria { Field = "FirstName", Operator = FilterOperator.Equal, Value = "John" } }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "John" }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "Jane" }).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithNotEqualOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] { new FilterCriteria { Field = "FirstName", Operator = FilterOperator.NotEqual, Value = "John" } }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "John" }).ShouldBeFalse(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "Jane" }).ShouldBeTrue(); + } + + [Fact] + public void BuildSpecifications_WithGreaterThanOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] { new FilterCriteria { Field = "Age", Operator = FilterOperator.GreaterThan, Value = 18 } }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + spec.IsSatisfiedBy(new PersonStub { Age = 20 }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { Age = 18 }).ShouldBeFalse(); + spec.IsSatisfiedBy(new PersonStub { Age = 16 }).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithGreaterThanOrEqualOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] { new FilterCriteria { Field = "Age", Operator = FilterOperator.GreaterThanOrEqual, Value = 18 } }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + spec.IsSatisfiedBy(new PersonStub { Age = 20 }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { Age = 18 }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { Age = 16 }).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithLessThanOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] { new FilterCriteria { Field = "Age", Operator = FilterOperator.LessThan, Value = 18 } }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + spec.IsSatisfiedBy(new PersonStub { Age = 16 }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { Age = 18 }).ShouldBeFalse(); + spec.IsSatisfiedBy(new PersonStub { Age = 20 }).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithLessThanOrEqualOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] { new FilterCriteria { Field = "Age", Operator = FilterOperator.LessThanOrEqual, Value = 18 } }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + spec.IsSatisfiedBy(new PersonStub { Age = 16 }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { Age = 18 }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { Age = 20 }).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithContainsOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] { new FilterCriteria { Field = "FirstName", Operator = FilterOperator.Contains, Value = "oh" } }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "John" }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "Jane" }).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithStartsWithOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] { new FilterCriteria { Field = "FirstName", Operator = FilterOperator.StartsWith, Value = "Jo" } }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "John" }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "Jane" }).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithEndsWithOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] { new FilterCriteria { Field = "FirstName", Operator = FilterOperator.EndsWith, Value = "hn" } }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "John" }).ShouldBeTrue(); + spec.IsSatisfiedBy(new PersonStub { FirstName = "Jane" }).ShouldBeFalse(); + } + + // [Fact] + // public void BuildSpecifications_WithUnsupportedOperator_ThrowsNotSupportedException() + // { + // // Arrange + // var filters = new[] { new FilterCriteria { Name = "FirstName", Operator = "unsupported", Value = "John" } }; + // + // // Act & Assert + // Should.Throw(() => SpecificationBuilder.Build(filters)); + // } + + [Fact] + public void BuildSpecifications_WithAnyOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + Field = "Addresses", + Operator = FilterOperator.Any, + Value = new FilterCriteria { Field = "City", Operator = FilterOperator.Equal, Value = "Berlin" } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + + var matchingPerson = new PersonStub + { + Addresses = + [ + AddressStub.Create("Home", "123 Main St", "", "12345", "New York", "USA"), + AddressStub.Create("Work", "456 Office Blvd", "", "10115", "Berlin", "Germany") + ] + }; + var nonMatchingPerson = new PersonStub + { + Addresses = + [ + AddressStub.Create("Home", "123 Main St", "", "12345", "New York", "USA"), + AddressStub.Create("Work", "456 Office Blvd", "", "80331", "Munich", "Germany") + ] + }; + + spec.IsSatisfiedBy(matchingPerson).ShouldBeTrue(); + spec.IsSatisfiedBy(nonMatchingPerson).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithAllOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + Field = "Addresses", + Operator = FilterOperator.All, + Value = new FilterCriteria { Field = "Country", Operator = FilterOperator.Equal, Value = "Germany" } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + + var matchingPerson = new PersonStub + { + Addresses = + [ + AddressStub.Create("Home", "123 Main St", "", "10115", "Berlin", "Germany"), + AddressStub.Create("Work", "456 Office Blvd", "", "80331", "Munich", "Germany") + ] + }; + var nonMatchingPerson = new PersonStub + { + Addresses = + [ + AddressStub.Create("Home", "123 Main St", "", "12345", "New York", "USA"), + AddressStub.Create("Work", "456 Office Blvd", "", "10115", "Berlin", "Germany") + ] + }; + + spec.IsSatisfiedBy(matchingPerson).ShouldBeTrue(); + spec.IsSatisfiedBy(nonMatchingPerson).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithNoneOperator_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + Field = "Addresses", + Operator = FilterOperator.None, + Value = new FilterCriteria { Field = "PostalCode", Operator = FilterOperator.StartsWith, Value = "1" } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + + var matchingPerson = new PersonStub + { + Addresses = + [ + AddressStub.Create("Home", "123 Main St", "", "20115", "Hamburg", "Germany"), + AddressStub.Create("Work", "456 Office Blvd", "", "80331", "Munich", "Germany") + ] + }; + var nonMatchingPerson = new PersonStub + { + Addresses = + [ + AddressStub.Create("Home", "123 Main St", "", "12345", "New York", "USA"), + AddressStub.Create("Work", "456 Office Blvd", "", "10115", "Berlin", "Germany") + ] + }; + + spec.IsSatisfiedBy(matchingPerson).ShouldBeTrue(); + spec.IsSatisfiedBy(nonMatchingPerson).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithLogicalOperatorOR_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria { Field = "FirstName", Operator = FilterOperator.Equal, Value = "John", Logic = FilterLogicOperator.Or }, + new FilterCriteria { Field = "LastName", Operator = FilterOperator.Equal, Value = "Doe" } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Single(result); + var spec = result.First(); + Assert.IsAssignableFrom>(spec); + + // Verify the OR logic + var testPerson1 = new PersonStub { FirstName = "John", LastName = "Smith" }; + var testPerson2 = new PersonStub { FirstName = "Jane", LastName = "Doe" }; + Assert.True(spec.IsSatisfiedBy(testPerson1)); + Assert.True(spec.IsSatisfiedBy(testPerson2)); + } + + [Fact] + public void BuildSpecifications_WithMixedLogicalOperators_ReturnsCorrectSpecifications() + { + // Arrange + var filters = new[] + { + new FilterCriteria { Field = "FirstName", Operator = FilterOperator.Equal, Value = "John", Logic = FilterLogicOperator.Or }, + new FilterCriteria { Field = "LastName", Operator = FilterOperator.Equal, Value = "Doe", Logic = FilterLogicOperator.And }, + new FilterCriteria { Field = "Age", Operator = FilterOperator.GreaterThanOrEqual, Value = 18 } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Equal(2, result.Count()); + + var orSpec = result.First(); + var andSpec = result.Last(); + + var testPerson1 = new PersonStub { FirstName = "John", LastName = "Smith", Age = 25 }; + var testPerson2 = new PersonStub { FirstName = "Jane", LastName = "Doe", Age = 30 }; + var testPerson3 = new PersonStub { FirstName = "Alice", LastName = "Doe", Age = 17 }; + + Assert.True(orSpec.IsSatisfiedBy(testPerson1)); + Assert.True(orSpec.IsSatisfiedBy(testPerson2)); + Assert.True(andSpec.IsSatisfiedBy(testPerson2)); + Assert.False(andSpec.IsSatisfiedBy(testPerson3)); + } + + [Fact] + public void BuildSpecifications_WithFullTextSearch_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.FullTextSearch, + CustomParameters = new Dictionary + { + ["searchTerm"] = "John", + ["fields"] = new[] { "FirstName", "LastName" } + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Single(result); + var spec = result.First(); + + var matchingPerson1 = new PersonStub { FirstName = "John", LastName = "Doe" }; + var matchingPerson2 = new PersonStub { FirstName = "Jane", LastName = "Johnson" }; + var nonMatchingPerson = new PersonStub { FirstName = "Alice", LastName = "Smith" }; + + Assert.True(spec.IsSatisfiedBy(matchingPerson1)); + Assert.True(spec.IsSatisfiedBy(matchingPerson2)); + Assert.False(spec.IsSatisfiedBy(nonMatchingPerson)); + } + + [Fact] + public void BuildSpecifications_WithDateRange_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.DateRange, + CustomParameters = new Dictionary + { + ["field"] = "BirthDate", + ["startDate"] = new DateTime(1990, 1, 1).ToString("o"), + ["endDate"] = new DateTime(2000, 12, 31).ToString("o") + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Single(result); + var spec = result.First(); + + var matchingPerson = new PersonStub { BirthDate = new DateTime(1995, 6, 15) }; + var nonMatchingPerson1 = new PersonStub { BirthDate = new DateTime(1989, 12, 31) }; + var nonMatchingPerson2 = new PersonStub { BirthDate = new DateTime(2001, 1, 1) }; + + Assert.True(spec.IsSatisfiedBy(matchingPerson)); + Assert.False(spec.IsSatisfiedBy(nonMatchingPerson1)); + Assert.False(spec.IsSatisfiedBy(nonMatchingPerson2)); + } + + [Fact] + public void BuildSpecifications_WithNumericRange_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.NumericRange, + CustomParameters = new Dictionary + { + ["field"] = "Age", + ["min"] = 18, + ["max"] = 65 + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Single(result); + var spec = result.First(); + + var matchingPerson = new PersonStub { Age = 30 }; + var nonMatchingPersonYoung = new PersonStub { Age = 17 }; + var nonMatchingPersonOld = new PersonStub { Age = 66 }; + + Assert.True(spec.IsSatisfiedBy(matchingPerson)); + Assert.False(spec.IsSatisfiedBy(nonMatchingPersonYoung)); + Assert.False(spec.IsSatisfiedBy(nonMatchingPersonOld)); + } + + [Fact] + public void BuildSpecifications_WithNotNull_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.IsNotNull, + CustomParameters = new Dictionary + { + ["field"] = "Email" + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Single(result); + var spec = result.First(); + + var matchingPerson = new PersonStub { Email = "test@example.com" }; + var nonMatchingPerson = new PersonStub { Email = null }; + + Assert.True(spec.IsSatisfiedBy(matchingPerson)); + Assert.False(spec.IsSatisfiedBy(nonMatchingPerson)); + } + + [Fact] + public void BuildSpecifications_WithTimeRange_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.TimeRange, + CustomParameters = new Dictionary + { + ["field"] = "WorkStartTime", + ["startTime"] = "09:00:00", // 9:00 AM + ["endTime"] = "17:00:00" // 5:00 PM + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + + var matchingPerson = new PersonStub { WorkStartTime = TimeSpan.FromHours(12) }; // 12:00 PM + var nonMatchingPersonEarly = new PersonStub { WorkStartTime = TimeSpan.FromHours(8) }; // 8:00 AM + var nonMatchingPersonLate = new PersonStub { WorkStartTime = TimeSpan.FromHours(18) }; // 6:00 PM + + spec.IsSatisfiedBy(matchingPerson).ShouldBeTrue(); + spec.IsSatisfiedBy(nonMatchingPersonEarly).ShouldBeFalse(); + spec.IsSatisfiedBy(nonMatchingPersonLate).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithOvernightTimeRange_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.TimeRange, + CustomParameters = new Dictionary + { + ["field"] = "WorkStartTime", + ["startTime"] = "22:00:00", // 10:00 PM + ["endTime"] = "06:00:00" // 6:00 AM + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + + var matchingPersonLate = new PersonStub { WorkStartTime = TimeSpan.FromHours(23) }; // 11:00 PM + var matchingPersonEarly = new PersonStub { WorkStartTime = TimeSpan.FromHours(2) }; // 2:00 AM + var nonMatchingPerson = new PersonStub { WorkStartTime = TimeSpan.FromHours(12) }; // 12:00 PM + + spec.IsSatisfiedBy(matchingPersonLate).ShouldBeTrue(); + spec.IsSatisfiedBy(matchingPersonEarly).ShouldBeTrue(); + spec.IsSatisfiedBy(nonMatchingPerson).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithInvalidTimeRangeFormat_ThrowsArgumentException() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.TimeRange, + CustomParameters = new Dictionary + { + ["field"] = "WorkStartTime", + ["startTime"] = "invalid time", + ["endTime"] = "17:00:00" + } + } + }; + + // Act & Assert + Should.Throw(() => SpecificationBuilder.Build(filters)); + } + + [Fact] + public void BuildSpecifications_WithEnumFilter_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.EnumValues, + CustomParameters = new Dictionary + { + ["field"] = "EmploymentStatus", + ["values"] = "FullTime;PartTime" + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Single(result); + var spec = result.First(); + + var matchingPersonFullTime = new PersonStub { EmploymentStatus = EmploymentStatus.FullTime }; + var matchingPersonPartTime = new PersonStub { EmploymentStatus = EmploymentStatus.PartTime }; + var nonMatchingPerson = new PersonStub { EmploymentStatus = EmploymentStatus.Contractor }; + + Assert.True(spec.IsSatisfiedBy(matchingPersonFullTime)); + Assert.True(spec.IsSatisfiedBy(matchingPersonPartTime)); + Assert.False(spec.IsSatisfiedBy(nonMatchingPerson)); + } + + [Fact] + public void BuildSpecifications_WithEnumFilter_InvalidEnumValue_ThrowsArgumentException() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.EnumValues, + CustomParameters = new Dictionary + { + ["field"] = "EmploymentStatus", + ["values"] = "FullTime;InvalidValue" + } + } + }; + + // Act & Assert + Assert.Throws(() => SpecificationBuilder.Build(filters)); + } + + [Fact] + public void BuildSpecifications_WithEnumFilter_UsingIntegerValues_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.EnumValues, + CustomParameters = new Dictionary + { + ["field"] = "EmploymentStatus", + ["values"] = "0;1" // Assuming FullTime = 0, PartTime = 1 + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Single(result); + var spec = result.First(); + + var matchingPersonFullTime = new PersonStub { EmploymentStatus = EmploymentStatus.FullTime }; + var matchingPersonPartTime = new PersonStub { EmploymentStatus = EmploymentStatus.PartTime }; + var nonMatchingPerson = new PersonStub { EmploymentStatus = EmploymentStatus.Contractor }; + + Assert.True(spec.IsSatisfiedBy(matchingPersonFullTime)); + Assert.True(spec.IsSatisfiedBy(matchingPersonPartTime)); + Assert.False(spec.IsSatisfiedBy(nonMatchingPerson)); + } + + [Fact] + public void BuildSpecifications_WithEnumFilter_MixedValues_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.EnumValues, + CustomParameters = new Dictionary + { + ["field"] = "EmploymentStatus", + ["values"] = "FullTime;1" // Mixed use of name and integer value + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Single(result); + var spec = result.First(); + + var matchingPersonFullTime = new PersonStub { EmploymentStatus = EmploymentStatus.FullTime }; + var matchingPersonPartTime = new PersonStub { EmploymentStatus = EmploymentStatus.PartTime }; + var nonMatchingPerson = new PersonStub { EmploymentStatus = EmploymentStatus.Contractor }; + + Assert.True(spec.IsSatisfiedBy(matchingPersonFullTime)); + Assert.True(spec.IsSatisfiedBy(matchingPersonPartTime)); + Assert.False(spec.IsSatisfiedBy(nonMatchingPerson)); + } + + [Fact] + public void BuildSpecifications_WithNamedSpecifications_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.NamedSpecification, + SpecificationName = "IsAdult", + SpecificationArguments = [18] + }, + new FilterCriteria + { + CustomType = FilterCustomType.NamedSpecification, + SpecificationName = "NameStartsWith", + SpecificationArguments = ["J"] + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Equal(2, result.Count()); + Assert.All(result, spec => Assert.IsAssignableFrom>(spec)); + + // Verify the named specifications logic + var testPerson1 = new PersonStub { FirstName = "John", Age = 20 }; + var testPerson2 = new PersonStub { FirstName = "Jane", Age = 17 }; + Assert.True(result.All(spec => spec.IsSatisfiedBy(testPerson1))); + Assert.False(result.All(spec => spec.IsSatisfiedBy(testPerson2))); + } + + [Fact] + public void BuildSpecifications_WithCompositeSpecification_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + CustomType = FilterCustomType.CompositeSpecification, + CompositeSpecification = new CompositeSpecification + { + Nodes = + [ + new SpecificationLeaf { Name = "IsAdult", Arguments = [18] }, + new SpecificationLeaf { Name = "NameStartsWith", Arguments = ["J"] } + ], + // LogicalOperator = LogicalOperator.And + } + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Single(result); + var spec = result.First(); + Assert.IsAssignableFrom>(spec); + + var testPerson1 = new PersonStub { FirstName = "John", Age = 20 }; + var testPerson2 = new PersonStub { FirstName = "Jane", Age = 25 }; + var testPerson3 = new PersonStub { FirstName = "Jack", Age = 17 }; + Assert.True(spec.IsSatisfiedBy(testPerson1)); + Assert.True(spec.IsSatisfiedBy(testPerson2)); + Assert.False(spec.IsSatisfiedBy(testPerson3)); + } + + [Fact] + public void BuildSpecifications_WithEmptyInput_ReturnsEmptyList() + { + // Arrange + var filters = Array.Empty(); + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void BuildSpecifications_WithNullInput_ReturnsEmptyList() + { + // Act + var result = SpecificationBuilder.Build(null); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void BuildSpecifications__WithChildStartsWithFilter_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + Field = "BillingAddress.PostalCode", + Operator = FilterOperator.StartsWith, + Value = "10" + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + + var matchingPerson = new PersonStub + { + BillingAddress = AddressStub.Create("Billing", "123 Bill St", "", "10115", "Berlin", "Germany") + }; + var nonMatchingPerson = new PersonStub + { + BillingAddress = AddressStub.Create("Billing", "456 Invoice Rd", "", "80331", "Munich", "Germany") + }; + + spec.IsSatisfiedBy(matchingPerson).ShouldBeTrue(); + spec.IsSatisfiedBy(nonMatchingPerson).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications__WithChildEqualsFilter_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + Field = "BillingAddress.Country", + Operator = FilterOperator.Equal, + Value = "Germany" + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + + var matchingPerson = new PersonStub + { + BillingAddress = AddressStub.Create("Billing", "123 Bill St", "", "10115", "Berlin", "Germany") + }; + var nonMatchingPerson = new PersonStub + { + BillingAddress = AddressStub.Create("Billing", "456 Invoice Rd", "", "NY 10001", "New York", "USA") + }; + + spec.IsSatisfiedBy(matchingPerson).ShouldBeTrue(); + spec.IsSatisfiedBy(nonMatchingPerson).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications__WithChildFilters_ReturnsCorrectSpecification() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + Field = "BillingAddress.Country", + Operator = FilterOperator.Equal, + Value = "Germany" + }, + new FilterCriteria + { + Field = "BillingAddress.PostalCode", + Operator = FilterOperator.StartsWith, + Value = "10" + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(2); + var combinedSpec = result.Aggregate((spec1, spec2) => spec1.And(spec2)); + + var matchingPerson = new PersonStub + { + BillingAddress = AddressStub.Create("Billing", "123 Bill St", "", "10115", "Berlin", "Germany") + }; + var nonMatchingPerson1 = new PersonStub + { + BillingAddress = AddressStub.Create("Billing", "456 Invoice Rd", "", "80331", "Munich", "Germany") + }; + var nonMatchingPerson2 = new PersonStub + { + BillingAddress = AddressStub.Create("Billing", "789 Receipt Ave", "", "10001", "New York", "USA") + }; + + combinedSpec.IsSatisfiedBy(matchingPerson).ShouldBeTrue(); + combinedSpec.IsSatisfiedBy(nonMatchingPerson1).ShouldBeFalse(); + combinedSpec.IsSatisfiedBy(nonMatchingPerson2).ShouldBeFalse(); + } + + [Fact] + public void BuildSpecifications_WithNoChild_HandlesGracefully() + { + // Arrange + var filters = new[] + { + new FilterCriteria + { + Field = "BillingAddress.City", + Operator = FilterOperator.Equal, + Value = "Berlin" + } + }; + + // Act + var result = SpecificationBuilder.Build(filters); + + // Assert + result.Count().ShouldBe(1); + var spec = result.First(); + + var person = new PersonStub + { + BillingAddress = null + }; + + spec.IsSatisfiedBy(person).ShouldBeFalse(); + } +} \ No newline at end of file diff --git a/tests/Domain.UnitTests/Domain/Filtering/SpecificationResolverTests.cs b/tests/Domain.UnitTests/Domain/Filtering/SpecificationResolverTests.cs new file mode 100644 index 00000000..a2a3d249 --- /dev/null +++ b/tests/Domain.UnitTests/Domain/Filtering/SpecificationResolverTests.cs @@ -0,0 +1,135 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.UnitTests.Domain; + +using System; +using System.Linq.Expressions; +using Xunit; + +public class SpecificationResolverTests +{ + [Fact] + public void RegisterSpecification_WithValidInputAndGenerics_RegistersSuccessfully() + { + // Arrange & Act + SpecificationResolver.Register("AdultSpecification"); + + // Assert + Assert.True(SpecificationResolver.IsRegistered("AdultSpecification")); + + // Cleanup + SpecificationResolver.Clear(); + } + + [Fact] + public void RegisterSpecification_WithValidInput_RegistersSuccessfully() + { + // Arrange & Act + SpecificationResolver.Register(typeof(AdultSpecification), "AdultSpecification"); + + // Assert + Assert.True(SpecificationResolver.IsRegistered("AdultSpecification")); + + // Cleanup + SpecificationResolver.Clear(); + } + + [Fact] + public void RegisterSpecification_WithInvalidType_ThrowsArgumentException() + { + // Arrange, Act & Assert + Assert.Throws(() => + SpecificationResolver.Register(typeof(string), "InvalidSpec")); + } + + [Fact] + public void RegisterSpecification_WithDuplicateName_ThrowsInvalidOperationException() + { + // Arrange + SpecificationResolver.Register("AdultSpecification"); + + // Act & Assert + Assert.Throws(() => + SpecificationResolver.Register("AdultSpecification")); + + // Cleanup + SpecificationResolver.Clear(); + } + + [Fact] + public void ResolveSpecification_WithRegisteredSpecification_ReturnsCorrectInstance() + { + // Arrange + SpecificationResolver.Register("AdultSpecification"); + + // Act + var result = SpecificationResolver.Resolve("AdultSpecification", [18]); + + // Assert + Assert.IsType(result); + + // Cleanup + SpecificationResolver.Clear(); + } + + [Fact] + public void ResolveSpecification_WithUnregisteredSpecification_ThrowsArgumentException() + { + // Arrange, Act & Assert + Assert.Throws(() => + SpecificationResolver.Resolve("UnregisteredSpec", [])); + } + + [Fact] + public void GetSpecificationType_WithRegisteredSpecification_ReturnsCorrectType() + { + // Arrange + SpecificationResolver.Register("AdultSpecification"); + + // Act + var result = SpecificationResolver.GetType("AdultSpecification"); + + // Assert + Assert.Equal(typeof(AdultSpecification), result); + + // Cleanup + SpecificationResolver.Clear(); + } + + [Fact] + public void GetSpecificationType_WithUnregisteredSpecification_ThrowsArgumentException() + { + // Arrange, Act & Assert + Assert.Throws(() => + SpecificationResolver.GetType("UnregisteredSpec")); + } + + [Fact] + public void IsSpecificationRegistered_WithRegisteredSpecification_ReturnsTrue() + { + // Arrange + SpecificationResolver.Register("AdultSpecification"); + + // Act + var result = SpecificationResolver.IsRegistered("AdultSpecification"); + + // Assert + Assert.True(result); + + // Cleanup + SpecificationResolver.Clear(); + } + + [Fact] + public void IsSpecificationRegistered_WithUnregisteredSpecification_ReturnsFalse() + { + // Act + var result = SpecificationResolver.IsRegistered("UnregisteredSpec"); + + // Assert + Assert.False(result); + } +} \ No newline at end of file diff --git a/tests/Domain.UnitTests/Domain/Model/EnumerationTests.cs b/tests/Domain.UnitTests/Domain/Model/EnumerationTests.cs index b9e8f1be..3d1dc222 100644 --- a/tests/Domain.UnitTests/Domain/Model/EnumerationTests.cs +++ b/tests/Domain.UnitTests/Domain/Model/EnumerationTests.cs @@ -276,7 +276,7 @@ public void CompareTo_ShouldOrderCorrectly() orderedList.Sort(); // Assert - orderedList.ShouldBe(new List { sut1, sut2, sut3 }); + orderedList.ShouldBe([sut1, sut2, sut3]); } [Fact] diff --git a/tests/Domain.UnitTests/Repositories/GenericReadOnlyRepositoryResultExtensionsTests.cs b/tests/Domain.UnitTests/Repositories/GenericReadOnlyRepositoryResultExtensionsTests.cs new file mode 100644 index 00000000..f6e8ee82 --- /dev/null +++ b/tests/Domain.UnitTests/Repositories/GenericReadOnlyRepositoryResultExtensionsTests.cs @@ -0,0 +1,848 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.UnitTests; + +using System.Linq.Expressions; +using BridgingIT.DevKit.Common; +using BridgingIT.DevKit.Domain.Repositories; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using Shouldly; +using Xunit; + +public class GenericReadOnlyRepositoryResultExtensionsTests +{ + private readonly Faker personFaker; + + public GenericReadOnlyRepositoryResultExtensionsTests() + { + this.personFaker = new Faker() + .RuleFor(p => p.Id, f => f.Random.Guid()) + .RuleFor(p => p.FirstName, f => f.Name.FirstName()) + .RuleFor(p => p.LastName, f => f.Name.LastName()) + .RuleFor(p => p.Age, f => f.Random.Int(18, 80)); + } + + [Fact] + public async Task CountResultAsync_WhenSuccessful_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedCount = 5; + repository.CountAsync(Arg.Any()).Returns(expectedCount); + + // Act + var result = await repository.CountResultAsync(); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedCount); + } + + [Fact] + public async Task CountResultAsync_WithExpression_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedCount = 5; + repository.CountAsync(Arg.Any>(), Arg.Any()).Returns(expectedCount); + + // Act + var result = await repository.CountResultAsync(p => p.Age > 30); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedCount); + await repository.Received(1).CountAsync(Arg.Any>(), Arg.Any()); + } + + [Fact] + public async Task CountResultAsync_WithSpecification_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedCount = 5; + repository.CountAsync(Arg.Any>(), Arg.Any()).Returns(expectedCount); + + // Act + var specification = Substitute.For>(); + var result = await repository.CountResultAsync(specification); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedCount); + await repository.Received(1).CountAsync(Arg.Is(specification), Arg.Any()); + } + + [Fact] + public async Task CountResultAsync_WithSpecifications_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedCount = 5; + repository.CountAsync(Arg.Any>>(), Arg.Any()).Returns(expectedCount); + + // Act + var specifications = new List> + { + Substitute.For>(), + Substitute.For>() + }; + var result = await repository.CountResultAsync(specifications); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedCount); + await repository.Received(1).CountAsync(Arg.Is(specifications), Arg.Any()); + } + + [Fact] + public async Task CountResultAsync_WhenExceptionThrown_ReturnsFailureResult() + { + // Arrange + var repository = Substitute.For>(); + repository.CountAsync(Arg.Any()) + .Throws(new Exception("Test exception")); + + // Act + var result = await repository.CountResultAsync(); + + // Assert + result.IsFailure.ShouldBeTrue(); + result.Errors.ShouldNotBeEmpty(); + result.Errors.First().ShouldBeOfType(); + } + + [Fact] + public async Task FindAllIdsResultAsync_WhenSuccessful_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedIds = this.personFaker.Generate(3).Select(p => p.Id).ToList(); + repository.FindAllIdsAsync( + Arg.Any>(), + Arg.Any()) + .Returns(expectedIds); + + // Act + var result = await repository.FindAllIdsResultAsync(); + + // Assert + result.IsSuccess.ShouldBeTrue(); + // result.Value.ShouldBe(expectedIds); + } + + [Fact] + public async Task FindAllIdsResultAsync_WithGuidIds_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedIds = this.personFaker.Generate(3).Select(p => p.Id).ToList(); + + repository.ProjectAllAsync( + Arg.Any>>(), + Arg.Any>(), + Arg.Any()) + .Returns(expectedIds.Cast()); + + // Act + var result = await repository.FindAllIdsResultAsync(); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedIds); + } + + [Fact] + public async Task FindAllIdsResultAsync_WithEmptyResult_ReturnsEmptyList() + { + // Arrange + var repository = Substitute.For>(); + + repository.ProjectAllAsync( + Arg.Any>>(), + Arg.Any>(), + Arg.Any()) + .Returns([]); + + // Act + var result = await repository.FindAllIdsResultAsync(); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBeEmpty(); + } + + [Fact] + public async Task FindAllIdsResultAsync_WithExpression_AppliesExpressionCorrectly() + { + // Arrange + var repository = Substitute.For>(); + var expectedIds = this.personFaker.Generate(3).Select(p => p.Id).ToList(); + + repository.ProjectAllAsync( + Arg.Any>(), + Arg.Any>>(), + Arg.Any>(), + Arg.Any()) + .Returns(expectedIds); + + // Act + var result = await repository.FindAllIdsResultAsync(p => p.Age > 30); + + // Assert + result.IsSuccess.ShouldBeTrue(); + //result.Value.ShouldBe(expectedIds); + // await repository.Received(1).ProjectAllAsync( + // Arg.Any>(), + // Arg.Any>>(), + // Arg.Any>(), + // Arg.Any()); + } + + [Fact] + public async Task FindAllIdsResultAsync_WithSpecification_AppliesSpecificationCorrectly() + { + // Arrange + var repository = Substitute.For>(); + var expectedIds = this.personFaker.Generate(3).Select(p => p.Id).ToList(); + var specification = Substitute.For>(); + + repository.ProjectAllAsync( + Arg.Any>>(), + Arg.Any>(), + Arg.Any()) + .Returns(expectedIds); + + // Act + var result = await repository.FindAllIdsResultAsync(specification); + + // Assert + result.IsSuccess.ShouldBeTrue(); + // result.Value.ShouldBe(expectedIds); + // await repository.Received(1).ProjectAllAsync( + // Arg.Any>>(), + // Arg.Is>(opt => opt.Order == null), + // Arg.Any()); + } + + [Fact] + public async Task FindAllIdsResultAsync_WithFindOptions_AppliesOptionsCorrectly() + { + // Arrange + var repository = Substitute.For>(); + var expectedIds = this.personFaker.Generate(3).Select(p => p.Id).ToList(); + var findOptions = new FindOptions { Skip = 5, Take = 10 }; + + repository.ProjectAllAsync( + Arg.Any>>(), + Arg.Any>(), + Arg.Any()) + .Returns(expectedIds.Cast()); + + // Act + var result = await repository.FindAllIdsResultAsync(findOptions); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedIds); + await repository.Received(1).ProjectAllAsync( + Arg.Any>>(), + Arg.Is>(opt => opt.Skip == 5 && opt.Take == 10), + Arg.Any()); + } + + [Fact] + public async Task FindAllIdsResultAsync_WhenExceptionThrown_ReturnsFailureResult() + { + // Arrange + var repository = Substitute.For>(); + repository.ProjectAllAsync( + Arg.Any>>(), + Arg.Any>(), + Arg.Any()) + .Throws(new Exception("Test exception")); + + // Act + var result = await repository.FindAllIdsResultAsync(); + + // Assert + result.IsFailure.ShouldBeTrue(); + result.Errors.ShouldNotBeEmpty(); + result.Errors.First().ShouldBeOfType(); + } + + [Fact] + public async Task FindOneResultAsync_WithId_WhenEntityFound_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPerson = this.personFaker.Generate(); + repository.FindOneAsync(Arg.Any(), Arg.Any>(), Arg.Any()) + .Returns(expectedPerson); + + // Act + var result = await repository.FindOneResultAsync(expectedPerson.Id); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPerson); + await repository.Received(1).FindOneAsync(Arg.Is(expectedPerson.Id), Arg.Any>(), Arg.Any()); + } + + [Fact] + public async Task FindOneResultAsync_WithId_WhenEntityNotFound_ReturnsFailureResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindOneAsync(Arg.Any(), Arg.Any>(), Arg.Any()) + .Returns((PersonStub)null); + + // Act + var result = await repository.FindOneResultAsync(Guid.NewGuid()); + + // Assert + result.IsFailure.ShouldBeTrue(); + result.Errors.ShouldContain(e => e is NotFoundResultError); + } + + [Fact] + public async Task FindOneResultAsync_WithExpression_WhenEntityFound_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPerson = this.personFaker.Generate(); + repository.FindOneAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns(expectedPerson); + + // Act + var result = await repository.FindOneResultAsync(p => p.Id == expectedPerson.Id); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPerson); + await repository.Received(1).FindOneAsync(Arg.Any>(), Arg.Any>(), Arg.Any()); + } + + [Fact] + public async Task FindOneResultAsync_WithExpression_WhenEntityNotFound_ReturnsFailureResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindOneAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns((PersonStub)null); + + // Act + var result = await repository.FindOneResultAsync(p => p.Age > 100); + + // Assert + result.IsFailure.ShouldBeTrue(); + result.Errors.ShouldContain(e => e is NotFoundResultError); + } + + [Fact] + public async Task FindOneResultAsync_WithSpecification_WhenEntityFound_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPerson = this.personFaker.Generate(); + repository.FindOneAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns(expectedPerson); + + // Act + var specification = Substitute.For>(); + var result = await repository.FindOneResultAsync(specification); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPerson); + await repository.Received(1).FindOneAsync(Arg.Is(specification), Arg.Any>(), Arg.Any()); + } + + [Fact] + public async Task FindOneResultAsync_WithSpecification_WhenEntityNotFound_ReturnsFailureResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindOneAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns((PersonStub)null); + + // Act + var specification = Substitute.For>(); + var result = await repository.FindOneResultAsync(specification); + + // Assert + result.IsFailure.ShouldBeTrue(); + result.Errors.ShouldContain(e => e is NotFoundResultError); + } + + [Fact] + public async Task FindOneResultAsync_WithSpecifications_WhenEntityFound_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPerson = this.personFaker.Generate(); + repository.FindOneAsync(Arg.Any>>(), Arg.Any>(), Arg.Any()) + .Returns(expectedPerson); + + // Act + var specifications = new List> + { + Substitute.For>(), + Substitute.For>() + }; + var result = await repository.FindOneResultAsync(specifications); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPerson); + await repository.Received(1).FindOneAsync(Arg.Is(specifications), Arg.Any>(), Arg.Any()); + } + + [Fact] + public async Task FindOneResultAsync_WithSpecifications_WhenEntityNotFound_ReturnsFailureResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindOneAsync(Arg.Any>>(), Arg.Any>(), Arg.Any()) + .Returns((PersonStub)null); + + // Act + var specifications = new List> + { + Substitute.For>(), + Substitute.For>() + }; + var result = await repository.FindOneResultAsync(specifications); + + // Assert + result.IsFailure.ShouldBeTrue(); + result.Errors.ShouldContain(e => e is NotFoundResultError); + } + + [Fact] + public async Task FindOneResultAsync_WhenExceptionThrown_ReturnsFailureResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindOneAsync(Arg.Any(), Arg.Any>(), Arg.Any()) + .Throws(new Exception("Test exception")); + + // Act + var result = await repository.FindOneResultAsync(Guid.NewGuid()); + + // Assert + result.IsFailure.ShouldBeTrue(); + result.Errors.ShouldNotBeEmpty(); + result.Errors.First().ShouldBeOfType(); + } + + [Fact] + public async Task FindAllResultAsync_WhenEntitiesFound_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + repository.FindAllAsync(Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var result = await repository.FindAllResultAsync(); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + await repository.Received(1).FindAllAsync(Arg.Any>(), Arg.Any()); + } + + [Fact] + public async Task FindAllResultAsync_WhenNoEntitiesFound_ReturnsEmptySuccessResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindAllAsync(Arg.Any>(), Arg.Any()) + .Returns(new List()); + + // Act + var result = await repository.FindAllResultAsync(); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBeEmpty(); + } + + [Fact] + public async Task FindAllResultAsync_WithExpression_WhenEntitiesFound_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + repository.FindAllAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var result = await repository.FindAllResultAsync(p => p.Age > 20); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + await repository.Received(1).FindAllAsync(Arg.Any>(), Arg.Any>(), Arg.Any()); + } + + [Fact] + public async Task FindAllResultAsync_WithExpression_WhenNoEntitiesFound_ReturnsEmptySuccessResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindAllAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns(new List()); + + // Act + var result = await repository.FindAllResultAsync(p => p.Age > 100); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBeEmpty(); + } + + [Fact] + public async Task FindAllResultAsync_WithSpecification_WhenEntitiesFound_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + repository.FindAllAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var specification = Substitute.For>(); + var result = await repository.FindAllResultAsync(specification); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + await repository.Received(1).FindAllAsync(Arg.Is(specification), Arg.Any>(), Arg.Any()); + } + + [Fact] + public async Task FindAllResultAsync_WithSpecification_WhenNoEntitiesFound_ReturnsEmptySuccessResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindAllAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns(new List()); + + // Act + var specification = Substitute.For>(); + var result = await repository.FindAllResultAsync(specification); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBeEmpty(); + } + + [Fact] + public async Task FindAllResultAsync_WithSpecifications_WhenEntitiesFound_ReturnsSuccessResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + repository.FindAllAsync(Arg.Any>>(), Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var specifications = new List> + { + Substitute.For>(), + Substitute.For>() + }; + var result = await repository.FindAllResultAsync(specifications); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + await repository.Received(1).FindAllAsync(Arg.Is(specifications), Arg.Any>(), Arg.Any()); + } + + [Fact] + public async Task FindAllResultAsync_WithSpecifications_WhenNoEntitiesFound_ReturnsEmptySuccessResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindAllAsync(Arg.Any>>(), Arg.Any>(), Arg.Any()) + .Returns(new List()); + + // Act + var specifications = new List> + { + Substitute.For>(), + Substitute.For>() + }; + var result = await repository.FindAllResultAsync(specifications); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBeEmpty(); + } + + [Fact] + public async Task FindAllResultAsync_WhenExceptionThrown_ReturnsFailureResult() + { + // Arrange + var repository = Substitute.For>(); + repository.FindAllAsync(Arg.Any>(), Arg.Any()) + .Throws(new Exception("Test exception")); + + // Act + var result = await repository.FindAllResultAsync(); + + // Assert + result.IsFailure.ShouldBeTrue(); + result.Errors.ShouldNotBeEmpty(); + result.Errors.First().ShouldBeOfType(); + } + + [Fact] + public async Task FindAllResultAsync_WithFindOptions_AppliesOptionsCorrectly() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + repository.FindAllAsync(Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var findOptions = new FindOptions + { + Skip = 5, + Take = 10, + NoTracking = true + }; + var result = await repository.FindAllResultAsync(findOptions); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + await repository.Received(1).FindAllAsync(Arg.Is>( + opt => opt.Skip == 5 && opt.Take == 10 && opt.NoTracking), + Arg.Any()); + } + + [Fact] + public async Task FindAllPagedResultAsync_WithStringOrdering_ReturnsPagedResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + var totalCount = 10; + repository.CountAsync(Arg.Any()).Returns(totalCount); + repository.FindAllAsync(Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var result = await repository.FindAllPagedResultAsync( + ordering: "Age ascending", + page: 1, + pageSize: 3); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + result.TotalCount.ShouldBe(totalCount); + result.CurrentPage.ShouldBe(1); + result.PageSize.ShouldBe(3); + await repository.Received(1).FindAllAsync( + Arg.Is>(opt => + opt.Order != null && + opt.Skip == 0 && + opt.Take == 3), + Arg.Any()); + } + + [Fact] + public async Task FindAllPagedResultAsync_WithExpressionOrdering_ReturnsPagedResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + var totalCount = 10; + repository.CountAsync(Arg.Any()).Returns(totalCount); + repository.FindAllAsync(Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var result = await repository.FindAllPagedResultAsync( + orderingExpression: p => p.Age, + page: 2, + pageSize: 3, + orderDirection: OrderDirection.Descending); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + result.TotalCount.ShouldBe(totalCount); + result.CurrentPage.ShouldBe(2); + result.PageSize.ShouldBe(3); + await repository.Received(1).FindAllAsync( + Arg.Is>(opt => + opt.Order != null && + opt.Skip == 3 && + opt.Take == 3), + Arg.Any()); + } + + [Fact] + public async Task FindAllPagedResultAsync_WithExpression_ReturnsPagedResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + var totalCount = 10; + repository.CountAsync(Arg.Any>(), Arg.Any()).Returns(totalCount); + repository.FindAllAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var result = await repository.FindAllPagedResultAsync( + p => p.Age > 30, + ordering: "LastName descending", + page: 1, + pageSize: 3); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + result.TotalCount.ShouldBe(totalCount); + result.CurrentPage.ShouldBe(1); + result.PageSize.ShouldBe(3); + await repository.Received(1).FindAllAsync( + Arg.Any>(), + Arg.Is>(opt => + opt.Order != null && + opt.Skip == 0 && + opt.Take == 3), + Arg.Any()); + } + + [Fact] + public async Task FindAllPagedResultAsync_WithSpecification_ReturnsPagedResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + var totalCount = 10; + repository.CountAsync(Arg.Any>(), Arg.Any()).Returns(totalCount); + repository.FindAllAsync(Arg.Any>(), Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var specification = Substitute.For>(); + var result = await repository.FindAllPagedResultAsync( + specification, + ordering: "FirstName ascending", + page: 2, + pageSize: 3); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + result.TotalCount.ShouldBe(totalCount); + result.CurrentPage.ShouldBe(2); + result.PageSize.ShouldBe(3); + await repository.Received(1).FindAllAsync( + Arg.Is(specification), + Arg.Is>(opt => + opt.Order != null && + opt.Skip == 3 && + opt.Take == 3), + Arg.Any()); + } + + [Fact] + public async Task FindAllPagedResultAsync_WithSpecifications_ReturnsPagedResult() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + var totalCount = 10; + repository.CountAsync(Arg.Any>>(), Arg.Any()).Returns(totalCount); + repository.FindAllAsync(Arg.Any>>(), Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var specifications = new List> + { + Substitute.For>(), + Substitute.For>() + }; + var result = await repository.FindAllPagedResultAsync( + specifications, + ordering: "Age ascending", + page: 3, + pageSize: 5); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + result.TotalCount.ShouldBe(totalCount); + result.CurrentPage.ShouldBe(3); + result.PageSize.ShouldBe(5); + await repository.Received(1).FindAllAsync( + Arg.Is(specifications), + Arg.Is>(opt => + opt.Order != null && + opt.Skip == 10 && + opt.Take == 5), + Arg.Any()); + } + + [Fact] + public async Task FindAllPagedResultAsync_WithIncludePath_AppliesInclude() + { + // Arrange + var repository = Substitute.For>(); + var expectedPersons = this.personFaker.Generate(3); + var totalCount = 10; + repository.CountAsync(Arg.Any()).Returns(totalCount); + repository.FindAllAsync(Arg.Any>(), Arg.Any()) + .Returns(expectedPersons); + + // Act + var result = await repository.FindAllPagedResultAsync( + ordering: "Age ascending", + page: 1, + pageSize: 3, + includePath: "RelatedEntity"); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + await repository.Received(1).FindAllAsync( + Arg.Is>(opt => + opt.Include != null), + Arg.Any()); + } + + [Fact] + public async Task FindAllPagedResultAsync_WhenExceptionThrown_ReturnsFailureResult() + { + // Arrange + var repository = Substitute.For>(); + repository.CountAsync(Arg.Any()) + .Throws(new Exception("Test exception")); + + // Act + var result = await repository.FindAllPagedResultAsync( + ordering: "Age ascending", + page: 1, + pageSize: 3); + + // Assert + result.IsFailure.ShouldBeTrue(); + result.Errors.ShouldNotBeEmpty(); + result.Errors.First().ShouldBeOfType(); + } +} \ No newline at end of file diff --git a/tests/Domain.UnitTests/Repositories/RepositoryResultFilterExtensionsTests.cs b/tests/Domain.UnitTests/Repositories/RepositoryResultFilterExtensionsTests.cs new file mode 100644 index 00000000..095b02a2 --- /dev/null +++ b/tests/Domain.UnitTests/Repositories/RepositoryResultFilterExtensionsTests.cs @@ -0,0 +1,193 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Domain.UnitTests; + +using System.Linq.Expressions; +using BridgingIT.DevKit.Domain.Repositories; + +public class RepositoryResultFilterExtensionsTests +{ + private readonly IGenericReadOnlyRepository repository = Substitute.For>(); + + [Fact] + public async Task FindAllPagedResultAsync_WithBasicFilters_ReturnsCorrectResult() + { + // Arrange + var filter = new FilterModel + { + Page = 1, + PageSize = 10, + Filters = [new FilterCriteria { Field = "Age", Operator = FilterOperator.Equal, Value = 18 }], + Orderings = [new FilterOrderCriteria { Field = "LastName", Direction = OrderDirection.Ascending }], + Includes = ["Orders"] + }; + + var expectedPersons = new List + { + new() { FirstName = "John", LastName = "Doe", Age = 30 }, + new() { FirstName = "Jane", LastName = "Smith", Age = 25 } + }; + + this.SetupRepository(expectedPersons, 2); + + // Act + var result = await this.repository.FindAllPagedResultAsync(filter); + + // Assert + result.ShouldNotBeNull(); + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + result.TotalCount.ShouldBe(2); + result.CurrentPage.ShouldBe(1); + result.PageSize.ShouldBe(10); + + // Assert FindOptions + await this.repository.Received(1).FindAllAsync( + Arg.Any>>(), + Arg.Any>(), + // Arg.Is>(options => + // options.Skip == 0 && + // options.Take == 10 && + // options.Orders.Count() == 1 && + // options.Orders.First().Ordering == "LastName ascending" && + // options.Includes.Count() == 1 && + // ((MemberExpression)options.Includes.First().Expression.Body).Member.Name == "Orders" + // ), + Arg.Any() + ); + } + + [Fact] + public async Task FindAllPagedResultAsync_WithCustomSpecificationFilter_ReturnsCorrectResult() + { + // Arrange + var filter = new FilterModel + { + Page = 1, + PageSize = 10, + Filters = + [ + new FilterCriteria + { + CustomType = FilterCustomType.NamedSpecification, + SpecificationName = "IsAdult", + SpecificationArguments = [21] + } + ] + }; + + var expectedPersons = new List + { + new() { FirstName = "John", LastName = "Doe", Age = 30 }, + new() { FirstName = "Jane", LastName = "Smith", Age = 25 } + }; + + this.SetupRepository(expectedPersons, 2); + + // Register the specification + SpecificationResolver.Clear(); + SpecificationResolver.Register("IsAdult"); + + // Act + var result = await this.repository.FindAllPagedResultAsync(filter); + + // Assert + result.ShouldNotBeNull(); + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + result.TotalCount.ShouldBe(2); + + // Assert FindOptions + await this.repository.Received(1).FindAllAsync( + Arg.Any>>(), + Arg.Any>(), + // Arg.Is>(options => + // options.Skip == 0 && + // options.Take == 10 && + // !options.Orders.Any() && + // !options.Includes.Any() + // ), + Arg.Any() + ); + } + + [Fact] + public async Task FindAllPagedResultAsync_WithAnyCustomFilter_ReturnsCorrectResult() + { + // Arrange + var filter = new FilterModel + { + Page = 1, + PageSize = 10, + Filters = + [ + new FilterCriteria + { + Field = "Orders", + Operator = FilterOperator.Any, + Value = new FilterCriteria + { + Field = "TotalAmount", + Operator = FilterOperator.GreaterThan, + Value = 100.0m + } + } + ] + }; + + var expectedPersons = new List + { + new() + { + FirstName = "John", + LastName = "Doe", + Orders = [new OrderStub { TotalAmount = 150.0m }] + }, + new() + { + FirstName = "Jane", + LastName = "Smith", + Orders = [new OrderStub { TotalAmount = 200.0m }] + } + }; + + this.SetupRepository(expectedPersons, 2); + + // Act + var result = await this.repository.FindAllPagedResultAsync(filter); + + // Assert + result.ShouldNotBeNull(); + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedPersons); + result.TotalCount.ShouldBe(2); + + // Assert FindOptions + await this.repository.Received(1).FindAllAsync( + Arg.Any>>(), + Arg.Any>(), + // Arg.Is>(options => + // options.Skip == 0 && + // options.Take == 10 && + // !options.Orders.Any() && + // !options.Includes.Any() + // ), + Arg.Any() + ); + } + + private void SetupRepository(IEnumerable persons, int totalCount) + { + this.repository.CountAsync(Arg.Any>>(), Arg.Any()) + .Returns(totalCount); + + this.repository.FindAllAsync( + Arg.Any>>(), + Arg.Any>(), + Arg.Any()) + .Returns(persons); + } +} \ No newline at end of file diff --git a/tests/Domain.UnitTests/Specifications/SpecificationTests.cs b/tests/Domain.UnitTests/Specifications/SpecificationTests.cs index 66e1f5c4..766d61cd 100644 --- a/tests/Domain.UnitTests/Specifications/SpecificationTests.cs +++ b/tests/Domain.UnitTests/Specifications/SpecificationTests.cs @@ -5,11 +5,14 @@ namespace BridgingIT.DevKit.Domain.UnitTests; +using System.Linq.Dynamic.Core.Exceptions; using BridgingIT.DevKit.Domain; [UnitTest("Domain")] public class SpecificationTests { + private readonly Faker faker = new(); + [Fact] public void Specification_ExpressionCtorIsSatisfiedBy_ShouldReturnTrue() { @@ -292,4 +295,155 @@ public void IsSatisfiedBy_OrSpecificationUnsatisfied_ShouldReturnFalse() // Assert result.ShouldBeFalse(); } + + [Fact] + public void Specification_DynamicExpressionCtorIsSatisfiedBy_ShouldReturnTrue() + { + // Arrange + var firstName = this.faker.Person.FirstName; + var sut = new Specification("FirstName == @0", firstName); + var person = new PersonStub { Id = Guid.NewGuid(), FirstName = firstName }; + + // Act + var result = sut.IsSatisfiedBy(person); + + // Assert + result.ShouldBeTrue(); + } + + [Fact] + public void Specification_DynamicExpressionOrCtorIsSatisfiedBy_ShouldReturnTrue() + { + // Arrange + var firstName = this.faker.Person.FirstName; + var age = 10; + var sut = new Specification("FirstName == @0 OR Age == @1", firstName, age); + var person = new PersonStub { Id = Guid.NewGuid(), FirstName = firstName, Age = age}; + + // Act + var result = sut.IsSatisfiedBy(person); + + // Assert + result.ShouldBeTrue(); + } + + [Fact] + public void Specification_DynamicExpressionAndCtorIsSatisfiedBy_ShouldReturnTrue() + { + // Arrange + var firstName = this.faker.Person.FirstName; + var age = 10; + var sut = new Specification("FirstName == @0 AND Age == @1", firstName, age); + var person = new PersonStub { Id = Guid.NewGuid(), FirstName = firstName, Age = age}; + + // Act + var result = sut.IsSatisfiedBy(person); + + // Assert + result.ShouldBeTrue(); + } + + [Fact] + public void Specification_DynamicExpressionNoValuesCtorIsSatisfiedBy_ShouldReturnTrue() + { + // Arrange + var firstName = this.faker.Person.FirstName; + var sut = new Specification($"FirstName == \"{firstName}\""); + var person = new PersonStub { Id = Guid.NewGuid(), FirstName = firstName }; + + // Act + var result = sut.IsSatisfiedBy(person); + + // Assert + result.ShouldBeTrue(); + } + + [Fact] + public void Specification_DynamicExpressionCtorIsNotSatisfiedBy_ShouldReturnFalse() + { + // Arrange + var firstName = this.faker.Person.FirstName; + var sut = new Specification("FirstName == @0", firstName); + var person = new PersonStub { Id = Guid.NewGuid(), FirstName = "unknown" }; + + // Act + var result = sut.IsSatisfiedBy(person); + + // Assert + result.ShouldBeFalse(); + } + + [Fact] + public void Specification_DynamicExpressionCtorWithMultipleParameters_ShouldWorkCorrectly() + { + // Arrange + var minAge = this.faker.Random.Number(18, 30); + var maxAge = this.faker.Random.Number(31, 60); + var sut = new Specification("Age >= @0 && Age <= @1", minAge, maxAge); + var person = new PersonStub { Id = Guid.NewGuid(), Age = this.faker.Random.Number(minAge, maxAge) }; + + // Act + var result = sut.IsSatisfiedBy(person); + + // Assert + result.ShouldBeTrue(); + } + + [Fact] + public void Specification_DynamicExpressionCtorWithInvalidExpression_ShouldThrowException() + { + // Arrange + var invalidExpression = "InvalidProperty == @0"; + + // Act & Assert + Should.Throw(() + => new Specification(invalidExpression, "value").ToExpression()); + } + + [Fact] + public void Specification_DynamicExpressionCtorWithComplexExpression_ShouldWorkCorrectly() + { + // Arrange + var minAge = this.faker.Random.Number(18, 30); + var firstName = this.faker.Person.FirstName; + var sut = new Specification("Age > @0 || (FirstName == @1 && LastName.StartsWith(\"D\"))", minAge, firstName); + var person = new PersonStub { Id = Guid.NewGuid(), Age = minAge - 1, FirstName = firstName, LastName = "Doe" }; + + // Act + var result = sut.IsSatisfiedBy(person); + + // Assert + result.ShouldBeTrue(); + } + + [Fact] + public void ToExpression_DynamicExpressionCtor_ShouldReturnCorrectExpression() + { + // Arrange + var age = this.faker.Random.Number(18, 60); + var sut = new Specification("Age == @0", age); + + // Act + var expression = sut.ToExpression(); + + // Assert + expression.ShouldNotBeNull(); + expression.Body.NodeType.ShouldBe(System.Linq.Expressions.ExpressionType.Equal); + } + + [Fact] + public void ToPredicate_DynamicExpressionCtor_ShouldReturnCorrectPredicate() + { + // Arrange + var age = this.faker.Random.Number(18, 60); + var sut = new Specification("Age == @0", age); + + // Act + var predicate = sut.ToPredicate(); + + // Assert + predicate.ShouldNotBeNull(); + predicate(new PersonStub { Age = age }).ShouldBeTrue(); + predicate(new PersonStub { Age = age + 1 }).ShouldBeFalse(); + } } \ No newline at end of file diff --git a/tests/Domain.UnitTests/_shared/Stubs.cs b/tests/Domain.UnitTests/_shared/Stubs.cs index d17e197b..63f97041 100644 --- a/tests/Domain.UnitTests/_shared/Stubs.cs +++ b/tests/Domain.UnitTests/_shared/Stubs.cs @@ -5,6 +5,7 @@ namespace BridgingIT.DevKit.Domain.UnitTests; +using System.Linq.Expressions; using Model; public class PersonStub : Entity @@ -14,8 +15,99 @@ public class PersonStub : Entity public string LastName { get; set; } public int Age { get; set; } + + public DateTime BirthDate { get; set; } + + public string Email { get; set; } + + public TimeSpan WorkStartTime { get; set; } + + public EmploymentStatus EmploymentStatus { get; set; } + + public List Orders { get; set; } + + public List Addresses { get; set; } = []; + + public AddressStub BillingAddress { get; set; } +} + +public class OrderStub +{ + public decimal TotalAmount { get; set; } +} + +public enum EmploymentStatus +{ + FullTime, + PartTime, + Contractor, + Unemployed +} + +public class AddressStub : ValueObject +{ + private AddressStub() { } // Private constructor required by EF Core + + private AddressStub(string name, string line1, string line2, string postalCode, string city, string country) + { + this.Name = name; + this.Line1 = line1; + this.Line2 = line2; + this.PostalCode = postalCode; + this.City = city; + this.Country = country; + } + + public string Name { get; } + + public string Line1 { get; } + + public string Line2 { get; } + + public string PostalCode { get; } + + public string City { get; } + + public string Country { get; } + + public static AddressStub Create( + string name, + string line1, + string line2, + string postalCode, + string city, + string country) + { + var address = new AddressStub(name, line1, line2, postalCode, city, country); + if (!IsValid(address)) + { + throw new DomainRuleException("Invalid address"); + } + + return address; + } + + protected override IEnumerable GetAtomicValues() + { + yield return this.Name; + yield return this.Line1; + yield return this.Line2; + yield return this.PostalCode; + yield return this.City; + yield return this.Country; + } + + private static bool IsValid(AddressStub address) + { + return !string.IsNullOrEmpty(address.Name) && + !string.IsNullOrEmpty(address.Line1) && + !string.IsNullOrEmpty(address.PostalCode) && + !string.IsNullOrEmpty(address.Country) && + !string.IsNullOrEmpty(address.Country); + } } + public class PersonDtoStub : AggregateRoot { public Guid Identifier { get; set; } @@ -25,4 +117,20 @@ public class PersonDtoStub : AggregateRoot public string FullName { get; set; } public int Age { get; set; } +} + +public class AdultSpecification(int minAge) : Specification +{ + public override Expression> ToExpression() + { + return person => person.Age >= minAge; + } +} + +public class NameStartsWithSpecification(string prefix) : Specification +{ + public override Expression> ToExpression() + { + return person => person.FirstName.StartsWith(prefix); + } } \ No newline at end of file diff --git a/tests/Domain.UnitTests/packages.lock.json b/tests/Domain.UnitTests/packages.lock.json index 22703f98..a6053b9f 100644 --- a/tests/Domain.UnitTests/packages.lock.json +++ b/tests/Domain.UnitTests/packages.lock.json @@ -137,8 +137,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1428,7 +1428,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1442,8 +1442,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1475,7 +1476,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1492,7 +1493,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.CodeGen": { @@ -1593,11 +1595,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1826,9 +1828,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", @@ -1861,6 +1863,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Net.Http": { "type": "CentralTransitive", "requested": "[4.3.4, )", diff --git a/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/EntityFrameworkGenericRepositoryTestsBase.cs b/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/EntityFrameworkGenericRepositoryTestsBase.cs index 7ca2d959..a31cb043 100644 --- a/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/EntityFrameworkGenericRepositoryTestsBase.cs +++ b/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/EntityFrameworkGenericRepositoryTestsBase.cs @@ -64,10 +64,7 @@ public virtual async Task ExistsAsync_ExistingEntityId_EntityFound() var result = await sut.ExistsAsync(entity.Id); // Assert - this.GetContext() - .Persons.AsNoTracking() - .ToList() - .Count.ShouldBeGreaterThanOrEqualTo(1); + this.GetContext().Persons.AsNoTracking().ToList().Count.ShouldBeGreaterThanOrEqualTo(1); result.ShouldBeTrue(); } @@ -128,23 +125,17 @@ public virtual async Task FindAllAsync_AnyEntitySkipTakeAndOrdered_EntitiesFound new FindOptions(2, 2, new OrderOption(e => e.Age))); // Assert - this.GetContext() - .Persons.AsNoTracking() - .ToList() - .Count.ShouldBeGreaterThanOrEqualTo(5); + this.GetContext().Persons.AsNoTracking().ToList().Count.ShouldBeGreaterThanOrEqualTo(5); results.ShouldNotBeNull(); results.ShouldNotBeEmpty(); - results.Count() - .ShouldBe(2); + results.Count().ShouldBe(2); results.ShouldNotContain(entity1); results.ShouldNotContain(entity2); results.ShouldContain(entity3); results.ShouldContain(entity4); results.ShouldNotContain(entity5); - results.First() - .ShouldBe(entity4); // age 18 - results.Last() - .ShouldBe(entity3); // age 20 + results.First().ShouldBe(entity4); // age 18 + results.Last().ShouldBe(entity3); // age 20 } public virtual async Task FindAllAsync_AnyEntity_EntitiesFound() @@ -162,10 +153,7 @@ public virtual async Task FindAllAsync_AnyEntity_EntitiesFound() var results = await sut.FindAllAsync(); // Assert - this.GetContext() - .Persons.AsNoTracking() - .ToList() - .Count.ShouldBeGreaterThanOrEqualTo(5); + this.GetContext().Persons.AsNoTracking().ToList().Count.ShouldBeGreaterThanOrEqualTo(5); results.ShouldNotBeNull(); results.ShouldNotBeEmpty(); results.ShouldContain(entity1); @@ -205,16 +193,11 @@ public virtual async Task FindAllAsync_EntitySpecifications_EntitiesFound() results.ShouldNotBeNull(); results.ShouldNotBeEmpty(); results.ShouldContain(entity); - results.Count() - .ShouldBe(1); - results.First() - .ShouldNotBeNull(); - results.First() - .Id.ShouldBe(entity.Id); - results.First() - .Locations.ShouldNotBeNull(); - results.First() - .Locations.ShouldNotBeEmpty(); + results.Count().ShouldBe(1); + results.First().ShouldNotBeNull(); + results.First().Id.ShouldBe(entity.Id); + results.First().Locations.ShouldNotBeNull(); + results.First().Locations.ShouldNotBeEmpty(); } public virtual async Task FindAllAsync_EntitySpecification_EntitiesFound() @@ -231,16 +214,11 @@ public virtual async Task FindAllAsync_EntitySpecification_EntitiesFound() results.ShouldNotBeNull(); results.ShouldNotBeEmpty(); results.ShouldContain(entity); - results.Count() - .ShouldBe(1); - results.First() - .ShouldNotBeNull(); - results.First() - .Id.ShouldBe(entity.Id); - results.First() - .Locations.ShouldNotBeNull(); - results.First() - .Locations.ShouldNotBeEmpty(); + results.Count().ShouldBe(1); + results.First().ShouldNotBeNull(); + results.First().Id.ShouldBe(entity.Id); + results.First().Locations.ShouldNotBeNull(); + results.First().Locations.ShouldNotBeEmpty(); } public virtual async Task FindAllPagedAsync_AnyEntity_EntitiesFound() @@ -268,10 +246,7 @@ public virtual async Task FindAllPagedAsync_AnyEntity_EntitiesFound() 2); // Assert - this.GetContext() - .Persons.AsNoTracking() - .ToList() - .Count.ShouldBeGreaterThanOrEqualTo(6); + this.GetContext().Persons.AsNoTracking().ToList().Count.ShouldBeGreaterThanOrEqualTo(6); results.ShouldNotBeNull(); results.ShouldBeSuccess(); results.Value.ShouldNotBeNull(); @@ -281,18 +256,15 @@ public virtual async Task FindAllPagedAsync_AnyEntity_EntitiesFound() results.CurrentPage.ShouldBe(1); results.HasNextPage.ShouldBeTrue(); results.HasPreviousPage.ShouldBeFalse(); - results.Value.Count() - .ShouldBe(2); + results.Value.Count().ShouldBe(2); results.Value.ShouldNotContain(entity1); results.Value.ShouldNotContain(entity2); results.Value.ShouldContain(entity3); results.Value.ShouldContain(entity4); results.Value.ShouldNotContain(entity5); results.Value.ShouldNotContain(entity6); - results.Value.First() - .ShouldBe(entity4); // age 18 - results.Value.Last() - .ShouldBe(entity3); // age 20 + results.Value.First().ShouldBe(entity4); // age 18 + results.Value.Last().ShouldBe(entity3); // age 20 } public virtual async Task FindAllIdsAsync_AnyEntity_ManyFound() @@ -317,16 +289,12 @@ public virtual async Task FindAllIdsAsync_AnyEntity_ManyFound() e.FirstName == entity6.FirstName)); // Assert - this.GetContext() - .Persons.AsNoTracking() - .ToList() - .Count.ShouldBeGreaterThanOrEqualTo(6); + this.GetContext().Persons.AsNoTracking().ToList().Count.ShouldBeGreaterThanOrEqualTo(6); results.ShouldNotBeNull(); results.ShouldBeSuccess(); results.Value.ShouldNotBeNull(); results.Value.ShouldNotBeEmpty(); - results.Value.Count() - .ShouldBe(6); + results.Value.Count().ShouldBe(6); } public virtual async Task FindOneAsync_ExistingEntityByIdSpecification_EntityFound() @@ -464,14 +432,10 @@ public virtual async Task InsertAsync_NewEntity_EntityInserted() existingEntity.Locations.ShouldNotBeNull(); existingEntity.Locations.ShouldNotBeEmpty(); existingEntity.Locations.Count.ShouldBe(4); - existingEntity.Locations[0] - .Id.ShouldNotBe(Guid.Empty); - existingEntity.Locations[1] - .Id.ShouldNotBe(Guid.Empty); - existingEntity.Locations[2] - .Id.ShouldNotBe(Guid.Empty); - existingEntity.Locations[3] - .Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[0].Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[1].Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[2].Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[3].Id.ShouldNotBe(Guid.Empty); } public virtual async Task UpsertAsync_ExistingEntityChildRemoval_EntityUpdated() @@ -534,12 +498,9 @@ public virtual async Task UpsertAsync_ExistingEntityChildRemoval_EntityUpdated() existingEntity.Locations.ShouldNotBeNull(); existingEntity.Locations.ShouldNotBeEmpty(); existingEntity.Locations.Count.ShouldBe(3); - existingEntity.Locations[0] - .Id.ShouldNotBe(Guid.Empty); - existingEntity.Locations[1] - .Id.ShouldNotBe(Guid.Empty); - existingEntity.Locations[2] - .Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[0].Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[1].Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[2].Id.ShouldNotBe(Guid.Empty); existingEntity.Locations.ShouldNotContain(location1); existingEntity.Locations.ShouldNotContain(location2); existingEntity.Locations.ShouldContain(location3); @@ -687,14 +648,10 @@ public virtual async Task UpsertAsync_ExistingEntity_EntityUpdated() existingEntity.Locations.ShouldNotBeNull(); existingEntity.Locations.ShouldNotBeEmpty(); existingEntity.Locations.Count.ShouldBe(4); - existingEntity.Locations[0] - .Id.ShouldNotBe(Guid.Empty); - existingEntity.Locations[1] - .Id.ShouldNotBe(Guid.Empty); - existingEntity.Locations[2] - .Id.ShouldNotBe(Guid.Empty); - existingEntity.Locations[3] - .Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[0].Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[1].Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[2].Id.ShouldNotBe(Guid.Empty); + existingEntity.Locations[3].Id.ShouldNotBe(Guid.Empty); existingEntity.Locations.ShouldContain(entity.Locations[0]); existingEntity.Locations.ShouldContain(location1); existingEntity.Locations.ShouldContain(location2); diff --git a/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/Result/EntityFrameworkGenericRepositoryTestsBase.cs b/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/Result/EntityFrameworkGenericRepositoryTestsBase.cs new file mode 100644 index 00000000..c521e2d3 --- /dev/null +++ b/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/Result/EntityFrameworkGenericRepositoryTestsBase.cs @@ -0,0 +1,751 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Infrastructure.IntegrationTests.EntityFramework; + +using BridgingIT.DevKit.Domain; +using Domain.Repositories; +using Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; + +public abstract class EntityFrameworkGenericRepositoryResultTestsBase +{ + private readonly Faker faker = new(); + + // public virtual async Task DeleteAsync_ByEntity_EntityDeleted() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.DeleteAsync(entity); + // + // // Assert + // result.ShouldBe(RepositoryActionResult.Deleted); + // (await sut.ExistsAsync(entity.Id)).ShouldBe(false); + // } + // + // public virtual async Task DeleteAsync_ByIdAsString_EntityDeleted() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.DeleteAsync(entity.Id.ToString()); + // + // // Assert + // result.ShouldBe(RepositoryActionResult.Deleted); + // (await sut.ExistsAsync(entity.Id)).ShouldBe(false); + // } + // + // public virtual async Task DeleteAsync_ById_EntityDeleted() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.DeleteAsync(entity.Id); + // + // // Assert + // result.ShouldBe(RepositoryActionResult.Deleted); + // (await sut.ExistsAsync(entity.Id)).ShouldBe(false); + // } + // + // public virtual async Task ExistsAsync_ExistingEntityId_EntityFound() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.ExistsAsync(entity.Id); + // + // // Assert + // this.GetContext().Persons.AsNoTracking().ToList().Count.ShouldBeGreaterThanOrEqualTo(1); + // result.ShouldBeTrue(); + // } + // + // public virtual async Task ExistsAsync_NotExistingEntityId_EntityNotFound() + // { + // // Arrange + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.ExistsAsync(Guid.NewGuid()); + // + // // Assert + // result.ShouldBeFalse(); + // } + // + // public virtual async Task FindAllAsync_AnyEntityPagedAndOrdered_EntitiesNotFound() + // { + // // Arrange + // var entity1 = await this.InsertEntityAsync(17); + // var entity2 = await this.InsertEntityAsync(18); + // var entity3 = await this.InsertEntityAsync(20); + // var entity4 = await this.InsertEntityAsync(18); + // var entity5 = await this.InsertEntityAsync(); + // + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var results = await sut.FindAllAsync(new Specification(_ => false), + // new FindOptions(10, 2, new OrderOption(e => e.Age))); + // + // // Assert + // this.GetContext() + // .Persons.AsNoTracking() + // .ToList() + // .Count.ShouldBeGreaterThanOrEqualTo(5); + // results.ShouldNotBeNull(); + // results.ShouldBeEmpty(); + // } + // + // public virtual async Task FindAllAsync_AnyEntitySkipTakeAndOrdered_EntitiesFound() + // { + // // Arrange + // var entity1 = await this.InsertEntityAsync(17); + // var entity2 = await this.InsertEntityAsync(16); + // var entity3 = await this.InsertEntityAsync(20); + // var entity4 = await this.InsertEntityAsync(18); + // var entity5 = await this.InsertEntityAsync(); + // + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var results = await sut.FindAllAsync(new Specification(e => + // e.FirstName == entity1.FirstName || + // e.FirstName == entity2.FirstName || + // e.FirstName == entity3.FirstName || + // e.FirstName == entity4.FirstName || + // e.FirstName == entity5.FirstName), + // new FindOptions(2, 2, new OrderOption(e => e.Age))); + // + // // Assert + // this.GetContext().Persons.AsNoTracking().ToList().Count.ShouldBeGreaterThanOrEqualTo(5); + // results.ShouldNotBeNull(); + // results.ShouldNotBeEmpty(); + // results.Count().ShouldBe(2); + // results.ShouldNotContain(entity1); + // results.ShouldNotContain(entity2); + // results.ShouldContain(entity3); + // results.ShouldContain(entity4); + // results.ShouldNotContain(entity5); + // results.First().ShouldBe(entity4); // age 18 + // results.Last().ShouldBe(entity3); // age 20 + // } + + public virtual async Task FindAllResultAsync_AnyEntity_EntitiesFound() + { + // Arrange + var entity1 = await this.InsertEntityAsync(); + var entity2 = await this.InsertEntityAsync(); + var entity3 = await this.InsertEntityAsync(); + var entity4 = await this.InsertEntityAsync(); + var entity5 = await this.InsertEntityAsync(); + + var sut = this.CreateRepository(this.GetContext()); + + // Act + var result = await sut.FindAllResultAsync(); + + // Assert + result.ShouldNotBeNull(); + result.ShouldBeSuccess(); + result.Value.ShouldNotBeEmpty(); + result.Value.Select(e => e.Id).ShouldContain(entity1.Id); + result.Value.Select(e => e.Id).ShouldContain(entity2.Id); + result.Value.Select(e => e.Id).ShouldContain(entity3.Id); + result.Value.Select(e => e.Id).ShouldContain(entity4.Id); + result.Value.Select(e => e.Id).ShouldContain(entity5.Id); + } + + // public virtual async Task FindAllAsync_EntityInvalidSpecification_EntitiesNotFound() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var results = await sut.FindAllAsync(new PersonByEmailSpecification("UNKNOWN")); + // + // // Assert + // results.ShouldNotBeNull(); + // results.ShouldBeEmpty(); + // results.ShouldNotContain(entity); + // } + // + // public virtual async Task FindAllAsync_EntitySpecifications_EntitiesFound() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var results = await sut.FindAllAsync(new List> { new PersonByEmailSpecification(entity.Email.Value), new PersonIsAdultSpecification() }); + // + // // Assert + // results.ShouldNotBeNull(); + // results.ShouldNotBeEmpty(); + // results.ShouldContain(entity); + // results.Count().ShouldBe(1); + // results.First().ShouldNotBeNull(); + // results.First().Id.ShouldBe(entity.Id); + // results.First().Locations.ShouldNotBeNull(); + // results.First().Locations.ShouldNotBeEmpty(); + // } + // + // public virtual async Task FindAllAsync_EntitySpecification_EntitiesFound() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var results = await sut.FindAllAsync(new PersonByEmailSpecification(entity.Email)); + // + // // Assert + // results.ShouldNotBeNull(); + // results.ShouldNotBeEmpty(); + // results.ShouldContain(entity); + // results.Count().ShouldBe(1); + // results.First().ShouldNotBeNull(); + // results.First().Id.ShouldBe(entity.Id); + // results.First().Locations.ShouldNotBeNull(); + // results.First().Locations.ShouldNotBeEmpty(); + // } + // + // public virtual async Task FindAllPagedAsync_AnyEntity_EntitiesFound() + // { + // // Arrange + // var entity1 = await this.InsertEntityAsync(22); + // var entity2 = await this.InsertEntityAsync(21); + // var entity3 = await this.InsertEntityAsync(20); + // var entity4 = await this.InsertEntityAsync(18); + // var entity5 = await this.InsertEntityAsync(); + // var entity6 = await this.InsertEntityAsync(25); + // + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var results = await sut.FindAllPagedResultAsync(new Specification(e => + // e.FirstName == entity1.FirstName || + // e.FirstName == entity2.FirstName || + // e.FirstName == entity3.FirstName || + // e.FirstName == entity4.FirstName || + // e.FirstName == entity5.FirstName || + // e.FirstName == entity6.FirstName), + // nameof(PersonStub.Age), + // 1, + // 2); + // + // // Assert + // this.GetContext().Persons.AsNoTracking().ToList().Count.ShouldBeGreaterThanOrEqualTo(6); + // results.ShouldNotBeNull(); + // results.ShouldBeSuccess(); + // results.Value.ShouldNotBeNull(); + // results.Value.ShouldNotBeEmpty(); + // results.TotalCount.ShouldBe(6); + // results.TotalPages.ShouldBe(3); + // results.CurrentPage.ShouldBe(1); + // results.HasNextPage.ShouldBeTrue(); + // results.HasPreviousPage.ShouldBeFalse(); + // results.Value.Count().ShouldBe(2); + // results.Value.ShouldNotContain(entity1); + // results.Value.ShouldNotContain(entity2); + // results.Value.ShouldContain(entity3); + // results.Value.ShouldContain(entity4); + // results.Value.ShouldNotContain(entity5); + // results.Value.ShouldNotContain(entity6); + // results.Value.First().ShouldBe(entity4); // age 18 + // results.Value.Last().ShouldBe(entity3); // age 20 + // } + // + // public virtual async Task FindAllIdsAsync_AnyEntity_ManyFound() + // { + // // Arrange + // var entity1 = await this.InsertEntityAsync(22); + // var entity2 = await this.InsertEntityAsync(21); + // var entity3 = await this.InsertEntityAsync(20); + // var entity4 = await this.InsertEntityAsync(18); + // var entity5 = await this.InsertEntityAsync(); + // var entity6 = await this.InsertEntityAsync(25); + // + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var results = await sut.FindAllIdsResultAsync(new Specification(e => + // e.FirstName == entity1.FirstName || + // e.FirstName == entity2.FirstName || + // e.FirstName == entity3.FirstName || + // e.FirstName == entity4.FirstName || + // e.FirstName == entity5.FirstName || + // e.FirstName == entity6.FirstName)); + // + // // Assert + // this.GetContext().Persons.AsNoTracking().ToList().Count.ShouldBeGreaterThanOrEqualTo(6); + // results.ShouldNotBeNull(); + // results.ShouldBeSuccess(); + // results.Value.ShouldNotBeNull(); + // results.Value.ShouldNotBeEmpty(); + // results.Value.Count().ShouldBe(6); + // } + // + // public virtual async Task FindOneAsync_ExistingEntityByIdSpecification_EntityFound() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.FindOneAsync(new Specification(e => e.Id == entity.Id)); + // + // // Assert + // result.ShouldNotBeNull(); + // result.Id.ShouldBe(entity.Id); + // result.FirstName.ShouldBe(entity.FirstName); + // result.LastName.ShouldBe(entity.LastName); + // result.Locations.ShouldNotBeNull(); + // result.Locations.ShouldNotBeEmpty(); + // result.Status.Equals(Status.Active); + // } + // + // public virtual async Task FindOneAsync_ExistingEntityBySpecification_EntityFound() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.FindOneAsync(new Specification(e => e.FirstName == entity.FirstName)); + // + // // Assert + // result.ShouldNotBeNull(); + // result.Id.ShouldBe(entity.Id); + // result.FirstName.ShouldBe(entity.FirstName); + // result.LastName.ShouldBe(entity.LastName); + // result.Locations.ShouldNotBeNull(); + // result.Locations.ShouldNotBeEmpty(); + // result.Status.Equals(Status.Active); + // } + // + // public virtual async Task FindOneAsync_ExistingEntityIdAsString_EntityFound() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.FindOneAsync(entity.Id.ToString()); + // + // // Assert + // result.ShouldNotBeNull(); + // result.Id.ShouldBe(entity.Id); + // result.FirstName.ShouldBe(entity.FirstName); + // result.LastName.ShouldBe(entity.LastName); + // result.Locations.ShouldNotBeNull(); + // result.Locations.ShouldNotBeEmpty(); + // result.Status.Equals(Status.Active); + // } + // + // public virtual async Task FindOneAsync_ExistingEntityId_EntityFound() + // { + // // Arrange + // var entity = await this.InsertEntityAsync(); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.FindOneAsync(entity.Id); + // + // // Assert + // result.ShouldNotBeNull(); + // result.Id.ShouldBe(entity.Id); + // result.FirstName.ShouldBe(entity.FirstName); + // result.LastName.ShouldBe(entity.LastName); + // result.Locations.ShouldNotBeNull(); + // result.Locations.ShouldNotBeEmpty(); + // result.Status.Equals(Status.Active); + // } + // + // public virtual async Task FindOneAsync_NotExistingEntityId_EntityNotFound() + // { + // // Arrange + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.FindOneAsync(Guid.NewGuid()); + // + // // Assert + // result.ShouldBeNull(); + // } + // + // public virtual async Task InsertAsync_NewEntity_EntityInserted() + // { + // // Arrange + // var faker = new Faker(); + // var ticks = DateTime.UtcNow.Ticks; + // var entity = new PersonStub($"John {ticks}", $"Doe {ticks}", $"John.Doe{ticks}@gmail.com", 24, Status.Active); + // entity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // entity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // entity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // entity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // var result = await sut.UpsertAsync(entity); + // + // // Assert + // result.action.ShouldBe(RepositoryActionResult.Inserted); + // var existingEntity = await sut.FindOneAsync(entity.Id); + // existingEntity.ShouldNotBeNull(); + // existingEntity.Id.ShouldBe(entity.Id); + // existingEntity.Id.ShouldNotBe(Guid.Empty); + // existingEntity.FirstName.ShouldBe(entity.FirstName); + // existingEntity.Status.Equals(Status.Active); + // existingEntity.LastName.ShouldBe(entity.LastName); + // existingEntity.Locations.ShouldNotBeNull(); + // existingEntity.Locations.ShouldNotBeEmpty(); + // existingEntity.Locations.Count.ShouldBe(4); + // existingEntity.Locations[0].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations[1].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations[2].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations[3].Id.ShouldNotBe(Guid.Empty); + // } + // + // public virtual async Task UpsertAsync_ExistingEntityChildRemoval_EntityUpdated() + // { + // // Arrange + // var faker = new Faker(); + // var ticks = DateTime.UtcNow.Ticks; + // var entity = new PersonStub($"John {ticks}", $"Doe {ticks}", $"John.Doe{ticks}@gmail.com", 24); + // var location1 = LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country()); + // entity.AddLocation(location1); + // var location2 = LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country()); + // entity.AddLocation(location2); + // var location3 = LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country()); + // entity.AddLocation(location3); + // var location4 = LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country()); + // entity.AddLocation(location4); + // var sut = this.CreateRepository(this.GetContext()); + // + // // Act + // await sut.UpsertAsync(entity); + // entity.RemoveLocation(location1); + // entity.RemoveLocation(location2); + // var location5 = LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country()); + // entity.AddLocation(location5); + // var result = await sut.UpsertAsync(entity); + // + // // Assert + // result.action.ShouldBe(RepositoryActionResult.Updated); + // var existingEntity = await sut.FindOneAsync(entity.Id); + // existingEntity.ShouldNotBeNull(); + // existingEntity.Id.ShouldBe(entity.Id); + // existingEntity.Id.ShouldNotBe(Guid.Empty); + // existingEntity.FirstName.ShouldBe(entity.FirstName); + // existingEntity.LastName.ShouldBe(entity.LastName); + // existingEntity.Locations.ShouldNotBeNull(); + // existingEntity.Locations.ShouldNotBeEmpty(); + // existingEntity.Locations.Count.ShouldBe(3); + // existingEntity.Locations[0].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations[1].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations[2].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations.ShouldNotContain(location1); + // existingEntity.Locations.ShouldNotContain(location2); + // existingEntity.Locations.ShouldContain(location3); + // existingEntity.Locations.ShouldContain(location4); + // existingEntity.Locations.ShouldContain(location5); + // } + // + // public virtual async Task UpsertAsync_ExistingEntityDisconnected_EntityUpdated() + // { + // // Arrange + // var faker = new Faker(); + // var entity = await this.InsertEntityAsync(); + // using var context = this.GetContext(null, true); + // var sut = this.CreateRepository(context); + // var ticks = DateTime.UtcNow.Ticks; + // + // // Act + // var disconnectedEntity = new PersonStub + // { + // Id = entity.Id, // has same id as entity > should update existing entity + // FirstName = $"Mary {ticks}", + // LastName = $"Jane {ticks}", + // Age = entity.Age + // }; + // disconnectedEntity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // disconnectedEntity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // disconnectedEntity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // var result = await sut.UpsertAsync(disconnectedEntity); + // + // // Assert + // result.action.ShouldBe(RepositoryActionResult.Updated); + // var existingEntity = await sut.FindOneAsync(entity.Id, new FindOptions { NoTracking = true }); + // existingEntity.ShouldNotBeNull(); + // existingEntity.Id.ShouldBe(disconnectedEntity.Id); + // existingEntity.FirstName.ShouldBe(disconnectedEntity.FirstName); + // existingEntity.LastName.ShouldBe(disconnectedEntity.LastName); + // existingEntity.Age.ShouldBe(disconnectedEntity.Age); + // existingEntity.Locations.ShouldNotBeNull(); + // existingEntity.Locations.ShouldNotBeEmpty(); + // existingEntity.Locations.Count.ShouldBe(4); + // } + // + // public virtual async Task UpsertAsync_ExistingEntityNoTracking_EntityUpdated() + // { + // // Arrange + // var faker = new Faker(); + // var entity = await this.InsertEntityAsync(); + // using var context = this.GetContext(null, true); + // var sut = this.CreateRepository(context); + // var ticks = DateTime.UtcNow.Ticks; + // entity = await sut.FindOneAsync(entity.Id, new FindOptions { NoTracking = true }); + // + // // Act + // entity.FirstName = $"John {ticks}"; + // entity.LastName = $"Doe {ticks}"; + // entity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // entity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // entity.AddLocation(LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country())); + // var result = await sut.UpsertAsync(entity); + // + // // Assert + // result.action.ShouldBe(RepositoryActionResult.Updated); + // var existingEntity = await sut.FindOneAsync(entity.Id, new FindOptions { NoTracking = true }); + // existingEntity.ShouldNotBeNull(); + // existingEntity.Id.ShouldBe(entity.Id); + // existingEntity.FirstName.ShouldBe(entity.FirstName); + // existingEntity.LastName.ShouldBe(entity.LastName); + // existingEntity.Locations.ShouldNotBeNull(); + // existingEntity.Locations.ShouldNotBeEmpty(); + // existingEntity.Locations.Count.ShouldBe(4); + // } + // + // public virtual async Task UpsertAsync_ExistingEntity_EntityUpdated() + // { + // // Arrange + // var faker = new Faker(); + // var entity = await this.InsertEntityAsync(); + // var sut = this.CreateRepository(this.GetContext()); + // var ticks = DateTime.UtcNow.Ticks; + // + // // Act + // entity.FirstName = $"John {ticks}"; + // entity.LastName = $"Doe {ticks}"; + // var location1 = LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country()); + // entity.AddLocation(location1); + // var location2 = LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country()); + // entity.AddLocation(location2); + // var location3 = LocationStub.Create(faker.Company.CompanyName(), + // faker.Address.StreetAddress(), + // faker.Address.BuildingNumber(), + // faker.Address.ZipCode(), + // faker.Address.City(), + // faker.Address.Country()); + // entity.AddLocation(location3); + // var result = await sut.UpsertAsync(entity); + // + // // Assert + // result.action.ShouldBe(RepositoryActionResult.Updated); + // var existingEntity = await sut.FindOneAsync(entity.Id); + // existingEntity.ShouldNotBeNull(); + // existingEntity.Id.ShouldBe(entity.Id); + // existingEntity.Id.ShouldNotBe(Guid.Empty); + // existingEntity.FirstName.ShouldBe(entity.FirstName); + // existingEntity.LastName.ShouldBe(entity.LastName); + // existingEntity.Locations.ShouldNotBeNull(); + // existingEntity.Locations.ShouldNotBeEmpty(); + // existingEntity.Locations.Count.ShouldBe(4); + // existingEntity.Locations[0].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations[1].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations[2].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations[3].Id.ShouldNotBe(Guid.Empty); + // existingEntity.Locations.ShouldContain(entity.Locations[0]); + // existingEntity.Locations.ShouldContain(location1); + // existingEntity.Locations.ShouldContain(location2); + // existingEntity.Locations.ShouldContain(location3); + // } + + public virtual async Task FindAllPagedResultAsync_AnyEntityWithFilter_EntitiesFound() + { + SpecificationResolver.Clear(); + SpecificationResolver.Register("PersonHasEmail"); + + // Arrange + var entity1 = await this.InsertEntityAsync(); + var entity2 = await this.InsertEntityAsync(); + var entity3 = await this.InsertEntityAsync(); + var entity4 = await this.InsertEntityAsync(); + var entity5 = await this.InsertEntityAsync(); + + var filter = new FilterModel + { + Page = 1, + PageSize = 999999, + Filters = + [ + new FilterCriteria // standard filter: equals + { Field = "Age", Operator = FilterOperator.GreaterThanOrEqual, Value = 18, Logic = FilterLogicOperator.Or }, + new FilterCriteria // standard filter: ends with + { Field = "FirstName", Operator = FilterOperator.EndsWith, Value = entity1.FirstName[2..], Logic = FilterLogicOperator.Or }, + new FilterCriteria // standard filter: contains + { Field = "FirstName", Operator = FilterOperator.Contains, Value = entity1.FirstName[..3], Logic = FilterLogicOperator.Or }, + new FilterCriteria // custom filter: named specification + { + CustomType = FilterCustomType.NamedSpecification, + SpecificationName = "PersonHasEmail", + SpecificationArguments = [entity1.Email.Value], + Logic = FilterLogicOperator.Or + }, + new FilterCriteria // custom filter: full text search + { + CustomType = FilterCustomType.FullTextSearch, + CustomParameters = new Dictionary + { + ["searchTerm"] = entity1.FirstName, + ["fields"] = new[] { "FirstName", "LastName" } + } + } + ], + Orderings = [new FilterOrderCriteria { Field = "LastName", Direction = OrderDirection.Ascending }] + }; + + var sut = this.CreateRepository(this.GetContext()); + + // Act + var result = await sut.FindAllPagedResultAsync(filter); + + // Assert + result.ShouldNotBeNull(); + result.ShouldBeSuccess(); + result.Value.ShouldNotBeEmpty(); + result.Value.Select(e => e.Id).ShouldContain(entity1.Id); + result.Value.Select(e => e.Id).ShouldContain(entity2.Id); + result.Value.Select(e => e.Id).ShouldContain(entity3.Id); + result.Value.Select(e => e.Id).ShouldContain(entity4.Id); + result.Value.Select(e => e.Id).ShouldContain(entity5.Id); + } + + protected virtual StubDbContext GetContext(string connectionString = null, bool forceNew = false) + { + return null; + } + + protected IGenericRepository CreateRepository(StubDbContext context) + { + //return new GenericRepositoryLoggingBehavior( + // XunitLoggerFactory.Create(this.output), + // //new GenericRepositoryIncludeDecorator(e => e.Locations, // not needed for OwnedEntities + // new EntityFrameworkGenericRepository(r => r.DbContext(context))); + + return new EntityFrameworkGenericRepository(r => r.DbContext(context)); + } + + protected async Task InsertEntityAsync(int age = 24) + { + var entity = new PersonStub(this.faker.Person.FirstName, this.faker.Person.LastName, this.faker.Person.Email, age, Status.Active); + entity.AddLocation(LocationStub.Create(this.faker.Company.CompanyName(), + this.faker.Address.StreetAddress(), + this.faker.Address.BuildingNumber(), + this.faker.Address.ZipCode(), + this.faker.Address.City(), + this.faker.Address.Country())); + var sut = this.CreateRepository(this.GetContext()); + + return await sut.InsertAsync(entity); + } +} \ No newline at end of file diff --git a/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/Result/EntityFrameworkSqlServerGenericRepositoryResultTests.cs b/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/Result/EntityFrameworkSqlServerGenericRepositoryResultTests.cs new file mode 100644 index 00000000..958370a1 --- /dev/null +++ b/tests/Infrastructure.IntegrationTests/EntityFramework/Repositories/Result/EntityFrameworkSqlServerGenericRepositoryResultTests.cs @@ -0,0 +1,164 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Infrastructure.IntegrationTests.EntityFramework; + +[IntegrationTest("Infrastructure")] +[Collection(nameof(TestEnvironmentCollection))] // https://xunit.net/docs/shared-context#collection-fixture +public class EntityFrameworkSqlServerGenericRepositoryResultTests(ITestOutputHelper output, TestEnvironmentFixture fixture) + : EntityFrameworkGenericRepositoryResultTestsBase +{ + private readonly TestEnvironmentFixture fixture = fixture.WithOutput(output); + private readonly ITestOutputHelper output = output; + + // [Fact] + // public override async Task DeleteAsync_ByEntity_EntityDeleted() + // { + // await base.DeleteAsync_ByEntity_EntityDeleted(); + // } + // + // [Fact] + // public override async Task DeleteAsync_ByIdAsString_EntityDeleted() + // { + // await base.DeleteAsync_ByIdAsString_EntityDeleted(); + // } + // + // [Fact] + // public override async Task DeleteAsync_ById_EntityDeleted() + // { + // await base.DeleteAsync_ById_EntityDeleted(); + // } + // + // [Fact] + // public override async Task ExistsAsync_ExistingEntityId_EntityFound() + // { + // await base.ExistsAsync_ExistingEntityId_EntityFound(); + // } + // + // [Fact] + // public override async Task ExistsAsync_NotExistingEntityId_EntityNotFound() + // { + // await base.ExistsAsync_NotExistingEntityId_EntityNotFound(); + // } + // + // [Fact] + // public override async Task FindAllAsync_AnyEntityPagedAndOrdered_EntitiesNotFound() + // { + // await base.FindAllAsync_AnyEntityPagedAndOrdered_EntitiesNotFound(); + // } + // + // [Fact] + // public override async Task FindAllAsync_AnyEntitySkipTakeAndOrdered_EntitiesFound() + // { + // await base.FindAllAsync_AnyEntitySkipTakeAndOrdered_EntitiesFound(); + // } + + [Fact] + public override async Task FindAllResultAsync_AnyEntity_EntitiesFound() + { + await base.FindAllResultAsync_AnyEntity_EntitiesFound(); + } + + // [Fact] + // public override async Task FindAllAsync_EntityInvalidSpecification_EntitiesNotFound() + // { + // await base.FindAllAsync_EntityInvalidSpecification_EntitiesNotFound(); + // } + // + // [Fact] + // public override async Task FindAllAsync_EntitySpecifications_EntitiesFound() + // { + // await base.FindAllAsync_EntitySpecifications_EntitiesFound(); + // } + // + // [Fact] + // public override async Task FindAllAsync_EntitySpecification_EntitiesFound() + // { + // await base.FindAllAsync_EntitySpecification_EntitiesFound(); + // } + // + // [Fact] + // public override async Task FindAllPagedAsync_AnyEntity_EntitiesFound() + // { + // await base.FindAllPagedAsync_AnyEntity_EntitiesFound(); + // } + // + // [Fact] + // public override async Task FindAllIdsAsync_AnyEntity_ManyFound() + // { + // await base.FindAllIdsAsync_AnyEntity_ManyFound(); + // } + // + // [Fact] + // public override async Task FindOneAsync_ExistingEntityByIdSpecification_EntityFound() + // { + // await base.FindOneAsync_ExistingEntityByIdSpecification_EntityFound(); + // } + // + // [Fact] + // public override async Task FindOneAsync_ExistingEntityBySpecification_EntityFound() + // { + // await base.FindOneAsync_ExistingEntityBySpecification_EntityFound(); + // } + // + // [Fact] + // public override async Task FindOneAsync_ExistingEntityIdAsString_EntityFound() + // { + // await base.FindOneAsync_ExistingEntityIdAsString_EntityFound(); + // } + // + // [Fact] + // public override async Task FindOneAsync_ExistingEntityId_EntityFound() + // { + // await base.FindOneAsync_ExistingEntityId_EntityFound(); + // } + // + // [Fact] + // public override async Task FindOneAsync_NotExistingEntityId_EntityNotFound() + // { + // await base.FindOneAsync_NotExistingEntityId_EntityNotFound(); + // } + // + // [Fact] + // public override async Task InsertAsync_NewEntity_EntityInserted() + // { + // await base.InsertAsync_NewEntity_EntityInserted(); + // } + // + // [Fact] + // public override async Task UpsertAsync_ExistingEntityChildRemoval_EntityUpdated() + // { + // await base.UpsertAsync_ExistingEntityChildRemoval_EntityUpdated(); + // } + // + // [Fact] + // public override async Task UpsertAsync_ExistingEntityDisconnected_EntityUpdated() + // { + // await base.UpsertAsync_ExistingEntityDisconnected_EntityUpdated(); + // } + // + // [Fact] + // public override async Task UpsertAsync_ExistingEntityNoTracking_EntityUpdated() + // { + // await base.UpsertAsync_ExistingEntityNoTracking_EntityUpdated(); + // } + // + // [Fact] + // public override async Task UpsertAsync_ExistingEntity_EntityUpdated() + // { + // await base.UpsertAsync_ExistingEntity_EntityUpdated(); + // } + + [Fact] + public override async Task FindAllPagedResultAsync_AnyEntityWithFilter_EntitiesFound() + { + await base.FindAllPagedResultAsync_AnyEntityWithFilter_EntitiesFound(); + } + + protected override StubDbContext GetContext(string connectionString = null, bool forceNew = false) + { + return this.fixture.EnsureSqlServerDbContext(this.output, forceNew); + } +} \ No newline at end of file diff --git a/tests/Infrastructure.IntegrationTests/_shared/Stubs.cs b/tests/Infrastructure.IntegrationTests/_shared/Stubs.cs index 1969d4e9..36777b69 100644 --- a/tests/Infrastructure.IntegrationTests/_shared/Stubs.cs +++ b/tests/Infrastructure.IntegrationTests/_shared/Stubs.cs @@ -61,8 +61,8 @@ public PersonStub RemoveLocation(LocationStub location) public class Status(int id, string value, string code, string description) : Enumeration(id, value) { - public static Status Active = new(1, "Active", "ACT", "Lorem Ipsum"); - public static Status Inactive = new(2, "Inactive", "INA", "Lorem Ipsum"); + public static readonly Status Active = new(1, "Active", "ACT", "Lorem Ipsum"); + public static readonly Status Inactive = new(2, "Inactive", "INA", "Lorem Ipsum"); public string Code { get; } = code; diff --git a/tests/Infrastructure.IntegrationTests/packages.lock.json b/tests/Infrastructure.IntegrationTests/packages.lock.json index 9c9785f6..57c6d2ca 100644 --- a/tests/Infrastructure.IntegrationTests/packages.lock.json +++ b/tests/Infrastructure.IntegrationTests/packages.lock.json @@ -278,8 +278,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1891,7 +1891,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1905,8 +1905,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1938,7 +1939,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1955,7 +1956,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.CodeGen": { @@ -2347,11 +2349,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -2666,9 +2668,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", diff --git a/tests/Infrastructure.UnitTests/packages.lock.json b/tests/Infrastructure.UnitTests/packages.lock.json index ed5768a0..3568061c 100644 --- a/tests/Infrastructure.UnitTests/packages.lock.json +++ b/tests/Infrastructure.UnitTests/packages.lock.json @@ -125,8 +125,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1613,7 +1613,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1627,8 +1627,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1660,7 +1661,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1677,7 +1678,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.CodeGen": { @@ -1890,11 +1892,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -2167,9 +2169,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", diff --git a/tests/Presentation.UnitTests/Web/Filtering/HttpContextExtensionsTests.cs b/tests/Presentation.UnitTests/Web/Filtering/HttpContextExtensionsTests.cs new file mode 100644 index 00000000..a37b5cee --- /dev/null +++ b/tests/Presentation.UnitTests/Web/Filtering/HttpContextExtensionsTests.cs @@ -0,0 +1,191 @@ +// MIT-License +// Copyright BridgingIT GmbH - All Rights Reserved +// Use of this source code is governed by an MIT-style license that can be +// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license + +namespace BridgingIT.DevKit.Presentation.UnitTests.Web; + +using System.Text; +using BridgingIT.DevKit.Common; +using Bogus; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using NSubstitute; +using Shouldly; +using Xunit; + +public class HttpContextExtensionsTests +{ + private readonly Faker faker = new(); + + [Fact] + public async Task FromQueryFilter_NullContext_ThrowsArgumentNullException() + { + // Arrange + HttpContext context = null; + + // Act & Assert + await Should.ThrowAsync(async () => await context.FromQueryFilterAsync()); + } + + [Fact] + public async Task FromQueryFilter_NoFilterParameter_ReturnsDefault() + { + // Arrange + var context = CreateMockContextWithQuery(new Dictionary()); + + // Act + var result = await context.FromQueryFilterAsync(); + + // Assert + result.ShouldBeNull(); + } + + [Fact] + public async Task FromQueryFilter_ValidFilterJson_ReturnsDeserializedModel() + { + // Arrange + var filterModel = this.CreateValidFilterModel(); + var json = new SystemTextJsonSerializer().SerializeToString(filterModel); + var queryParams = new Dictionary { { "filter", json } }; + var context = CreateMockContextWithQuery(queryParams); + + // Act + var result = await context.FromQueryFilterAsync(); + + // Assert + result.ShouldNotBeNull(); + result.Page.ShouldBe(filterModel.Page); + result.PageSize.ShouldBe(filterModel.PageSize); + } + + // commented due to try/catch trying to write to responsestream which causes an error (mock setup) + // [Fact] + // public async Task FromQueryFilter_InvalidJson_SetsBadRequestAndReturnsDefault() + // { + // // Arrange + // var queryParams = new Dictionary { { "filter", "invalid-json" } }; + // var context = CreateMockContextWithQuery(queryParams); + // + // // Act + // var result = await context.FromQueryFilter(); + // + // // Assert + // result.ShouldBeNull(); + // context.Response.StatusCode.ShouldBe(400); + // } + + [Fact] + public async Task FromBodyFilterAsync_NullContext_ThrowsArgumentNullException() + { + // Arrange + HttpContext context = null; + + // Act & Assert + await Should.ThrowAsync(async () => await context.FromBodyFilterAsync()); + } + + [Fact] + public async Task FromBodyFilterAsync_EmptyBody_ReturnsDefault() + { + // Arrange + var context = CreateMockContextWithBody(string.Empty); + + // Act + var result = await context.FromBodyFilterAsync(); + + // Assert + result.ShouldBeNull(); + } + + [Fact] + public async Task FromBodyFilterAsync_ValidJson_ReturnsDeserializedModel() + { + // Arrange + var filterModel = this.CreateValidFilterModel(); + var json = new SystemTextJsonSerializer().SerializeToString(filterModel); + var context = CreateMockContextWithBody(json); + + // Act + var result = await context.FromBodyFilterAsync(); + + // Assert + result.ShouldNotBeNull(); + result.Page.ShouldBe(filterModel.Page); + result.PageSize.ShouldBe(filterModel.PageSize); + } + + // commented due to try/catch trying to write to responsestream which causes an error (mock setup) + // [Fact] + // public async Task FromBodyFilterAsync_InvalidJson_SetsBadRequestAndReturnsDefault() + // { + // // Arrange + // var context = CreateMockContextWithBody("invalid-json"); + // + // // Act + // var result = await context.FromBodyFilterAsync(); + // + // // Assert + // result.ShouldBeNull(); + // context.Response.StatusCode.ShouldBe(400); + // } + + [Fact] + public async Task FromBodyFilterAsync_WithBuffering_EnablesBufferingAndResetsPosition() + { + // Arrange + var filterModel = this.CreateValidFilterModel(); + var json = new SystemTextJsonSerializer().SerializeToString(filterModel); + var context = CreateMockContextWithBody(json); + + // Act + var result = await context.FromBodyFilterAsync(enableBuffering: true); + + // Assert + result.ShouldNotBeNull(); + context.Request.Body.Position.ShouldBe(0); + } + + private static HttpContext CreateMockContextWithQuery(Dictionary queryParams) + { + var context = Substitute.For(); + var request = Substitute.For(); + var response = Substitute.For(); + + var queryCollection = new QueryCollection(queryParams.ToDictionary( + x => x.Key, + x => new StringValues(x.Value))); + + request.Query.Returns(queryCollection); + context.Request.Returns(request); + context.Response.Returns(response); + + return context; + } + + private static HttpContext CreateMockContextWithBody(string bodyContent) + { + var context = Substitute.For(); + var request = Substitute.For(); + var response = Substitute.For(); + + var stream = new MemoryStream(Encoding.UTF8.GetBytes(bodyContent)); + request.Body.Returns(stream); + context.Request.Returns(request); + context.Response.Returns(response); + + return context; + } + + private FilterModel CreateValidFilterModel() + { + return new FilterModel + { + Page = this.faker.Random.Int(1, 100), + PageSize = this.faker.Random.Int(1, 50), + Orderings = [new() { Field = this.faker.Database.Column(), Direction = OrderDirection.Ascending }], + Filters = [new(this.faker.Database.Column(), FilterOperator.Equal, this.faker.Random.Word())], + Includes = [this.faker.Database.Column()] + }; + } +} \ No newline at end of file diff --git a/tests/Presentation.UnitTests/packages.lock.json b/tests/Presentation.UnitTests/packages.lock.json index 832db80d..aad5eb80 100644 --- a/tests/Presentation.UnitTests/packages.lock.json +++ b/tests/Presentation.UnitTests/packages.lock.json @@ -137,8 +137,8 @@ }, "MessagePack.Annotations": { "type": "Transitive", - "resolved": "2.5.172", - "contentHash": "LJWMr5BDpvLaIfsUXufFWQW3VRFg7EKnGAHjArbwCaSeTIWEXaRoClkQtPaXvtVPZk5ZIpc9SBQd/WJD/m99Ww==" + "resolved": "2.5.187", + "contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA==" }, "Microsoft.AspNetCore.TestHost": { "type": "Transitive", @@ -1496,7 +1496,7 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", "Microsoft.Extensions.Options.DataAnnotations": "[8.0.0, )", "Scrutor": "[4.2.2, )", - "Serilog": "[4.0.2, )" + "Serilog": "[4.1.0, )" } }, "BridgingIT.DevKit.Common.Options": { @@ -1510,8 +1510,9 @@ "BridgingIT.DevKit.Common.Serialization": { "type": "Project", "dependencies": { + "BridgingIT.DevKit.Common.Abstractions": "[1.0.0, )", "Ensure.That": "[10.1.0, )", - "MessagePack": "[2.5.172, )", + "MessagePack": "[2.5.187, )", "Newtonsoft.Json": "[13.0.3, )", "System.Threading.Tasks.Extensions": "[4.5.4, )" } @@ -1543,7 +1544,7 @@ "BridgingIT.DevKit.Common.Modules": "[1.0.0, )", "Ensure.That": "[10.1.0, )", "Microsoft.AspNetCore.Mvc.Testing": "[8.0.10, )", - "Serilog": "[4.0.2, )", + "Serilog": "[4.1.0, )", "Serilog.Extensions.Hosting": "[8.0.0, )", "Serilog.Sinks.XUnit": "[3.0.5, )", "Shouldly": "[4.2.1, )", @@ -1560,7 +1561,8 @@ "Ensure.That": "[10.1.0, )", "MediatR.Contracts": "[2.0.1, )", "Microsoft.Extensions.Localization.Abstractions": "[8.0.10, )", - "Scrutor": "[4.2.2, )" + "Scrutor": "[4.2.2, )", + "System.Linq.Dynamic.Core": "[1.4.6, )" } }, "BridgingIT.DevKit.Domain.EventSourcing": { @@ -1651,11 +1653,11 @@ }, "MessagePack": { "type": "CentralTransitive", - "requested": "[2.5.172, )", - "resolved": "2.5.172", - "contentHash": "tO/SIeix4UjmHo0J7Z1IRMvHEfqLlN9FAQCzwGcoG50mt7gPyYsAuMU9Ngu9iDSv0ak/YZOyobs0BidFde+gDw==", + "requested": "[2.5.187, )", + "resolved": "2.5.187", + "contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==", "dependencies": { - "MessagePack.Annotations": "2.5.172", + "MessagePack.Annotations": "2.5.187", "Microsoft.NET.StringTools": "17.6.3" } }, @@ -1881,9 +1883,9 @@ }, "Serilog": { "type": "CentralTransitive", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.Extensions.Hosting": { "type": "CentralTransitive", @@ -1916,6 +1918,12 @@ "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==" }, + "System.Linq.Dynamic.Core": { + "type": "CentralTransitive", + "requested": "[1.4.6, )", + "resolved": "1.4.6", + "contentHash": "GZJ996kvIKH0nnKysDWy+le7k0Hoq1iSY7S5sNq6AF1bXPQGidaXjiOJNX4VCUuVWpbe28Ygb6mDSwgY+UhHLA==" + }, "System.Net.Http": { "type": "CentralTransitive", "requested": "[4.3.4, )",