Skip to content

feat(hooks): implement customization lifecycle hooks#100

Merged
j-d-ha merged 12 commits into
mainfrom
feature/add-hooks
Apr 13, 2026
Merged

feat(hooks): implement customization lifecycle hooks#100
j-d-ha merged 12 commits into
mainfrom
feature/add-hooks

Conversation

@j-d-ha
Copy link
Copy Markdown
Collaborator

@j-d-ha j-d-ha commented Apr 13, 2026

🚀 Pull Request

📋 Summary

Implements the four customization lifecycle hooks described in the docs (BeforeToItem, AfterToItem, BeforeFromItem, AfterFromItem) that allow users to inject logic into the mapping pipeline without modifying core mapping code.

Hooks are static partial void methods — the generator detects whether each hook is declared in the mapper class and only emits calls when they exist. Mappers with no hooks preserve the existing compact expression-body form with zero overhead.

Primary use cases enabled:

  • Single-table design PK/SK composition (AfterToItem)
  • TTL attribute injection (AfterToItem)
  • Record type discrimination and validation (BeforeFromItem)
  • Post-hydration computed properties (AfterFromItem)
  • Attribute bags for forward compatibility (AfterFromItem)

✅ Checklist

  • My changes build cleanly
  • I've added/updated relevant tests
  • I've added/updated documentation or README
  • I've followed the coding style for this project
  • I've tested the changes locally (if applicable)

🧪 Related Issues or PRs

No related issue.


💬 Notes for Reviewers

MapperClassInfo.cs — Added ToItemParameterName + four hook booleans. IsHookPresent() scans class members for static partial void methods matching by name and parameter count.

Mapper.scribanToItem switches from expression body to block body (var item = ...; item.SetXxx(...); AfterToItem(...); return item;) only when a To-hook is detected. Existing chainable .SetXxx(...) assignments work in block-body form by prepending item in the template — no changes to PropertyMappingCodeRenderer. FromItem gets BeforeFromItem/AfterFromItem calls injected at the right lifecycle points.

Snapshot tests (HooksVerifyTests) — 7 cases: each hook in isolation, all four together, and a regression case confirming expression body is preserved when no hooks are declared.

Integration tests (HooksMapperTests) — 11 runtime tests verifying actual hook execution order (lifecycle phase marker), pk/sk injection, BeforeFromItem entity-type validation (including throw-on-mismatch), AfterFromItem computed property population, and round-trip correctness.

Diagnostics — DM0401–DM0403 added (warnings for invalid hook signatures, non-static hooks, type mismatches). Registered in AnalyzerReleases.Unshipped.md.

j-d-ha added 5 commits April 12, 2026 20:57
- Introduced lifecycle hooks `BeforeToItem`, `AfterToItem`, `BeforeFromItem`, and `AfterFromItem`.
- Added validation diagnostics for hook signatures, static declaration, and parameter type checks.
- Updated templates to incorporate hooks when present.
- Extended `MapperClassInfo` model to support hook-related metadata.
- Added unit tests for all hook combinations: BeforeToItem, AfterToItem, BeforeFromItem, and AfterFromItem.
- Verified preservation of expression bodies when no hooks are used.
- Added snapshot test cases for all scenarios to ensure generated code accuracy.
- Covered edge cases for mappers with partial hooks and varying source fields.
- Added DM0401 to validate hook signature format.
- Added DM0402 to ensure hook methods are static.
- Added DM0403 to validate parameter types in hooks.
- Updated AnalyzerReleases.Unshipped.md with the new diagnostic rules.
- Expanded explanation of zero-cost abstraction for unimplemented hooks.
- Clarified behavior for generated code with and without declared hooks.
- Updated Phase 2 section to note planned DSL-based hook configuration.
- Added examples and diagnostics for common hook signature errors.
…delMapper

- Added comprehensive tests for all hook scenarios: BeforeToItem, AfterToItem, BeforeFromItem, AfterFromItem.
- Verified round-trip consistency of mapped properties and hook-added keys.
- Ensured unmapped properties like NormalizedName are ignored in ToItem and derived in FromItem.
- Covered edge cases for entity type validation and lifecycle marker injection.
Copy link
Copy Markdown
Contributor

@ncipollina ncipollina left a comment

Choose a reason for hiding this comment

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

Code review via sr-net-reviewer. 9 findings below — 1 critical, 3 major, 5 minor.

Comment thread src/LayeredCraft.DynamoMapper.Generators/Templates/Mapper.scriban Outdated
Comment thread src/LayeredCraft.DynamoMapper.Generators/Models/MapperClassInfo.cs Outdated
Comment thread src/LayeredCraft.DynamoMapper.Generators/Models/MapperClassInfo.cs Outdated
Comment thread src/LayeredCraft.DynamoMapper.Generators/Models/MapperClassInfo.cs
Comment thread src/LayeredCraft.DynamoMapper.Generators/Models/MapperClassInfo.cs Outdated
@ncipollina
Copy link
Copy Markdown
Contributor

💡 Minor: Custom Equals on a record in MapperInfo.cs explicitly excludes Context and HelperRegistry (src/LayeredCraft.DynamoMapper.Generators/Models/MapperInfo.cs:16)

This is pre-existing, but the hooks feature now routes more through this equality path. The auto-generated record equality would include Context and HelperRegistry, causing non-deterministic inequality and breaking incremental generation — the override is intentional and correct.

Worth adding a comment explaining why these fields are excluded. Future maintainers will question why the record overrides its own equality, and the answer (Roslyn incremental pipeline requires stable equality; GeneratorContext and HelperMethodRegistry are not equatable) is not obvious from the code.

Comment thread test/LayeredCraft.DynamoMapper.Generators.Tests/HooksVerifyTests.cs
Copy link
Copy Markdown
Contributor

@ncipollina ncipollina left a comment

Choose a reason for hiding this comment

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

Reviewed via sr-net-reviewer. See inline comments for 1 critical, 3 major, and 5 minor findings. The 3 major issues (silent hook failures on malformed signatures, missing parameter type validation, and dead diagnostics) should be addressed before merge.

@j-d-ha
Copy link
Copy Markdown
Collaborator Author

j-d-ha commented Apr 13, 2026

Addressed in ad0a043. Added an explicit comment above MapperInfo.Equals documenting why Context and HelperRegistry are intentionally excluded from record equality for incremental generator stability.

j-d-ha added 2 commits April 13, 2026 09:01
- Added `references/hooks.md` to cover hook definitions, rules, and execution order.
- Updated `diagnostics.md` to include DM0401, DM0402, and DM0403 as hook-related diagnostics.
- Clarified lifecycle hook behavior in `core-usage.md` and `gotchas.md`.
- Revised `SKILL.md` to include hooks as an extension point for pre/post mapping logic.
- Updated `Directory.Build.props` to increase `<VersionPrefix>` from 1.3.0 to 1.4.0.
@j-d-ha j-d-ha requested a review from ncipollina April 13, 2026 13:03
Copy link
Copy Markdown
Contributor

@ncipollina ncipollina left a comment

Choose a reason for hiding this comment

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

✅ Left one comment, but otherwise looks good!

Comment thread .config/dotnet-tools.json
@j-d-ha j-d-ha merged commit ed4c5c3 into main Apr 13, 2026
3 checks passed
@j-d-ha j-d-ha deleted the feature/add-hooks branch April 13, 2026 13:09
@j-d-ha j-d-ha restored the feature/add-hooks branch April 14, 2026 16:53
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