Skip to content

v0.0.20#54

Merged
yilmaztayfun merged 3 commits into
release-v1.0from
master
Mar 22, 2026
Merged

v0.0.20#54
yilmaztayfun merged 3 commits into
release-v1.0from
master

Conversation

@yilmaztayfun
Copy link
Copy Markdown
Contributor

@yilmaztayfun yilmaztayfun commented Mar 22, 2026

Summary by Sourcery

Introduce Mapperly as the default object-mapping implementation alongside an opt-in AutoMapper package, and wire both into the framework’s DI, documentation, and publishing pipeline.

New Features:

  • Add a Mapperly-based mapping package with base classes, mapper interfaces, and an adapter to provide lifecycle-aware, compile-time mapping as the default implementation.
  • Add a dedicated AutoMapper-based mapping package with configurable license options and DI registration for teams opting into the commercial dependency.

Enhancements:

  • Document the new dual-mapper architecture, detailing Mapperly as the default and AutoMapper as an opt-in alternative, with updated usage and integration guidance.
  • Refine the core mapper documentation to emphasise dynamic dispatch, generic injection, and the new mapper packages.

Build:

  • Include the new Mapperly and AutoMapper projects in the NuGet publish workflow so they are packaged and published with the rest of the framework.

Documentation:

  • Significantly rewrite the mapper documentation to cover Mapperly and AutoMapper packages, configuration, usage patterns, and application service integration in a clearer, more structured way.

Chores:

  • Add a CLAUDE.md contributor guide describing the repository architecture, build commands, package management, and documentation layout for AI-assisted development.

Summary by CodeRabbit

  • New Features

    • Introduced Mapperly mapper package as primary mapping solution
    • Added optional AutoMapper integration package
    • New service registration extensions for mapper configuration
  • Refactor

    • Extracted AutoMapper support from Infrastructure package into dedicated package
    • Restructured mapper abstractions to support multiple mapping implementations
  • Documentation

    • Updated mapping documentation with Mapperly defaults and AutoMapper guidance

yilmaztayfun and others added 3 commits March 22, 2026 22:47
…ckages

Moves all mapping concerns out of BBT.Aether.Infrastructure into two
new standalone packages:

- BBT.Aether.Mapperly (default): compile-time source generator adapter
  with MapperBase<TSource,TDestination> and TwoWayMapperBase for
  bidirectional mapping, IMapperlyMapper / IReverseMapperlyMapper
  interfaces, and BeforeMap/AfterMap/BeforeReverseMap/AfterReverseMap
  lifecycle hooks. DI registration accepts List<Type> (assemblies
  derived internally).

- BBT.Aether.AutoMapper (opt-in): runtime reflection adapter with
  AutoMapperOptions.LicenseKey for commercial license configuration.
  Emits NU1903 only for projects that explicitly opt in.

BBT.Aether.Infrastructure no longer depends on AutoMapper or
Riok.Mapperly. Projects using only Mapperly no longer receive the
AutoMapper CVE build warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Change AddAetherAutoMapperMapper and AddAetherMapperlyMapper to accept IEnumerable<Type> for more flexible input. In AetherMapperlyServiceCollectionExtensions, unify interface registration and register implementations of IMapperlyMapper<,>, IReverseMapperlyMapper<,> and IObjectMapper<,> by checking generic interface definitions. In MapperlyAdapter, add a reverse fallback that looks up IReverseMapperlyMapper<TDestination, TSource> and invokes its BeforeReverseMap/ReverseMap/AfterReverseMap methods before throwing, enabling two-way mappers to be used when a direct forward mapper is not registered.
…pperly

feat(mapper): extract Mapperly and AutoMapper into dedicated NuGet packages
@yilmaztayfun yilmaztayfun self-assigned this Mar 22, 2026
@yilmaztayfun yilmaztayfun requested review from a team and ikarakayali March 22, 2026 19:59
@yilmaztayfun yilmaztayfun merged commit 3960f59 into release-v1.0 Mar 22, 2026
1 of 2 checks passed
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 22, 2026

