Table of Contents

Adapter Pattern API Reference

Complete API documentation for the Adapter pattern in PatternKit.

Namespace

using PatternKit.Structural.Adapter;

Adapter<TIn, TOut>

Fluent adapter that maps input to output with ordered transformations and validation.

public sealed class Adapter<TIn, TOut>

Type Parameters

Parameter Description
TIn Input type to adapt from
TOut Output type to adapt to

Delegates

public delegate TOut Seed();
public delegate TOut SeedFrom(in TIn input);
public delegate void MapStep(in TIn input, TOut output);
public delegate string? Validator(in TIn input, TOut output);
Delegate Description
Seed Creates destination without input
SeedFrom Creates destination using input
MapStep Transforms source data into destination
Validator Returns error message or null if valid

Static Methods

Method Returns Description
Create(Seed seed) Builder Create builder with parameterless seed
Create(SeedFrom seedFrom) Builder Create builder with input-aware seed

Instance Methods

Method Returns Description
Adapt(in TIn input) TOut Adapt input, throws on validation failure
TryAdapt(in TIn input, out TOut output, out string? error) bool Safe adaptation, returns false on failure

Exceptions

Method Exception Condition
Adapt InvalidOperationException First validator returns non-null error

Example

var adapter = Adapter<Source, Dest>
    .Create(static () => new Dest())
    .Map(static (in Source s, Dest d) => d.Name = s.Name)
    .Require(static (in Source _, Dest d) =>
        string.IsNullOrEmpty(d.Name) ? "Name required" : null)
    .Build();

var result = adapter.Adapt(source);

Adapter<TIn, TOut>.Builder

Builder for configuring the adapter.

public sealed class Builder

Methods

Method Returns Description
Map(MapStep step) Builder Append mapping step
Require(Validator rule) Builder Append validator
Build() Adapter<TIn, TOut> Build immutable adapter

Semantics

  • Mapping order preserved: Steps run in registration order
  • Validation order preserved: Validators run after all mapping; first error wins
  • Build captures state: Further modifications don't affect built adapters

AsyncAdapter<TIn, TOut>

Async adapter for I/O-bound transformations.

public sealed class AsyncAdapter<TIn, TOut>

Delegates

public delegate TOut Seed();
public delegate TOut SeedFrom(in TIn input);
public delegate ValueTask MapStep(TIn input, TOut output, CancellationToken ct);
public delegate ValueTask<string?> Validator(TIn input, TOut output, CancellationToken ct);

Static Methods

Method Returns Description
Create(Seed seed) Builder Create builder with parameterless seed
Create(SeedFrom seedFrom) Builder Create builder with input-aware seed

Instance Methods

Method Returns Description
AdaptAsync(TIn input, CancellationToken ct) ValueTask<TOut> Async adapt, throws on failure
TryAdaptAsync(TIn input, CancellationToken ct) ValueTask<(bool, TOut?, string?)> Safe async adaptation

Example

var adapter = AsyncAdapter<Request, Response>
    .Create(static () => new Response())
    .Map(async static (Request r, Response resp, CancellationToken ct) =>
    {
        resp.Data = await service.GetAsync(r.Id, ct);
    })
    .Build();

var result = await adapter.AdaptAsync(request, cancellationToken);

AsyncAdapter<TIn, TOut>.Builder

Builder for async adapter.

public sealed class Builder

Methods

Method Returns Description
Map(MapStep step) Builder Append async mapping step
Require(Validator rule) Builder Append async validator
Build() AsyncAdapter<TIn, TOut> Build immutable adapter

Execution Flow

Adapt Flow

sequenceDiagram
    participant C as Caller
    participant A as Adapter
    participant S as Seed
    participant M as Map Steps
    participant V as Validators

    C->>A: Adapt(input)
    A->>S: Create destination
    S-->>A: TOut instance
    loop Each map step
        A->>M: MapStep(input, output)
    end
    loop Each validator
        A->>V: Validate(input, output)
        alt Returns error
            V-->>A: Error message
            A-->>C: Throw InvalidOperationException
        end
    end
    A-->>C: Return output

