Skip to content

Add Wolfgang.Etl.ErrorPolicies package (lockstep 0.21.0) - #347

Merged
Chris-Wolfgang merged 4 commits into
vNextfrom
feat/error-policies-package
Aug 3, 2026
Merged

Add Wolfgang.Etl.ErrorPolicies package (lockstep 0.21.0)#347
Chris-Wolfgang merged 4 commits into
vNextfrom
feat/error-policies-package

Conversation

@Chris-Wolfgang

Copy link
Copy Markdown
Owner

Adds a second package built from this repo, Wolfgang.Etl.ErrorPolicies, versioned in lockstep with Wolfgang.Etl.Abstractions (same <Version>, one tag — the TestKit + TestKit.Xunit pattern). It hosts a shared generic ItemErrorPolicy factory for the family's OnError hook, so the ready-made error policies are defined once instead of duplicated per format library.

Why a separate package (decision: "C′")

The policies are format-agnostic and pair with the #84 ItemError* types already in Abstractions. But two of them need Microsoft.Extensions.Logging.Abstractions (SkipAndLog) and System.Threading.Channels (the channel dead-letter overloads). Rather than push those deps onto the deliberately-lean core (every Abstractions consumer would inherit them), they live only in this package. The core assembly's dependency set is unchanged; the six format leaves reference nothing new (OnError is Func<ItemErrorContext, ItemErrorAction>? — pure Abstractions types); end users opt into ErrorPolicies for the ready-mades or write their own lambda.

Surface (ItemErrorPolicy)

  • Skip / AbortFunc<ItemErrorContext, ItemErrorAction> properties
  • SkipAndLog(ILogger)
  • SkipAndDeadLetter(ICollection<ItemErrorContext>) / SkipAndDeadLetter(ChannelWriter<ItemErrorContext>)
  • SkipDeadLetterAndLog(ICollection<…>, ILogger) / SkipDeadLetterAndLog(ChannelWriter<…>, ILogger)

Dead-letter sinks are caller-owned (bounded by the caller); the channel overload is the thread-safe concurrent-producer path (TryWrite). The ICollection overloads add without locking — documented as safe for a single serial stage.

Dependencies (this package only)

  • Wolfgang.Etl.Abstractions 0.21.0 (lockstep, all TFMs)
  • Microsoft.Extensions.Logging.Abstractions 10.0.10 (all TFMs)
  • System.Threading.Channels 10.0.10 (net462/net472/net48/net481/netstandard2.0 only — in-box net5.0+)

Release mechanics

  • Bumps Abstractions <Version> 0.20.0 → 0.21.0 (lockstep); PackageValidation baseline stays at last-published 0.20.0. New package has no baseline on its first release (add next cycle).
  • No release.yaml change needed: it already packs every src/ project, validates the tag against any src <Version>, and pushes all *.nupkg.

Verification (local, full matrix net462 → net10.0)

  • Package + tests build clean, 0 warnings / 0 errors (strict analyzers + RS0016 PublicAPI)
  • 14 unit tests pass on net462 (Channels package path), net8.0, net10.0
  • dotnet pack emits correct per-TFM dependency groups (verified Abstractions 0.21.0 lockstep dep on every TFM; Channels only on netFx/ns2.0)

Release routing is yours — this bumps to 0.21.0 for a lockstep release; route via vNext/tag per the per-repo-release-pilot flow (standalone, or bundled with other 0.21.0 work).

🤖 Generated with Claude Code

New second package built from this repo, versioned in lockstep with
Wolfgang.Etl.Abstractions (same <Version>, one tag — like TestKit +
TestKit.Xunit). Hosts the shared generic ItemErrorPolicy factory for the
family's OnError hook so the policies are defined once instead of per format:

- Skip / Abort (Func<ItemErrorContext, ItemErrorAction> properties)
- SkipAndLog(ILogger)
- SkipAndDeadLetter / SkipDeadLetterAndLog, each overloaded for a caller-owned
  ICollection<ItemErrorContext> or a ChannelWriter<ItemErrorContext>

The core Abstractions assembly keeps its minimal deps; only this package takes
Microsoft.Extensions.Logging.Abstractions (all TFMs) and System.Threading.Channels
(netFx / netstandard2.0 only — in-box on net5.0+). Bumps Abstractions to 0.21.0
for the lockstep release; PackageValidation baseline stays at last-published 0.20.0.
No release.yaml change needed — it already packs every src/ project, validates the
tag against any src <Version>, and pushes all nupkgs.

