From 874c2c54a6c34ab3f041e620570189d6c9229e36 Mon Sep 17 00:00:00 2001 From: JerrettDavis Date: Wed, 27 May 2026 00:49:25 -0500 Subject: [PATCH] feat(messaging): add message expiration pattern --- Directory.Packages.props | 2 +- README.md | 6 +- .../Messaging/MessageExpirationBenchmarks.cs | 38 + docs/examples/order-message-expiration.md | 20 + docs/examples/toc.yml | 3 + docs/generators/index.md | 1 + docs/generators/message-expiration.md | 27 + docs/generators/toc.yml | 3 + docs/guides/benchmark-results.md | 14 +- docs/guides/benchmarks.md | 2 + docs/guides/pattern-coverage.md | 1 + docs/patterns/messaging/message-expiration.md | 23 + docs/patterns/toc.yml | 2 + .../Reliability/MessageExpiration.cs | 203 ++ ...rnKitExampleServiceCollectionExtensions.cs | 12 + .../OrderMessageExpirationExample.cs | 80 + .../PatternKitExampleCatalog.cs | 8 + .../PatternKitPatternCatalog.cs | 13 + src/PatternKit.Examples/packages.lock.json | 1772 +++++++---------- .../Messaging/MessageExpirationAttributes.cs | 35 + .../AnalyzerReleases.Unshipped.md | 2 + .../Messaging/MessageExpirationGenerator.cs | 142 ++ src/PatternKit.Generators/packages.lock.json | 106 +- .../OrderMessageExpirationExampleTests.cs | 66 + .../PatternKitBenchmarkCoverageTests.cs | 2 +- .../PatternKitPatternCatalogTests.cs | 3 +- .../packages.lock.json | 900 ++++++++- .../AbstractionsAttributeCoverageTests.cs | 18 + .../MessageExpirationGeneratorTests.cs | 135 ++ .../packages.lock.json | 744 ++++++- .../Reliability/MessageExpirationTests.cs | 136 ++ test/PatternKit.Tests/packages.lock.json | 692 ++++++- 32 files changed, 4124 insertions(+), 1087 deletions(-) create mode 100644 benchmarks/PatternKit.Benchmarks/Messaging/MessageExpirationBenchmarks.cs create mode 100644 docs/examples/order-message-expiration.md create mode 100644 docs/generators/message-expiration.md create mode 100644 docs/patterns/messaging/message-expiration.md create mode 100644 src/PatternKit.Core/Messaging/Reliability/MessageExpiration.cs create mode 100644 src/PatternKit.Examples/Messaging/OrderMessageExpirationExample.cs create mode 100644 src/PatternKit.Generators.Abstractions/Messaging/MessageExpirationAttributes.cs create mode 100644 src/PatternKit.Generators/Messaging/MessageExpirationGenerator.cs create mode 100644 test/PatternKit.Examples.Tests/Messaging/OrderMessageExpirationExampleTests.cs create mode 100644 test/PatternKit.Generators.Tests/MessageExpirationGeneratorTests.cs create mode 100644 test/PatternKit.Tests/Messaging/Reliability/MessageExpirationTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 0c520340..632f45d3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,7 +7,7 @@ - + diff --git a/README.md b/README.md index 04c4fb31..92121ed9 100644 --- a/README.md +++ b/README.md @@ -446,7 +446,7 @@ var cachedRemoteProxy = Proxy.Create(id => remoteProxy.Execute(id)) --- ## Patterns Table -PatternKit currently tracks 97 production-readiness patterns. Each catalog pattern is represented in tests, documentation, real-world examples, IoC integration, and the BenchmarkDotNet coverage matrix. +PatternKit currently tracks 98 production-readiness patterns. Each catalog pattern is represented in tests, documentation, real-world examples, IoC integration, and the BenchmarkDotNet coverage matrix. | Category | Count | Patterns | | --- | ---: | --- | @@ -454,7 +454,7 @@ PatternKit currently tracks 97 production-readiness patterns. Each catalog patte | Behavioral | 11 | Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor | | Cloud Architecture | 17 | Ambassador, Backends for Frontends, Bulkhead, Cache-Aside, Circuit Breaker, External Configuration Store, Gateway Aggregation, Gateway Routing, Health Endpoint Monitoring, Leader Election, Priority Queue, Queue-Based Load Leveling, Rate Limiting, Retry, Scheduler Agent Supervisor, Sidecar, Strangler Fig | | Creational | 5 | Abstract Factory, Builder, Factory Method, Prototype, Singleton | -| Enterprise Integration | 38 | Aggregator, Canonical Data Model, Channel Adapter, Channel Purger, Claim Check, Competing Consumers, Content-Based Router, Control Bus, Correlation Identifier, Dead Letter Channel, Durable Subscriber, Dynamic Router, Event Notification, Event-Carried State Transfer, Event-Driven Consumer, Invalid Message Channel, Mailbox, Message Bus, Message Channel, Message Envelope, Message Filter, Message History, Message Store, Message Translator, Messaging Bridge, Messaging Gateway, Pipes and Filters, Polling Consumer, Publish-Subscribe, Recipient List, Request-Reply, Resequencer, Routing Slip, Saga / Process Manager, Scatter-Gather, Service Activator, Splitter, Wire Tap | +| Enterprise Integration | 39 | Aggregator, Canonical Data Model, Channel Adapter, Channel Purger, Claim Check, Competing Consumers, Content-Based Router, Control Bus, Correlation Identifier, Dead Letter Channel, Durable Subscriber, Dynamic Router, Event Notification, Event-Carried State Transfer, Event-Driven Consumer, Invalid Message Channel, Mailbox, Message Bus, Message Channel, Message Envelope, Message Expiration, Message Filter, Message History, Message Store, Message Translator, Messaging Bridge, Messaging Gateway, Pipes and Filters, Polling Consumer, Publish-Subscribe, Recipient List, Request-Reply, Resequencer, Routing Slip, Saga / Process Manager, Scatter-Gather, Service Activator, Splitter, Wire Tap | | Messaging Reliability | 3 | Idempotent Receiver, Inbox, Outbox | | Structural | 7 | Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy | @@ -572,6 +572,8 @@ BenchmarkDotNet guidance is documented in [docs/guides/benchmarks.md](docs/guide | Message Envelope | Execution | 455.486 ns | 2,752 B | 427.664 ns | 2,752 B | Same allocation; generated was slightly faster for message context enrichment. | | Message Filter | Construction | 25.431 ns | 232 B | 25.626 ns | 232 B | Effectively equivalent for this microbenchmark. | | Message Filter | Execution | 44.637 ns | 424 B | 45.826 ns | 424 B | Same allocation; fluent was slightly faster for order fraud screening. | +| Message Expiration | Construction | 22.55 ns | 248 B | 14.63 ns | 144 B | Generated policy construction reduced time and allocation. | +| Message Expiration | Execution | 88.54 ns | 728 B | 103.86 ns | 536 B | Generated reduced allocation; fluent was faster for the stamp-and-evaluate flow. | | Message Routing | Construction | 23.42 ns | 224 B | 23.33 ns | 224 B | Effectively equivalent for this microbenchmark. | | Message Routing | Execution | 707.34 ns | 4,744 B | 679.97 ns | 4,632 B | Generated reduced execution time and allocation for the route/split/aggregate workflow. | | Message Store | Construction | 18.824 ns | 216 B | 18.721 ns | 216 B | Effectively equivalent for this microbenchmark. | diff --git a/benchmarks/PatternKit.Benchmarks/Messaging/MessageExpirationBenchmarks.cs b/benchmarks/PatternKit.Benchmarks/Messaging/MessageExpirationBenchmarks.cs new file mode 100644 index 00000000..939de36b --- /dev/null +++ b/benchmarks/PatternKit.Benchmarks/Messaging/MessageExpirationBenchmarks.cs @@ -0,0 +1,38 @@ +using BenchmarkDotNet.Attributes; +using PatternKit.Examples.Messaging; +using PatternKit.Messaging; +using PatternKit.Messaging.Reliability; + +namespace PatternKit.Benchmarks.Messaging; + +[BenchmarkCategory("EnterpriseIntegration", "Messaging", "MessageExpiration")] +public class MessageExpirationBenchmarks +{ + private static readonly DateTimeOffset Now = new(2026, 5, 27, 12, 0, 0, TimeSpan.Zero); + private static readonly ExpiringOrderCommand Command = new("order-100", "customer-42"); + + [Benchmark(Baseline = true, Description = "Fluent: create message expiration")] + [BenchmarkCategory("Fluent", "Construction")] + public MessageExpiration Fluent_CreateMessageExpiration() + => OrderMessageExpirations.Create(Now); + + [Benchmark(Description = "Generated: create message expiration")] + [BenchmarkCategory("Generated", "Construction")] + public MessageExpiration Generated_CreateMessageExpiration() + => GeneratedOrderMessageExpiration.Create(); + + [Benchmark(Description = "Fluent: stamp and evaluate order command")] + [BenchmarkCategory("Fluent", "Execution")] + public OrderExpirationSummary Fluent_StampAndEvaluate() + => OrderMessageExpirationExampleRunner.RunFluent(Command, Now); + + [Benchmark(Description = "Generated: stamp and evaluate order command")] + [BenchmarkCategory("Generated", "Execution")] + public OrderExpirationSummary Generated_StampAndEvaluate() + { + var expiration = GeneratedOrderMessageExpiration.Create(); + var message = expiration.Stamp(Message.Create(Command)); + var result = expiration.Evaluate(message); + return new OrderExpirationSummary(result.Expired, result.ExpiresAt, result.Reason); + } +} diff --git a/docs/examples/order-message-expiration.md b/docs/examples/order-message-expiration.md new file mode 100644 index 00000000..365d1b4f --- /dev/null +++ b/docs/examples/order-message-expiration.md @@ -0,0 +1,20 @@ +# Order Message Expiration + +The order message-expiration example shows how to stamp fulfillment commands with a deadline and reject stale commands before processing. + +```csharp +var services = new ServiceCollection() + .AddOrderMessageExpirationDemo() + .BuildServiceProvider(); + +var service = services.GetRequiredService(); +var message = service.Accept(new ExpiringOrderCommand("o-1", "c-1")); +var summary = service.Evaluate(message); +``` + +The example exposes both paths: + +- `OrderMessageExpirations.Create(...)` for fluent construction and deterministic tests. +- `GeneratedOrderMessageExpiration.Create()` for source-generated, contract-style configuration. + +The DI extension registers the generated policy, the service, and an example runner so the same shape can be imported into `IServiceCollection` in a worker, ASP.NET Core app, or test host. diff --git a/docs/examples/toc.yml b/docs/examples/toc.yml index 86b4e7f9..aa714fbb 100644 --- a/docs/examples/toc.yml +++ b/docs/examples/toc.yml @@ -112,6 +112,9 @@ - name: Order Message Filter href: order-message-filter.md +- name: Order Message Expiration + href: order-message-expiration.md + - name: Order Message Store href: order-message-store.md diff --git a/docs/generators/index.md b/docs/generators/index.md index 84379f83..d55e9e8d 100644 --- a/docs/generators/index.md +++ b/docs/generators/index.md @@ -103,6 +103,7 @@ PatternKit includes a Roslyn incremental generator package (`PatternKit.Generato | [**Dead Letter Channel**](dead-letter-channel.md) | Failed-message capture and replay handoff | `[GenerateDeadLetterChannel]` | | [**Content Router**](messaging.md#generated-content-router) | Content-based message routing factories | `[GenerateContentRouter]` | | [**Message Filter**](message-filter.md) | Named allow-rule filters for message consumers | `[GenerateMessageFilter]` | +| [**Message Expiration**](message-expiration.md) | Deadline stamping and stale-message evaluation policies | `[GenerateMessageExpiration]` | | [**Message Store**](message-store.md) | Message audit, lookup, and replay store factories | `[GenerateMessageStore]` | | [**Wire Tap**](wire-tap.md) | Side-channel message observability factories | `[GenerateWireTap]` | | [**Control Bus**](control-bus.md) | Operational command bus factories for message processors | `[GenerateControlBus]` | diff --git a/docs/generators/message-expiration.md b/docs/generators/message-expiration.md new file mode 100644 index 00000000..c1d63281 --- /dev/null +++ b/docs/generators/message-expiration.md @@ -0,0 +1,27 @@ +# Message Expiration Generator + +`[GenerateMessageExpiration]` emits a typed factory for `MessageExpiration`. + +```csharp +[GenerateMessageExpiration( + typeof(OrderCommand), + FactoryName = "Create", + PolicyName = "order-message-expiration", + HeaderName = "x-order-expires-at", + DefaultTtlMilliseconds = 1200000, + ExpiredReason = "Order command expired before fulfillment accepted it.")] +public static partial class GeneratedOrderMessageExpiration; +``` + +The generated route is useful when expiration metadata is part of a stable messaging contract. It keeps policy names, header names, TTLs, and rejection reasons declared once and benchmarkable. + +## Options + +| Option | Default | Purpose | +| --- | --- | --- | +| `FactoryName` | `Create` | Generated factory method name. | +| `PolicyName` | `message-expiration` | Name returned in evaluation results. | +| `HeaderName` | `expires-at` | Deadline header. | +| `DefaultTtlMilliseconds` | `0` | Positive values configure the default `Stamp` TTL. | +| `PreserveExisting` | `true` | Keeps existing deadlines when stamping. | +| `ExpiredReason` | `Message expired before processing.` | Reason returned for expired messages. | diff --git a/docs/generators/toc.yml b/docs/generators/toc.yml index e9b6ef0d..c63d60f7 100644 --- a/docs/generators/toc.yml +++ b/docs/generators/toc.yml @@ -151,6 +151,9 @@ - name: Message Filter href: message-filter.md +- name: Message Expiration + href: message-expiration.md + - name: Message Store href: message-store.md diff --git a/docs/guides/benchmark-results.md b/docs/guides/benchmark-results.md index 44c53feb..3a88250c 100644 --- a/docs/guides/benchmark-results.md +++ b/docs/guides/benchmark-results.md @@ -119,6 +119,8 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149 | Message Envelope | Execution | 455.486 ns | 2,752 B | 427.664 ns | 2,752 B | Same allocation; generated was slightly faster for message context enrichment. | | Message Filter | Construction | 25.431 ns | 232 B | 25.626 ns | 232 B | Effectively equivalent for this microbenchmark. | | Message Filter | Execution | 44.637 ns | 424 B | 45.826 ns | 424 B | Same allocation; fluent was slightly faster for order fraud screening. | +| Message Expiration | Construction | 22.55 ns | 248 B | 14.63 ns | 144 B | Generated policy construction reduced time and allocation. | +| Message Expiration | Execution | 88.54 ns | 728 B | 103.86 ns | 536 B | Generated reduced allocation; fluent was faster for the stamp-and-evaluate flow. | | Message Routing | Construction | 23.42 ns | 224 B | 23.33 ns | 224 B | Effectively equivalent for this microbenchmark. | | Message Routing | Execution | 707.34 ns | 4,744 B | 679.97 ns | 4,632 B | Generated reduced execution time and allocation for the route/split/aggregate workflow. | | Message Store | Construction | 18.824 ns | 216 B | 18.721 ns | 216 B | Effectively equivalent for this microbenchmark. | @@ -212,7 +214,7 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149 ## Coverage Matrix Summary -The coverage matrix currently publishes 97 catalog patterns and 388 pattern route results. Each pattern has four BenchmarkDotNet routes: fluent construction, fluent execution, source-generated construction, and source-generated execution. +The coverage matrix currently publishes 98 catalog patterns and 392 pattern route results. Each pattern has four BenchmarkDotNet routes: fluent construction, fluent execution, source-generated construction, and source-generated execution. | Category | Patterns | Published route results | | --- | ---: | ---: | @@ -220,11 +222,11 @@ The coverage matrix currently publishes 97 catalog patterns and 388 pattern rout | Behavioral | 11 | 44 | | Cloud Architecture | 17 | 68 | | Creational | 5 | 20 | -| Enterprise Integration | 38 | 152 | +| Enterprise Integration | 39 | 156 | | Messaging Reliability | 3 | 12 | | Structural | 7 | 28 | -The generator matrix currently publishes 93 generator source route results. +The generator matrix currently publishes 94 generator source route results. ## Pattern Matrix Results @@ -301,8 +303,9 @@ The generator matrix currently publishes 93 generator source route results. | Enterprise Integration | Mailbox | Covered | Covered | Covered | Covered | | Enterprise Integration | Message Channel | Covered | Covered | Covered | Covered | | Enterprise Integration | Message Envelope | Covered | Covered | Covered | Covered | -| Enterprise Integration | Message Filter | Covered | Covered | Covered | Covered | -| Enterprise Integration | Message Store | Covered | Covered | Covered | Covered | +| Enterprise Integration | Message Filter | Covered | Covered | Covered | Covered | +| Enterprise Integration | Message Expiration | Covered | Covered | Covered | Covered | +| Enterprise Integration | Message Store | Covered | Covered | Covered | Covered | | Enterprise Integration | Message Translator | Covered | Covered | Covered | Covered | | Enterprise Integration | Messaging Gateway | Covered | Covered | Covered | Covered | | Enterprise Integration | Pipes and Filters | Covered | Covered | Covered | Covered | @@ -388,6 +391,7 @@ The generator matrix currently publishes 93 generator source route results. | MessageChannelGenerator | `src/PatternKit.Generators/Messaging/MessageChannelGenerator.cs` | Covered | | MessageEnvelopeGenerator | `src/PatternKit.Generators/Messaging/MessageEnvelopeGenerator.cs` | Covered | | MessageFilterGenerator | `src/PatternKit.Generators/Messaging/MessageFilterGenerator.cs` | Covered | +| MessageExpirationGenerator | `src/PatternKit.Generators/Messaging/MessageExpirationGenerator.cs` | Covered | | CorrelationIdentifierGenerator | `src/PatternKit.Generators/Messaging/CorrelationIdentifierGenerator.cs` | Covered | | MessageHistoryGenerator | `src/PatternKit.Generators/Messaging/MessageHistoryGenerator.cs` | Covered | | MessageStoreGenerator | `src/PatternKit.Generators/Messaging/MessageStoreGenerator.cs` | Covered | diff --git a/docs/guides/benchmarks.md b/docs/guides/benchmarks.md index be5c10ec..1bb0cbf1 100644 --- a/docs/guides/benchmarks.md +++ b/docs/guides/benchmarks.md @@ -134,6 +134,8 @@ The following numbers were captured on Windows 11, Intel Core i9-14900K, .NET SD | Message Envelope | Execution | 455.486 ns | 2,752 B | 427.664 ns | 2,752 B | Same allocation; generated was slightly faster for message context enrichment. | | Message Filter | Construction | 25.431 ns | 232 B | 25.626 ns | 232 B | Effectively equivalent for this microbenchmark. | | Message Filter | Execution | 44.637 ns | 424 B | 45.826 ns | 424 B | Same allocation; fluent was slightly faster for order fraud screening. | +| Message Expiration | Construction | 22.55 ns | 248 B | 14.63 ns | 144 B | Generated policy construction reduced time and allocation. | +| Message Expiration | Execution | 88.54 ns | 728 B | 103.86 ns | 536 B | Generated reduced allocation; fluent was faster for the stamp-and-evaluate flow. | | Message Routing | Construction | 23.42 ns | 224 B | 23.33 ns | 224 B | Effectively equivalent for this microbenchmark. | | Message Routing | Execution | 707.34 ns | 4,744 B | 679.97 ns | 4,632 B | Generated reduced execution time and allocation for the route/split/aggregate workflow. | | Message Store | Construction | 18.824 ns | 216 B | 18.721 ns | 216 B | Effectively equivalent for this microbenchmark. | diff --git a/docs/guides/pattern-coverage.md b/docs/guides/pattern-coverage.md index 915f5567..aa78a6e9 100644 --- a/docs/guides/pattern-coverage.md +++ b/docs/guides/pattern-coverage.md @@ -66,6 +66,7 @@ The source of truth is `PatternKitPatternCatalog` in `src/PatternKit.Examples/Pr | Enterprise Integration | Correlation Identifier | `CorrelationIdentifier` | Correlation Identifier generator | | Enterprise Integration | Message History | `MessageHistory` | Message History generator | | Enterprise Integration | Message Filter | `MessageFilter` | Message Filter generator | +| Enterprise Integration | Message Expiration | `MessageExpiration` | Message Expiration generator | | Enterprise Integration | Message Store | `MessageStore` | Message Store generator | | Enterprise Integration | Wire Tap | `WireTap` | Wire Tap generator | | Enterprise Integration | Control Bus | `ControlBus` | Control Bus generator | diff --git a/docs/patterns/messaging/message-expiration.md b/docs/patterns/messaging/message-expiration.md new file mode 100644 index 00000000..6ef53714 --- /dev/null +++ b/docs/patterns/messaging/message-expiration.md @@ -0,0 +1,23 @@ +# Message Expiration + +Message Expiration stamps messages with a deadline and lets consumers reject stale work before it mutates state. Use it at queue, inbox, handler, or workflow boundaries where a command has business value only for a limited time. + +```csharp +var expiration = MessageExpiration.Create() + .Name("order-message-expiration") + .Header("x-order-expires-at") + .DefaultTtl(TimeSpan.FromMinutes(20)) + .ExpiredReason("Order command expired before fulfillment accepted it.") + .Build(); + +var accepted = expiration.Stamp(Message.Create(command)); +var result = expiration.Evaluate(accepted); +``` + +`Stamp` preserves an existing deadline by default so upstream transport or gateway policies keep precedence. Call `PreserveExisting(false)` when the local boundary owns the deadline. + +## Production Notes + +- Store the expiration deadline as message metadata, not payload state, so routers, filters, and consumers can evaluate it consistently. +- Use a deterministic `Clock` in tests and a UTC clock in production. +- Treat expired messages as rejected work: route to a dead-letter channel, audit log, or compensating workflow instead of silently dropping state-changing commands. diff --git a/docs/patterns/toc.yml b/docs/patterns/toc.yml index 83ed0cd4..763cdf1f 100644 --- a/docs/patterns/toc.yml +++ b/docs/patterns/toc.yml @@ -339,6 +339,8 @@ href: messaging/dead-letter-channel.md - name: Message Filter href: messaging/message-filter.md + - name: Message Expiration + href: messaging/message-expiration.md - name: Message Store href: messaging/message-store.md - name: Wire Tap diff --git a/src/PatternKit.Core/Messaging/Reliability/MessageExpiration.cs b/src/PatternKit.Core/Messaging/Reliability/MessageExpiration.cs new file mode 100644 index 00000000..6d5779b0 --- /dev/null +++ b/src/PatternKit.Core/Messaging/Reliability/MessageExpiration.cs @@ -0,0 +1,203 @@ +namespace PatternKit.Messaging.Reliability; + +/// +/// Stamps and evaluates message expiration metadata so stale messages can be rejected before processing. +/// +public sealed class MessageExpiration +{ + private readonly string _name; + private readonly string _headerName; + private readonly TimeSpan? _defaultTtl; + private readonly Func _clock; + private readonly bool _preserveExisting; + private readonly string _expiredReason; + + private MessageExpiration( + string name, + string headerName, + TimeSpan? defaultTtl, + Func clock, + bool preserveExisting, + string expiredReason) + => (_name, _headerName, _defaultTtl, _clock, _preserveExisting, _expiredReason) = + (name, headerName, defaultTtl, clock, preserveExisting, expiredReason); + + /// The header used to store the expiration deadline. + public string HeaderName => _headerName; + + /// Reads the expiration deadline from a message when present and parseable. + public DateTimeOffset? Read(Message message) + { + if (message is null) + throw new ArgumentNullException(nameof(message)); + + return message.Headers.TryGetDateTimeOffset(_headerName, out var expiresAt) ? expiresAt : null; + } + + /// Returns a new message with an expiration deadline based on the configured or supplied TTL. + public Message Stamp(Message message, TimeSpan? ttl = null) + { + if (message is null) + throw new ArgumentNullException(nameof(message)); + + if (_preserveExisting && Read(message) is not null) + return message; + + var effectiveTtl = ttl ?? _defaultTtl; + if (effectiveTtl is null || effectiveTtl.Value <= TimeSpan.Zero) + throw new InvalidOperationException("Message expiration requires a positive TTL."); + + return WithDeadline(message, _clock().Add(effectiveTtl.Value)); + } + + /// Returns a new message with an explicit expiration deadline. + public Message WithDeadline(Message message, DateTimeOffset expiresAt) + { + if (message is null) + throw new ArgumentNullException(nameof(message)); + + return message.WithHeader(_headerName, expiresAt); + } + + /// Evaluates whether a message is expired at the current clock time. + public MessageExpirationResult Evaluate(Message message, MessageContext? context = null) + { + if (message is null) + throw new ArgumentNullException(nameof(message)); + + var checkedAt = _clock(); + var expiresAt = Read(message); + if (expiresAt is null && context is not null && context.Headers.TryGetDateTimeOffset(_headerName, out var contextDeadline)) + expiresAt = contextDeadline; + + if (expiresAt is null) + return MessageExpirationResult.Accepted(message, _name, checkedAt, null); + + return expiresAt.Value <= checkedAt + ? MessageExpirationResult.Reject(message, _name, checkedAt, expiresAt.Value, _expiredReason) + : MessageExpirationResult.Accepted(message, _name, checkedAt, expiresAt.Value); + } + + /// Creates a new message-expiration builder. + public static Builder Create() => new(); + + /// Fluent builder for . + public sealed class Builder + { + private string _name = "message-expiration"; + private string _headerName = "expires-at"; + private TimeSpan? _defaultTtl; + private Func _clock = static () => DateTimeOffset.UtcNow; + private bool _preserveExisting = true; + private string _expiredReason = "Message expired before processing."; + + /// Assigns a policy name used in evaluation results. + public Builder Name(string name) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Message expiration name cannot be null, empty, or whitespace.", nameof(name)); + + _name = name; + return this; + } + + /// Assigns the header used to store expiration deadlines. + public Builder Header(string headerName) + { + if (string.IsNullOrWhiteSpace(headerName)) + throw new ArgumentException("Message expiration header cannot be null, empty, or whitespace.", nameof(headerName)); + + _headerName = headerName; + return this; + } + + /// Configures the default time-to-live used by . + public Builder DefaultTtl(TimeSpan ttl) + { + if (ttl <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException(nameof(ttl), "Message expiration TTL must be positive."); + + _defaultTtl = ttl; + return this; + } + + /// Configures the clock used for stamping and evaluation. + public Builder Clock(Func clock) + { + _clock = clock ?? throw new ArgumentNullException(nameof(clock)); + return this; + } + + /// Configures whether stamping keeps an existing expiration deadline. + public Builder PreserveExisting(bool preserveExisting = true) + { + _preserveExisting = preserveExisting; + return this; + } + + /// Configures the rejection reason returned for expired messages. + public Builder ExpiredReason(string reason) + { + if (string.IsNullOrWhiteSpace(reason)) + throw new ArgumentException("Message expiration reason cannot be null, empty, or whitespace.", nameof(reason)); + + _expiredReason = reason; + return this; + } + + /// Builds an immutable message-expiration policy. + public MessageExpiration Build() + => new(_name, _headerName, _defaultTtl, _clock, _preserveExisting, _expiredReason); + } +} + +/// +/// Result returned by a message-expiration evaluation. +/// +public sealed class MessageExpirationResult +{ + private MessageExpirationResult( + Message message, + string policyName, + DateTimeOffset checkedAt, + DateTimeOffset? expiresAt, + bool expired, + string? reason) + => (Message, PolicyName, CheckedAt, ExpiresAt, Expired, Reason) = + (message, policyName, checkedAt, expiresAt, expired, reason); + + /// The message evaluated by the policy. + public Message Message { get; } + + /// The policy name that evaluated the message. + public string PolicyName { get; } + + /// The time used to evaluate expiration. + public DateTimeOffset CheckedAt { get; } + + /// The expiration deadline, or null when no expiration metadata was present. + public DateTimeOffset? ExpiresAt { get; } + + /// True when the message has expired. + public bool Expired { get; } + + /// Human-readable rejection reason when the message is expired. + public string? Reason { get; } + + /// Creates an accepted result. + public static MessageExpirationResult Accepted( + Message message, + string policyName, + DateTimeOffset checkedAt, + DateTimeOffset? expiresAt) + => new(message, policyName, checkedAt, expiresAt, false, null); + + /// Creates an expired result. + public static MessageExpirationResult Reject( + Message message, + string policyName, + DateTimeOffset checkedAt, + DateTimeOffset expiresAt, + string reason) + => new(message, policyName, checkedAt, expiresAt, true, reason); +} diff --git a/src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs b/src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs index e9e2a757..62f00156 100644 --- a/src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs +++ b/src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs @@ -85,6 +85,7 @@ using PatternKit.Messaging.Bridges; using PatternKit.Messaging.Gateways; using PatternKit.Messaging.Routing; +using PatternKit.Messaging.Reliability; using PatternKit.Messaging.Storage; using PatternKit.Messaging.ControlBus; using PatternKit.Messaging.CompetingConsumers; @@ -166,6 +167,7 @@ public sealed record GeneratedDeadLetterChannelExample(FulfillmentDeadLetterChan public sealed record GeneratedRecipientListExample(RecipientListGeneratorExampleRunner Runner); public sealed record GeneratedSplitterAggregatorExample(MessageRoutingExampleRunner Runner); public sealed record OrderMessageFilterExampleService(MessageFilter Filter, OrderMessageFilterService Service); +public sealed record OrderMessageExpirationExampleService(MessageExpiration Expiration, OrderMessageExpirationService Service); public sealed record OrderMessageStoreExampleService(MessageStore Store, OrderMessageStoreService Service); public sealed record OrderDurableSubscriberExampleService(DurableSubscriber Subscriber, OrderDurableSubscriberService Service); public sealed record OrderDynamicRouterExampleService(DynamicRouter Router, FulfillmentRoutingService Service); @@ -272,6 +274,7 @@ public static IServiceCollection AddPatternKitExamples(this IServiceCollection s .AddGeneratedRecipientListExample() .AddGeneratedSplitterAggregatorExample() .AddOrderMessageFilterExample() + .AddOrderMessageExpirationExample() .AddOrderMessageStoreExample() .AddOrderDurableSubscriberExample() .AddOrderDynamicRouterExample() @@ -688,6 +691,15 @@ public static IServiceCollection AddOrderMessageFilterExample(this IServiceColle return services.RegisterExample("Order Message Filter", ExampleIntegrationSurface.Messaging | ExampleIntegrationSurface.SourceGenerator | ExampleIntegrationSurface.DependencyInjection); } + public static IServiceCollection AddOrderMessageExpirationExample(this IServiceCollection services) + { + services.AddOrderMessageExpirationDemo(); + services.AddSingleton(sp => new( + sp.GetRequiredService>(), + sp.GetRequiredService())); + return services.RegisterExample("Order Message Expiration", ExampleIntegrationSurface.Messaging | ExampleIntegrationSurface.SourceGenerator | ExampleIntegrationSurface.DependencyInjection); + } + public static IServiceCollection AddOrderMessageStoreExample(this IServiceCollection services) { services.AddOrderMessageStoreDemo(); diff --git a/src/PatternKit.Examples/Messaging/OrderMessageExpirationExample.cs b/src/PatternKit.Examples/Messaging/OrderMessageExpirationExample.cs new file mode 100644 index 00000000..ce00b346 --- /dev/null +++ b/src/PatternKit.Examples/Messaging/OrderMessageExpirationExample.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.DependencyInjection; +using PatternKit.Generators.Messaging; +using PatternKit.Messaging; +using PatternKit.Messaging.Reliability; + +namespace PatternKit.Examples.Messaging; + +/// Order work item used by the message-expiration example. +public sealed record ExpiringOrderCommand(string OrderId, string CustomerId); + +/// Summary returned by the order message-expiration example. +public sealed record OrderExpirationSummary(bool Expired, DateTimeOffset? ExpiresAt, string? Reason); + +/// +/// Service that stamps order commands with a processing deadline and rejects stale commands. +/// +public sealed class OrderMessageExpirationService(MessageExpiration expiration) +{ + public Message Accept(ExpiringOrderCommand command) + => expiration.Stamp(Message.Create(command)); + + public OrderExpirationSummary Evaluate(Message message) + { + var result = expiration.Evaluate(message); + return new OrderExpirationSummary(result.Expired, result.ExpiresAt, result.Reason); + } +} + +/// Fluent message-expiration builder used by applications that do not enable generators. +public static class OrderMessageExpirations +{ + public static MessageExpiration Create(DateTimeOffset? now = null) + => MessageExpiration.Create() + .Name("order-message-expiration") + .Header("x-order-expires-at") + .DefaultTtl(TimeSpan.FromMinutes(20)) + .Clock(() => now ?? DateTimeOffset.UtcNow) + .ExpiredReason("Order command expired before fulfillment accepted it.") + .Build(); +} + +/// Source-generated message-expiration policy for order commands. +[GenerateMessageExpiration( + typeof(ExpiringOrderCommand), + FactoryName = "Create", + PolicyName = "order-message-expiration", + HeaderName = "x-order-expires-at", + DefaultTtlMilliseconds = 1200000, + ExpiredReason = "Order command expired before fulfillment accepted it.")] +public static partial class GeneratedOrderMessageExpiration; + +/// Runner that demonstrates both fluent and generated message-expiration paths. +public sealed class OrderMessageExpirationExampleRunner(OrderMessageExpirationService service) +{ + public OrderExpirationSummary RunGenerated(ExpiringOrderCommand command) + { + var accepted = service.Accept(command); + return service.Evaluate(accepted); + } + + public static OrderExpirationSummary RunFluent(ExpiringOrderCommand command, DateTimeOffset now) + { + var expiration = OrderMessageExpirations.Create(now); + var accepted = expiration.Stamp(Message.Create(command)); + var result = expiration.Evaluate(accepted); + return new OrderExpirationSummary(result.Expired, result.ExpiresAt, result.Reason); + } +} + +/// DI helpers for importing the order message-expiration example into standard .NET containers. +public static class OrderMessageExpirationExampleServiceCollectionExtensions +{ + public static IServiceCollection AddOrderMessageExpirationDemo(this IServiceCollection services) + { + services.AddSingleton(_ => GeneratedOrderMessageExpiration.Create()); + services.AddSingleton(); + services.AddSingleton(); + return services; + } +} diff --git a/src/PatternKit.Examples/ProductionReadiness/PatternKitExampleCatalog.cs b/src/PatternKit.Examples/ProductionReadiness/PatternKitExampleCatalog.cs index 919863f2..305e4cd6 100644 --- a/src/PatternKit.Examples/ProductionReadiness/PatternKitExampleCatalog.cs +++ b/src/PatternKit.Examples/ProductionReadiness/PatternKitExampleCatalog.cs @@ -376,6 +376,14 @@ public sealed class PatternKitExampleCatalog : IPatternKitExampleCatalog ExampleIntegrationSurface.Messaging | ExampleIntegrationSurface.SourceGenerator | ExampleIntegrationSurface.DependencyInjection, ["MessageFilter"], ["fraud-screening allow rules", "source-generated filter", "DI composition"]), + Descriptor( + "Order Message Expiration", + "src/PatternKit.Examples/Messaging/OrderMessageExpirationExample.cs", + "test/PatternKit.Examples.Tests/Messaging/OrderMessageExpirationExampleTests.cs", + "docs/examples/order-message-expiration.md", + ExampleIntegrationSurface.Messaging | ExampleIntegrationSurface.SourceGenerator | ExampleIntegrationSurface.DependencyInjection, + ["MessageExpiration"], + ["deadline stamping", "source-generated expiration policy", "DI composition"]), Descriptor( "Order Message Store", "src/PatternKit.Examples/Messaging/OrderMessageStoreExample.cs", diff --git a/src/PatternKit.Examples/ProductionReadiness/PatternKitPatternCatalog.cs b/src/PatternKit.Examples/ProductionReadiness/PatternKitPatternCatalog.cs index 30417712..f804bf97 100644 --- a/src/PatternKit.Examples/ProductionReadiness/PatternKitPatternCatalog.cs +++ b/src/PatternKit.Examples/ProductionReadiness/PatternKitPatternCatalog.cs @@ -584,6 +584,19 @@ public sealed class PatternKitPatternCatalog : IPatternKitPatternCatalog "test/PatternKit.Examples.Tests/Messaging/OrderMessageFilterExampleTests.cs", ["fluent message filter", "generated allow-rule filter", "DI-importable order fraud-screening example"]), + Pattern("Message Expiration", PatternFamily.EnterpriseIntegration, + "docs/patterns/messaging/message-expiration.md", + "src/PatternKit.Core/Messaging/Reliability/MessageExpiration.cs", + "test/PatternKit.Tests/Messaging/Reliability/MessageExpirationTests.cs", + "docs/generators/message-expiration.md", + "src/PatternKit.Generators/Messaging/MessageExpirationGenerator.cs", + "test/PatternKit.Generators.Tests/MessageExpirationGeneratorTests.cs", + null, + "docs/examples/order-message-expiration.md", + "src/PatternKit.Examples/Messaging/OrderMessageExpirationExample.cs", + "test/PatternKit.Examples.Tests/Messaging/OrderMessageExpirationExampleTests.cs", + ["fluent deadline stamping", "generated expiration policy", "DI-importable order freshness example"]), + Pattern("Message Store", PatternFamily.EnterpriseIntegration, "docs/patterns/messaging/message-store.md", "src/PatternKit.Core/Messaging/Storage/MessageStore.cs", diff --git a/src/PatternKit.Examples/packages.lock.json b/src/PatternKit.Examples/packages.lock.json index 51d2ea82..49243173 100644 --- a/src/PatternKit.Examples/packages.lock.json +++ b/src/PatternKit.Examples/packages.lock.json @@ -1,1011 +1,763 @@ -{ - "version": 2, - "dependencies": { - "net10.0": { - "JetBrains.Annotations": { - "type": "Direct", - "requested": "[2025.2.4, )", - "resolved": "2025.2.4", - "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" - }, - "Microsoft.Extensions.Configuration": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "R3NN1X+kVu14uoxLEW6sBSQyhogDSbaOQzILnCtuXxBN4hx22AgjWPwZX6v/suERFkEDgU1lk12AglHTrUxhlw==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Hosting": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "VfEyM2BipThcSd0GG/FS2ZPCVCTiosVq2zLKEDsfeMIg78sOVZPEmS7CgWlb+dqTlgXvLSL4OG2q6sM4xRhHNg==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Binder": "10.0.8", - "Microsoft.Extensions.Configuration.CommandLine": "10.0.8", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.8", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", - "Microsoft.Extensions.Configuration.Json": "10.0.8", - "Microsoft.Extensions.Configuration.UserSecrets": "10.0.8", - "Microsoft.Extensions.DependencyInjection": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Diagnostics": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Physical": "10.0.8", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging.Configuration": "10.0.8", - "Microsoft.Extensions.Logging.Console": "10.0.8", - "Microsoft.Extensions.Logging.Debug": "10.0.8", - "Microsoft.Extensions.Logging.EventLog": "10.0.8", - "Microsoft.Extensions.Logging.EventSource": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Hosting.Abstractions": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Options": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "VBD+131DpTNCNDfA4kIyKTiCySvJGNhwibdWBSdFRu7GMfXLXcXODkgA+KStKbbhzraLglZWUN4nXyHgW4JIRA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "VOapXeO3lhBH0zYoyAH7tjapuo4V5pTHlevPpiSHueEquAajqd5nF0mttm+h/uE/exwAEuM5s26SzOJtletE3w==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Binder": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Options.DataAnnotations": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "HhxwIGECGGJ8ox2kvm6/hkN/w1ZyKrO5uu/rLAL51V0ypPdahoNf+dHS6Er/DJs2aeUmH38ZTTzACfLy1O6w3Q==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.CommandLine": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "nQXq1a4MiInYh+0VF9fguxAl06q2ftmOyYQ+5e933s4rk57xjgkbTjUdFUySzjrcrvDeWsSqlZB+TE8+TbM2HA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.EnvironmentVariables": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.FileExtensions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Physical": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.Json": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.UserSecrets": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Json": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Physical": "10.0.8" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "21nbDV60SRPWGIivsyl6lqBeEJNG1sginhhfWgRrr3Ais7aQ12To25OAHQxgoiJkjqy1aQ6RxpZBGYuTi7Ge6A==" - }, - "Microsoft.Extensions.Diagnostics": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "uduyw9d3Fi+sbredO5drA1S44AQS2FRNFyn72UmB2vmQIO1qaXprpp1U/2lYhYi8yFdVERfY9sy/pxw/qPOU9w==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" - } - }, - "Microsoft.Extensions.Diagnostics.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "+f4C5g78QCGNyxzUfrTYsB7qYx06Zca0e88s3qFlea9/lQhgPImYdNprlgzl1uHhRU3fVHLfmbijayU2sJEZ6w==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.FileProviders.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.FileProviders.Physical": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", - "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.FileSystemGlobbing": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" - }, - "Microsoft.Extensions.Logging": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "K60JhWC2hN/Gi7TP68tBxSzk5ACWOs7lkmPzsfA8Bcf/IXTajujt2ORMf9rSMk1bsng6Lv4Y3fuxp3bm1+15ug==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "fdVadZmsC8jRP0KvKy8mO8f6GV/HyBvElfcSxEhd+5FM5boAw/01iSaCto5G3G37ApJira4A3pNaVvBv8cUiLQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Configuration": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "rxSLTO7xTbcC3DuEJHNEijBr8g14Jj62zQ+DeFu68bsoTYoU8jLcMhc1735PV21bESXsATlL5LsfaWH71FOWAg==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Binder": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Console": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "6cv53sHsPnFS56PJw8X4GbNcjeX1KGyFJRxJWvxOgK63cnqeSB1k1eRwjUdkse0tBhwlH6qc9EOYDlan+CYTuw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging.Configuration": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Debug": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "4HW3M1lGHHDwEYcDZHRNptBQ48LCI2yW+XV4vuxdfQUqafTpVT8j9RqAsez08krZKhIiaArWu8iQq5uRKZ9Ffg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.EventLog": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "kK/C3SLIoGrcZvddYQw4eMm6YaROiSYBO7YgUR5Hdv5l+GIjBmbvQK5cST2FqjeubiAOPqFEimBT2N/8wVI+3A==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "System.Diagnostics.EventLog": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.EventSource": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "HX2M0MgzwQM8jpLe3AYAEMd0YsUfOP5RgGrDuk+Ki9n7HSuMbvLm9TEV3qRI3Pg9aqxc56GfgK/KdMRBhfWwKw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" - }, - "System.Diagnostics.EventLog": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "+Ro7WgIom+BDNH+YhTuZKL6QJ0ctfOpTyfUG/h3aU5KwXt3OaNf0wYWrTvoBUj+34Dy5V8dN9yCco1hAJQ4txw==" - }, - "patternkit.core": { - "type": "Project" - }, - "patternkit.generators.abstractions": { - "type": "Project" - } - }, - "net8.0": { - "JetBrains.Annotations": { - "type": "Direct", - "requested": "[2025.2.4, )", - "resolved": "2025.2.4", - "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" - }, - "Microsoft.Extensions.Configuration": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "R3NN1X+kVu14uoxLEW6sBSQyhogDSbaOQzILnCtuXxBN4hx22AgjWPwZX6v/suERFkEDgU1lk12AglHTrUxhlw==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Hosting": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "VfEyM2BipThcSd0GG/FS2ZPCVCTiosVq2zLKEDsfeMIg78sOVZPEmS7CgWlb+dqTlgXvLSL4OG2q6sM4xRhHNg==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Binder": "10.0.8", - "Microsoft.Extensions.Configuration.CommandLine": "10.0.8", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.8", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", - "Microsoft.Extensions.Configuration.Json": "10.0.8", - "Microsoft.Extensions.Configuration.UserSecrets": "10.0.8", - "Microsoft.Extensions.DependencyInjection": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Diagnostics": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Physical": "10.0.8", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging.Configuration": "10.0.8", - "Microsoft.Extensions.Logging.Console": "10.0.8", - "Microsoft.Extensions.Logging.Debug": "10.0.8", - "Microsoft.Extensions.Logging.EventLog": "10.0.8", - "Microsoft.Extensions.Logging.EventSource": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Hosting.Abstractions": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Options": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "VBD+131DpTNCNDfA4kIyKTiCySvJGNhwibdWBSdFRu7GMfXLXcXODkgA+KStKbbhzraLglZWUN4nXyHgW4JIRA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "VOapXeO3lhBH0zYoyAH7tjapuo4V5pTHlevPpiSHueEquAajqd5nF0mttm+h/uE/exwAEuM5s26SzOJtletE3w==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Binder": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Options.DataAnnotations": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "HhxwIGECGGJ8ox2kvm6/hkN/w1ZyKrO5uu/rLAL51V0ypPdahoNf+dHS6Er/DJs2aeUmH38ZTTzACfLy1O6w3Q==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.CommandLine": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "nQXq1a4MiInYh+0VF9fguxAl06q2ftmOyYQ+5e933s4rk57xjgkbTjUdFUySzjrcrvDeWsSqlZB+TE8+TbM2HA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.EnvironmentVariables": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.FileExtensions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Physical": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.Json": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "System.Text.Json": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.UserSecrets": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Json": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Physical": "10.0.8" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "21nbDV60SRPWGIivsyl6lqBeEJNG1sginhhfWgRrr3Ais7aQ12To25OAHQxgoiJkjqy1aQ6RxpZBGYuTi7Ge6A==" - }, - "Microsoft.Extensions.Diagnostics": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "uduyw9d3Fi+sbredO5drA1S44AQS2FRNFyn72UmB2vmQIO1qaXprpp1U/2lYhYi8yFdVERfY9sy/pxw/qPOU9w==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" - } - }, - "Microsoft.Extensions.Diagnostics.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "+f4C5g78QCGNyxzUfrTYsB7qYx06Zca0e88s3qFlea9/lQhgPImYdNprlgzl1uHhRU3fVHLfmbijayU2sJEZ6w==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "System.Diagnostics.DiagnosticSource": "10.0.8" - } - }, - "Microsoft.Extensions.FileProviders.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.FileProviders.Physical": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", - "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.FileSystemGlobbing": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" - }, - "Microsoft.Extensions.Logging": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "K60JhWC2hN/Gi7TP68tBxSzk5ACWOs7lkmPzsfA8Bcf/IXTajujt2ORMf9rSMk1bsng6Lv4Y3fuxp3bm1+15ug==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "fdVadZmsC8jRP0KvKy8mO8f6GV/HyBvElfcSxEhd+5FM5boAw/01iSaCto5G3G37ApJira4A3pNaVvBv8cUiLQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "System.Diagnostics.DiagnosticSource": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Configuration": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "rxSLTO7xTbcC3DuEJHNEijBr8g14Jj62zQ+DeFu68bsoTYoU8jLcMhc1735PV21bESXsATlL5LsfaWH71FOWAg==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Binder": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Console": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "6cv53sHsPnFS56PJw8X4GbNcjeX1KGyFJRxJWvxOgK63cnqeSB1k1eRwjUdkse0tBhwlH6qc9EOYDlan+CYTuw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging.Configuration": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "System.Text.Json": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Debug": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "4HW3M1lGHHDwEYcDZHRNptBQ48LCI2yW+XV4vuxdfQUqafTpVT8j9RqAsez08krZKhIiaArWu8iQq5uRKZ9Ffg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.EventLog": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "kK/C3SLIoGrcZvddYQw4eMm6YaROiSYBO7YgUR5Hdv5l+GIjBmbvQK5cST2FqjeubiAOPqFEimBT2N/8wVI+3A==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "System.Diagnostics.EventLog": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.EventSource": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "HX2M0MgzwQM8jpLe3AYAEMd0YsUfOP5RgGrDuk+Ki9n7HSuMbvLm9TEV3qRI3Pg9aqxc56GfgK/KdMRBhfWwKw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8", - "System.Text.Json": "10.0.8" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" - }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "rQHsK7Xr8Uz3449860ayVXp/CaLmrhHlMPxbpT/ibOPtp/dTTsr6+f/SxaGO2NzxHf+0siLE0UfdVN5z1I0EgQ==" - }, - "System.Diagnostics.EventLog": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "+Ro7WgIom+BDNH+YhTuZKL6QJ0ctfOpTyfUG/h3aU5KwXt3OaNf0wYWrTvoBUj+34Dy5V8dN9yCco1hAJQ4txw==" - }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "HvFIJXM/CMTRu6PBQmjukQZ/O32Vx5fOEYZs0kq0OD5s9vJQ19KHLWCVmnfh3gNC7pbYomm06tzOTgJBflr/nQ==" - }, - "System.Text.Json": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "/fysUDkD7oFGaRPoA7IaFs0wRoO3GlwlCNq2P+xWZqxLy1R4cktRSKfMjJDy9ymS4grL7IDVdt8de8L9a0z55Q==", - "dependencies": { - "System.IO.Pipelines": "10.0.8", - "System.Text.Encodings.Web": "10.0.8" - } - }, - "patternkit.core": { - "type": "Project" - }, - "patternkit.generators.abstractions": { - "type": "Project" - } - }, - "net9.0": { - "JetBrains.Annotations": { - "type": "Direct", - "requested": "[2025.2.4, )", - "resolved": "2025.2.4", - "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" - }, - "Microsoft.Extensions.Configuration": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "R3NN1X+kVu14uoxLEW6sBSQyhogDSbaOQzILnCtuXxBN4hx22AgjWPwZX6v/suERFkEDgU1lk12AglHTrUxhlw==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Hosting": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "VfEyM2BipThcSd0GG/FS2ZPCVCTiosVq2zLKEDsfeMIg78sOVZPEmS7CgWlb+dqTlgXvLSL4OG2q6sM4xRhHNg==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Binder": "10.0.8", - "Microsoft.Extensions.Configuration.CommandLine": "10.0.8", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.8", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", - "Microsoft.Extensions.Configuration.Json": "10.0.8", - "Microsoft.Extensions.Configuration.UserSecrets": "10.0.8", - "Microsoft.Extensions.DependencyInjection": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Diagnostics": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Physical": "10.0.8", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging.Configuration": "10.0.8", - "Microsoft.Extensions.Logging.Console": "10.0.8", - "Microsoft.Extensions.Logging.Debug": "10.0.8", - "Microsoft.Extensions.Logging.EventLog": "10.0.8", - "Microsoft.Extensions.Logging.EventSource": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Hosting.Abstractions": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Options": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "VBD+131DpTNCNDfA4kIyKTiCySvJGNhwibdWBSdFRu7GMfXLXcXODkgA+KStKbbhzraLglZWUN4nXyHgW4JIRA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "VOapXeO3lhBH0zYoyAH7tjapuo4V5pTHlevPpiSHueEquAajqd5nF0mttm+h/uE/exwAEuM5s26SzOJtletE3w==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Binder": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Options.DataAnnotations": { - "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "HhxwIGECGGJ8ox2kvm6/hkN/w1ZyKrO5uu/rLAL51V0ypPdahoNf+dHS6Er/DJs2aeUmH38ZTTzACfLy1O6w3Q==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.CommandLine": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "nQXq1a4MiInYh+0VF9fguxAl06q2ftmOyYQ+5e933s4rk57xjgkbTjUdFUySzjrcrvDeWsSqlZB+TE8+TbM2HA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.EnvironmentVariables": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.FileExtensions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Physical": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.Json": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "System.Text.Json": "10.0.8" - } - }, - "Microsoft.Extensions.Configuration.UserSecrets": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Json": "10.0.8", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileProviders.Physical": "10.0.8" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "21nbDV60SRPWGIivsyl6lqBeEJNG1sginhhfWgRrr3Ais7aQ12To25OAHQxgoiJkjqy1aQ6RxpZBGYuTi7Ge6A==" - }, - "Microsoft.Extensions.Diagnostics": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "uduyw9d3Fi+sbredO5drA1S44AQS2FRNFyn72UmB2vmQIO1qaXprpp1U/2lYhYi8yFdVERfY9sy/pxw/qPOU9w==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" - } - }, - "Microsoft.Extensions.Diagnostics.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "+f4C5g78QCGNyxzUfrTYsB7qYx06Zca0e88s3qFlea9/lQhgPImYdNprlgzl1uHhRU3fVHLfmbijayU2sJEZ6w==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "System.Diagnostics.DiagnosticSource": "10.0.8" - } - }, - "Microsoft.Extensions.FileProviders.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.FileProviders.Physical": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", - "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", - "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8" - } - }, - "Microsoft.Extensions.FileSystemGlobbing": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" - }, - "Microsoft.Extensions.Logging": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "K60JhWC2hN/Gi7TP68tBxSzk5ACWOs7lkmPzsfA8Bcf/IXTajujt2ORMf9rSMk1bsng6Lv4Y3fuxp3bm1+15ug==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "fdVadZmsC8jRP0KvKy8mO8f6GV/HyBvElfcSxEhd+5FM5boAw/01iSaCto5G3G37ApJira4A3pNaVvBv8cUiLQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "System.Diagnostics.DiagnosticSource": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Configuration": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "rxSLTO7xTbcC3DuEJHNEijBr8g14Jj62zQ+DeFu68bsoTYoU8jLcMhc1735PV21bESXsATlL5LsfaWH71FOWAg==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.8", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", - "Microsoft.Extensions.Configuration.Binder": "10.0.8", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Console": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "6cv53sHsPnFS56PJw8X4GbNcjeX1KGyFJRxJWvxOgK63cnqeSB1k1eRwjUdkse0tBhwlH6qc9EOYDlan+CYTuw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging.Configuration": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "System.Text.Json": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.Debug": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "4HW3M1lGHHDwEYcDZHRNptBQ48LCI2yW+XV4vuxdfQUqafTpVT8j9RqAsez08krZKhIiaArWu8iQq5uRKZ9Ffg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.EventLog": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "kK/C3SLIoGrcZvddYQw4eMm6YaROiSYBO7YgUR5Hdv5l+GIjBmbvQK5cST2FqjeubiAOPqFEimBT2N/8wVI+3A==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "System.Diagnostics.EventLog": "10.0.8" - } - }, - "Microsoft.Extensions.Logging.EventSource": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "HX2M0MgzwQM8jpLe3AYAEMd0YsUfOP5RgGrDuk+Ki9n7HSuMbvLm9TEV3qRI3Pg9aqxc56GfgK/KdMRBhfWwKw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", - "Microsoft.Extensions.Logging": "10.0.8", - "Microsoft.Extensions.Logging.Abstractions": "10.0.8", - "Microsoft.Extensions.Options": "10.0.8", - "Microsoft.Extensions.Primitives": "10.0.8", - "System.Text.Json": "10.0.8" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" - }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "rQHsK7Xr8Uz3449860ayVXp/CaLmrhHlMPxbpT/ibOPtp/dTTsr6+f/SxaGO2NzxHf+0siLE0UfdVN5z1I0EgQ==" - }, - "System.Diagnostics.EventLog": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "+Ro7WgIom+BDNH+YhTuZKL6QJ0ctfOpTyfUG/h3aU5KwXt3OaNf0wYWrTvoBUj+34Dy5V8dN9yCco1hAJQ4txw==" - }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "HvFIJXM/CMTRu6PBQmjukQZ/O32Vx5fOEYZs0kq0OD5s9vJQ19KHLWCVmnfh3gNC7pbYomm06tzOTgJBflr/nQ==" - }, - "System.Text.Json": { - "type": "Transitive", - "resolved": "10.0.8", - "contentHash": "/fysUDkD7oFGaRPoA7IaFs0wRoO3GlwlCNq2P+xWZqxLy1R4cktRSKfMjJDy9ymS4grL7IDVdt8de8L9a0z55Q==", - "dependencies": { - "System.IO.Pipelines": "10.0.8", - "System.Text.Encodings.Web": "10.0.8" - } - }, - "patternkit.core": { - "type": "Project" - }, - "patternkit.generators.abstractions": { - "type": "Project" - } - } - } +{ + "version": 2, + "dependencies": { + "net10.0": { + "JetBrains.Annotations": { + "type": "Direct", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==" + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==" + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "R3NN1X+kVu14uoxLEW6sBSQyhogDSbaOQzILnCtuXxBN4hx22AgjWPwZX6v/suERFkEDgU1lk12AglHTrUxhlw==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==" + }, + "Microsoft.Extensions.Hosting": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VfEyM2BipThcSd0GG/FS2ZPCVCTiosVq2zLKEDsfeMIg78sOVZPEmS7CgWlb+dqTlgXvLSL4OG2q6sM4xRhHNg==" + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==" + }, + "Microsoft.Extensions.Options": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VBD+131DpTNCNDfA4kIyKTiCySvJGNhwibdWBSdFRu7GMfXLXcXODkgA+KStKbbhzraLglZWUN4nXyHgW4JIRA==" + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VOapXeO3lhBH0zYoyAH7tjapuo4V5pTHlevPpiSHueEquAajqd5nF0mttm+h/uE/exwAEuM5s26SzOJtletE3w==" + }, + "Microsoft.Extensions.Options.DataAnnotations": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "HhxwIGECGGJ8ox2kvm6/hkN/w1ZyKrO5uu/rLAL51V0ypPdahoNf+dHS6Er/DJs2aeUmH38ZTTzACfLy1O6w3Q==" + }, + "patternkit.core": { + "type": "Project" + }, + "patternkit.generators.abstractions": { + "type": "Project" + } + }, + "net8.0": { + "JetBrains.Annotations": { + "type": "Direct", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "R3NN1X+kVu14uoxLEW6sBSQyhogDSbaOQzILnCtuXxBN4hx22AgjWPwZX6v/suERFkEDgU1lk12AglHTrUxhlw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VfEyM2BipThcSd0GG/FS2ZPCVCTiosVq2zLKEDsfeMIg78sOVZPEmS7CgWlb+dqTlgXvLSL4OG2q6sM4xRhHNg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.Configuration.CommandLine": "10.0.8", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.Configuration.UserSecrets": "10.0.8", + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Logging.Console": "10.0.8", + "Microsoft.Extensions.Logging.Debug": "10.0.8", + "Microsoft.Extensions.Logging.EventLog": "10.0.8", + "Microsoft.Extensions.Logging.EventSource": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Options": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VBD+131DpTNCNDfA4kIyKTiCySvJGNhwibdWBSdFRu7GMfXLXcXODkgA+KStKbbhzraLglZWUN4nXyHgW4JIRA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VOapXeO3lhBH0zYoyAH7tjapuo4V5pTHlevPpiSHueEquAajqd5nF0mttm+h/uE/exwAEuM5s26SzOJtletE3w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.DataAnnotations": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "HhxwIGECGGJ8ox2kvm6/hkN/w1ZyKrO5uu/rLAL51V0ypPdahoNf+dHS6Er/DJs2aeUmH38ZTTzACfLy1O6w3Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "nQXq1a4MiInYh+0VF9fguxAl06q2ftmOyYQ+5e933s4rk57xjgkbTjUdFUySzjrcrvDeWsSqlZB+TE8+TbM2HA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "21nbDV60SRPWGIivsyl6lqBeEJNG1sginhhfWgRrr3Ais7aQ12To25OAHQxgoiJkjqy1aQ6RxpZBGYuTi7Ge6A==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "uduyw9d3Fi+sbredO5drA1S44AQS2FRNFyn72UmB2vmQIO1qaXprpp1U/2lYhYi8yFdVERfY9sy/pxw/qPOU9w==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+f4C5g78QCGNyxzUfrTYsB7qYx06Zca0e88s3qFlea9/lQhgPImYdNprlgzl1uHhRU3fVHLfmbijayU2sJEZ6w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "K60JhWC2hN/Gi7TP68tBxSzk5ACWOs7lkmPzsfA8Bcf/IXTajujt2ORMf9rSMk1bsng6Lv4Y3fuxp3bm1+15ug==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "fdVadZmsC8jRP0KvKy8mO8f6GV/HyBvElfcSxEhd+5FM5boAw/01iSaCto5G3G37ApJira4A3pNaVvBv8cUiLQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rxSLTO7xTbcC3DuEJHNEijBr8g14Jj62zQ+DeFu68bsoTYoU8jLcMhc1735PV21bESXsATlL5LsfaWH71FOWAg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6cv53sHsPnFS56PJw8X4GbNcjeX1KGyFJRxJWvxOgK63cnqeSB1k1eRwjUdkse0tBhwlH6qc9EOYDlan+CYTuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "4HW3M1lGHHDwEYcDZHRNptBQ48LCI2yW+XV4vuxdfQUqafTpVT8j9RqAsez08krZKhIiaArWu8iQq5uRKZ9Ffg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "kK/C3SLIoGrcZvddYQw4eMm6YaROiSYBO7YgUR5Hdv5l+GIjBmbvQK5cST2FqjeubiAOPqFEimBT2N/8wVI+3A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.EventLog": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HX2M0MgzwQM8jpLe3AYAEMd0YsUfOP5RgGrDuk+Ki9n7HSuMbvLm9TEV3qRI3Pg9aqxc56GfgK/KdMRBhfWwKw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rQHsK7Xr8Uz3449860ayVXp/CaLmrhHlMPxbpT/ibOPtp/dTTsr6+f/SxaGO2NzxHf+0siLE0UfdVN5z1I0EgQ==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+Ro7WgIom+BDNH+YhTuZKL6QJ0ctfOpTyfUG/h3aU5KwXt3OaNf0wYWrTvoBUj+34Dy5V8dN9yCco1hAJQ4txw==" + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HvFIJXM/CMTRu6PBQmjukQZ/O32Vx5fOEYZs0kq0OD5s9vJQ19KHLWCVmnfh3gNC7pbYomm06tzOTgJBflr/nQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "/fysUDkD7oFGaRPoA7IaFs0wRoO3GlwlCNq2P+xWZqxLy1R4cktRSKfMjJDy9ymS4grL7IDVdt8de8L9a0z55Q==", + "dependencies": { + "System.IO.Pipelines": "10.0.8", + "System.Text.Encodings.Web": "10.0.8" + } + }, + "patternkit.core": { + "type": "Project" + }, + "patternkit.generators.abstractions": { + "type": "Project" + } + }, + "net9.0": { + "JetBrains.Annotations": { + "type": "Direct", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "R3NN1X+kVu14uoxLEW6sBSQyhogDSbaOQzILnCtuXxBN4hx22AgjWPwZX6v/suERFkEDgU1lk12AglHTrUxhlw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VfEyM2BipThcSd0GG/FS2ZPCVCTiosVq2zLKEDsfeMIg78sOVZPEmS7CgWlb+dqTlgXvLSL4OG2q6sM4xRhHNg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.Configuration.CommandLine": "10.0.8", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.Configuration.UserSecrets": "10.0.8", + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Logging.Console": "10.0.8", + "Microsoft.Extensions.Logging.Debug": "10.0.8", + "Microsoft.Extensions.Logging.EventLog": "10.0.8", + "Microsoft.Extensions.Logging.EventSource": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Options": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VBD+131DpTNCNDfA4kIyKTiCySvJGNhwibdWBSdFRu7GMfXLXcXODkgA+KStKbbhzraLglZWUN4nXyHgW4JIRA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VOapXeO3lhBH0zYoyAH7tjapuo4V5pTHlevPpiSHueEquAajqd5nF0mttm+h/uE/exwAEuM5s26SzOJtletE3w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.DataAnnotations": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "HhxwIGECGGJ8ox2kvm6/hkN/w1ZyKrO5uu/rLAL51V0ypPdahoNf+dHS6Er/DJs2aeUmH38ZTTzACfLy1O6w3Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "nQXq1a4MiInYh+0VF9fguxAl06q2ftmOyYQ+5e933s4rk57xjgkbTjUdFUySzjrcrvDeWsSqlZB+TE8+TbM2HA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "21nbDV60SRPWGIivsyl6lqBeEJNG1sginhhfWgRrr3Ais7aQ12To25OAHQxgoiJkjqy1aQ6RxpZBGYuTi7Ge6A==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "uduyw9d3Fi+sbredO5drA1S44AQS2FRNFyn72UmB2vmQIO1qaXprpp1U/2lYhYi8yFdVERfY9sy/pxw/qPOU9w==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+f4C5g78QCGNyxzUfrTYsB7qYx06Zca0e88s3qFlea9/lQhgPImYdNprlgzl1uHhRU3fVHLfmbijayU2sJEZ6w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "K60JhWC2hN/Gi7TP68tBxSzk5ACWOs7lkmPzsfA8Bcf/IXTajujt2ORMf9rSMk1bsng6Lv4Y3fuxp3bm1+15ug==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "fdVadZmsC8jRP0KvKy8mO8f6GV/HyBvElfcSxEhd+5FM5boAw/01iSaCto5G3G37ApJira4A3pNaVvBv8cUiLQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rxSLTO7xTbcC3DuEJHNEijBr8g14Jj62zQ+DeFu68bsoTYoU8jLcMhc1735PV21bESXsATlL5LsfaWH71FOWAg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6cv53sHsPnFS56PJw8X4GbNcjeX1KGyFJRxJWvxOgK63cnqeSB1k1eRwjUdkse0tBhwlH6qc9EOYDlan+CYTuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "4HW3M1lGHHDwEYcDZHRNptBQ48LCI2yW+XV4vuxdfQUqafTpVT8j9RqAsez08krZKhIiaArWu8iQq5uRKZ9Ffg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "kK/C3SLIoGrcZvddYQw4eMm6YaROiSYBO7YgUR5Hdv5l+GIjBmbvQK5cST2FqjeubiAOPqFEimBT2N/8wVI+3A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.EventLog": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HX2M0MgzwQM8jpLe3AYAEMd0YsUfOP5RgGrDuk+Ki9n7HSuMbvLm9TEV3qRI3Pg9aqxc56GfgK/KdMRBhfWwKw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rQHsK7Xr8Uz3449860ayVXp/CaLmrhHlMPxbpT/ibOPtp/dTTsr6+f/SxaGO2NzxHf+0siLE0UfdVN5z1I0EgQ==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+Ro7WgIom+BDNH+YhTuZKL6QJ0ctfOpTyfUG/h3aU5KwXt3OaNf0wYWrTvoBUj+34Dy5V8dN9yCco1hAJQ4txw==" + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HvFIJXM/CMTRu6PBQmjukQZ/O32Vx5fOEYZs0kq0OD5s9vJQ19KHLWCVmnfh3gNC7pbYomm06tzOTgJBflr/nQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "/fysUDkD7oFGaRPoA7IaFs0wRoO3GlwlCNq2P+xWZqxLy1R4cktRSKfMjJDy9ymS4grL7IDVdt8de8L9a0z55Q==", + "dependencies": { + "System.IO.Pipelines": "10.0.8", + "System.Text.Encodings.Web": "10.0.8" + } + }, + "patternkit.core": { + "type": "Project" + }, + "patternkit.generators.abstractions": { + "type": "Project" + } + } + } } \ No newline at end of file diff --git a/src/PatternKit.Generators.Abstractions/Messaging/MessageExpirationAttributes.cs b/src/PatternKit.Generators.Abstractions/Messaging/MessageExpirationAttributes.cs new file mode 100644 index 00000000..7a369b33 --- /dev/null +++ b/src/PatternKit.Generators.Abstractions/Messaging/MessageExpirationAttributes.cs @@ -0,0 +1,35 @@ +using System; + +namespace PatternKit.Generators.Messaging; + +/// +/// Generates a typed message-expiration policy factory for a partial class or struct. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] +public sealed class GenerateMessageExpirationAttribute : Attribute +{ + /// Creates a message-expiration generator attribute. + public GenerateMessageExpirationAttribute(Type payloadType) + => PayloadType = payloadType ?? throw new ArgumentNullException(nameof(payloadType)); + + /// Payload type stamped and evaluated by the generated expiration policy. + public Type PayloadType { get; } + + /// Name of the generated factory method. + public string FactoryName { get; set; } = "Create"; + + /// Name assigned to the generated expiration policy. + public string PolicyName { get; set; } = "message-expiration"; + + /// Header used to store expiration deadlines. + public string HeaderName { get; set; } = "expires-at"; + + /// Default TTL, in milliseconds, used by Stamp. Values less than or equal to zero omit the default TTL. + public int DefaultTtlMilliseconds { get; set; } + + /// Whether stamping keeps an existing expiration deadline. + public bool PreserveExisting { get; set; } = true; + + /// Reason returned when a message is expired. + public string ExpiredReason { get; set; } = "Message expired before processing."; +} diff --git a/src/PatternKit.Generators/AnalyzerReleases.Unshipped.md b/src/PatternKit.Generators/AnalyzerReleases.Unshipped.md index 66a79827..dacdd0f7 100644 --- a/src/PatternKit.Generators/AnalyzerReleases.Unshipped.md +++ b/src/PatternKit.Generators/AnalyzerReleases.Unshipped.md @@ -350,6 +350,8 @@ PKMS001 | PatternKit.Generators.Messaging | Error | Message store type must be p PKMS002 | PatternKit.Generators.Messaging | Error | Message store identity signature is invalid. PKMS003 | PatternKit.Generators.Messaging | Error | Message store retention signature is invalid. PKMS004 | PatternKit.Generators.Messaging | Error | Message store hook is duplicated. +PKMEXP001 | PatternKit.Generators.Messaging | Error | Message expiration type must be partial. +PKMEXP002 | PatternKit.Generators.Messaging | Error | Message expiration configuration is invalid. PKCTL001 | PatternKit.Generators.Messaging | Error | Control Bus host type must be partial. PKCTL002 | PatternKit.Generators.Messaging | Error | Control Bus must declare at least one handler. PKCTL003 | PatternKit.Generators.Messaging | Error | Control Bus handler signature is invalid. diff --git a/src/PatternKit.Generators/Messaging/MessageExpirationGenerator.cs b/src/PatternKit.Generators/Messaging/MessageExpirationGenerator.cs new file mode 100644 index 00000000..f3b411a9 --- /dev/null +++ b/src/PatternKit.Generators/Messaging/MessageExpirationGenerator.cs @@ -0,0 +1,142 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Linq; +using System.Text; + +namespace PatternKit.Generators.Messaging; + +[Generator] +public sealed class MessageExpirationGenerator : IIncrementalGenerator +{ + private static readonly DiagnosticDescriptor MustBePartial = new( + "PKMEXP001", + "Message expiration type must be partial", + "Type '{0}' is marked with [GenerateMessageExpiration] but is not declared as partial", + "PatternKit.Generators.Messaging", + DiagnosticSeverity.Error, + true); + + private static readonly DiagnosticDescriptor InvalidConfiguration = new( + "PKMEXP002", + "Message expiration configuration is invalid", + "Type '{0}' has invalid message-expiration configuration: factory, policy, header, and reason values must not be empty", + "PatternKit.Generators.Messaging", + DiagnosticSeverity.Error, + true); + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var candidates = context.SyntaxProvider.ForAttributeWithMetadataName( + "PatternKit.Generators.Messaging.GenerateMessageExpirationAttribute", + static (node, _) => node is TypeDeclarationSyntax, + static (ctx, _) => (Type: (INamedTypeSymbol)ctx.TargetSymbol, Node: (TypeDeclarationSyntax)ctx.TargetNode, Attributes: ctx.Attributes)); + + context.RegisterSourceOutput(candidates, static (spc, candidate) => + { + var attr = candidate.Attributes.FirstOrDefault(a => + a.AttributeClass?.ToDisplayString() == "PatternKit.Generators.Messaging.GenerateMessageExpirationAttribute"); + if (attr is null) + return; + + Generate(spc, candidate.Type, candidate.Node, attr); + }); + } + + private static void Generate( + SourceProductionContext context, + INamedTypeSymbol type, + TypeDeclarationSyntax node, + AttributeData attribute) + { + if (!node.Modifiers.Any(static modifier => modifier.Text == "partial")) + { + context.ReportDiagnostic(Diagnostic.Create(MustBePartial, node.Identifier.GetLocation(), type.Name)); + return; + } + + var payloadType = attribute.ConstructorArguments.Length >= 1 + ? attribute.ConstructorArguments[0].Value as INamedTypeSymbol + : null; + if (payloadType is null) + return; + + var factoryName = GetNamedString(attribute, "FactoryName") ?? "Create"; + var policyName = GetNamedString(attribute, "PolicyName") ?? "message-expiration"; + var headerName = GetNamedString(attribute, "HeaderName") ?? "expires-at"; + var expiredReason = GetNamedString(attribute, "ExpiredReason") ?? "Message expired before processing."; + if (string.IsNullOrWhiteSpace(factoryName) || + string.IsNullOrWhiteSpace(policyName) || + string.IsNullOrWhiteSpace(headerName) || + string.IsNullOrWhiteSpace(expiredReason)) + { + context.ReportDiagnostic(Diagnostic.Create(InvalidConfiguration, node.Identifier.GetLocation(), type.Name)); + return; + } + + var ttlMilliseconds = GetNamedInt(attribute, "DefaultTtlMilliseconds"); + var preserveExisting = GetNamedBool(attribute, "PreserveExisting", defaultValue: true); + + context.AddSource($"{type.Name}.MessageExpiration.g.cs", SourceText.From( + GenerateSource(type, payloadType, factoryName, policyName, headerName, ttlMilliseconds, preserveExisting, expiredReason), + Encoding.UTF8)); + } + + private static string GenerateSource( + INamedTypeSymbol type, + INamedTypeSymbol payloadType, + string factoryName, + string policyName, + string headerName, + int ttlMilliseconds, + bool preserveExisting, + string expiredReason) + { + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine("#nullable enable"); + sb.AppendLine(); + + var ns = type.ContainingNamespace.IsGlobalNamespace ? null : type.ContainingNamespace.ToDisplayString(); + if (ns is not null) + { + sb.Append("namespace ").Append(ns).AppendLine(";"); + sb.AppendLine(); + } + + sb.Append("partial ").Append(GetKind(type)).Append(' ').Append(type.Name).AppendLine(); + sb.AppendLine("{"); + sb.Append(" public static global::PatternKit.Messaging.Reliability.MessageExpiration<") + .Append(payloadType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) + .Append("> ") + .Append(factoryName) + .AppendLine("()"); + sb.Append(" => global::PatternKit.Messaging.Reliability.MessageExpiration<") + .Append(payloadType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) + .AppendLine(">.Create()"); + sb.Append(" .Name(").Append(ToLiteral(policyName)).AppendLine(")"); + sb.Append(" .Header(").Append(ToLiteral(headerName)).AppendLine(")"); + if (ttlMilliseconds > 0) + sb.Append(" .DefaultTtl(global::System.TimeSpan.FromMilliseconds(").Append(ttlMilliseconds).AppendLine("))"); + sb.Append(" .PreserveExisting(").Append(preserveExisting ? "true" : "false").AppendLine(")"); + sb.Append(" .ExpiredReason(").Append(ToLiteral(expiredReason)).AppendLine(")"); + sb.AppendLine(" .Build();"); + sb.AppendLine("}"); + return sb.ToString(); + } + + private static string GetKind(INamedTypeSymbol type) + => type.TypeKind == TypeKind.Struct ? "struct" : "class"; + + private static string? GetNamedString(AttributeData attribute, string name) + => attribute.NamedArguments.FirstOrDefault(kv => kv.Key == name).Value.Value as string; + + private static int GetNamedInt(AttributeData attribute, string name) + => attribute.NamedArguments.FirstOrDefault(kv => kv.Key == name).Value.Value as int? ?? 0; + + private static bool GetNamedBool(AttributeData attribute, string name, bool defaultValue) + => attribute.NamedArguments.FirstOrDefault(kv => kv.Key == name).Value.Value as bool? ?? defaultValue; + + private static string ToLiteral(string value) + => "@\"" + value.Replace("\"", "\"\"") + "\""; +} diff --git a/src/PatternKit.Generators/packages.lock.json b/src/PatternKit.Generators/packages.lock.json index 39306387..8d8dba5d 100644 --- a/src/PatternKit.Generators/packages.lock.json +++ b/src/PatternKit.Generators/packages.lock.json @@ -1,7 +1,7 @@ { "version": 2, "dependencies": { - "net9.0": { + ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.Analyzers": { "type": "Direct", "requested": "[5.3.0, )", @@ -10,30 +10,116 @@ }, "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", - "requested": "[5.3.0, )", - "resolved": "5.3.0", - "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.Common": "[5.0.0]", + "System.Buffers": "4.6.0", + "System.Collections.Immutable": "9.0.0", + "System.Memory": "4.6.0", + "System.Numerics.Vectors": "4.6.0", + "System.Reflection.Metadata": "9.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.1.0", + "System.Text.Encoding.CodePages": "8.0.0", + "System.Threading.Tasks.Extensions": "4.6.0" + } + }, + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", - "Microsoft.CodeAnalysis.Common": "[5.3.0]" + "Microsoft.NETCore.Platforms": "1.1.0" } }, "System.Collections.Immutable": { "type": "Direct", "requested": "[10.0.8, )", "resolved": "10.0.8", - "contentHash": "rvMGYko6bcE/ZTpj1Q/EM22rcZNiQyjH+71capjjfmqG+DDncVE4kSaE5ysCv2vEFIHt8+zALWYXgIETFvcFzw==" + "contentHash": "rvMGYko6bcE/ZTpj1Q/EM22rcZNiQyjH+71capjjfmqG+DDncVE4kSaE5ysCv2vEFIHt8+zALWYXgIETFvcFzw==", + "dependencies": { + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" + } }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "5.3.0", - "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", + "resolved": "5.0.0", + "contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "System.Buffers": "4.6.0", + "System.Collections.Immutable": "9.0.0", + "System.Memory": "4.6.0", + "System.Numerics.Vectors": "4.6.0", + "System.Reflection.Metadata": "9.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.1.0", + "System.Text.Encoding.CodePages": "8.0.0", + "System.Threading.Tasks.Extensions": "4.6.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.6.1", + "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.6.3", + "contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==", + "dependencies": { + "System.Buffers": "4.6.1", + "System.Numerics.Vectors": "4.6.1", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.6.1", + "contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", + "dependencies": { + "System.Collections.Immutable": "9.0.0", + "System.Memory": "4.5.5" + } + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.1.2", + "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw==" + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1" + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "patternkit.generators.abstractions": { "type": "Project" + }, + "System.Threading.Tasks.Extensions": { + "type": "CentralTransitive", + "requested": "[4.6.3, )", + "resolved": "4.6.0", + "contentHash": "I5G6Y8jb0xRtGUC9Lahy7FUvlYlnGMMkbuKAQBy8Jb7Y6Yn8OlBEiUOY0PqZ0hy6Ua8poVA1ui1tAIiXNxGdsg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.1.0" + } } } } diff --git a/test/PatternKit.Examples.Tests/Messaging/OrderMessageExpirationExampleTests.cs b/test/PatternKit.Examples.Tests/Messaging/OrderMessageExpirationExampleTests.cs new file mode 100644 index 00000000..c28c4ce8 --- /dev/null +++ b/test/PatternKit.Examples.Tests/Messaging/OrderMessageExpirationExampleTests.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.DependencyInjection; +using PatternKit.Examples.Messaging; +using PatternKit.Messaging; +using TinyBDD; + +namespace PatternKit.Examples.Tests.Messaging; + +public sealed class OrderMessageExpirationExampleTests +{ + private static readonly DateTimeOffset Now = new(2026, 5, 27, 12, 0, 0, TimeSpan.Zero); + + [Scenario("Fluent Expiration Stamps Fresh Order Command")] + [Fact] + public void Fluent_Expiration_Stamps_Fresh_Order_Command() + { + var summary = OrderMessageExpirationExampleRunner.RunFluent(new ExpiringOrderCommand("o-1", "c-1"), Now); + + ScenarioExpect.False(summary.Expired); + ScenarioExpect.Equal(Now.AddMinutes(20), summary.ExpiresAt); + ScenarioExpect.Null(summary.Reason); + } + + [Scenario("Generated Expiration Stamps Fresh Order Command")] + [Fact] + public void Generated_Expiration_Stamps_Fresh_Order_Command() + { + var expiration = GeneratedOrderMessageExpiration.Create(); + var message = expiration.Stamp(Message.Create(new ExpiringOrderCommand("o-1", "c-1"))); + + var result = expiration.Evaluate(message); + + ScenarioExpect.False(result.Expired); + ScenarioExpect.Equal("x-order-expires-at", expiration.HeaderName); + ScenarioExpect.NotNull(result.ExpiresAt); + } + + [Scenario("Generated Expiration Rejects Stale Order Command")] + [Fact] + public void Generated_Expiration_Rejects_Stale_Order_Command() + { + var expiration = GeneratedOrderMessageExpiration.Create(); + var message = Message + .Create(new ExpiringOrderCommand("o-1", "c-1")) + .WithHeader("x-order-expires-at", DateTimeOffset.UtcNow.AddMinutes(-1)); + + var result = expiration.Evaluate(message); + + ScenarioExpect.True(result.Expired); + ScenarioExpect.Equal("Order command expired before fulfillment accepted it.", result.Reason); + } + + [Scenario("Expiration Example Registers With ServiceCollection")] + [Fact] + public void Expiration_Example_Registers_With_ServiceCollection() + { + using var provider = new ServiceCollection() + .AddOrderMessageExpirationDemo() + .BuildServiceProvider(validateScopes: true); + var runner = provider.GetRequiredService(); + + var summary = runner.RunGenerated(new ExpiringOrderCommand("o-1", "c-1")); + + ScenarioExpect.False(summary.Expired); + ScenarioExpect.NotNull(summary.ExpiresAt); + } +} diff --git a/test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitBenchmarkCoverageTests.cs b/test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitBenchmarkCoverageTests.cs index e7c23592..1a7db925 100644 --- a/test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitBenchmarkCoverageTests.cs +++ b/test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitBenchmarkCoverageTests.cs @@ -88,7 +88,7 @@ public Task Published_Benchmark_Results_Include_Every_Catalog_Pattern() .Then("every catalog pattern appears in the benchmark results matrix", ctx => ScenarioExpect.Empty(ctx.MissingPatterns)) .And("the guide publishes the route result total", ctx => - ScenarioExpect.Contains("388 pattern route results", ctx.ResultsGuide)) + ScenarioExpect.Contains("392 pattern route results", ctx.ResultsGuide)) .AssertPassed(); [Scenario("Published benchmark results include every generator source")] diff --git a/test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitPatternCatalogTests.cs b/test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitPatternCatalogTests.cs index 7bafb52c..6a2fe541 100644 --- a/test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitPatternCatalogTests.cs +++ b/test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitPatternCatalogTests.cs @@ -61,6 +61,7 @@ public sealed class PatternKitPatternCatalogTests(ITestOutputHelper output) : Ti "Message History", "Content-Based Router", "Message Filter", + "Message Expiration", "Message Store", "Wire Tap", "Control Bus", @@ -152,7 +153,7 @@ public Task Catalog_Includes_Enterprise_Integration_And_Architecture_Patterns() ScenarioExpect.Equal(EnterprisePatternAdditions.OrderBy(static x => x), patterns.Select(static p => p.Name).OrderBy(static x => x))) .And("enterprise entries are grouped by integration reliability and architecture families", patterns => { - ScenarioExpect.Equal(38, patterns.Count(static p => p.Family == PatternFamily.EnterpriseIntegration)); + ScenarioExpect.Equal(39, patterns.Count(static p => p.Family == PatternFamily.EnterpriseIntegration)); ScenarioExpect.Equal(3, patterns.Count(static p => p.Family == PatternFamily.MessagingReliability)); ScenarioExpect.Equal(17, patterns.Count(static p => p.Family == PatternFamily.CloudArchitecture)); ScenarioExpect.Equal(16, patterns.Count(static p => p.Family == PatternFamily.ApplicationArchitecture)); diff --git a/test/PatternKit.Examples.Tests/packages.lock.json b/test/PatternKit.Examples.Tests/packages.lock.json index d5540c45..434408ee 100644 --- a/test/PatternKit.Examples.Tests/packages.lock.json +++ b/test/PatternKit.Examples.Tests/packages.lock.json @@ -1,6 +1,870 @@ { "version": 2, "dependencies": { + "net10.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "27jXSV/0DbVqF5jDrAxuQFZ9oaz6gmG03p8ttxAFk+X0M4woFYj7MoWDLCna5EGLb0CE6OE7X6ZH3Wt5smTtaA==" + }, + "JetBrains.Annotations": { + "type": "Direct", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==" + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[18.6.0, )", + "resolved": "18.6.0", + "contentHash": "kAIBt0MsYR0o2RULmlW5BhQ1ha50aGEgLKG4f1p0kePBGLJCprqs3S+NxRrYN8UH7mSQRPKpeiH9mwPMEKUObQ==", + "dependencies": { + "Microsoft.CodeCoverage": "18.6.0", + "Microsoft.TestPlatform.TestHost": "18.6.0" + } + }, + "MQTTnet": { + "type": "Direct", + "requested": "[5.1.0.1559, )", + "resolved": "5.1.0.1559", + "contentHash": "9mkk0H+76CToIzp9ru5Uj5BuC3P5hQdkhvXM+CLmmTDdZWqajjICCHuUMbDd1yVBt9BWL77Rvzt0A+/PA4WMCA==" + }, + "RabbitMQ.Client": { + "type": "Direct", + "requested": "[7.2.1, )", + "resolved": "7.2.1", + "contentHash": "YKXEfg9fVQiTKgZlvIhAfPSFaamEgi8DsQmisCH0IAsU4FYLrtoguDrDj6JtJVGUt40QPnBLRH6fTQcAC4qsOg==" + }, + "System.Collections.Immutable": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "rvMGYko6bcE/ZTpj1Q/EM22rcZNiQyjH+71capjjfmqG+DDncVE4kSaE5ysCv2vEFIHt8+zALWYXgIETFvcFzw==" + }, + "Testcontainers": { + "type": "Direct", + "requested": "[4.12.0, )", + "resolved": "4.12.0", + "contentHash": "PTZRdG1ZVkFMsFbc3cK/VUaOB5L3l4wYL+OkWAK33/cvgd/5FcmZlQ6NhMAl3PWBqYkpdWmeYmQW9U2OIXqtFA==", + "dependencies": { + "Docker.DotNet.Enhanced": "4.2.0", + "Docker.DotNet.Enhanced.X509": "4.2.0", + "SSH.NET": "2025.1.0", + "SharpZipLib": "1.4.2" + } + }, + "Testcontainers.RabbitMq": { + "type": "Direct", + "requested": "[4.12.0, )", + "resolved": "4.12.0", + "contentHash": "wsFsFmpwXRQVkaK9s2sNk1g0gcl9p03xFZ0lirSQ6J9pjuEBUPA8wjnTmMJ/tzwC9N/TFT3u62OOMGhlU1eItg==", + "dependencies": { + "Testcontainers": "4.12.0" + } + }, + "TinyBDD.Xunit": { + "type": "Direct", + "requested": "[0.19.8, )", + "resolved": "0.19.8", + "contentHash": "x5YhK3YbgqPltVvlIaDF5WREwjv7zYAkKb/0OUB5SVrO0vT3b4xE1eub6Jw3Ih7lytdbrOcecz20QAhoFyo2FA==", + "dependencies": { + "TinyBDD": "0.19.8", + "xunit.abstractions": "2.0.3", + "xunit.extensibility.core": "2.9.3" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.5, )", + "resolved": "3.1.5", + "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + }, + "BouncyCastle.Cryptography": { + "type": "Transitive", + "resolved": "2.6.2", + "contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" + }, + "Docker.DotNet.Enhanced": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "tm2V/DpnaURbBhMQ7Z3orNR3u+H863KQuYfA/sgGjI5py07dEeV0I02f6pGrx2869KG9uNM/E96puf9i0gId2w==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0", + "Docker.DotNet.Enhanced.LegacyHttp": "4.2.0", + "Docker.DotNet.Enhanced.NPipe": "4.2.0", + "Docker.DotNet.Enhanced.NativeHttp": "4.2.0", + "Docker.DotNet.Enhanced.Unix": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.Handler.Abstractions": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "cQNxpdadEPdNdfjFCl9vgoCQIK3aVHRn1Qlu36aZUFpp4xHfPrk4hRPNVLR/CpobIFJ+dAt8AceTKMlCfPSccw==" + }, + "Docker.DotNet.Enhanced.LegacyHttp": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "sfbMX1HBPUec3PEMoqlP5ak6skXclcTBmu4gG3aUJatP34J2DgvYMP13bvz/rfrjVkAhPqnIiDKiHAkBCokajg==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.NativeHttp": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "/ll+2ePYm1qrsMdgMO5BzCQnbfTGmPJAc9SqXEManbliVBZvEpBKHXLugx/OeEca2oC/b4RV+UNPtue5u4jAuA==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.NPipe": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "8wyYOD6VkvqRkITwsvkt3UbW/1WDl6NFypNAsIIDaMiglNRzFrQcK0nK9VUEZa6Oja8Bso3UYySDoL8qatatAA==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.Unix": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "x0wNcbww1+p9nUfw8i+JvsSArBDGkoZ9GI2PZ1wPo85B2OiFrdzp89omounNhO2GKyaIRWAqAm5jYZyNg9EnxA==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.X509": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "nMw+FHGwGZieDi7kBgpIVl+E8MzjzXeXHvMQpidLADT06fts2Gw6G+K+p0hMGv7liZULxyYiZnQ1UbE2B9NNQg==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "bkmCXn/65Cd0LdO2zTb/ValGAJ1H8y/CgYOiBb3jsDyHI3Y1ljKx6RBvhvn3e5D/4R4I00RRwLf+Bd2Sn6bJjA==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "em1eLz5Q46+hsCtAXdXggWAPd9gQyT4ngdsQ7k1eWvQgpsjtS/wAOJ/5TteieFdiAvrEq1iVn00LtusAxRaVmQ==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.6.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "SharpZipLib": { + "type": "Transitive", + "resolved": "1.4.2", + "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==" + }, + "SSH.NET": { + "type": "Transitive", + "resolved": "2025.1.0", + "contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", + "dependencies": { + "BouncyCastle.Cryptography": "2.6.2" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "patternkit.core": { + "type": "Project" + }, + "patternkit.examples": { + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2025.2.4, )", + "PatternKit.Core": "[1.0.0, )", + "PatternKit.Generators.Abstractions": "[1.0.0, )" + } + }, + "patternkit.generators.abstractions": { + "type": "Project" + }, + "TinyBDD": { + "type": "CentralTransitive", + "requested": "[0.19.14, )", + "resolved": "0.19.8", + "contentHash": "W5J/Ftk6MMphFsu54Nm4rePdQvPQSANAeU6yAPs1+SbdBsETNMldNFj6wJdpr/nh7GLJfOPHmbhER1MubLv+tg==" + } + }, + "net8.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "27jXSV/0DbVqF5jDrAxuQFZ9oaz6gmG03p8ttxAFk+X0M4woFYj7MoWDLCna5EGLb0CE6OE7X6ZH3Wt5smTtaA==" + }, + "JetBrains.Annotations": { + "type": "Direct", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[18.6.0, )", + "resolved": "18.6.0", + "contentHash": "kAIBt0MsYR0o2RULmlW5BhQ1ha50aGEgLKG4f1p0kePBGLJCprqs3S+NxRrYN8UH7mSQRPKpeiH9mwPMEKUObQ==", + "dependencies": { + "Microsoft.CodeCoverage": "18.6.0", + "Microsoft.TestPlatform.TestHost": "18.6.0" + } + }, + "MQTTnet": { + "type": "Direct", + "requested": "[5.1.0.1559, )", + "resolved": "5.1.0.1559", + "contentHash": "9mkk0H+76CToIzp9ru5Uj5BuC3P5hQdkhvXM+CLmmTDdZWqajjICCHuUMbDd1yVBt9BWL77Rvzt0A+/PA4WMCA==" + }, + "RabbitMQ.Client": { + "type": "Direct", + "requested": "[7.2.1, )", + "resolved": "7.2.1", + "contentHash": "YKXEfg9fVQiTKgZlvIhAfPSFaamEgi8DsQmisCH0IAsU4FYLrtoguDrDj6JtJVGUt40QPnBLRH6fTQcAC4qsOg==" + }, + "System.Collections.Immutable": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "rvMGYko6bcE/ZTpj1Q/EM22rcZNiQyjH+71capjjfmqG+DDncVE4kSaE5ysCv2vEFIHt8+zALWYXgIETFvcFzw==" + }, + "Testcontainers": { + "type": "Direct", + "requested": "[4.12.0, )", + "resolved": "4.12.0", + "contentHash": "PTZRdG1ZVkFMsFbc3cK/VUaOB5L3l4wYL+OkWAK33/cvgd/5FcmZlQ6NhMAl3PWBqYkpdWmeYmQW9U2OIXqtFA==", + "dependencies": { + "Docker.DotNet.Enhanced": "4.2.0", + "Docker.DotNet.Enhanced.X509": "4.2.0", + "SSH.NET": "2025.1.0", + "SharpZipLib": "1.4.2" + } + }, + "Testcontainers.RabbitMq": { + "type": "Direct", + "requested": "[4.12.0, )", + "resolved": "4.12.0", + "contentHash": "wsFsFmpwXRQVkaK9s2sNk1g0gcl9p03xFZ0lirSQ6J9pjuEBUPA8wjnTmMJ/tzwC9N/TFT3u62OOMGhlU1eItg==", + "dependencies": { + "Testcontainers": "4.12.0" + } + }, + "TinyBDD.Xunit": { + "type": "Direct", + "requested": "[0.19.8, )", + "resolved": "0.19.8", + "contentHash": "x5YhK3YbgqPltVvlIaDF5WREwjv7zYAkKb/0OUB5SVrO0vT3b4xE1eub6Jw3Ih7lytdbrOcecz20QAhoFyo2FA==", + "dependencies": { + "TinyBDD": "0.19.8", + "xunit.abstractions": "2.0.3", + "xunit.extensibility.core": "2.9.3" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.5, )", + "resolved": "3.1.5", + "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + }, + "BouncyCastle.Cryptography": { + "type": "Transitive", + "resolved": "2.6.2", + "contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" + }, + "Docker.DotNet.Enhanced": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "tm2V/DpnaURbBhMQ7Z3orNR3u+H863KQuYfA/sgGjI5py07dEeV0I02f6pGrx2869KG9uNM/E96puf9i0gId2w==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0", + "Docker.DotNet.Enhanced.LegacyHttp": "4.2.0", + "Docker.DotNet.Enhanced.NPipe": "4.2.0", + "Docker.DotNet.Enhanced.NativeHttp": "4.2.0", + "Docker.DotNet.Enhanced.Unix": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.Handler.Abstractions": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "cQNxpdadEPdNdfjFCl9vgoCQIK3aVHRn1Qlu36aZUFpp4xHfPrk4hRPNVLR/CpobIFJ+dAt8AceTKMlCfPSccw==" + }, + "Docker.DotNet.Enhanced.LegacyHttp": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "sfbMX1HBPUec3PEMoqlP5ak6skXclcTBmu4gG3aUJatP34J2DgvYMP13bvz/rfrjVkAhPqnIiDKiHAkBCokajg==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.NativeHttp": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "/ll+2ePYm1qrsMdgMO5BzCQnbfTGmPJAc9SqXEManbliVBZvEpBKHXLugx/OeEca2oC/b4RV+UNPtue5u4jAuA==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.NPipe": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "8wyYOD6VkvqRkITwsvkt3UbW/1WDl6NFypNAsIIDaMiglNRzFrQcK0nK9VUEZa6Oja8Bso3UYySDoL8qatatAA==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.Unix": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "x0wNcbww1+p9nUfw8i+JvsSArBDGkoZ9GI2PZ1wPo85B2OiFrdzp89omounNhO2GKyaIRWAqAm5jYZyNg9EnxA==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Docker.DotNet.Enhanced.X509": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "nMw+FHGwGZieDi7kBgpIVl+E8MzjzXeXHvMQpidLADT06fts2Gw6G+K+p0hMGv7liZULxyYiZnQ1UbE2B9NNQg==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.2.0" + } + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "bkmCXn/65Cd0LdO2zTb/ValGAJ1H8y/CgYOiBb3jsDyHI3Y1ljKx6RBvhvn3e5D/4R4I00RRwLf+Bd2Sn6bJjA==" + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "nQXq1a4MiInYh+0VF9fguxAl06q2ftmOyYQ+5e933s4rk57xjgkbTjUdFUySzjrcrvDeWsSqlZB+TE8+TbM2HA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "21nbDV60SRPWGIivsyl6lqBeEJNG1sginhhfWgRrr3Ais7aQ12To25OAHQxgoiJkjqy1aQ6RxpZBGYuTi7Ge6A==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "uduyw9d3Fi+sbredO5drA1S44AQS2FRNFyn72UmB2vmQIO1qaXprpp1U/2lYhYi8yFdVERfY9sy/pxw/qPOU9w==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+f4C5g78QCGNyxzUfrTYsB7qYx06Zca0e88s3qFlea9/lQhgPImYdNprlgzl1uHhRU3fVHLfmbijayU2sJEZ6w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "K60JhWC2hN/Gi7TP68tBxSzk5ACWOs7lkmPzsfA8Bcf/IXTajujt2ORMf9rSMk1bsng6Lv4Y3fuxp3bm1+15ug==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "fdVadZmsC8jRP0KvKy8mO8f6GV/HyBvElfcSxEhd+5FM5boAw/01iSaCto5G3G37ApJira4A3pNaVvBv8cUiLQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rxSLTO7xTbcC3DuEJHNEijBr8g14Jj62zQ+DeFu68bsoTYoU8jLcMhc1735PV21bESXsATlL5LsfaWH71FOWAg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6cv53sHsPnFS56PJw8X4GbNcjeX1KGyFJRxJWvxOgK63cnqeSB1k1eRwjUdkse0tBhwlH6qc9EOYDlan+CYTuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "4HW3M1lGHHDwEYcDZHRNptBQ48LCI2yW+XV4vuxdfQUqafTpVT8j9RqAsez08krZKhIiaArWu8iQq5uRKZ9Ffg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "kK/C3SLIoGrcZvddYQw4eMm6YaROiSYBO7YgUR5Hdv5l+GIjBmbvQK5cST2FqjeubiAOPqFEimBT2N/8wVI+3A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.EventLog": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HX2M0MgzwQM8jpLe3AYAEMd0YsUfOP5RgGrDuk+Ki9n7HSuMbvLm9TEV3qRI3Pg9aqxc56GfgK/KdMRBhfWwKw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "em1eLz5Q46+hsCtAXdXggWAPd9gQyT4ngdsQ7k1eWvQgpsjtS/wAOJ/5TteieFdiAvrEq1iVn00LtusAxRaVmQ==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.6.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "SharpZipLib": { + "type": "Transitive", + "resolved": "1.4.2", + "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==" + }, + "SSH.NET": { + "type": "Transitive", + "resolved": "2025.1.0", + "contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", + "dependencies": { + "BouncyCastle.Cryptography": "2.6.2" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rQHsK7Xr8Uz3449860ayVXp/CaLmrhHlMPxbpT/ibOPtp/dTTsr6+f/SxaGO2NzxHf+0siLE0UfdVN5z1I0EgQ==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+Ro7WgIom+BDNH+YhTuZKL6QJ0ctfOpTyfUG/h3aU5KwXt3OaNf0wYWrTvoBUj+34Dy5V8dN9yCco1hAJQ4txw==" + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HvFIJXM/CMTRu6PBQmjukQZ/O32Vx5fOEYZs0kq0OD5s9vJQ19KHLWCVmnfh3gNC7pbYomm06tzOTgJBflr/nQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "/fysUDkD7oFGaRPoA7IaFs0wRoO3GlwlCNq2P+xWZqxLy1R4cktRSKfMjJDy9ymS4grL7IDVdt8de8L9a0z55Q==", + "dependencies": { + "System.IO.Pipelines": "10.0.8", + "System.Text.Encodings.Web": "10.0.8" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "patternkit.core": { + "type": "Project" + }, + "patternkit.examples": { + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2025.2.4, )", + "Microsoft.Extensions.Configuration": "[10.0.8, )", + "Microsoft.Extensions.Configuration.Abstractions": "[10.0.8, )", + "Microsoft.Extensions.Configuration.Binder": "[10.0.8, )", + "Microsoft.Extensions.DependencyInjection": "[10.0.8, )", + "Microsoft.Extensions.Hosting": "[10.0.8, )", + "Microsoft.Extensions.Hosting.Abstractions": "[10.0.8, )", + "Microsoft.Extensions.Options": "[10.0.8, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.8, )", + "Microsoft.Extensions.Options.DataAnnotations": "[10.0.8, )", + "PatternKit.Core": "[1.0.0, )", + "PatternKit.Generators.Abstractions": "[1.0.0, )" + } + }, + "patternkit.generators.abstractions": { + "type": "Project" + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "R3NN1X+kVu14uoxLEW6sBSQyhogDSbaOQzILnCtuXxBN4hx22AgjWPwZX6v/suERFkEDgU1lk12AglHTrUxhlw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VfEyM2BipThcSd0GG/FS2ZPCVCTiosVq2zLKEDsfeMIg78sOVZPEmS7CgWlb+dqTlgXvLSL4OG2q6sM4xRhHNg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.Configuration.CommandLine": "10.0.8", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.Configuration.UserSecrets": "10.0.8", + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Logging.Console": "10.0.8", + "Microsoft.Extensions.Logging.Debug": "10.0.8", + "Microsoft.Extensions.Logging.EventLog": "10.0.8", + "Microsoft.Extensions.Logging.EventSource": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Options": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VBD+131DpTNCNDfA4kIyKTiCySvJGNhwibdWBSdFRu7GMfXLXcXODkgA+KStKbbhzraLglZWUN4nXyHgW4JIRA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VOapXeO3lhBH0zYoyAH7tjapuo4V5pTHlevPpiSHueEquAajqd5nF0mttm+h/uE/exwAEuM5s26SzOJtletE3w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.DataAnnotations": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "HhxwIGECGGJ8ox2kvm6/hkN/w1ZyKrO5uu/rLAL51V0ypPdahoNf+dHS6Er/DJs2aeUmH38ZTTzACfLy1O6w3Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "TinyBDD": { + "type": "CentralTransitive", + "requested": "[0.19.14, )", + "resolved": "0.19.8", + "contentHash": "W5J/Ftk6MMphFsu54Nm4rePdQvPQSANAeU6yAPs1+SbdBsETNMldNFj6wJdpr/nh7GLJfOPHmbhER1MubLv+tg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.5", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5" + } + } + }, "net9.0": { "coverlet.collector": { "type": "Direct", @@ -66,11 +930,7 @@ "type": "Direct", "requested": "[7.2.1, )", "resolved": "7.2.1", - "contentHash": "YKXEfg9fVQiTKgZlvIhAfPSFaamEgi8DsQmisCH0IAsU4FYLrtoguDrDj6JtJVGUt40QPnBLRH6fTQcAC4qsOg==", - "dependencies": { - "System.IO.Pipelines": "8.0.0", - "System.Threading.RateLimiting": "8.0.0" - } + "contentHash": "YKXEfg9fVQiTKgZlvIhAfPSFaamEgi8DsQmisCH0IAsU4FYLrtoguDrDj6JtJVGUt40QPnBLRH6fTQcAC4qsOg==" }, "System.Collections.Immutable": { "type": "Direct", @@ -86,7 +946,6 @@ "dependencies": { "Docker.DotNet.Enhanced": "4.2.0", "Docker.DotNet.Enhanced.X509": "4.2.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.3", "SSH.NET": "2025.1.0", "SharpZipLib": "1.4.2" } @@ -151,17 +1010,13 @@ "Docker.DotNet.Enhanced.LegacyHttp": "4.2.0", "Docker.DotNet.Enhanced.NPipe": "4.2.0", "Docker.DotNet.Enhanced.NativeHttp": "4.2.0", - "Docker.DotNet.Enhanced.Unix": "4.2.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.3" + "Docker.DotNet.Enhanced.Unix": "4.2.0" } }, "Docker.DotNet.Enhanced.Handler.Abstractions": { "type": "Transitive", "resolved": "4.2.0", - "contentHash": "cQNxpdadEPdNdfjFCl9vgoCQIK3aVHRn1Qlu36aZUFpp4xHfPrk4hRPNVLR/CpobIFJ+dAt8AceTKMlCfPSccw==", - "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "8.0.3" - } + "contentHash": "cQNxpdadEPdNdfjFCl9vgoCQIK3aVHRn1Qlu36aZUFpp4xHfPrk4hRPNVLR/CpobIFJ+dAt8AceTKMlCfPSccw==" }, "Docker.DotNet.Enhanced.LegacyHttp": { "type": "Transitive", @@ -399,10 +1254,7 @@ "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.6.0", - "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==", - "dependencies": { - "System.Reflection.Metadata": "8.0.0" - } + "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==" }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", @@ -428,8 +1280,7 @@ "resolved": "2025.1.0", "contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "dependencies": { - "BouncyCastle.Cryptography": "2.6.2", - "Microsoft.Extensions.Logging.Abstractions": "8.0.3" + "BouncyCastle.Cryptography": "2.6.2" } }, "System.Diagnostics.DiagnosticSource": { @@ -447,14 +1298,6 @@ "resolved": "10.0.8", "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" }, - "System.Reflection.Metadata": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", - "dependencies": { - "System.Collections.Immutable": "8.0.0" - } - }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "10.0.8", @@ -469,11 +1312,6 @@ "System.Text.Encodings.Web": "10.0.8" } }, - "System.Threading.RateLimiting": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "7mu9v0QDv66ar3DpGSZHg9NuNcxDaaAcnMULuZlaTpP9+hwXhrxNGsF5GmLkSHxFdb5bBc1TzeujsRgTrPWi+Q==" - }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.3", diff --git a/test/PatternKit.Generators.Tests/AbstractionsAttributeCoverageTests.cs b/test/PatternKit.Generators.Tests/AbstractionsAttributeCoverageTests.cs index c0e1a6a7..ada45855 100644 --- a/test/PatternKit.Generators.Tests/AbstractionsAttributeCoverageTests.cs +++ b/test/PatternKit.Generators.Tests/AbstractionsAttributeCoverageTests.cs @@ -188,6 +188,7 @@ private enum TestTrigger { typeof(ContentRouteDefaultAttribute), AttributeTargets.Method, false, false }, { typeof(GenerateMessageFilterAttribute), AttributeTargets.Class | AttributeTargets.Struct, false, false }, { typeof(MessageFilterRuleAttribute), AttributeTargets.Method, false, false }, + { typeof(GenerateMessageExpirationAttribute), AttributeTargets.Class | AttributeTargets.Struct, false, false }, { typeof(GenerateMessageStoreAttribute), AttributeTargets.Class | AttributeTargets.Struct, false, false }, { typeof(MessageStoreIdentityAttribute), AttributeTargets.Method, false, false }, { typeof(MessageStoreRetentionAttribute), AttributeTargets.Method, false, false }, @@ -1227,6 +1228,15 @@ public void Flyweight_Iterator_And_Messaging_Attributes_Expose_Defaults_And_Conf RejectionReason = "manual review" }; var messageFilterRule = new MessageFilterRuleAttribute("trusted", 9); + var messageExpiration = new GenerateMessageExpirationAttribute(typeof(string)) + { + FactoryName = "BuildExpiration", + PolicyName = "orders", + HeaderName = "x-expires-at", + DefaultTtlMilliseconds = 1000, + PreserveExisting = false, + ExpiredReason = "too old" + }; var messageStore = new GenerateMessageStoreAttribute(typeof(string)) { FactoryName = "BuildStore", @@ -1411,6 +1421,13 @@ public void Flyweight_Iterator_And_Messaging_Attributes_Expose_Defaults_And_Conf ScenarioExpect.Equal("manual review", messageFilter.RejectionReason); ScenarioExpect.Equal("trusted", messageFilterRule.Name); ScenarioExpect.Equal(9, messageFilterRule.Order); + ScenarioExpect.Equal(typeof(string), messageExpiration.PayloadType); + ScenarioExpect.Equal("BuildExpiration", messageExpiration.FactoryName); + ScenarioExpect.Equal("orders", messageExpiration.PolicyName); + ScenarioExpect.Equal("x-expires-at", messageExpiration.HeaderName); + ScenarioExpect.Equal(1000, messageExpiration.DefaultTtlMilliseconds); + ScenarioExpect.False(messageExpiration.PreserveExisting); + ScenarioExpect.Equal("too old", messageExpiration.ExpiredReason); ScenarioExpect.Equal(typeof(string), messageStore.PayloadType); ScenarioExpect.Equal("BuildStore", messageStore.FactoryName); ScenarioExpect.Equal("order-audit", messageStore.StoreName); @@ -1552,6 +1569,7 @@ public void Flyweight_Iterator_And_Messaging_Attributes_Expose_Defaults_And_Conf ScenarioExpect.Throws(() => new ContentRouteAttribute("name", 1, "")); ScenarioExpect.Throws(() => new GenerateMessageFilterAttribute(null!)); ScenarioExpect.Throws(() => new MessageFilterRuleAttribute("", 1)); + ScenarioExpect.Throws(() => new GenerateMessageExpirationAttribute(null!)); ScenarioExpect.Throws(() => new GenerateMessageStoreAttribute(null!)); ScenarioExpect.IsType(new MessageStoreIdentityAttribute()); ScenarioExpect.IsType(new MessageStoreRetentionAttribute()); diff --git a/test/PatternKit.Generators.Tests/MessageExpirationGeneratorTests.cs b/test/PatternKit.Generators.Tests/MessageExpirationGeneratorTests.cs new file mode 100644 index 00000000..d85ab890 --- /dev/null +++ b/test/PatternKit.Generators.Tests/MessageExpirationGeneratorTests.cs @@ -0,0 +1,135 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using PatternKit.Generators.Messaging; +using TinyBDD; + +namespace PatternKit.Generators.Tests; + +public sealed class MessageExpirationGeneratorTests +{ + [Scenario("GeneratesMessageExpirationFactory")] + [Fact] + public void GeneratesMessageExpirationFactory() + { + var source = """ + using PatternKit.Generators.Messaging; + using PatternKit.Messaging; + + namespace MyApp; + + public sealed record Order(string Id); + + [GenerateMessageExpiration( + typeof(Order), + FactoryName = "Build", + PolicyName = "orders", + HeaderName = "x-expires-at", + DefaultTtlMilliseconds = 30000, + PreserveExisting = false, + ExpiredReason = "too old")] + public static partial class OrderExpiration; + + public static class Demo + { + public static string Run() + => OrderExpiration.Build().HeaderName; + } + """; + + var comp = CreateCompilation(source, nameof(GeneratesMessageExpirationFactory)); + var gen = new MessageExpirationGenerator(); + _ = RoslynTestHelpers.Run(comp, gen, out var run, out var updated); + + ScenarioExpect.All(run.Results, result => ScenarioExpect.Empty(result.Diagnostics)); + var generated = ScenarioExpect.Single(run.Results.SelectMany(result => result.GeneratedSources)); + ScenarioExpect.Equal("OrderExpiration.MessageExpiration.g.cs", generated.HintName); + var text = generated.SourceText.ToString(); + ScenarioExpect.Contains("MessageExpiration", text); + ScenarioExpect.Contains(".Name(@\"orders\")", text); + ScenarioExpect.Contains(".Header(@\"x-expires-at\")", text); + ScenarioExpect.Contains(".DefaultTtl(global::System.TimeSpan.FromMilliseconds(30000))", text); + ScenarioExpect.Contains(".PreserveExisting(false)", text); + + var emit = updated.Emit(Stream.Null); + ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics)); + } + + [Scenario("GeneratesDefaultsForStructHost")] + [Fact] + public void GeneratesDefaultsForStructHost() + { + var source = """ + using PatternKit.Generators.Messaging; + + namespace MyApp; + + public sealed record Order(string Id); + + [GenerateMessageExpiration(typeof(Order))] + public readonly partial struct OrderExpiration; + """; + + var comp = CreateCompilation(source, nameof(GeneratesDefaultsForStructHost)); + var gen = new MessageExpirationGenerator(); + _ = RoslynTestHelpers.Run(comp, gen, out var run, out var updated); + + ScenarioExpect.Empty(run.Results.SelectMany(result => result.Diagnostics)); + var generated = ScenarioExpect.Single(run.Results.SelectMany(result => result.GeneratedSources)).SourceText.ToString(); + ScenarioExpect.Contains("partial struct OrderExpiration", generated); + ScenarioExpect.Contains(".Header(@\"expires-at\")", generated); + ScenarioExpect.DoesNotContain("DefaultTtl", generated); + ScenarioExpect.True(updated.Emit(Stream.Null).Success); + } + + [Scenario("ReportsDiagnosticForNonPartialExpiration")] + [Fact] + public void ReportsDiagnosticForNonPartialExpiration() + { + var source = """ + using PatternKit.Generators.Messaging; + + namespace MyApp; + + public sealed record Order(string Id); + + [GenerateMessageExpiration(typeof(Order))] + public static class OrderExpiration; + """; + + var comp = CreateCompilation(source, nameof(ReportsDiagnosticForNonPartialExpiration)); + var gen = new MessageExpirationGenerator(); + _ = RoslynTestHelpers.Run(comp, gen, out var run, out _); + + var diagnostic = ScenarioExpect.Single(run.Results.SelectMany(result => result.Diagnostics)); + ScenarioExpect.Equal("PKMEXP001", diagnostic.Id); + } + + [Scenario("ReportsDiagnosticForInvalidConfiguration")] + [Fact] + public void ReportsDiagnosticForInvalidConfiguration() + { + var source = """ + using PatternKit.Generators.Messaging; + + namespace MyApp; + + public sealed record Order(string Id); + + [GenerateMessageExpiration(typeof(Order), HeaderName = "")] + public static partial class OrderExpiration; + """; + + var comp = CreateCompilation(source, nameof(ReportsDiagnosticForInvalidConfiguration)); + var gen = new MessageExpirationGenerator(); + _ = RoslynTestHelpers.Run(comp, gen, out var run, out _); + + var diagnostic = ScenarioExpect.Single(run.Results.SelectMany(result => result.Diagnostics)); + ScenarioExpect.Equal("PKMEXP002", diagnostic.Id); + } + + private static CSharpCompilation CreateCompilation(string source, string assemblyName) + => RoslynTestHelpers.CreateCompilation( + source, + assemblyName, + extra: MetadataReference.CreateFromFile(typeof(PatternKit.Messaging.Message<>).Assembly.Location)); +} diff --git a/test/PatternKit.Generators.Tests/packages.lock.json b/test/PatternKit.Generators.Tests/packages.lock.json index 2330b15d..d487514d 100644 --- a/test/PatternKit.Generators.Tests/packages.lock.json +++ b/test/PatternKit.Generators.Tests/packages.lock.json @@ -1,7 +1,7 @@ { "version": 2, "dependencies": { - "net9.0": { + "net10.0": { "coverlet.collector": { "type": "Direct", "requested": "[10.0.1, )", @@ -10,12 +10,205 @@ }, "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.Common": "[5.0.0]" + } + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[18.6.0, )", + "resolved": "18.6.0", + "contentHash": "kAIBt0MsYR0o2RULmlW5BhQ1ha50aGEgLKG4f1p0kePBGLJCprqs3S+NxRrYN8UH7mSQRPKpeiH9mwPMEKUObQ==", + "dependencies": { + "Microsoft.CodeCoverage": "18.6.0", + "Microsoft.TestPlatform.TestHost": "18.6.0" + } + }, + "System.Collections.Immutable": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "rvMGYko6bcE/ZTpj1Q/EM22rcZNiQyjH+71capjjfmqG+DDncVE4kSaE5ysCv2vEFIHt8+zALWYXgIETFvcFzw==" + }, + "TinyBDD": { + "type": "Direct", + "requested": "[0.19.8, )", + "resolved": "0.19.8", + "contentHash": "W5J/Ftk6MMphFsu54Nm4rePdQvPQSANAeU6yAPs1+SbdBsETNMldNFj6wJdpr/nh7GLJfOPHmbhER1MubLv+tg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.5", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5" + } + }, + "TinyBDD.Xunit": { + "type": "Direct", + "requested": "[0.19.8, )", + "resolved": "0.19.8", + "contentHash": "x5YhK3YbgqPltVvlIaDF5WREwjv7zYAkKb/0OUB5SVrO0vT3b4xE1eub6Jw3Ih7lytdbrOcecz20QAhoFyo2FA==", + "dependencies": { + "TinyBDD": "0.19.8", + "xunit.abstractions": "2.0.3", + "xunit.extensibility.core": "2.9.3" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.5, )", + "resolved": "3.1.5", + "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0" + } + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "bkmCXn/65Cd0LdO2zTb/ValGAJ1H8y/CgYOiBb3jsDyHI3Y1ljKx6RBvhvn3e5D/4R4I00RRwLf+Bd2Sn6bJjA==" + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.5", + "contentHash": "iVMtq9eRvzyhx8949EGT0OCYJfXi737SbRVzWXE5GrOgGj5AaZ9eUuxA/BSUfmOMALKn/g8KfFaNQw0eiB3lyA==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "em1eLz5Q46+hsCtAXdXggWAPd9gQyT4ngdsQ7k1eWvQgpsjtS/wAOJ/5TteieFdiAvrEq1iVn00LtusAxRaVmQ==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.6.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "patternkit.core": { + "type": "Project" + }, + "patternkit.examples": { + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2025.2.4, )", + "PatternKit.Core": "[1.0.0, )", + "PatternKit.Generators.Abstractions": "[1.0.0, )" + } + }, + "patternkit.generators": { + "type": "Project", + "dependencies": { + "PatternKit.Generators.Abstractions": "[1.0.0, )" + } + }, + "patternkit.generators.abstractions": { + "type": "Project" + }, + "JetBrains.Annotations": { + "type": "CentralTransitive", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "CentralTransitive", "requested": "[5.3.0, )", - "resolved": "5.3.0", - "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", + "resolved": "3.11.0", + "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.5", + "contentHash": "v1SVsowG6YE1YnHVGmLWz57YTRCQRx9pH5ebIESXfm5isI9gA3QaMyg/oMTzPpXYZwSAVDzYItGJKfmV+pqXkQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5" + } + }, + "xunit.extensibility.core": { + "type": "CentralTransitive", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + } + }, + "net8.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "27jXSV/0DbVqF5jDrAxuQFZ9oaz6gmG03p8ttxAFk+X0M4woFYj7MoWDLCna5EGLb0CE6OE7X6ZH3Wt5smTtaA==" + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Direct", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", - "Microsoft.CodeAnalysis.Common": "[5.3.0]" + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.Common": "[5.0.0]", + "System.Collections.Immutable": "9.0.0", + "System.Reflection.Metadata": "9.0.0" } }, "Microsoft.NET.Test.Sdk": { @@ -74,10 +267,12 @@ }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "5.3.0", - "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", + "resolved": "5.0.0", + "contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1" + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "System.Collections.Immutable": "9.0.0", + "System.Reflection.Metadata": "9.0.0" } }, "Microsoft.CodeCoverage": { @@ -276,10 +471,7 @@ "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.6.0", - "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==", - "dependencies": { - "System.Reflection.Metadata": "8.0.0" - } + "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==" }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", @@ -312,12 +504,530 @@ }, "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "resolved": "9.0.0", + "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", + "dependencies": { + "System.Collections.Immutable": "9.0.0" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HvFIJXM/CMTRu6PBQmjukQZ/O32Vx5fOEYZs0kq0OD5s9vJQ19KHLWCVmnfh3gNC7pbYomm06tzOTgJBflr/nQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "/fysUDkD7oFGaRPoA7IaFs0wRoO3GlwlCNq2P+xWZqxLy1R4cktRSKfMjJDy9ymS4grL7IDVdt8de8L9a0z55Q==", + "dependencies": { + "System.IO.Pipelines": "10.0.8", + "System.Text.Encodings.Web": "10.0.8" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "patternkit.core": { + "type": "Project" + }, + "patternkit.examples": { + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2025.2.4, )", + "Microsoft.Extensions.Configuration": "[10.0.8, )", + "Microsoft.Extensions.Configuration.Abstractions": "[10.0.8, )", + "Microsoft.Extensions.Configuration.Binder": "[10.0.8, )", + "Microsoft.Extensions.DependencyInjection": "[10.0.8, )", + "Microsoft.Extensions.Hosting": "[10.0.8, )", + "Microsoft.Extensions.Hosting.Abstractions": "[10.0.8, )", + "Microsoft.Extensions.Options": "[10.0.8, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.8, )", + "Microsoft.Extensions.Options.DataAnnotations": "[10.0.8, )", + "PatternKit.Core": "[1.0.0, )", + "PatternKit.Generators.Abstractions": "[1.0.0, )" + } + }, + "patternkit.generators": { + "type": "Project", "dependencies": { - "System.Collections.Immutable": "8.0.0" + "PatternKit.Generators.Abstractions": "[1.0.0, )", + "System.Collections.Immutable": "[10.0.8, )" } }, + "patternkit.generators.abstractions": { + "type": "Project" + }, + "JetBrains.Annotations": { + "type": "CentralTransitive", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "CentralTransitive", + "requested": "[5.3.0, )", + "resolved": "3.11.0", + "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" + }, + "Microsoft.Extensions.Configuration": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "R3NN1X+kVu14uoxLEW6sBSQyhogDSbaOQzILnCtuXxBN4hx22AgjWPwZX6v/suERFkEDgU1lk12AglHTrUxhlw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VfEyM2BipThcSd0GG/FS2ZPCVCTiosVq2zLKEDsfeMIg78sOVZPEmS7CgWlb+dqTlgXvLSL4OG2q6sM4xRhHNg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.Configuration.CommandLine": "10.0.8", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.Configuration.UserSecrets": "10.0.8", + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Logging.Console": "10.0.8", + "Microsoft.Extensions.Logging.Debug": "10.0.8", + "Microsoft.Extensions.Logging.EventLog": "10.0.8", + "Microsoft.Extensions.Logging.EventSource": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Options": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VBD+131DpTNCNDfA4kIyKTiCySvJGNhwibdWBSdFRu7GMfXLXcXODkgA+KStKbbhzraLglZWUN4nXyHgW4JIRA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VOapXeO3lhBH0zYoyAH7tjapuo4V5pTHlevPpiSHueEquAajqd5nF0mttm+h/uE/exwAEuM5s26SzOJtletE3w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.DataAnnotations": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "HhxwIGECGGJ8ox2kvm6/hkN/w1ZyKrO5uu/rLAL51V0ypPdahoNf+dHS6Er/DJs2aeUmH38ZTTzACfLy1O6w3Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "xunit.extensibility.core": { + "type": "CentralTransitive", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + } + }, + "net9.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "27jXSV/0DbVqF5jDrAxuQFZ9oaz6gmG03p8ttxAFk+X0M4woFYj7MoWDLCna5EGLb0CE6OE7X6ZH3Wt5smTtaA==" + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Direct", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.Common": "[5.0.0]" + } + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[18.6.0, )", + "resolved": "18.6.0", + "contentHash": "kAIBt0MsYR0o2RULmlW5BhQ1ha50aGEgLKG4f1p0kePBGLJCprqs3S+NxRrYN8UH7mSQRPKpeiH9mwPMEKUObQ==", + "dependencies": { + "Microsoft.CodeCoverage": "18.6.0", + "Microsoft.TestPlatform.TestHost": "18.6.0" + } + }, + "System.Collections.Immutable": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "rvMGYko6bcE/ZTpj1Q/EM22rcZNiQyjH+71capjjfmqG+DDncVE4kSaE5ysCv2vEFIHt8+zALWYXgIETFvcFzw==" + }, + "TinyBDD": { + "type": "Direct", + "requested": "[0.19.8, )", + "resolved": "0.19.8", + "contentHash": "W5J/Ftk6MMphFsu54Nm4rePdQvPQSANAeU6yAPs1+SbdBsETNMldNFj6wJdpr/nh7GLJfOPHmbhER1MubLv+tg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.5", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5" + } + }, + "TinyBDD.Xunit": { + "type": "Direct", + "requested": "[0.19.8, )", + "resolved": "0.19.8", + "contentHash": "x5YhK3YbgqPltVvlIaDF5WREwjv7zYAkKb/0OUB5SVrO0vT3b4xE1eub6Jw3Ih7lytdbrOcecz20QAhoFyo2FA==", + "dependencies": { + "TinyBDD": "0.19.8", + "xunit.abstractions": "2.0.3", + "xunit.extensibility.core": "2.9.3" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.5, )", + "resolved": "3.1.5", + "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0" + } + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "bkmCXn/65Cd0LdO2zTb/ValGAJ1H8y/CgYOiBb3jsDyHI3Y1ljKx6RBvhvn3e5D/4R4I00RRwLf+Bd2Sn6bJjA==" + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "nQXq1a4MiInYh+0VF9fguxAl06q2ftmOyYQ+5e933s4rk57xjgkbTjUdFUySzjrcrvDeWsSqlZB+TE8+TbM2HA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "21nbDV60SRPWGIivsyl6lqBeEJNG1sginhhfWgRrr3Ais7aQ12To25OAHQxgoiJkjqy1aQ6RxpZBGYuTi7Ge6A==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "uduyw9d3Fi+sbredO5drA1S44AQS2FRNFyn72UmB2vmQIO1qaXprpp1U/2lYhYi8yFdVERfY9sy/pxw/qPOU9w==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+f4C5g78QCGNyxzUfrTYsB7qYx06Zca0e88s3qFlea9/lQhgPImYdNprlgzl1uHhRU3fVHLfmbijayU2sJEZ6w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "K60JhWC2hN/Gi7TP68tBxSzk5ACWOs7lkmPzsfA8Bcf/IXTajujt2ORMf9rSMk1bsng6Lv4Y3fuxp3bm1+15ug==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "fdVadZmsC8jRP0KvKy8mO8f6GV/HyBvElfcSxEhd+5FM5boAw/01iSaCto5G3G37ApJira4A3pNaVvBv8cUiLQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rxSLTO7xTbcC3DuEJHNEijBr8g14Jj62zQ+DeFu68bsoTYoU8jLcMhc1735PV21bESXsATlL5LsfaWH71FOWAg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6cv53sHsPnFS56PJw8X4GbNcjeX1KGyFJRxJWvxOgK63cnqeSB1k1eRwjUdkse0tBhwlH6qc9EOYDlan+CYTuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "4HW3M1lGHHDwEYcDZHRNptBQ48LCI2yW+XV4vuxdfQUqafTpVT8j9RqAsez08krZKhIiaArWu8iQq5uRKZ9Ffg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "kK/C3SLIoGrcZvddYQw4eMm6YaROiSYBO7YgUR5Hdv5l+GIjBmbvQK5cST2FqjeubiAOPqFEimBT2N/8wVI+3A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.EventLog": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HX2M0MgzwQM8jpLe3AYAEMd0YsUfOP5RgGrDuk+Ki9n7HSuMbvLm9TEV3qRI3Pg9aqxc56GfgK/KdMRBhfWwKw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "em1eLz5Q46+hsCtAXdXggWAPd9gQyT4ngdsQ7k1eWvQgpsjtS/wAOJ/5TteieFdiAvrEq1iVn00LtusAxRaVmQ==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.6.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rQHsK7Xr8Uz3449860ayVXp/CaLmrhHlMPxbpT/ibOPtp/dTTsr6+f/SxaGO2NzxHf+0siLE0UfdVN5z1I0EgQ==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+Ro7WgIom+BDNH+YhTuZKL6QJ0ctfOpTyfUG/h3aU5KwXt3OaNf0wYWrTvoBUj+34Dy5V8dN9yCco1hAJQ4txw==" + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" + }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "10.0.8", @@ -403,8 +1113,8 @@ "Microsoft.CodeAnalysis.Analyzers": { "type": "CentralTransitive", "requested": "[5.3.0, )", - "resolved": "5.3.0-2.25625.1", - "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" + "resolved": "3.11.0", + "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" }, "Microsoft.Extensions.Configuration": { "type": "CentralTransitive", diff --git a/test/PatternKit.Tests/Messaging/Reliability/MessageExpirationTests.cs b/test/PatternKit.Tests/Messaging/Reliability/MessageExpirationTests.cs new file mode 100644 index 00000000..b0e4660b --- /dev/null +++ b/test/PatternKit.Tests/Messaging/Reliability/MessageExpirationTests.cs @@ -0,0 +1,136 @@ +using PatternKit.Messaging; +using PatternKit.Messaging.Reliability; +using TinyBDD; + +namespace PatternKit.Tests.Messaging.Reliability; + +public sealed class MessageExpirationTests +{ + private static readonly DateTimeOffset Now = new(2026, 5, 27, 12, 0, 0, TimeSpan.Zero); + + [Scenario("Stamp Applies Default Ttl")] + [Fact] + public void Stamp_Applies_Default_Ttl() + { + var expiration = MessageExpiration.Create() + .Name("order-expiration") + .DefaultTtl(TimeSpan.FromMinutes(15)) + .Clock(() => Now) + .Build(); + + var stamped = expiration.Stamp(Message.Create(new Order("o-1"))); + + ScenarioExpect.Equal("expires-at", expiration.HeaderName); + ScenarioExpect.Equal(Now.AddMinutes(15), expiration.Read(stamped)); + } + + [Scenario("Stamp Preserves Existing Deadline By Default")] + [Fact] + public void Stamp_Preserves_Existing_Deadline_By_Default() + { + var existing = Now.AddMinutes(5); + var expiration = MessageExpiration.Create() + .DefaultTtl(TimeSpan.FromMinutes(15)) + .Clock(() => Now) + .Build(); + var message = Message.Create(new Order("o-1")).WithHeader("expires-at", existing); + + var stamped = expiration.Stamp(message); + + ScenarioExpect.Same(message, stamped); + ScenarioExpect.Equal(existing, expiration.Read(stamped)); + } + + [Scenario("Stamp Can Replace Existing Deadline")] + [Fact] + public void Stamp_Can_Replace_Existing_Deadline() + { + var expiration = MessageExpiration.Create() + .DefaultTtl(TimeSpan.FromMinutes(15)) + .Clock(() => Now) + .PreserveExisting(false) + .Build(); + var message = Message.Create(new Order("o-1")).WithHeader("expires-at", Now.AddMinutes(5)); + + var stamped = expiration.Stamp(message); + + ScenarioExpect.NotSame(message, stamped); + ScenarioExpect.Equal(Now.AddMinutes(15), expiration.Read(stamped)); + } + + [Scenario("Evaluate Accepts Fresh Messages")] + [Fact] + public void Evaluate_Accepts_Fresh_Messages() + { + var expiration = MessageExpiration.Create() + .Name("order-expiration") + .Clock(() => Now) + .Build(); + var message = Message.Create(new Order("o-1")).WithHeader("expires-at", Now.AddSeconds(1)); + + var result = expiration.Evaluate(message); + + ScenarioExpect.False(result.Expired); + ScenarioExpect.Equal("order-expiration", result.PolicyName); + ScenarioExpect.Equal(Now, result.CheckedAt); + ScenarioExpect.Equal(Now.AddSeconds(1), result.ExpiresAt); + ScenarioExpect.Null(result.Reason); + } + + [Scenario("Evaluate Rejects Expired Messages")] + [Fact] + public void Evaluate_Rejects_Expired_Messages() + { + var expiration = MessageExpiration.Create() + .ExpiredReason("Order command expired.") + .Clock(() => Now) + .Build(); + var message = Message.Create(new Order("o-1")).WithHeader("expires-at", Now); + + var result = expiration.Evaluate(message); + + ScenarioExpect.True(result.Expired); + ScenarioExpect.Equal("Order command expired.", result.Reason); + } + + [Scenario("Evaluate Uses Context Deadline When Message Has None")] + [Fact] + public void Evaluate_Uses_Context_Deadline_When_Message_Has_None() + { + var expiration = MessageExpiration.Create().Clock(() => Now).Build(); + var context = new MessageContext(MessageHeaders.Empty.With("expires-at", Now.AddMinutes(-1))); + + var result = expiration.Evaluate(Message.Create(new Order("o-1")), context); + + ScenarioExpect.True(result.Expired); + ScenarioExpect.Equal(Now.AddMinutes(-1), result.ExpiresAt); + } + + [Scenario("Builder Rejects Invalid Configuration")] + [Fact] + public void Builder_Rejects_Invalid_Configuration() + { + ScenarioExpect.Throws(() => MessageExpiration.Create().Name("")); + ScenarioExpect.Throws(() => MessageExpiration.Create().Header("")); + ScenarioExpect.Throws(() => MessageExpiration.Create().DefaultTtl(TimeSpan.Zero)); + ScenarioExpect.Throws(() => MessageExpiration.Create().Clock(null!)); + ScenarioExpect.Throws(() => MessageExpiration.Create().ExpiredReason("")); + ScenarioExpect.Throws(() => MessageExpiration.Create().Build().Stamp(Message.Create(new Order("o-1")))); + } + + [Scenario("Methods Reject Null Messages")] + [Fact] + public void Methods_Reject_Null_Messages() + { + var expiration = MessageExpiration.Create() + .DefaultTtl(TimeSpan.FromMinutes(1)) + .Build(); + + ScenarioExpect.Throws(() => expiration.Read(null!)); + ScenarioExpect.Throws(() => expiration.Stamp(null!)); + ScenarioExpect.Throws(() => expiration.WithDeadline(null!, Now)); + ScenarioExpect.Throws(() => expiration.Evaluate(null!)); + } + + private sealed record Order(string Id); +} diff --git a/test/PatternKit.Tests/packages.lock.json b/test/PatternKit.Tests/packages.lock.json index 342973d0..66459acc 100644 --- a/test/PatternKit.Tests/packages.lock.json +++ b/test/PatternKit.Tests/packages.lock.json @@ -1,7 +1,182 @@ { "version": 2, "dependencies": { - "net9.0": { + "net10.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "27jXSV/0DbVqF5jDrAxuQFZ9oaz6gmG03p8ttxAFk+X0M4woFYj7MoWDLCna5EGLb0CE6OE7X6ZH3Wt5smTtaA==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[18.6.0, )", + "resolved": "18.6.0", + "contentHash": "kAIBt0MsYR0o2RULmlW5BhQ1ha50aGEgLKG4f1p0kePBGLJCprqs3S+NxRrYN8UH7mSQRPKpeiH9mwPMEKUObQ==", + "dependencies": { + "Microsoft.CodeCoverage": "18.6.0", + "Microsoft.TestPlatform.TestHost": "18.6.0" + } + }, + "System.Collections.Immutable": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "rvMGYko6bcE/ZTpj1Q/EM22rcZNiQyjH+71capjjfmqG+DDncVE4kSaE5ysCv2vEFIHt8+zALWYXgIETFvcFzw==" + }, + "System.Linq.Async": { + "type": "Direct", + "requested": "[7.0.1, )", + "resolved": "7.0.1", + "contentHash": "gwQtBHVY/WgqWgAYSe4JspXR+f1FvMbVIW4ixsJpGV/Kj8Nun3zp1ajIdvCWfmac8ektJGVLiJ/OR8JU9nZnMg==", + "dependencies": { + "System.Interactive.Async": "7.0.1" + } + }, + "TinyBDD.Xunit": { + "type": "Direct", + "requested": "[0.19.8, )", + "resolved": "0.19.8", + "contentHash": "x5YhK3YbgqPltVvlIaDF5WREwjv7zYAkKb/0OUB5SVrO0vT3b4xE1eub6Jw3Ih7lytdbrOcecz20QAhoFyo2FA==", + "dependencies": { + "TinyBDD": "0.19.8", + "xunit.abstractions": "2.0.3", + "xunit.extensibility.core": "2.9.3" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.5, )", + "resolved": "3.1.5", + "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "bkmCXn/65Cd0LdO2zTb/ValGAJ1H8y/CgYOiBb3jsDyHI3Y1ljKx6RBvhvn3e5D/4R4I00RRwLf+Bd2Sn6bJjA==" + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.5", + "contentHash": "iVMtq9eRvzyhx8949EGT0OCYJfXi737SbRVzWXE5GrOgGj5AaZ9eUuxA/BSUfmOMALKn/g8KfFaNQw0eiB3lyA==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "em1eLz5Q46+hsCtAXdXggWAPd9gQyT4ngdsQ7k1eWvQgpsjtS/wAOJ/5TteieFdiAvrEq1iVn00LtusAxRaVmQ==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.6.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "System.Interactive.Async": { + "type": "Transitive", + "resolved": "7.0.1", + "contentHash": "oL1iox7sAJL8i+muGzVMQjDB0axQgOoT5CkwYdap8cQJMkWDWMRErNqhOcZkn31+aKr/uCfgMEdhUARCU4G7gg==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "patternkit.core": { + "type": "Project" + }, + "patternkit.examples": { + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2025.2.4, )", + "PatternKit.Core": "[1.0.0, )", + "PatternKit.Generators.Abstractions": "[1.0.0, )" + } + }, + "patternkit.generators.abstractions": { + "type": "Project" + }, + "JetBrains.Annotations": { + "type": "CentralTransitive", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.5", + "contentHash": "v1SVsowG6YE1YnHVGmLWz57YTRCQRx9pH5ebIESXfm5isI9gA3QaMyg/oMTzPpXYZwSAVDzYItGJKfmV+pqXkQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5" + } + }, + "TinyBDD": { + "type": "CentralTransitive", + "requested": "[0.19.14, )", + "resolved": "0.19.8", + "contentHash": "W5J/Ftk6MMphFsu54Nm4rePdQvPQSANAeU6yAPs1+SbdBsETNMldNFj6wJdpr/nh7GLJfOPHmbhER1MubLv+tg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.5", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5" + } + } + }, + "net8.0": { "coverlet.collector": { "type": "Direct", "requested": "[10.0.1, )", @@ -267,10 +442,7 @@ "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.6.0", - "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==", - "dependencies": { - "System.Reflection.Metadata": "8.0.0" - } + "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==" }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", @@ -314,14 +486,516 @@ "resolved": "10.0.6", "contentHash": "1Ttf7dyLoKXks2bVBVULmDPOSjxaMEE3JRTe/KGEJl3rfepCc4ipUEGMIKa/iBKLSN8rjEedF8Qv3UCtiwaDBw==" }, - "System.Reflection.Metadata": { + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HvFIJXM/CMTRu6PBQmjukQZ/O32Vx5fOEYZs0kq0OD5s9vJQ19KHLWCVmnfh3gNC7pbYomm06tzOTgJBflr/nQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "/fysUDkD7oFGaRPoA7IaFs0wRoO3GlwlCNq2P+xWZqxLy1R4cktRSKfMjJDy9ymS4grL7IDVdt8de8L9a0z55Q==", + "dependencies": { + "System.IO.Pipelines": "10.0.8", + "System.Text.Encodings.Web": "10.0.8" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.execution": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "patternkit.core": { + "type": "Project" + }, + "patternkit.examples": { + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2025.2.4, )", + "Microsoft.Extensions.Configuration": "[10.0.8, )", + "Microsoft.Extensions.Configuration.Abstractions": "[10.0.8, )", + "Microsoft.Extensions.Configuration.Binder": "[10.0.8, )", + "Microsoft.Extensions.DependencyInjection": "[10.0.8, )", + "Microsoft.Extensions.Hosting": "[10.0.8, )", + "Microsoft.Extensions.Hosting.Abstractions": "[10.0.8, )", + "Microsoft.Extensions.Options": "[10.0.8, )", + "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.8, )", + "Microsoft.Extensions.Options.DataAnnotations": "[10.0.8, )", + "PatternKit.Core": "[1.0.0, )", + "PatternKit.Generators.Abstractions": "[1.0.0, )" + } + }, + "patternkit.generators.abstractions": { + "type": "Project" + }, + "JetBrains.Annotations": { + "type": "CentralTransitive", + "requested": "[2025.2.4, )", + "resolved": "2025.2.4", + "contentHash": "TwbgxAkXxY+vNEhNVx/QXjJ4vqxmepOjsgRvvImQPbHkHMMb4W+ahL3laMsxXKtNT7iMy+E1B3xkqao2hf1n3A==" + }, + "Microsoft.Extensions.Configuration": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", "dependencies": { - "System.Collections.Immutable": "8.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" } }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "R3NN1X+kVu14uoxLEW6sBSQyhogDSbaOQzILnCtuXxBN4hx22AgjWPwZX6v/suERFkEDgU1lk12AglHTrUxhlw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "daf62xHIrq8pnE709hgaZZN9tSam9TGGepWe1+bE6V3GEuVwJiMs6ib+38lfMCyAJAHiX0vapxBhsuMSV7U+cg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VfEyM2BipThcSd0GG/FS2ZPCVCTiosVq2zLKEDsfeMIg78sOVZPEmS7CgWlb+dqTlgXvLSL4OG2q6sM4xRhHNg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.Configuration.CommandLine": "10.0.8", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.Configuration.UserSecrets": "10.0.8", + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Logging.Console": "10.0.8", + "Microsoft.Extensions.Logging.Debug": "10.0.8", + "Microsoft.Extensions.Logging.EventLog": "10.0.8", + "Microsoft.Extensions.Logging.EventSource": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "MoOWFPT88/pDfmWpbU9PydKRX/rJFQkliowE/L9wbQcl94IicUphb5BFgepkWiDkYYxPnuEqjN4buzOGW4vJpQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Options": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VBD+131DpTNCNDfA4kIyKTiCySvJGNhwibdWBSdFRu7GMfXLXcXODkgA+KStKbbhzraLglZWUN4nXyHgW4JIRA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "VOapXeO3lhBH0zYoyAH7tjapuo4V5pTHlevPpiSHueEquAajqd5nF0mttm+h/uE/exwAEuM5s26SzOJtletE3w==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Options.DataAnnotations": { + "type": "CentralTransitive", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "HhxwIGECGGJ8ox2kvm6/hkN/w1ZyKrO5uu/rLAL51V0ypPdahoNf+dHS6Er/DJs2aeUmH38ZTTzACfLy1O6w3Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "TinyBDD": { + "type": "CentralTransitive", + "requested": "[0.19.14, )", + "resolved": "0.19.8", + "contentHash": "W5J/Ftk6MMphFsu54Nm4rePdQvPQSANAeU6yAPs1+SbdBsETNMldNFj6wJdpr/nh7GLJfOPHmbhER1MubLv+tg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.5", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5" + } + } + }, + "net9.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "27jXSV/0DbVqF5jDrAxuQFZ9oaz6gmG03p8ttxAFk+X0M4woFYj7MoWDLCna5EGLb0CE6OE7X6ZH3Wt5smTtaA==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[18.6.0, )", + "resolved": "18.6.0", + "contentHash": "kAIBt0MsYR0o2RULmlW5BhQ1ha50aGEgLKG4f1p0kePBGLJCprqs3S+NxRrYN8UH7mSQRPKpeiH9mwPMEKUObQ==", + "dependencies": { + "Microsoft.CodeCoverage": "18.6.0", + "Microsoft.TestPlatform.TestHost": "18.6.0" + } + }, + "System.Collections.Immutable": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "rvMGYko6bcE/ZTpj1Q/EM22rcZNiQyjH+71capjjfmqG+DDncVE4kSaE5ysCv2vEFIHt8+zALWYXgIETFvcFzw==" + }, + "System.Linq.Async": { + "type": "Direct", + "requested": "[7.0.1, )", + "resolved": "7.0.1", + "contentHash": "gwQtBHVY/WgqWgAYSe4JspXR+f1FvMbVIW4ixsJpGV/Kj8Nun3zp1ajIdvCWfmac8ektJGVLiJ/OR8JU9nZnMg==", + "dependencies": { + "System.Interactive.Async": "7.0.1", + "System.Linq.AsyncEnumerable": "10.0.6" + } + }, + "TinyBDD.Xunit": { + "type": "Direct", + "requested": "[0.19.8, )", + "resolved": "0.19.8", + "contentHash": "x5YhK3YbgqPltVvlIaDF5WREwjv7zYAkKb/0OUB5SVrO0vT3b4xE1eub6Jw3Ih7lytdbrOcecz20QAhoFyo2FA==", + "dependencies": { + "TinyBDD": "0.19.8", + "xunit.abstractions": "2.0.3", + "xunit.extensibility.core": "2.9.3" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.5, )", + "resolved": "3.1.5", + "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "bkmCXn/65Cd0LdO2zTb/ValGAJ1H8y/CgYOiBb3jsDyHI3Y1ljKx6RBvhvn3e5D/4R4I00RRwLf+Bd2Sn6bJjA==" + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "nQXq1a4MiInYh+0VF9fguxAl06q2ftmOyYQ+5e933s4rk57xjgkbTjUdFUySzjrcrvDeWsSqlZB+TE8+TbM2HA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "21nbDV60SRPWGIivsyl6lqBeEJNG1sginhhfWgRrr3Ais7aQ12To25OAHQxgoiJkjqy1aQ6RxpZBGYuTi7Ge6A==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "uduyw9d3Fi+sbredO5drA1S44AQS2FRNFyn72UmB2vmQIO1qaXprpp1U/2lYhYi8yFdVERfY9sy/pxw/qPOU9w==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+f4C5g78QCGNyxzUfrTYsB7qYx06Zca0e88s3qFlea9/lQhgPImYdNprlgzl1uHhRU3fVHLfmbijayU2sJEZ6w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "K60JhWC2hN/Gi7TP68tBxSzk5ACWOs7lkmPzsfA8Bcf/IXTajujt2ORMf9rSMk1bsng6Lv4Y3fuxp3bm1+15ug==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "fdVadZmsC8jRP0KvKy8mO8f6GV/HyBvElfcSxEhd+5FM5boAw/01iSaCto5G3G37ApJira4A3pNaVvBv8cUiLQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "System.Diagnostics.DiagnosticSource": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rxSLTO7xTbcC3DuEJHNEijBr8g14Jj62zQ+DeFu68bsoTYoU8jLcMhc1735PV21bESXsATlL5LsfaWH71FOWAg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Binder": "10.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "6cv53sHsPnFS56PJw8X4GbNcjeX1KGyFJRxJWvxOgK63cnqeSB1k1eRwjUdkse0tBhwlH6qc9EOYDlan+CYTuw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging.Configuration": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "4HW3M1lGHHDwEYcDZHRNptBQ48LCI2yW+XV4vuxdfQUqafTpVT8j9RqAsez08krZKhIiaArWu8iQq5uRKZ9Ffg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "kK/C3SLIoGrcZvddYQw4eMm6YaROiSYBO7YgUR5Hdv5l+GIjBmbvQK5cST2FqjeubiAOPqFEimBT2N/8wVI+3A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "System.Diagnostics.EventLog": "10.0.8" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HX2M0MgzwQM8jpLe3AYAEMd0YsUfOP5RgGrDuk+Ki9n7HSuMbvLm9TEV3qRI3Pg9aqxc56GfgK/KdMRBhfWwKw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.8", + "Microsoft.Extensions.Logging": "10.0.8", + "Microsoft.Extensions.Logging.Abstractions": "10.0.8", + "Microsoft.Extensions.Options": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "gQTW4BIfM2ZLxixo9ITXoulLKjn20FiiHtqTsx9PENqTrX7368ZeJ5L0QZJyReXDWORPRV8jXwZR6Aar8JOyaA==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "18.6.0", + "contentHash": "em1eLz5Q46+hsCtAXdXggWAPd9gQyT4ngdsQ7k1eWvQgpsjtS/wAOJ/5TteieFdiAvrEq1iVn00LtusAxRaVmQ==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.6.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "rQHsK7Xr8Uz3449860ayVXp/CaLmrhHlMPxbpT/ibOPtp/dTTsr6+f/SxaGO2NzxHf+0siLE0UfdVN5z1I0EgQ==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "+Ro7WgIom+BDNH+YhTuZKL6QJ0ctfOpTyfUG/h3aU5KwXt3OaNf0wYWrTvoBUj+34Dy5V8dN9yCco1hAJQ4txw==" + }, + "System.Interactive.Async": { + "type": "Transitive", + "resolved": "7.0.1", + "contentHash": "oL1iox7sAJL8i+muGzVMQjDB0axQgOoT5CkwYdap8cQJMkWDWMRErNqhOcZkn31+aKr/uCfgMEdhUARCU4G7gg==", + "dependencies": { + "System.Linq.AsyncEnumerable": "10.0.6" + } + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" + }, + "System.Linq.AsyncEnumerable": { + "type": "Transitive", + "resolved": "10.0.6", + "contentHash": "1Ttf7dyLoKXks2bVBVULmDPOSjxaMEE3JRTe/KGEJl3rfepCc4ipUEGMIKa/iBKLSN8rjEedF8Qv3UCtiwaDBw==" + }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "10.0.8",