Reviewer's Guide

Introduces Mapperly as the new default mapping implementation, splits mapping support into two dedicated NuGet packages (BBT.Aether.Mapperly and BBT.Aether.AutoMapper), wires them into DI and publishing, and overhauls the mapper documentation to describe the new architecture and usage patterns.

Sequence diagram for MapperlyAdapter mapping resolution

sequenceDiagram
    participant AppService
    participant IObjectMapper as IObjectMapper
    participant MapperlyAdapter as MapperlyAdapter
    participant ServiceProvider as IServiceProvider
    participant ForwardMapper as IMapperlyMapper_TSource_TDestination_
    participant ReverseMapper as IReverseMapperlyMapper_TDestination_TSource_

    AppService->>IObjectMapper: Map~TSource,TDestination~(source)
    IObjectMapper->>MapperlyAdapter: Map~TSource,TDestination~(source)

    alt Forward mapper registered
        MapperlyAdapter->>ServiceProvider: GetService(IMapperlyMapper_TSource_TDestination_)
        ServiceProvider-->>MapperlyAdapter: ForwardMapper
        MapperlyAdapter->>ForwardMapper: BeforeMap(source)
        MapperlyAdapter->>ForwardMapper: Map(source)
        ForwardMapper-->>MapperlyAdapter: destination
        MapperlyAdapter->>ForwardMapper: AfterMap(source, destination)
        MapperlyAdapter-->>IObjectMapper: destination
        IObjectMapper-->>AppService: destination
    else No forward mapper, reverse mapper registered
        MapperlyAdapter->>ServiceProvider: GetService(IMapperlyMapper_TSource_TDestination_)
        ServiceProvider-->>MapperlyAdapter: null
        MapperlyAdapter->>ServiceProvider: GetService(IReverseMapperlyMapper_TDestination_TSource_)
        ServiceProvider-->>MapperlyAdapter: ReverseMapper
        MapperlyAdapter->>ReverseMapper: BeforeReverseMap(source)
        MapperlyAdapter->>ReverseMapper: ReverseMap(source)
        ReverseMapper-->>MapperlyAdapter: destination
        MapperlyAdapter->>ReverseMapper: AfterReverseMap(source, destination)
        MapperlyAdapter-->>IObjectMapper: destination
        IObjectMapper-->>AppService: destination
    else No mapper registered
        MapperlyAdapter-->>IObjectMapper: throw InvalidOperationException
        IObjectMapper-->>AppService: exception
    end
Loading

Class diagram for new Mapperly mapping architecture

