Skip to content

Latest commit

Β 

History

History
390 lines (301 loc) Β· 10.1 KB

File metadata and controls

390 lines (301 loc) Β· 10.1 KB

πŸ”„ Enum Mapping Sample

This sample demonstrates the EnumMappingGenerator in action with realistic enum mapping scenarios including special case handling, bidirectional mappings, and case-insensitive matching.

πŸ“‚ Project Location

sample/Atc.SourceGenerators.EnumMapping/

πŸš€ Running the Sample

cd sample/Atc.SourceGenerators.EnumMapping
dotnet run

Expected output:

=== Atc.SourceGenerators - Enum Mapping Sample ===

1. Testing PetStatusEntity β†’ PetStatusDto mapping:
   - Special case: None β†’ Unknown
   - Bidirectional: true

   None β†’ Unknown
   Available β†’ Available
   Pending β†’ Pending
   Adopted β†’ Adopted

2. Testing PetStatusDto β†’ PetStatusEntity (reverse mapping):
   Unknown β†’ None

3. Testing FeatureState β†’ FeatureFlag mapping:
   - Exact name matching
   - Bidirectional: false

   Active β†’ Active
   Inactive β†’ Inactive
   Testing β†’ Testing

4. Testing case-insensitive matching:
   All enum values match regardless of casing

5. Performance characteristics:
   βœ“ Zero runtime cost - pure switch expressions
   βœ“ Compile-time safety with exhaustive checking
   βœ“ ArgumentOutOfRangeException for unmapped values

=== All tests completed successfully! ===

🎯 What This Sample Demonstrates

1. Special Case Mapping: None β†’ Unknown

PetStatusEntity.cs:

[MapTo(typeof(PetStatusDto), Bidirectional = true)]
public enum PetStatusEntity
{
    None,       // Special case: maps to PetStatusDto.Unknown
    Pending,
    Available,
    Adopted,
}

PetStatusDto.cs:

public enum PetStatusDto
{
    Unknown,    // Special case: maps from PetStatusEntity.None
    Available,
    Pending,
    Adopted,
}

Key Points:

  • None automatically maps to Unknown (common pattern)
  • Bidirectional = true generates both forward and reverse mappings
  • All other values map by exact name match

2. Exact Name Matching

FeatureState.cs:

[MapTo(typeof(FeatureFlag))]
public enum FeatureState
{
    Active,      // Exact match to FeatureFlag.Active
    Inactive,    // Exact match to FeatureFlag.Inactive
    Testing,     // Exact match to FeatureFlag.Testing
}

FeatureFlag.cs:

public enum FeatureFlag
{
    Active,      // Exact match from FeatureState.Active
    Inactive,    // Exact match from FeatureState.Inactive
    Testing,     // Exact match from FeatureState.Testing
}

Key Points:

  • All values match by exact name
  • Unidirectional mapping (Bidirectional = false)

πŸ“ Project Structure

sample/Atc.SourceGenerators.EnumMapping/
β”œβ”€β”€ Atc.SourceGenerators.EnumMapping.csproj
β”œβ”€β”€ GlobalUsings.cs
β”œβ”€β”€ Program.cs
└── Enums/
    β”œβ”€β”€ PetStatusEntity.cs      (Database layer enum)
    β”œβ”€β”€ PetStatusDto.cs         (API layer enum)
    β”œβ”€β”€ FeatureState.cs         (Domain layer enum)
    └── FeatureFlag.cs          (Configuration layer enum)

πŸ” Generated Code

The source generator creates extension methods in the Atc.Mapping namespace:

EnumMappingExtensions.g.cs (simplified):

namespace Atc.Mapping;

public static class EnumMappingExtensions
{
    // Forward mapping: PetStatusEntity β†’ PetStatusDto
    public static PetStatusDto MapToPetStatusDto(this PetStatusEntity source)
    {
        return source switch
        {
            PetStatusEntity.None => PetStatusDto.Unknown,        // Special case
            PetStatusEntity.Pending => PetStatusDto.Pending,
            PetStatusEntity.Available => PetStatusDto.Available,
            PetStatusEntity.Adopted => PetStatusDto.Adopted,
            _ => throw new ArgumentOutOfRangeException(nameof(source), source, "Unmapped enum value"),
        };
    }

    // Reverse mapping: PetStatusDto β†’ PetStatusEntity (Bidirectional = true)
    public static PetStatusEntity MapToPetStatusEntity(this PetStatusDto source)
    {
        return source switch
        {
            PetStatusDto.Unknown => PetStatusEntity.None,        // Special case
            PetStatusDto.Available => PetStatusEntity.Available,
            PetStatusDto.Pending => PetStatusEntity.Pending,
            PetStatusDto.Adopted => PetStatusEntity.Adopted,
            _ => throw new ArgumentOutOfRangeException(nameof(source), source, "Unmapped enum value"),
        };
    }

    // Forward mapping: FeatureState β†’ FeatureFlag
    public static FeatureFlag MapToFeatureFlag(this FeatureState source)
    {
        return source switch
        {
            FeatureState.Active => FeatureFlag.Active,
            FeatureState.Inactive => FeatureFlag.Inactive,
            FeatureState.Testing => FeatureFlag.Testing,
            _ => throw new ArgumentOutOfRangeException(nameof(source), source, "Unmapped enum value"),
        };
    }
}