14 unit tests pass net462 -> net10.0; full-matrix build clean; pack emits the
correct per-TFM dependency groups (Abstractions 0.21.0 lockstep dep on every TFM).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 23:58

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BenchmarkDotNet

Details
Benchmark suite Current: 3d506c0 Previous: 5e5e958 Ratio
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_NoProgress(RecordCount: 1000) 31393.145802815754 ns (± 197.45291732008621) 31583.873189290363 ns (± 115.28689686427228) 0.99
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_WithProgress(RecordCount: 1000) 34512.90861002604 ns (± 247.65949195033753) 35160.96792602539 ns (± 104.53835631316898) 0.98
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_NoProgress(RecordCount: 100000) 3066479.3606770835 ns (± 12488.839573303052) 3107534.1640625 ns (± 17584.772051305485) 0.99
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_WithProgress(RecordCount: 100000) 3367265.0989583335 ns (± 2229.19415989582) 3448047.61328125 ns (± 4145.027564723044) 0.98
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.FluentPipeline(RecordCount: 1000) 29232.722712198894 ns (± 80.0839446999554) 29626.396423339844 ns (± 122.33354576748134) 0.99
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.ManualComposition(RecordCount: 1000) 28957.610438028973 ns (± 22.5270248379915) 28745.347229003906 ns (± 56.763752733836895) 1.01
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.BaseClassComposition(RecordCount: 1000) 67740.15051269531 ns (± 250.4486726511416) 69584.28100585938 ns (± 220.36328762445214) 0.97
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.FluentPipeline(RecordCount: 100000) 2894727.7265625 ns (± 1317.622871382007) 2835179.5182291665 ns (± 5152.038095929195) 1.02
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.ManualComposition(RecordCount: 100000) 2913623.6536458335 ns (± 716.0158873807541) 2925295.2018229165 ns (± 3396.484964473814) 1.00
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.BaseClassComposition(RecordCount: 100000) 6695888.294270833 ns (± 7163.478585605702) 6793926.755208333 ns (± 24027.364375768884) 0.99

This comment was automatically generated by workflow using github-action-benchmark.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The new package/docs repeatedly describe an "OnError" hook, but the Abstractions API names the policy hook OnItemError, so the current wording is misleading and should be corrected for accuracy.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR introduces a new lockstep-versioned package, Wolfgang.Etl.ErrorPolicies, to centralize reusable item-error policy delegates (skip/abort/log/dead-letter) so downstream format-specific ETL libraries don’t duplicate the same On*Error policy implementations. It also bumps Wolfgang.Etl.Abstractions to 0.21.0 to keep both packages aligned for release.

Changes:

  • Added new Wolfgang.Etl.ErrorPolicies package (multi-TFM) with ItemErrorPolicy + cached LoggerMessage delegates.
  • Added a new unit test project and coverage for the policy factory methods across TFMs.
  • Bumped Wolfgang.Etl.Abstractions version to 0.21.0 and updated solution + changelog for the new package.
File summaries
File Description
tests/Wolfgang.Etl.ErrorPolicies.Tests.Unit/Wolfgang.Etl.ErrorPolicies.Tests.Unit.csproj Adds new multi-targeted unit test project for the ErrorPolicies package.
tests/Wolfgang.Etl.ErrorPolicies.Tests.Unit/ItemErrorPolicyTests.cs Adds tests for Skip/Abort/Log/DeadLetter policy behaviors and null-guarding.
src/Wolfgang.Etl.ErrorPolicies/Wolfgang.Etl.ErrorPolicies.csproj Introduces the new packable library project and its package dependencies/metadata.
src/Wolfgang.Etl.ErrorPolicies/PublicAPI.Unshipped.txt Declares the initial public API surface for the new package.
src/Wolfgang.Etl.ErrorPolicies/PublicAPI.Shipped.txt Establishes shipped API baseline header for first release.
src/Wolfgang.Etl.ErrorPolicies/ItemErrorPolicyLog.cs Adds cached LoggerMessage delegates to minimize per-item logging allocations.
src/Wolfgang.Etl.ErrorPolicies/ItemErrorPolicy.cs Implements the ready-made policy factory methods (skip/abort/log/dead-letter).
src/Wolfgang.Etl.Abstractions/Wolfgang.Etl.Abstractions.csproj Bumps Abstractions version to 0.21.0 for lockstep release.
ETL-Abstractions.sln Adds the new library + test projects to the solution.
CHANGELOG.md Documents the new ErrorPolicies package and its surface area in Unreleased.
Review details