classDiagram
    direction LR

    class IObjectMapper {
        <<interface>>
        +TDestination Map~TSource,TDestination~(TSource source)
        +void Map~TSource,TDestination~(TSource source, TDestination destination)
    }

    class IObjectMapper_TSource_TDestination_ {
        <<interface>>
        +TDestination Map(TSource source)
        +TDestination Map(TSource source, TDestination destination)
    }

    class IMapperlyMapper_TSource_TDestination_ {
        <<interface>>
        +TDestination Map(TSource source)
        +TDestination Map(TSource source, TDestination destination)
        +void BeforeMap(TSource source)
        +void AfterMap(TSource source, TDestination destination)
    }

    class IReverseMapperlyMapper_TSource_TDestination_ {
        <<interface>>
        +TSource ReverseMap(TDestination destination)
        +void ReverseMap(TDestination destination, TSource source)
        +void BeforeReverseMap(TDestination destination)
        +void AfterReverseMap(TDestination destination, TSource source)
    }

    class MapperBase_TSource_TDestination_ {
        <<abstract>>
        +TDestination Map(TSource source)
        +TDestination Map(TSource source, TDestination destination)
        +void BeforeMap(TSource source)
        +void AfterMap(TSource source, TDestination destination)
    }

    class TwoWayMapperBase_TSource_TDestination_ {
        <<abstract>>
        +TSource ReverseMap(TDestination destination)
        +void ReverseMap(TDestination destination, TSource source)
        +void BeforeReverseMap(TDestination destination)
        +void AfterReverseMap(TDestination destination, TSource source)
    }

    class MapperlyAdapter {
        +MapperlyAdapter(IServiceProvider serviceProvider)
        +TDestination Map~TSource,TDestination~(TSource source)
        +void Map~TSource,TDestination~(TSource source, TDestination destination)
    }

    class AutoMapperOptions {
        +string LicenseKey
    }

    class AutoMapperAdapter {
        +AutoMapperAdapter(IMapper mapper)
        +TDestination Map~TSource,TDestination~(TSource source)
        +void Map~TSource,TDestination~(TSource source, TDestination destination)
    }

    class AutoMapperAdapter_TSource_TDestination_ {
        +TDestination Map(TSource source)
        +TDestination Map(TSource source, TDestination destination)
    }

    class AetherMapperlyServiceCollectionExtensions {
        +static IServiceCollection AddAetherMapperlyMapper(IServiceCollection services, IEnumerable~Type~ mapperTypes)
    }

    class AetherAutoMapperServiceCollectionExtensions {
        +static IServiceCollection AddAetherAutoMapperMapper(IServiceCollection services, IEnumerable~Type~ autoMapperTypes, Action~AutoMapperOptions~ configure)
    }

    class IServiceCollection {
    }

    class IServiceProvider {
    }

    class IMapper {
    }

    %% Inheritance and implementation
    MapperBase_TSource_TDestination_ ..|> IMapperlyMapper_TSource_TDestination_
    MapperBase_TSource_TDestination_ ..|> IObjectMapper_TSource_TDestination_

    TwoWayMapperBase_TSource_TDestination_ --|> MapperBase_TSource_TDestination_
    TwoWayMapperBase_TSource_TDestination_ ..|> IReverseMapperlyMapper_TSource_TDestination_

    MapperlyAdapter ..|> IObjectMapper

    AutoMapperAdapter ..|> IObjectMapper
    AutoMapperAdapter_TSource_TDestination_ ..|> IObjectMapper_TSource_TDestination_

    IReverseMapperlyMapper_TSource_TDestination_ ..|> IMapperlyMapper_TSource_TDestination_

    %% DI relationships
    MapperlyAdapter --> IServiceProvider
    AutoMapperAdapter --> IMapper

    AetherMapperlyServiceCollectionExtensions ..> MapperlyAdapter
    AetherMapperlyServiceCollectionExtensions ..> IMapperlyMapper_TSource_TDestination_
    AetherMapperlyServiceCollectionExtensions ..> IReverseMapperlyMapper_TSource_TDestination_
    AetherMapperlyServiceCollectionExtensions ..> IObjectMapper_TSource_TDestination_

    AetherAutoMapperServiceCollectionExtensions ..> AutoMapperAdapter
    AetherAutoMapperServiceCollectionExtensions ..> AutoMapperAdapter_TSource_TDestination_
    AetherAutoMapperServiceCollectionExtensions ..> AutoMapperOptions

    AetherMapperlyServiceCollectionExtensions ..> IServiceCollection
    AetherAutoMapperServiceCollectionExtensions ..> IServiceCollection

    MapperlyAdapter ..> MapperBase_TSource_TDestination_
    MapperlyAdapter ..> TwoWayMapperBase_TSource_TDestination_
Loading

File-Level Changes

Change Details Files
Introduce Mapperly-based default mapping stack with lifecycle-aware adapter and base classes, and DI registration for mapper discovery.
  • Add MapperlyAdapter implementing IObjectMapper that dispatches via DI to IMapperlyMapper/IReverseMapperlyMapper and invokes BeforeMap/AfterMap hooks, including reverse-direction fallback.
  • Define IMapperlyMapper and IReverseMapperlyMapper interfaces plus MapperBase and TwoWayMapperBase abstract classes to standardize one-way and bidirectional mapper implementations for Mapperly.
  • Add AddAetherMapperlyMapper extension that scans marker assemblies for Mapperly mapper implementations (IMapperlyMapper<,>, IReverseMapperlyMapper<,>, IObjectMapper<,>) and registers them as singletons, plus IObjectMapper via MapperlyAdapter.
framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/MapperlyAdapter.cs
framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/MapperBase.cs
framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/TwoWayMapperBase.cs
framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/IMapperlyMapper.cs
framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/IReverseMapperlyMapper.cs
framework/src/BBT.Aether.Mapperly/Microsoft/Extensions/DependencyInjection/AetherMapperlyServiceCollectionExtensions.cs
framework/src/BBT.Aether.Mapperly/BBT.Aether.Mapperly.csproj
Add opt-in AutoMapper package with configurable license key and register it as an alternative mapping implementation.
  • Move AutoMapperAdapter into new BBT.Aether.AutoMapper project and reference core mapping abstractions.
  • Introduce AutoMapperOptions to carry the AutoMapper commercial license key for configuration.
  • Add AddAetherAutoMapperMapper extension that configures AutoMapper with optional license key, scans marker assemblies for profiles, and registers AutoMapperAdapter as IObjectMapper and IObjectMapper<,>.
  • Include new AutoMapper project in solution and CI publish workflow.
framework/src/BBT.Aether.AutoMapper/BBT/Aether/Mapper/AutoMapper/AutoMapperAdapter.cs
framework/src/BBT.Aether.AutoMapper/BBT/Aether/Mapper/AutoMapper/AutoMapperOptions.cs
framework/src/BBT.Aether.AutoMapper/Microsoft/Extensions/DependencyInjection/AetherAutoMapperServiceCollectionExtensions.cs
framework/src/BBT.Aether.AutoMapper/BBT.Aether.AutoMapper.csproj
.github/workflows/publish-nuget.yml
framework/BBT.Aether.slnx
framework/src/BBT.Aether.Infrastructure/BBT.Aether.Infrastructure.csproj
Revise mapper documentation to describe the dual-package architecture, Mapperly-first guidance, and updated usage patterns including lifecycle hooks and application service integration.
  • Rewrite docs/mapper README to introduce separate Mapperly and AutoMapper packages with feature comparison and licensing notes.
  • Document new Mapperly abstractions (IMapperlyMapper, IReverseMapperlyMapper, MapperBase, TwoWayMapperBase) and how the MapperlyAdapter resolves forward/reverse mappers.
  • Update examples to show Mapperly-style mappers, DI registration via AddAetherMapperlyMapper, and how to choose between Mapperly and AutoMapper.
  • Retain and adapt examples for AutoMapper usage, including new registration method and license configuration.
framework/docs/mapper/README.md
Add repository-level guidance for AI tooling and ensure new mapper packages are part of the build/publish pipeline and central package management.
  • Introduce CLAUDE.md with build commands, architecture overview, and contribution guidance for AI code assistants.
  • Wire new Mapperly and AutoMapper projects into NuGet publish workflow and central package management files as needed.
  • Update solution and infrastructure project references to remove old in-infrastructure mapper registration in favor of dedicated packages.
CLAUDE.md
.github/workflows/publish-nuget.yml
Directory.Packages.props
framework/BBT.Aether.slnx
framework/src/BBT.Aether.Infrastructure/BBT.Aether.Infrastructure.csproj

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 22, 2026

Caution

Review failed

The pull request is closed.

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'review'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c070fd86-5a3e-4392-859d-fe8288f14110

📥 Commits

Reviewing files that changed from the base of the PR and between 11359c3 and 7aeeeda.

