Conversation
Anchor the PreferCqrsInterface (DSOFT006) suggestion to the whole type header — identifier through base list — instead of just the type name, so the IDE offers the ConvertToCqrs lightbulb when hovering the offending IRequest<T> base type too. DSOFT006 stays Info severity (suggestion dots, no squiggle); this only widens the lightbulb reach. Mirrors CqrsSemanticAnalyzerEnterprise (DiagnosticLocations.TypeHeader). Analyzer-only change: emits no source and does not alter compiled output, so there is no runtime impact.
…nversion The registration-API analyzer was an [Generator], which cannot see other generators' output. So it could not resolve the generated RegisterMediatorHandlers() or the AddMediator(builder) overload — DSOFT007 was effectively dead in real projects (it only fired against hand-written stubs in tests). Convert it to a DiagnosticAnalyzer: it runs after all source generators on the final compilation, so the semantic model resolves the generated members. Analysis is now per method body (operation-block) instead of compilation-wide. - Fixes DSOFT007: now fires on real builds, not just stubs. - Adds DSOFT008: parameterless AddMediator() that never registers handlers in the scope (no builder overload, no RegisterMediatorHandlers(), no manual AddTransient<IRequestHandler<,>,...>()) while handlers exist -> the runtime 'No service for IRequestHandler<...>' failure, caught at compile time. - Recognizes manual handler registration to avoid false positives. - Analyzer-only change: the runtime assembly and generated code are untouched (zero runtime/allocation impact). Tests: stub logic tests rerun via WithAnalyzers; new high-fidelity integration tests run the real DependencyInjectionGenerator + analyzer to lock the regression. Deliberate 'no handler' self-tests suppressed with #pragma. Full suite green.
…dep bumps Pre-release (rc.1) soaking before promotion to the stable 1.3.0. Added - IPipelineHandlerTypeAccessor (Abstractions): exposes the concrete request/ stream handler type at the tail of the pipeline chain to an outermost behavior, without resolving or instantiating it. Implemented by the internal BehaviorHandlerAdapter / StreamBehaviorHandlerAdapter (walk next -> handler). - OpenTelemetry: tag mediator.handler.type on request and stream spans (it already did for notification-handler spans). Read through the new accessor; the handler is never resolved or instantiated. Changed - DSOFT008 detection is now compilation-wide (reported from a CompilationEndAction), so splitting AddMediator() and the handler registration across different methods is no longer a false positive. - Dependency bumps: Microsoft.Extensions.DependencyInjection.Abstractions 10.0.9, Microsoft.Bcl.AsyncInterfaces 10.0.9, OpenTelemetry 1.16.0, Microsoft.Extensions.Caching.Hybrid 10.7.0. Microsoft.CodeAnalysis.CSharp is intentionally kept at 4.12.0 — that version is the minimum compiler host a consumer needs, so raising it would break consumers on older SDK/VS. - Align stale test/sample/benchmark package pins (NU1605 downgrade fixes). Security - Scriban 7.0.3 -> 7.2.4 (benchmarks, dev-only, not shipped) — GHSA-24c8-4792-22hx. Versions: Mediator/Abstractions 1.3.0-rc.1, OpenTelemetry 1.1.0-rc.1, HybridCache/FluentValidation 1.0.9-rc.1.
Contributor
SummarySummary
CoverageDSoftStudio.Mediator - 98.1%
DSoftStudio.Mediator.Abstractions - 100%
DSoftStudio.Mediator.FluentValidation - 100%
DSoftStudio.Mediator.Generators - 92.9%
DSoftStudio.Mediator.HybridCache - 100%
DSoftStudio.Mediator.OpenTelemetry - 97.1%
|
- Test the whole solution (dotnet test DSoftStudio.Mediator.slnx) instead of a hand-listed set of projects. The old steps silently skipped ModularMonolith.Tests (and the cross-project-mocking sample tests); the solution target discovers every test project, so new ones are covered with no workflow edit. 453 tests run (was a subset). - Merge per-project coverage with ReportGenerator before posting. Each project emits its own coverage.cobertura.xml covering the same assemblies only in the slice it exercises; the previous summary listed each assembly N times with different numbers (the misleading 34%). The merged report shows each assembly once with its real combined coverage (e.g. core ~92%).
The stream/notification/publish/pipeline source generators had ZERO test coverage. Add a shared in-memory harness (GeneratorTestHarness) that drives the real IIncrementalGenerators against a small user compilation and inspects the generated source, then cover each: - StreamGenerator 0% -> 96.7% (registry, AOT behavior closure, empty) - StreamInterceptorGenerator 0% -> 79.0% (CreateStream<,> interception, no-call-site) - NotificationGenerator 0% -> 90.6% (dispatch table, Publish(object) switch, empty) - PublishInterceptorGenerator 0% -> 88.1% (Publish<T> interception, Publish(object) excluded) - MediatorPipelineGenerator 0% -> 73.1% (MediatorRegistry entry points, empty skeleton) Harness note: the netstandard2.0 Abstractions exposes IAsyncEnumerable from Microsoft.Bcl.AsyncInterfaces, so stream call sites only bind once that assembly is referenced — the harness adds it. 11 tests, all green (project: 358).
Follow-up to the 0%->coverage pass. Add a shared in-memory harness RunChain (runs two generators in sequence, so the second sees the first's output) plus a Release/optimization toggle, and cover the deeper branches: - MediatorPipelineGenerator -> 94.1% (AOT open-generic behavior closure, self-handling request) - StreamGenerator -> 96.7% - NotificationGenerator -> 93.4% (multi-handler grouping, abstract/generic/file-local rejection) - PublishInterceptorGenerator -> 91.2% (explicit + inferred Publish, Release, non-publisher exclusion) - StreamInterceptorGenerator -> 92.0% (inferred CreateStream via the generated typed extension) Note: the inferred CreateStream path is NOT dead code (an earlier guess) — it is reachable once MediatorExtensionsGenerator emits the typed CreateStream(this IMediator, T) extension that makes the inferred call bind; RunChain exercises exactly that two-generator scenario. The remaining uncovered lines are defensive early-exits that only fire when the mediator interface is absent (impossible in a real project), config-flag (SuppressInterceptors) branches, and external-assembly scans — covering those needs dedicated harness machinery for marginal value.
… discovery
Close the remaining low-coverage generator helpers (the merged CI report showed
these well below the rest):
- EquatableArray<T> 35% -> 100% (direct unit tests: Equals/GetHashCode/enumeration/default)
- SelfHandlerDetail 24% -> 100% (direct struct equality + properties)
- SelfHandlerParam 29% -> 100%
- HandlerDiscovery 75% -> 95% (self-handler discovery across every Execute return
shape — sync/Task<T>/ValueTask<T>/void/Task — and param
kind — request/service/cancellation)
- SendInterceptorGenerator 79% -> 93% (inferred Send via the generated typed extension using
RunChain, Release path, non-sender + expression-tree exclusions)
Adds [InternalsVisibleTo("DSoftStudio.Mediator.Tests")] on the generators project
(matching the strong-name key) so the internal helper structs can be tested directly.
The pull_request:synchronize event for a6065c1 (EquatableArray / SendInterceptor / self-handler coverage) was skipped by GitHub Actions, so the CI workflow never ran and the merged coverage comment is stale. Empty commit to fire a fresh run.
- BehaviorTypeInfo 73%/20%br -> 100%/100%br (direct struct tests: properties, structural equality across all fields, null-safe GetHashCode) - StreamBehaviorHandlerAdapter 0% branch -> 100% branch (the stream half of the ADR-0049 IPipelineHandlerTypeAccessor seam; the request adapter was already tested, the stream chain-walk was not)
An interceptor cannot represent an open-generic call site: a single
[InterceptsLocation] is instantiated for every TRequest/TResponse the
enclosing generic method is called with, so no one concrete interceptor
can stand in for it. The three interceptor generators emitted a method
referencing the unbound type parameters anyway -> CS0246, breaking the
build of any consumer with a generic dispatch wrapper, e.g.:
ValueTask<TResp> Dispatch<TReq, TResp>(TReq req)
where TReq : IRequest<TResp> => _sender.Send<TReq, TResp>(req);
Guard with InterceptorHelpers.ContainsTypeParameter: such call sites are
skipped and dispatch through the real Mediator.Send/Publish/CreateStream
at runtime (which is exactly what those methods exist for).
This also covers the previously-untested non-intercepted Mediator dispatch
path (line 30% -> 100%, branch 17% -> 92%): an open-generic helper reaches
the real Mediator.Send/Publish/CreateStream, exercising every branch
(pipeline vs handler-cache, custom-publisher vs sequential, precompiled
stream vs invoker fallback).
Generator output for concrete call sites is byte-identical (the guard
returns false for closed types), so allocation/perf are unchanged:
Send 6.7ns/72B (ratio 1.00), Publish 4.4ns/0B, Stream 46ns/232B (ratio 1.00).
+12 tests (9 runtime dispatch, 3 generator regression), 395 green.
… stale docs Core-runtime audit cleanup -- no behavior change, hot paths byte-identical. - Remove RequestDispatch<>.Pipeline / TryInitialize and the PipelineBuilder class. The generator built and stored a per-request-type dispatch delegate that nothing ever invoked: Send / the interceptor / RequestObjectDispatch all dispatch via HasPipelineChain + the ThreadStatic PipelineChainCache/HandlerCache. (The stream side still uses StreamDispatch.Pipeline; the request side left it vestigial.) Saves one delegate allocation per request type at startup. The 3 tests that drove it are migrated to the live PipelineChainHandler path. - MediatorBuilder: collapse 4 identical interface-matching registration loops into a shared RegisterByOpenInterface helper. - Fix XML docs describing the removed mutable-index / reentrancy / PipelineBuilder design (PipelineChainHandler, BehaviorHandlerAdapter, ParallelNotificationPublisher) + broken brace indentation. Send 6.6ns/72B, Publish 4.4ns/0B, Stream 46ns/232B (ratio 1.00). All suites green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pre-release (rc.1) soaking ~2 weeks before promotion to the stable 1.3.0.
Package versions
DSoftStudio.Mediator·.Abstractions1.3.0-rc.1DSoftStudio.Mediator.OpenTelemetry1.1.0-rc.1DSoftStudio.Mediator.HybridCache·.FluentValidation1.0.9-rc.1Added
IPipelineHandlerTypeAccessor(Abstractions) — exposes the concrete request/stream handler type at the tail of the pipeline chain to an outermost behavior, without resolving or instantiating it. A behavior is open-generic and may serve many handlers; the correct one is only knowable by walking the chain it was handed asnext. Implemented by the internalBehaviorHandlerAdapter/StreamBehaviorHandlerAdapter.mediator.handler.typeon request & stream spans (it already did for notification-handler spans) — an imported OTLP/Jaeger trace now maps each span to its handler source and renders HTTP/DB child spans as dependencies under it. Read through the new accessor; the handler is never resolved.Changed
CompilationEndAction) — splittingAddMediator()and the handler registration across different methods is no longer a false positive.Microsoft.Extensions.DependencyInjection.Abstractions10.0.9,Microsoft.Bcl.AsyncInterfaces10.0.9,OpenTelemetry1.16.0,Microsoft.Extensions.Caching.Hybrid10.7.0.Microsoft.CodeAnalysis.CSharpintentionally kept at 4.12.0 — it's the minimum compiler host a consumer needs; raising it would break consumers on older SDK/VS.Security
Validation
dotnet list --vulnerable(solution-wide): 0 vulnerabilitiesdotnet pack: all 5.nupkgproduced; companion → core dependency resolves to1.3.0-rc.1Promotion to stable
When the rc soaks without issues: drop the
-rc.1suffix, move DSOFT008 fromAnalyzerReleases.Unshipped.md→Shipped.md(under "Release 1.3.0"), and date the CHANGELOG section.