🎨 Key Features Demonstrated

βœ… Special Case Detection

The generator automatically recognizes common enum naming patterns:

Pattern Equivalent Values Notes
Zero/Null States None ↔ Unknown, Default, NotSet Common default state mapping
Active States Active ↔ Enabled, On, Running Service/feature activation
Inactive States Inactive ↔ Disabled, Off, Stopped Service/feature deactivation
Deletion States Deleted ↔ Removed, Archived Soft delete patterns
Pending States Pending ↔ InProgress, Processing Async operation states
Completion States Completed ↔ Done, Finished Task completion states

Example:

// Database enum
public enum ServiceStatusEntity
{
    None,      // Maps to Unknown in API
    Active,    // Maps to Enabled in API
    Inactive   // Maps to Disabled in API
}

// API enum
public enum ServiceStatus
{
    Unknown,   // Maps from None in database
    Enabled,   // Maps from Active in database
    Disabled   // Maps from Inactive in database
}

Smart matching: The generator uses exact name matching first, then falls back to case-insensitive matching, and finally checks for special case patterns. This ensures predictable behavior while supporting common enum naming variations.

βœ… Bidirectional Mapping

With Bidirectional = true:

[MapTo(typeof(PetStatusDto), Bidirectional = true)]
public enum PetStatusEntity { ... }

You get two methods:

  • PetStatusEntity.MapToPetStatusDto() (forward)
  • PetStatusDto.MapToPetStatusEntity() (reverse)

βœ… Case-Insensitive Matching

Enum values match regardless of casing:

// These all match:
SourceEnum.ACTIVE    β†’ TargetEnum.Active
SourceEnum.active    β†’ TargetEnum.Active
SourceEnum.Active    β†’ TargetEnum.Active
SourceEnum.AcTiVe    β†’ TargetEnum.Active

βœ… Compile-Time Safety

Unmapped values generate warnings:

Warning ATCENUM002: Enum value 'SourceStatus.Deleted' has no matching value in target enum 'TargetStatus'

βœ… Runtime Safety

Unmapped values throw at runtime:

var status = SourceStatus.Deleted;  // Unmapped value
var dto = status.MapToTargetDto();  // Throws ArgumentOutOfRangeException

πŸ’‘ Usage Patterns

Pattern 1: Database β†’ Domain β†’ API

Use Case: Multi-layer architecture with enum separation

// Database layer
[MapTo(typeof(Domain.Status))]
public enum StatusEntity
{
    None,
    Active,
    Inactive,
}

// Domain layer
[MapTo(typeof(Api.StatusDto))]
public enum Status
{
    Unknown,
    Active,
    Inactive,
}

// API layer
public enum StatusDto
{
    Unknown,
    Active,
    Inactive,
}

// Complete chain
var entity = database.GetStatus();           // StatusEntity.None
var domain = entity.MapToStatus();          // Status.Unknown
var dto = domain.MapToStatusDto();          // StatusDto.Unknown

Pattern 2: Configuration β†’ Domain

Use Case: Feature flags with consistent naming

// Configuration (appsettings.json representation)
[MapTo(typeof(FeatureState))]
public enum FeatureFlag
{
    Active,
    Inactive,
}

// Domain (business logic representation)
public enum FeatureState
{
    Active,      // ← FeatureFlag.Active (exact match)
    Inactive,    // ← FeatureFlag.Inactive (exact match)
}

// Usage
var config = configuration.GetValue<FeatureFlag>("MyFeature");
var state = config.MapToFeatureState();

if (state == FeatureState.Active)
{
    // Feature is active
}

Pattern 3: External API β†’ Internal Domain

Use Case: Third-party API integration with case normalization

// External API enum (from SDK)
[MapTo(typeof(InternalStatus))]
public enum ExternalStatus
{
    NONE,
    PENDING,
    ACTIVE,
}

// Internal domain enum (your naming)
public enum InternalStatus
{
    Unknown,     // ← ExternalStatus.NONE (special case: None β†’ Unknown)
    Pending,     // ← ExternalStatus.PENDING (case-insensitive)
    Active,      // ← ExternalStatus.ACTIVE (case-insensitive)
}

// Usage
var external = apiClient.GetStatus();           // ExternalStatus.NONE
var internal = external.MapToInternalStatus();  // InternalStatus.Unknown

πŸ—οΈ Real-World Example: PetStore

This pattern is used in the PetStore sample to separate enum concerns across layers:

PetStatusEntity (DataAccess)
    ↓ MapToPetStatus()
PetStatus (Domain)
    ↓ MapToPetStatus() [different types, same method name]
PetStatus (Api.Contract)

Each layer has its own enum definition, and mappings are generated automatically.

πŸ“ Notes

  • No Reflection: All mappings use pure switch expressions for maximum performance
  • AOT Compatible: Works with Native AOT compilation
  • Type Safe: Compiler errors if target types are incorrect
  • IntelliSense Support: Generated methods appear in IDE autocomplete

πŸ“– Related Samples


Need more examples? Check the EnumMapping Generator documentation for comprehensive guides and patterns.