📒 Files selected for processing (19)
  • .github/workflows/publish-nuget.yml
  • .gitignore
  • CLAUDE.md
  • Directory.Packages.props
  • framework/BBT.Aether.slnx
  • framework/docs/mapper/README.md
  • framework/src/BBT.Aether.AutoMapper/BBT.Aether.AutoMapper.csproj
  • framework/src/BBT.Aether.AutoMapper/BBT/Aether/Mapper/AutoMapper/AutoMapperAdapter.cs
  • framework/src/BBT.Aether.AutoMapper/BBT/Aether/Mapper/AutoMapper/AutoMapperOptions.cs
  • framework/src/BBT.Aether.AutoMapper/Microsoft/Extensions/DependencyInjection/AetherAutoMapperServiceCollectionExtensions.cs
  • framework/src/BBT.Aether.Infrastructure/BBT.Aether.Infrastructure.csproj
  • framework/src/BBT.Aether.Infrastructure/Microsoft/Extensions/DependencyInjection/AetherMapperServiceCollectionExtensions.cs
  • framework/src/BBT.Aether.Mapperly/BBT.Aether.Mapperly.csproj
  • framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/IMapperlyMapper.cs
  • framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/IReverseMapperlyMapper.cs
  • framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/MapperBase.cs
  • framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/MapperlyAdapter.cs
  • framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/TwoWayMapperBase.cs
  • framework/src/BBT.Aether.Mapperly/Microsoft/Extensions/DependencyInjection/AetherMapperlyServiceCollectionExtensions.cs

📝 Walkthrough

Walkthrough

The PR introduces two new NuGet packages for object mapping: BBT.Aether.Mapperly (default implementation using Riok.Mapperly) and BBT.Aether.AutoMapper (opt-in using AutoMapper), extracted from the existing Infrastructure package. It updates the solution file, CI/CD workflow, and mapper documentation to reflect the dual-package approach.

Changes

Cohort / File(s) Summary
Mapperly Mapper Package
framework/src/BBT.Aether.Mapperly/BBT.Aether.Mapperly.csproj, framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/I*.cs, framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/*Base.cs, framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/MapperlyAdapter.cs, framework/src/BBT.Aether.Mapperly/Microsoft/Extensions/DependencyInjection/*
New Mapperly-based mapper package with interfaces (IMapperlyMapper<,>, IReverseMapperlyMapper<,>), base classes (MapperBase<,>, TwoWayMapperBase<,>), service adapter (MapperlyAdapter), and DI extension for mapper registration and discovery.
AutoMapper Package
framework/src/BBT.Aether.AutoMapper/BBT.Aether.AutoMapper.csproj, framework/src/BBT.Aether.AutoMapper/BBT/Aether/Mapper/AutoMapper/AutoMapper*.cs, framework/src/BBT.Aether.AutoMapper/Microsoft/Extensions/DependencyInjection/*
New AutoMapper-based mapper package with configuration options (AutoMapperOptions for license key), adapter, and DI extension supporting optional configuration and assembly scanning.
Infrastructure Refactoring
framework/src/BBT.Aether.Infrastructure/BBT.Aether.Infrastructure.csproj, framework/src/BBT.Aether.Infrastructure/Microsoft/Extensions/DependencyInjection/AetherMapperServiceCollectionExtensions.cs
Removed AutoMapper package reference and extracted AddAetherAutoMapperMapper() method from Infrastructure to new AutoMapper package.
Solution & Packaging Configuration
framework/BBT.Aether.slnx, .github/workflows/publish-nuget.yml, Directory.Packages.props
Added two mapper projects to solution; added projects to NuGet workflow PROJECTS list for automated packaging; added centralized Riok.Mapperly v4.1.1 package version.
Documentation & Configuration
CLAUDE.md, framework/docs/mapper/README.md, .gitignore
Added Claude development guide; replaced mapper documentation with dual-package approach (Mapperly default, AutoMapper opt-in) including service registration, lifecycle hooks, comparison table, and integration examples; added tooling ignore patterns.

Sequence Diagram

sequenceDiagram
    participant Client
    participant MapperlyAdapter
    participant ServiceProvider
    participant IMapperlyMapper as IMapperlyMapper<TSource, TDest>
    participant IReverseMapper as IReverseMapperlyMapper<TDest, TSource>

    Client->>MapperlyAdapter: Map(source)
    activate MapperlyAdapter
    MapperlyAdapter->>ServiceProvider: Resolve IMapperlyMapper<TSource, TDest>
    
    alt Forward Mapper Found
        ServiceProvider-->>MapperlyAdapter: mapper instance
        MapperlyAdapter->>IMapperlyMapper: BeforeMap(source)
        activate IMapperlyMapper
        MapperlyAdapter->>IMapperlyMapper: Map(source)
        MapperlyAdapter->>IMapperlyMapper: AfterMap(source, destination)
        IMapperlyMapper-->>MapperlyAdapter: destination
        deactivate IMapperlyMapper
        MapperlyAdapter-->>Client: destination
    else Forward Mapper Not Found
        ServiceProvider-->>MapperlyAdapter: null
        MapperlyAdapter->>ServiceProvider: Resolve IReverseMapperlyMapper<TDest, TSource>
        
        alt Reverse Mapper Found
            ServiceProvider-->>MapperlyAdapter: reverse mapper instance
            MapperlyAdapter->>IReverseMapper: BeforeReverseMap(source)
            activate IReverseMapper
            MapperlyAdapter->>IReverseMapper: ReverseMap(source)
            MapperlyAdapter->>IReverseMapper: AfterReverseMap(source, destination)
            IReverseMapper-->>MapperlyAdapter: destination
            deactivate IReverseMapper
            MapperlyAdapter-->>Client: destination
        else Neither Mapper Found
            MapperlyAdapter-->>Client: InvalidOperationException
        end
    end
    deactivate MapperlyAdapter
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • ikarakayali
  • middt

Poem

🐰 ✨ Two paths for mapping, both quite keen,
Mapperly swift and AutoMapper serene!
Adapters and base classes, all arranged with care,
From Infrastructure freed, new packages to share!
The adapter resolves forward and back with grace,
Lifecycle hooks dance at their destined place! 🎭

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch master

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can disable poems in the walkthrough.

Disable the reviews.poem setting to disable the poems in the walkthrough.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces Mapperly as the default object-to-object mapper within the Aether framework, while still providing AutoMapper as an opt-in alternative. The changes include new packages, modifications to the solution file, and updated documentation to guide developers in choosing and implementing the appropriate mapping strategy for their needs.

Highlights

  • Mapperly Integration: Introduced Mapperly as the default object-to-object mapper, offering compile-time code generation and zero-reflection mapping.
  • AutoMapper as Opt-In: Retained AutoMapper as an opt-in mapper, useful for teams with existing commercial licenses (v13+).
  • Enhanced Documentation: Added comprehensive documentation for both Mapperly and AutoMapper, including usage examples, configuration details, and best practices.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/publish-nuget.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In MapperlyAdapter, the reverse-mapping fallback appears to have its generic type parameters swapped (using IReverseMapperlyMapper<TDestination, TSource> and passing source into BeforeReverseMap/ReverseMap which expect a TDestination), and the exception message type parameter order for TwoWayMapperBase is inverted—both should be corrected to use a consistent <TSource, TDestination> direction and matching argument types.
  • The exception messages in the two MapperlyAdapter.Map overloads are slightly inconsistent (one mentions TwoWayMapperBase, the other does not) and could be unified to clearly describe both forward and reverse mapper expectations in the same way.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `MapperlyAdapter`, the reverse-mapping fallback appears to have its generic type parameters swapped (using `IReverseMapperlyMapper<TDestination, TSource>` and passing `source` into `BeforeReverseMap`/`ReverseMap` which expect a `TDestination`), and the exception message type parameter order for `TwoWayMapperBase` is inverted—both should be corrected to use a consistent `<TSource, TDestination>` direction and matching argument types.
- The exception messages in the two `MapperlyAdapter.Map` overloads are slightly inconsistent (one mentions `TwoWayMapperBase`, the other does not) and could be unified to clearly describe both forward and reverse mapper expectations in the same way.

## Individual Comments

### Comment 1
<location path="framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/MapperlyAdapter.cs" line_range="29-25" />
<code_context>
+        }
+
+        // Reverse fallback: look for a TwoWayMapper registered for the opposite direction.
+        var reverseMapper = serviceProvider.GetService<IReverseMapperlyMapper<TDestination, TSource>>();
+        if (reverseMapper is not null)
+        {
+            reverseMapper.BeforeReverseMap(source);
+            var destination = reverseMapper.ReverseMap(source);
+            reverseMapper.AfterReverseMap(source, destination);
+            return destination;
+        }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Reverse fallback type parameters and usage look conceptually inverted and may never match real TwoWayMapper registrations.

This fallback requests `IReverseMapperlyMapper<TDestination, TSource>` and then calls `ReverseMap(source)`, but your documented pattern is `IReverseMapperlyMapper<TSource, TDestination>` / `TwoWayMapperBase<TSource, TDestination>` with `TSource` as the forward direction. For an `Order -> OrderDto` mapper (`TwoWayMapperBase<Order, OrderDto>`), this code would instead search for `IReverseMapperlyMapper<OrderDto, Order>`, which likely doesn’t exist, and would call `ReverseMap` in the wrong direction. Consider either (a) looking up `IReverseMapperlyMapper<TSource, TDestination>` and using `Map` for `S -> D` and `ReverseMap` for `D -> S`, or (b) inverting the generic ordering in the docs/base types so the fallback can be configured consistently. As is, this path is very hard to wire correctly and probably never used.
</issue_to_address>

### Comment 2
<location path="framework/src/BBT.Aether.Mapperly/BBT/Aether/Mapper/Mapperly/MapperlyAdapter.cs" line_range="66-68" />
<code_context>
+            return;
+        }
+
+        throw new InvalidOperationException(
+            $"No mapper registered for {typeof(TSource).Name} → {typeof(TDestination).Name}. " +
+            $"Ensure a {nameof(MapperBase<TSource, TDestination>)} implementation is registered via AddAetherMapperlyMapper.");
+    }
+}
</code_context>
<issue_to_address>
**suggestion:** Exception messages are inconsistent about supported mapper types between the two overloads.

The parameterless overload lists both `MapperBase<TSource, TDestination>` and `TwoWayMapperBase<TDestination, TSource>` as valid, while this overload only mentions `MapperBase<TSource, TDestination>`. Please align the exception text between the two overloads to accurately reflect the set of supported mapper types for this path.

```suggestion
        throw new InvalidOperationException(
            $"No mapper registered for {typeof(TSource).Name} → {typeof(TDestination).Name}. " +
            $"Ensure a {nameof(MapperBase<TSource, TDestination>)} or {nameof(TwoWayMapperBase<TDestination, TSource>)} implementation is registered via AddAetherMapperlyMapper.");
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

mapper.BeforeMap(source);
var destination = mapper.Map(source);
mapper.AfterMap(source, destination);
return destination;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Reverse fallback type parameters and usage look conceptually inverted and may never match real TwoWayMapper registrations.

This fallback requests IReverseMapperlyMapper<TDestination, TSource> and then calls ReverseMap(source), but your documented pattern is IReverseMapperlyMapper<TSource, TDestination> / TwoWayMapperBase<TSource, TDestination> with TSource as the forward direction. For an Order -> OrderDto mapper (TwoWayMapperBase<Order, OrderDto>), this code would instead search for IReverseMapperlyMapper<OrderDto, Order>, which likely doesn’t exist, and would call ReverseMap in the wrong direction. Consider either (a) looking up IReverseMapperlyMapper<TSource, TDestination> and using Map for S -> D and ReverseMap for D -> S, or (b) inverting the generic ordering in the docs/base types so the fallback can be configured consistently. As is, this path is very hard to wire correctly and probably never used.

Comment on lines +66 to +68
throw new InvalidOperationException(
$"No mapper registered for {typeof(TSource).Name} → {typeof(TDestination).Name}. " +
$"Ensure a {nameof(MapperBase<TSource, TDestination>)} implementation is registered via AddAetherMapperlyMapper.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Exception messages are inconsistent about supported mapper types between the two overloads.

The parameterless overload lists both MapperBase<TSource, TDestination> and TwoWayMapperBase<TDestination, TSource> as valid, while this overload only mentions MapperBase<TSource, TDestination>. Please align the exception text between the two overloads to accurately reflect the set of supported mapper types for this path.

Suggested change
throw new InvalidOperationException(
$"No mapper registered for {typeof(TSource).Name}{typeof(TDestination).Name}. " +
$"Ensure a {nameof(MapperBase<TSource, TDestination>)} implementation is registered via AddAetherMapperlyMapper.");
throw new InvalidOperationException(
$"No mapper registered for {typeof(TSource).Name}{typeof(TDestination).Name}. " +
$"Ensure a {nameof(MapperBase<TSource, TDestination>)} or {nameof(TwoWayMapperBase<TDestination, TSource>)} implementation is registered via AddAetherMapperlyMapper.");

@sonarqubecloud
Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot 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

This pull request introduces a dual-mapper strategy, with Mapperly as the default and AutoMapper as an opt-in solution. The changes include new projects for both mappers, updated dependency injection wiring, and significantly improved documentation. My review focuses on the new Mapperly implementation, where I've suggested some improvements to parameter naming in the reverse mapping interfaces and base classes to enhance code clarity and maintainability.

Comment on lines +12 to +22
/// <summary>Maps <paramref name="destination"/> back to a new <typeparamref name="TSource"/> instance.</summary>
TSource ReverseMap(TDestination destination);

/// <summary>Maps <paramref name="destination"/> into the existing <paramref name="source"/> instance.</summary>
void ReverseMap(TDestination destination, TSource source);

/// <summary>Called before <see cref="ReverseMap(TDestination)"/> or <see cref="ReverseMap(TDestination,TSource)"/>.</summary>
void BeforeReverseMap(TDestination destination);

/// <summary>Called after <see cref="ReverseMap(TDestination)"/> or <see cref="ReverseMap(TDestination,TSource)"/>.</summary>
void AfterReverseMap(TDestination destination, TSource source);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The parameter names in the ReverseMap methods and their corresponding lifecycle hooks are a bit confusing. For instance, in ReverseMap(TDestination destination), the parameter destination is actually the source of the reverse mapping operation. To improve clarity and maintainability, I suggest renaming the parameters to reflect their roles within the reverse mapping context. This will make the code easier to understand, especially in the MapperlyAdapter implementation.

    /// <summary>Maps <paramref name="source"/> back to a new <typeparamref name="TSource"/> instance.</summary>
    TSource ReverseMap(TDestination source);

    /// <summary>Maps <paramref name="source"/> into the existing <paramref name="destination"/> instance.</summary>
    void ReverseMap(TDestination source, TSource destination);

    /// <summary>Called before <see cref="ReverseMap(TDestination)"/> or <see cref="ReverseMap(TDestination,TSource)"/>.</summary>
    void BeforeReverseMap(TDestination source);

    /// <summary>Called after <see cref="ReverseMap(TDestination)"/> or <see cref="ReverseMap(TDestination,TSource)"/>.</summary>
    void AfterReverseMap(TDestination source, TSource destination);

Comment on lines +25 to +35
/// <inheritdoc />
public abstract TSource ReverseMap(TDestination destination);

/// <inheritdoc />
public abstract void ReverseMap(TDestination destination, TSource source);

/// <inheritdoc />
public virtual void BeforeReverseMap(TDestination destination) { }

/// <inheritdoc />
public virtual void AfterReverseMap(TDestination destination, TSource source) { }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Following the suggestion on the IReverseMapperlyMapper interface, the parameter names in this base class should also be updated for consistency and clarity. This will make implementing two-way mappers less error-prone.

    /// <inheritdoc />
    public abstract TSource ReverseMap(TDestination source);

    /// <inheritdoc />
    public abstract void ReverseMap(TDestination source, TSource destination);

    /// <inheritdoc />
    public virtual void BeforeReverseMap(TDestination source) { }

    /// <inheritdoc />
    public virtual void AfterReverseMap(TDestination source, TSource destination) { }

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.

1 participant