TryAdapt Flow

sequenceDiagram
    participant C as Caller
    participant A as Adapter
    participant S as Seed
    participant M as Map Steps
    participant V as Validators

    C->>A: TryAdapt(input, out output, out error)
    A->>S: Create destination
    S-->>A: TOut instance
    loop Each map step
        A->>M: MapStep(input, output)
    end
    loop Each validator
        A->>V: Validate(input, output)
        alt Returns error
            V-->>A: Error message
            A-->>C: false, default, error
        end
    end
    A-->>C: true, output, null

Thread Safety

Component Thread-Safe
Builder No - single-threaded configuration
Adapter<TIn, TOut> Yes - immutable after build
AsyncAdapter<TIn, TOut> Yes - immutable after build
Adapt / AdaptAsync Yes - no shared state

Implementation Notes

  • Arrays of delegates compiled at Build() time
  • in TIn parameters avoid struct copies
  • No LINQ, reflection, or allocations in adapt path
  • Static lambdas recommended to avoid closure allocations

Complete Example

using PatternKit.Structural.Adapter;

// Define types
public record UserInput(string Email, string FirstName, string LastName, int Age);

public class CreateUserCommand
{
    public string Email { get; set; } = "";
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string FullName { get; set; } = "";
    public int Age { get; set; }
    public bool IsAdult { get; set; }
}

// Create adapter
public class UserInputAdapter
{
    private readonly Adapter<UserInput, CreateUserCommand> _adapter;

    public UserInputAdapter()
    {
        _adapter = Adapter<UserInput, CreateUserCommand>
            .Create(static () => new CreateUserCommand())
            .Map(static (in UserInput u, CreateUserCommand c) =>
                c.Email = u.Email?.Trim().ToLowerInvariant() ?? "")
            .Map(static (in UserInput u, CreateUserCommand c) =>
                c.FirstName = u.FirstName?.Trim() ?? "")
            .Map(static (in UserInput u, CreateUserCommand c) =>
                c.LastName = u.LastName?.Trim() ?? "")
            .Map(static (in UserInput u, CreateUserCommand c) =>
                c.FullName = $"{c.FirstName} {c.LastName}".Trim())
            .Map(static (in UserInput u, CreateUserCommand c) =>
                c.Age = u.Age)
            .Map(static (in UserInput u, CreateUserCommand c) =>
                c.IsAdult = u.Age >= 18)
            .Require(static (in UserInput _, CreateUserCommand c) =>
                IsValidEmail(c.Email) ? null : "Invalid email format")
            .Require(static (in UserInput _, CreateUserCommand c) =>
                string.IsNullOrEmpty(c.FirstName) ? "First name required" : null)
            .Require(static (in UserInput _, CreateUserCommand c) =>
                string.IsNullOrEmpty(c.LastName) ? "Last name required" : null)
            .Require(static (in UserInput _, CreateUserCommand c) =>
                c.Age is < 0 or > 150 ? "Invalid age" : null)
            .Build();
    }

    public CreateUserCommand Adapt(UserInput input) =>
        _adapter.Adapt(input);

    public bool TryAdapt(UserInput input, out CreateUserCommand? command, out string? error) =>
        _adapter.TryAdapt(input, out command, out error);

    private static bool IsValidEmail(string email) =>
        !string.IsNullOrEmpty(email) && email.Contains('@');
}

// Usage
var adapter = new UserInputAdapter();

// Success case
var command = adapter.Adapt(new UserInput("user@example.com", "John", "Doe", 25));

// Error handling
if (!adapter.TryAdapt(new UserInput("invalid", "", "Doe", 25), out var cmd, out var error))
{
    Console.WriteLine($"Validation failed: {error}");
}

See Also