Suppressed comments (1)

src/Wolfgang.Etl.ErrorPolicies/ItemErrorPolicy.cs:35

  • This comment refers to leaving "OnError" unset, but the core Abstractions API uses OnItemError (protected virtual) as the policy hook. Aligning the terminology here avoids confusion.
    /// A policy that re-throws the failure and stops the run. Equivalent to leaving <c>OnError</c>
    /// unset, provided for symmetry and explicitness.
  • Files reviewed: 10/10 changed files
  • Comments generated: 3
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread src/Wolfgang.Etl.ErrorPolicies/ItemErrorPolicy.cs
Comment thread src/Wolfgang.Etl.ErrorPolicies/Wolfgang.Etl.ErrorPolicies.csproj Outdated
Comment thread CHANGELOG.md Outdated
@Chris-Wolfgang
Chris-Wolfgang changed the base branch from main to vNext August 3, 2026 00:05
Chris-Wolfgang and others added 2 commits August 2, 2026 20:53
Makes ErrorPolicies' assign-a-policy design real. ExtractorBase / LoaderBase /
TransformerBase gain:

    public Func<ItemErrorContext, ItemErrorAction> ErrorPolicy { get; init; }

non-null, defaulting to a fail-fast AbortPolicy, throw-on-null. The base
OnItemError now delegates to it, so `ErrorPolicy = ItemErrorPolicy.SkipAndLog(...)`
works on any stage with no per-type property or override — while overriding
OnItemError is still available for stage-internal logic. Supersedes the earlier
"no public base property" decision; the rationale comment is rewritten and the
resume caveat (Skip = swallow-and-stop on formats that can't resume) documented.

- ErrorPolicy on the two/three-type-parameter bases (convenience bases inherit it)
- PublicAPI.Unshipped entries; +6 base tests (default fail-fast, assigned policy is
  used by the base OnItemError, null throws) across all three stages
- ItemErrorPolicy package docs + csproj description + CHANGELOG updated from the
  override-and-invoke wording back to the assignable `ErrorPolicy = ...` form

Full-matrix build clean (0/0); Abstractions PackageValidation passes vs 0.20.0
(additive); Abstractions unit suite 463 green, ErrorPolicies 14 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Chris-Wolfgang

Copy link
Copy Markdown
Owner Author

Update: added ErrorPolicy to the base stages

Folded in the piece that makes this package's assign-a-policy design actually work. ExtractorBase / LoaderBase / TransformerBase now expose:

public Func<ItemErrorContext, ItemErrorAction> ErrorPolicy { get; init; }   // non-null, default fail-fast, throw-on-null
protected virtual ItemErrorAction OnItemError(ItemErrorContext context) => ErrorPolicy(context);

So ErrorPolicy = ItemErrorPolicy.SkipAndLog(logger) works on any stage — no per-type property or OnItemError override needed (overriding is still available for stage-internal logic). The convenience bases (#344) inherit it.

This supersedes the earlier "the base classes deliberately expose no public error-handling property" decision in OnItemError's rationale comment — that comment is rewritten, and the resume caveat is documented (on a format that can't resume after a bad record, Skip means swallow-and-stop, not skip-and-continue; such leaves document it on their own type). The worker still owns the try/catch — that fact is unchanged.

Package docs / csproj description / CHANGELOG moved from the interim "override-and-invoke" wording back to the ErrorPolicy = … form. Skip/Abort naming settled (over OnError, which reads as an event and collides with the OnItemError method).

Verified: full-matrix build 0/0, Abstractions PackageValidation passes vs 0.20.0 (additive), Abstractions unit suite 463 green, ErrorPolicies 14 green.

The channel dead-letter policies use non-blocking TryWrite because the
OnItemError hook is synchronous. On a full bounded channel that drops the
failure record. SkipDeadLetterAndLog(ChannelWriter, ILogger) now logs a
distinct warning (EventId 2, ItemDeadLetterDropped) when the write is
dropped, so the loss is never silent — throwing would convert an error-sink
overflow into a full pipeline abort, which is worse. The logger-less
SkipAndDeadLetter(ChannelWriter) overload keeps the documented silent drop
(no logger to escalate to; caller controls overflow via BoundedChannelFullMode).

Adds a bounded-channel-full test and CHANGELOG note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants