From d76b94fe0d128c5afeb1147a183345ea84ad0364 Mon Sep 17 00:00:00 2001 From: davidkallesen Date: Mon, 17 Nov 2025 00:55:02 +0100 Subject: [PATCH] chore: example cleanup --- Atc.SourceGenerators.slnx | 1 + docs/samples/Mapping.md | 253 +++++++++++------- .../AddressDto.cs | 2 +- ...c.SourceGenerators.Mapping.Contract.csproj | 9 + .../UserDto.cs | 2 +- .../UserStatusDto.cs | 2 +- ...SourceGenerators.Mapping.DataAccess.csproj | 9 +- .../Entities/AddressEntity.cs | 5 +- .../Entities/UserEntity.cs | 5 +- .../GlobalUsings.cs | 5 +- .../Repositories/IUserRepository.cs | 20 ++ .../{ => Repositories}/UserRepository.cs | 18 +- .../Address.cs | 1 + ...Atc.SourceGenerators.Mapping.Domain.csproj | 23 +- .../GlobalUsings.cs | 7 +- .../IUserRepository.cs | 20 -- .../ServiceCollectionExtensions.cs | 20 -- .../User.cs | 1 + .../UserService.cs | 6 +- .../UserStatus.cs | 2 + .../Atc.SourceGenerators.Mapping.csproj | 31 ++- .../GlobalUsings.cs | 4 +- .../Atc.SourceGenerators.Mapping/Program.cs | 6 +- sample/PetStore.Api/Program.cs | 6 +- 24 files changed, 275 insertions(+), 183 deletions(-) rename sample/{Atc.SourceGenerators.Mapping.Domain/ApiContracts => Atc.SourceGenerators.Mapping.Contract}/AddressDto.cs (92%) create mode 100644 sample/Atc.SourceGenerators.Mapping.Contract/Atc.SourceGenerators.Mapping.Contract.csproj rename sample/{Atc.SourceGenerators.Mapping.Domain/ApiContracts => Atc.SourceGenerators.Mapping.Contract}/UserDto.cs (95%) rename sample/{Atc.SourceGenerators.Mapping.Domain/ApiContracts => Atc.SourceGenerators.Mapping.Contract}/UserStatusDto.cs (88%) create mode 100644 sample/Atc.SourceGenerators.Mapping.DataAccess/Repositories/IUserRepository.cs rename sample/Atc.SourceGenerators.Mapping.DataAccess/{ => Repositories}/UserRepository.cs (87%) delete mode 100644 sample/Atc.SourceGenerators.Mapping.Domain/IUserRepository.cs delete mode 100644 sample/Atc.SourceGenerators.Mapping.Domain/ServiceCollectionExtensions.cs diff --git a/Atc.SourceGenerators.slnx b/Atc.SourceGenerators.slnx index 1f6f3d9..fd68529 100644 --- a/Atc.SourceGenerators.slnx +++ b/Atc.SourceGenerators.slnx @@ -19,6 +19,7 @@ + diff --git a/docs/samples/Mapping.md b/docs/samples/Mapping.md index 82e7378..79d85ac 100644 --- a/docs/samples/Mapping.md +++ b/docs/samples/Mapping.md @@ -16,14 +16,18 @@ This sample demonstrates the **MappingGenerator** in a realistic 3-layer archite - **Atc.SourceGenerators.Mapping** - ASP.NET Core Minimal API (entry point) - **Atc.SourceGenerators.Mapping.Domain** - Domain models and business logic - **Atc.SourceGenerators.Mapping.DataAccess** - Data access layer with entities +- **Atc.SourceGenerators.Mapping.Contract** - API contracts (DTOs) ## 🏗️ Architecture ```mermaid -graph LR +graph TB subgraph "API Layer" API[Minimal API Endpoints] - DTO[DTOs: UserDto, AddressDto] + end + + subgraph "Contract Layer" + DTO[DTOs: UserDto, AddressDto, UserStatusDto] end subgraph "Domain Layer" @@ -36,32 +40,36 @@ graph LR EE[Entity Enums: UserStatusEntity] end - subgraph "Generated Mappings" - M1["UserEntity.MapToUser()"] - M2["User.MapToUserDto()"] - M3["AddressEntity.MapToAddress()"] - M4["Address.MapToAddressDto()"] + subgraph "Generated Bidirectional Mappings" + M1["User ↔ UserEntity"] + M2["User → UserDto"] + M3["Address ↔ AddressEntity"] + M4["Address → AddressDto"] + M5["UserStatus ↔ UserStatusEntity"] + M6["UserStatus → UserStatusDto"] end - ENT -->|MapToUser| M1 - M1 --> DM - DM -->|MapToUserDto| M2 - M2 --> DTO - - EE -.->|auto cast| DE - DE -.->|auto cast| DTO - - ENT -.->|nested| M3 - M3 --> DM - DM -.->|nested| M4 - API --> DTO - DTO --> API + DTO --> DM + DM --> M1 + DM --> M2 + DM --> M3 + DM --> M4 + DE --> M5 + DE --> M6 + M1 --> ENT + M3 --> ENT + M5 --> EE + M2 --> DTO + M4 --> DTO + M6 --> DTO style M1 fill:#2ea44f style M2 fill:#2ea44f style M3 fill:#2ea44f style M4 fill:#2ea44f + style M5 fill:#2ea44f + style M6 fill:#2ea44f ``` ## 🔄 Mapping Flow @@ -109,37 +117,42 @@ sequenceDiagram ### Data Access Layer ```csharp -using Atc.SourceGenerators.Annotations; - -namespace Atc.SourceGenerators.Mapping.DataAccess; +namespace Atc.SourceGenerators.Mapping.DataAccess.Entities; -// Entity with mapping to Domain -[MapTo(typeof(Domain.User))] -public partial class UserEntity +// Entity - NO mapping attribute (mapping defined in Domain) +public class UserEntity { public int DatabaseId { get; set; } public Guid Id { get; set; } - public string Name { get; set; } = string.Empty; + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public UserStatusEntity Status { get; set; } public AddressEntity? Address { get; set; } public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset? UpdatedAt { get; set; } public bool IsDeleted { get; set; } // DB-specific field + public byte[] RowVersion { get; set; } = []; // DB-specific field } -[MapTo(typeof(Domain.Address))] -public partial class AddressEntity +public class AddressEntity { + public int Id { get; set; } public string Street { get; set; } = string.Empty; public string City { get; set; } = string.Empty; + public string State { get; set; } = string.Empty; public string PostalCode { get; set; } = string.Empty; + public string Country { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } } public enum UserStatusEntity { Active = 0, Inactive = 1, - Suspended = 2 + Suspended = 2, + Deleted = 3 } ``` @@ -150,49 +163,76 @@ using Atc.SourceGenerators.Annotations; namespace Atc.SourceGenerators.Mapping.Domain; -// Domain model with mapping to DTO +// Domain model with BIDIRECTIONAL mapping to Entity and forward mapping to DTO [MapTo(typeof(UserDto))] +[MapTo(typeof(UserEntity), Bidirectional = true)] public partial class User { - public Guid Id { get; init; } - public string Name { get; init; } = string.Empty; - public string Email { get; init; } = string.Empty; - public UserStatus Status { get; init; } - public Address? Address { get; init; } - public DateTimeOffset CreatedAt { get; init; } + public Guid Id { get; set; } + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public UserStatus Status { get; set; } + public Address? Address { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset? UpdatedAt { get; set; } } [MapTo(typeof(AddressDto))] +[MapTo(typeof(AddressEntity), Bidirectional = true)] public partial class Address { - public string Street { get; init; } = string.Empty; - public string City { get; init; } = string.Empty; - public string PostalCode { get; init; } = string.Empty; + public string Street { get; set; } = string.Empty; + public string City { get; set; } = string.Empty; + public string State { get; set; } = string.Empty; + public string PostalCode { get; set; } = string.Empty; + public string Country { get; set; } = string.Empty; } +[MapTo(typeof(UserStatusDto))] +[MapTo(typeof(UserStatusEntity), Bidirectional = true)] public enum UserStatus { Active = 0, Inactive = 1, - Suspended = 2 + Suspended = 2, + Deleted = 3 } +``` + +### Contract Layer + +```csharp +namespace Atc.SourceGenerators.Mapping.Contract; -// DTOs +// DTOs - no mapping attributes needed (mapped from Domain) public class UserDto { public Guid Id { get; set; } - public string Name { get; set; } = string.Empty; + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; - public string Status { get; set; } = string.Empty; // Different type! + public UserStatusDto Status { get; set; } public AddressDto? Address { get; set; } public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset? UpdatedAt { get; set; } } public class AddressDto { public string Street { get; set; } = string.Empty; public string City { get; set; } = string.Empty; + public string State { get; set; } = string.Empty; public string PostalCode { get; set; } = string.Empty; + public string Country { get; set; } = string.Empty; +} + +public enum UserStatusDto +{ + Active = 0, + Inactive = 1, + Suspended = 2, + Deleted = 3 } ``` @@ -200,81 +240,82 @@ public class AddressDto ```csharp using Atc.Mapping; +using Atc.SourceGenerators.Mapping.Contract; using Atc.SourceGenerators.Mapping.Domain; using Microsoft.AspNetCore.Mvc; -var app = WebApplication.Create(); +var builder = WebApplication.CreateBuilder(args); + +// Register services +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); -// POST endpoint - Create user -app.MapPost("/users", async ([FromBody] CreateUserRequest request) => +var app = builder.Build(); + +// GET endpoint - Retrieve user by ID +app.MapGet("/users/{id:guid}", (Guid id, UserService userService) => { - // Convert DTO → Domain - var user = new User + var user = userService.GetById(id); + if (user is null) { - Id = Guid.NewGuid(), - Name = request.Name, - Email = request.Email, - Status = UserStatus.Active, - CreatedAt = DateTimeOffset.UtcNow - }; - - // Convert Domain → Entity - var entity = user.MapToUserEntity(); - // Save to database... - - // Convert Domain → DTO for response - var dto = user.MapToUserDto(); - return Results.Created($"/users/{user.Id}", dto); -}); - -// GET endpoint - Retrieve user -app.MapGet("/users/{id:guid}", async (Guid id) => -{ - // Fetch from database - var entity = await repository.GetByIdAsync(id); - if (entity == null) return Results.NotFound(); - - // Complete mapping chain: Entity → Domain → DTO - var user = entity.MapToUser(); // Auto-converts enum, nested Address - var dto = user.MapToUserDto(); // Auto-maps all properties + return Results.NotFound(new { message = $"User with ID {id} not found" }); + } - return Results.Ok(dto); -}); + // ✨ Use generated mapping: Domain → DTO + var data = user.MapToUserDto(); + return Results.Ok(data); +}) +.WithName("GetUserById") +.Produces(StatusCodes.Status200OK) +.Produces(StatusCodes.Status404NotFound); -app.Run(); +// GET endpoint - Retrieve all users +app.MapGet("/users", (UserService userService) => +{ + // ✨ Use generated mapping: Domain → DTO + var data = userService + .GetAll() + .Select(u => u.MapToUserDto()) + .ToList(); + return Results.Ok(data); +}) +.WithName("GetAllUsers") +.Produces>(StatusCodes.Status200OK); + +await app.RunAsync(); ``` ## 📝 Generated Code -The generator creates extension methods for each mapping: +The generator creates extension methods for bidirectional mappings: ```csharp // namespace Atc.Mapping; -public static partial class UserEntityExtensions +// Bidirectional mapping: User ↔ UserEntity +public static partial class UserExtensions { - public static Domain.User MapToUser(this UserEntity source) + public static UserEntity MapToUserEntity(this User source) { if (source is null) { return default!; } - return new Domain.User + return new UserEntity { Id = source.Id, - Name = source.Name, + FirstName = source.FirstName, + LastName = source.LastName, Email = source.Email, - Status = (Domain.UserStatus)source.Status, // ✨ Auto enum conversion - Address = source.Address?.MapToAddress()!, // ✨ Auto nested mapping - CreatedAt = source.CreatedAt + Status = source.Status.MapToUserStatusEntity(), // ✨ Safe enum mapping (bidirectional) + Address = source.Address?.MapToAddressEntity()!, // ✨ Auto nested mapping + CreatedAt = source.CreatedAt, + UpdatedAt = source.UpdatedAt }; } -} -public static partial class UserExtensions -{ public static UserDto MapToUserDto(this User source) { if (source is null) @@ -285,11 +326,37 @@ public static partial class UserExtensions return new UserDto { Id = source.Id, - Name = source.Name, + FirstName = source.FirstName, + LastName = source.LastName, + Email = source.Email, + Status = source.Status.MapToUserStatusDto(), // ✨ Safe enum mapping + Address = source.Address?.MapToAddressDto()!, // ✨ Nested mapping + CreatedAt = source.CreatedAt, + UpdatedAt = source.UpdatedAt + }; + } +} + +public static partial class UserEntityExtensions +{ + // Reverse mapping (from Bidirectional = true) + public static User MapToUser(this UserEntity source) + { + if (source is null) + { + return default!; + } + + return new User + { + Id = source.Id, + FirstName = source.FirstName, + LastName = source.LastName, Email = source.Email, - Status = source.Status.ToString(), // ✨ Enum to string - Address = source.Address?.MapToAddressDto()!, // ✨ Nested mapping - CreatedAt = source.CreatedAt + Status = source.Status.MapToUserStatus(), // ✨ Safe enum mapping (reverse) + Address = source.Address?.MapToAddress()!, // ✨ Auto nested mapping + CreatedAt = source.CreatedAt, + UpdatedAt = source.UpdatedAt }; } } diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/ApiContracts/AddressDto.cs b/sample/Atc.SourceGenerators.Mapping.Contract/AddressDto.cs similarity index 92% rename from sample/Atc.SourceGenerators.Mapping.Domain/ApiContracts/AddressDto.cs rename to sample/Atc.SourceGenerators.Mapping.Contract/AddressDto.cs index f898d78..c72877a 100644 --- a/sample/Atc.SourceGenerators.Mapping.Domain/ApiContracts/AddressDto.cs +++ b/sample/Atc.SourceGenerators.Mapping.Contract/AddressDto.cs @@ -1,4 +1,4 @@ -namespace Atc.SourceGenerators.Mapping.Domain.ApiContracts; +namespace Atc.SourceGenerators.Mapping.Contract; /// /// Data transfer object for Address (used for API responses). diff --git a/sample/Atc.SourceGenerators.Mapping.Contract/Atc.SourceGenerators.Mapping.Contract.csproj b/sample/Atc.SourceGenerators.Mapping.Contract/Atc.SourceGenerators.Mapping.Contract.csproj new file mode 100644 index 0000000..9ed914b --- /dev/null +++ b/sample/Atc.SourceGenerators.Mapping.Contract/Atc.SourceGenerators.Mapping.Contract.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/ApiContracts/UserDto.cs b/sample/Atc.SourceGenerators.Mapping.Contract/UserDto.cs similarity index 95% rename from sample/Atc.SourceGenerators.Mapping.Domain/ApiContracts/UserDto.cs rename to sample/Atc.SourceGenerators.Mapping.Contract/UserDto.cs index 35712bc..c4e506b 100644 --- a/sample/Atc.SourceGenerators.Mapping.Domain/ApiContracts/UserDto.cs +++ b/sample/Atc.SourceGenerators.Mapping.Contract/UserDto.cs @@ -1,4 +1,4 @@ -namespace Atc.SourceGenerators.Mapping.Domain.ApiContracts; +namespace Atc.SourceGenerators.Mapping.Contract; /// /// Data transfer object for User (used for API responses). diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/ApiContracts/UserStatusDto.cs b/sample/Atc.SourceGenerators.Mapping.Contract/UserStatusDto.cs similarity index 88% rename from sample/Atc.SourceGenerators.Mapping.Domain/ApiContracts/UserStatusDto.cs rename to sample/Atc.SourceGenerators.Mapping.Contract/UserStatusDto.cs index 17dc803..fefaeb0 100644 --- a/sample/Atc.SourceGenerators.Mapping.Domain/ApiContracts/UserStatusDto.cs +++ b/sample/Atc.SourceGenerators.Mapping.Contract/UserStatusDto.cs @@ -1,4 +1,4 @@ -namespace Atc.SourceGenerators.Mapping.Domain.ApiContracts; +namespace Atc.SourceGenerators.Mapping.Contract; /// /// Data transfer object for UserStatus (used for API responses). diff --git a/sample/Atc.SourceGenerators.Mapping.DataAccess/Atc.SourceGenerators.Mapping.DataAccess.csproj b/sample/Atc.SourceGenerators.Mapping.DataAccess/Atc.SourceGenerators.Mapping.DataAccess.csproj index f511120..a6da9ce 100644 --- a/sample/Atc.SourceGenerators.Mapping.DataAccess/Atc.SourceGenerators.Mapping.DataAccess.csproj +++ b/sample/Atc.SourceGenerators.Mapping.DataAccess/Atc.SourceGenerators.Mapping.DataAccess.csproj @@ -4,13 +4,16 @@ net10.0 enable enable - $(NoWarn);CS0436;SA0001;CS1591;IDE0005 + $(NoWarn);CS0436;IDE0005 + false + + + + - - \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping.DataAccess/Entities/AddressEntity.cs b/sample/Atc.SourceGenerators.Mapping.DataAccess/Entities/AddressEntity.cs index 42e55ce..8eab964 100644 --- a/sample/Atc.SourceGenerators.Mapping.DataAccess/Entities/AddressEntity.cs +++ b/sample/Atc.SourceGenerators.Mapping.DataAccess/Entities/AddressEntity.cs @@ -1,10 +1,9 @@ namespace Atc.SourceGenerators.Mapping.DataAccess.Entities; /// -/// Database entity for address (maps to Domain.Address). +/// Database entity for address. /// -[MapTo(typeof(Address))] -public partial class AddressEntity +public class AddressEntity { /// /// Gets or sets the database ID. diff --git a/sample/Atc.SourceGenerators.Mapping.DataAccess/Entities/UserEntity.cs b/sample/Atc.SourceGenerators.Mapping.DataAccess/Entities/UserEntity.cs index d446131..ccb3c63 100644 --- a/sample/Atc.SourceGenerators.Mapping.DataAccess/Entities/UserEntity.cs +++ b/sample/Atc.SourceGenerators.Mapping.DataAccess/Entities/UserEntity.cs @@ -1,10 +1,9 @@ namespace Atc.SourceGenerators.Mapping.DataAccess.Entities; /// -/// Database entity for user (maps to Domain.User). +/// Database entity for user. /// -[MapTo(typeof(User))] -public partial class UserEntity +public class UserEntity { /// /// Gets or sets the database ID (auto-increment). diff --git a/sample/Atc.SourceGenerators.Mapping.DataAccess/GlobalUsings.cs b/sample/Atc.SourceGenerators.Mapping.DataAccess/GlobalUsings.cs index 3e3b781..fc36262 100644 --- a/sample/Atc.SourceGenerators.Mapping.DataAccess/GlobalUsings.cs +++ b/sample/Atc.SourceGenerators.Mapping.DataAccess/GlobalUsings.cs @@ -1,5 +1,2 @@ -global using Atc.Mapping; - -global using Atc.SourceGenerators.Annotations; global using Atc.SourceGenerators.Mapping.DataAccess.Entities; -global using Atc.SourceGenerators.Mapping.Domain; \ No newline at end of file +global using Atc.SourceGenerators.Mapping.DataAccess.Repositories; \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping.DataAccess/Repositories/IUserRepository.cs b/sample/Atc.SourceGenerators.Mapping.DataAccess/Repositories/IUserRepository.cs new file mode 100644 index 0000000..a699eef --- /dev/null +++ b/sample/Atc.SourceGenerators.Mapping.DataAccess/Repositories/IUserRepository.cs @@ -0,0 +1,20 @@ +namespace Atc.SourceGenerators.Mapping.DataAccess.Repositories; + +/// +/// Repository interface for user operations. +/// +public interface IUserRepository +{ + /// + /// Gets a user entity by ID. + /// + /// The user ID. + /// The user entity, or null if not found. + UserEntity? GetById(Guid id); + + /// + /// Gets all user entities. + /// + /// Collection of user entities. + IEnumerable GetAll(); +} \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping.DataAccess/UserRepository.cs b/sample/Atc.SourceGenerators.Mapping.DataAccess/Repositories/UserRepository.cs similarity index 87% rename from sample/Atc.SourceGenerators.Mapping.DataAccess/UserRepository.cs rename to sample/Atc.SourceGenerators.Mapping.DataAccess/Repositories/UserRepository.cs index a115228..1590281 100644 --- a/sample/Atc.SourceGenerators.Mapping.DataAccess/UserRepository.cs +++ b/sample/Atc.SourceGenerators.Mapping.DataAccess/Repositories/UserRepository.cs @@ -1,4 +1,4 @@ -namespace Atc.SourceGenerators.Mapping.DataAccess; +namespace Atc.SourceGenerators.Mapping.DataAccess.Repositories; /// /// In-memory user repository for demo purposes. @@ -62,19 +62,19 @@ public class UserRepository : IUserRepository }; /// - /// Gets a user by ID. + /// Gets a user entity by ID. /// /// The user ID. - /// The user domain model, or null if not found. - public User? GetById(Guid id) + /// The user entity, or null if not found. + public UserEntity? GetById(Guid id) => !users.TryGetValue(id, out var entity) ? null - : entity.MapToUser(); + : entity; /// - /// Gets all users. + /// Gets all user entities. /// - /// Collection of user domain models. - public IEnumerable GetAll() - => users.Values.Select(e => e.MapToUser()); + /// Collection of user entities. + public IEnumerable GetAll() + => users.Values; } \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/Address.cs b/sample/Atc.SourceGenerators.Mapping.Domain/Address.cs index cd7dcc6..134889a 100644 --- a/sample/Atc.SourceGenerators.Mapping.Domain/Address.cs +++ b/sample/Atc.SourceGenerators.Mapping.Domain/Address.cs @@ -4,6 +4,7 @@ namespace Atc.SourceGenerators.Mapping.Domain; /// Represents a physical address. /// [MapTo(typeof(AddressDto))] +[MapTo(typeof(AddressEntity), Bidirectional = true)] public partial class Address { /// diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/Atc.SourceGenerators.Mapping.Domain.csproj b/sample/Atc.SourceGenerators.Mapping.Domain/Atc.SourceGenerators.Mapping.Domain.csproj index 33a5b0e..4332e79 100644 --- a/sample/Atc.SourceGenerators.Mapping.Domain/Atc.SourceGenerators.Mapping.Domain.csproj +++ b/sample/Atc.SourceGenerators.Mapping.Domain/Atc.SourceGenerators.Mapping.Domain.csproj @@ -1,19 +1,26 @@  + + net10.0 + enable + enable + $(NoWarn);CS0436;IDE0005 + false + + + + + + + + + - - - net10.0 - enable - enable - $(NoWarn);CS0436;SA0001;CS1591;IDE0005 - - \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/GlobalUsings.cs b/sample/Atc.SourceGenerators.Mapping.Domain/GlobalUsings.cs index 0604aea..b38e67c 100644 --- a/sample/Atc.SourceGenerators.Mapping.Domain/GlobalUsings.cs +++ b/sample/Atc.SourceGenerators.Mapping.Domain/GlobalUsings.cs @@ -1,3 +1,8 @@ +global using Atc.Mapping; + global using Atc.SourceGenerators.Annotations; -global using Atc.SourceGenerators.Mapping.Domain.ApiContracts; +global using Atc.SourceGenerators.Mapping.Contract; +global using Atc.SourceGenerators.Mapping.DataAccess.Entities; +global using Atc.SourceGenerators.Mapping.DataAccess.Repositories; + global using Microsoft.Extensions.DependencyInjection; \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/IUserRepository.cs b/sample/Atc.SourceGenerators.Mapping.Domain/IUserRepository.cs deleted file mode 100644 index 8d836da..0000000 --- a/sample/Atc.SourceGenerators.Mapping.Domain/IUserRepository.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Atc.SourceGenerators.Mapping.Domain; - -/// -/// Repository interface for user operations. -/// -public interface IUserRepository -{ - /// - /// Gets a user by ID. - /// - /// The user ID. - /// The user domain model, or null if not found. - User? GetById(Guid id); - - /// - /// Gets all users. - /// - /// Collection of user domain models. - IEnumerable GetAll(); -} \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/ServiceCollectionExtensions.cs b/sample/Atc.SourceGenerators.Mapping.Domain/ServiceCollectionExtensions.cs deleted file mode 100644 index 4614908..0000000 --- a/sample/Atc.SourceGenerators.Mapping.Domain/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Atc.SourceGenerators.Mapping.Domain; - -/// -/// Extension methods for service collection registration. -/// -public static class ServiceCollectionExtensions -{ - /// - /// Adds user services to the service collection. - /// Note: You must register an IUserRepository implementation separately. - /// - /// The service collection. - /// The service collection for chaining. - public static IServiceCollection AddUserServices( - this IServiceCollection services) - { - services.AddSingleton(); - return services; - } -} \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/User.cs b/sample/Atc.SourceGenerators.Mapping.Domain/User.cs index 93353e3..402fa59 100644 --- a/sample/Atc.SourceGenerators.Mapping.Domain/User.cs +++ b/sample/Atc.SourceGenerators.Mapping.Domain/User.cs @@ -4,6 +4,7 @@ namespace Atc.SourceGenerators.Mapping.Domain; /// Represents a user in the system. /// [MapTo(typeof(UserDto))] +[MapTo(typeof(UserEntity), Bidirectional = true)] public partial class User { /// diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/UserService.cs b/sample/Atc.SourceGenerators.Mapping.Domain/UserService.cs index 4e8b7fd..f97aa96 100644 --- a/sample/Atc.SourceGenerators.Mapping.Domain/UserService.cs +++ b/sample/Atc.SourceGenerators.Mapping.Domain/UserService.cs @@ -22,12 +22,14 @@ public UserService(IUserRepository repository) /// The user ID. /// The user domain model, or null if not found. public User? GetById(Guid id) - => repository.GetById(id); + => repository.GetById(id)?.MapToUser(); /// /// Gets all users. /// /// Collection of user domain models. public IEnumerable GetAll() - => repository.GetAll(); + => repository + .GetAll() + .Select(e => e.MapToUser()); } \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping.Domain/UserStatus.cs b/sample/Atc.SourceGenerators.Mapping.Domain/UserStatus.cs index e012cf5..9bbe077 100644 --- a/sample/Atc.SourceGenerators.Mapping.Domain/UserStatus.cs +++ b/sample/Atc.SourceGenerators.Mapping.Domain/UserStatus.cs @@ -3,6 +3,8 @@ namespace Atc.SourceGenerators.Mapping.Domain; /// /// Represents the status of a user account. /// +[MapTo(typeof(UserStatusDto))] +[MapTo(typeof(UserStatusEntity), Bidirectional = true)] public enum UserStatus { /// diff --git a/sample/Atc.SourceGenerators.Mapping/Atc.SourceGenerators.Mapping.csproj b/sample/Atc.SourceGenerators.Mapping/Atc.SourceGenerators.Mapping.csproj index ac3e4e1..acbdb34 100644 --- a/sample/Atc.SourceGenerators.Mapping/Atc.SourceGenerators.Mapping.csproj +++ b/sample/Atc.SourceGenerators.Mapping/Atc.SourceGenerators.Mapping.csproj @@ -1,22 +1,37 @@ + + net10.0 + enable + enable + $(NoWarn);CS0436;IDE0005 + + - + - - - net10.0 - enable - enable - $(NoWarn);CS0436;SA0001;CS1591;IDE0005 - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping/GlobalUsings.cs b/sample/Atc.SourceGenerators.Mapping/GlobalUsings.cs index 02b775c..d375cfc 100644 --- a/sample/Atc.SourceGenerators.Mapping/GlobalUsings.cs +++ b/sample/Atc.SourceGenerators.Mapping/GlobalUsings.cs @@ -1,7 +1,7 @@ global using Atc.Mapping; -global using Atc.SourceGenerators.Mapping.DataAccess; +global using Atc.SourceGenerators.Mapping.Contract; +global using Atc.SourceGenerators.Mapping.DataAccess.Repositories; global using Atc.SourceGenerators.Mapping.Domain; -global using Atc.SourceGenerators.Mapping.Domain.ApiContracts; global using Scalar.AspNetCore; \ No newline at end of file diff --git a/sample/Atc.SourceGenerators.Mapping/Program.cs b/sample/Atc.SourceGenerators.Mapping/Program.cs index 9b0deb4..9d89a84 100644 --- a/sample/Atc.SourceGenerators.Mapping/Program.cs +++ b/sample/Atc.SourceGenerators.Mapping/Program.cs @@ -7,7 +7,7 @@ builder.Services.AddSingleton(); // Register domain services -builder.Services.AddUserServices(); +builder.Services.AddSingleton(); // Add OpenAPI builder.Services.AddOpenApi(); @@ -25,7 +25,9 @@ app .MapGet("/", context => { - if (context.RequestServices.GetRequiredService().IsDevelopment()) + if (context.RequestServices + .GetRequiredService() + .IsDevelopment()) { context.Response.Redirect("/scalar/v1"); return Task.CompletedTask; diff --git a/sample/PetStore.Api/Program.cs b/sample/PetStore.Api/Program.cs index 592e623..c80638b 100644 --- a/sample/PetStore.Api/Program.cs +++ b/sample/PetStore.Api/Program.cs @@ -3,7 +3,7 @@ var builder = WebApplication.CreateBuilder(args); -// ✨ Scenario B: Register all services transitively (Domain + DataAccess) +// ✨ Register all services transitively (Domain + DataAccess) // This single call registers: // - PetService from PetStore.Domain // - PetRepository from PetStore.DataAccess (auto-detected as referenced assembly) @@ -34,7 +34,9 @@ app .MapGet("/", context => { - if (context.RequestServices.GetRequiredService().IsDevelopment()) + if (context.RequestServices + .GetRequiredService() + .IsDevelopment()) { context.Response.Redirect("/scalar/v1"); return Task.CompletedTask;