Table of Contents

Request Mapper

The request mapper translates the public-facing request model into the provider-specific API contract. Implement IApiRequestMapper<TRequest, TApiRequest> with a single Map method.

[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by DI container")]
internal sealed class AddressValidationRequestMapper : IApiRequestMapper<MyAddressValidationRequest, ApiRequest>
{
    public ApiRequest Map(MyAddressValidationRequest request)
    {
        return new ApiRequest
        {
            Address = new ApiRequest.AddressPayload
            {
                Lines = [.. request.AddressLines],
                City = request.CityOrTown,
                State = request.StateOrProvince,
                PostalCode = request.PostalCode,
                CountryCode = request.Country?.Value,
            },
        };
    }
}
Note

It is not necessary for the request mapper to be internal, but it is strongly recommended if redistributing as a library.

Response Mapper

The response mapper converts the provider's raw API response into the unified IAddressValidationResponse returned to callers. Implement IApiResponseMapper<TApiResponse> with a single Map method.

[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by DI container")]
internal sealed class AddressValidationResponseMapper : IApiResponseMapper<ApiResponse>
{
    public IAddressValidationResponse Map(ApiResponse response, IValidationResult? validationResult = null)
    {
        if ( response.Result is null )
        {
            return new EmptyAddressValidationResponse(validationResult);
        }

        return new AddressValidationResponse(response, validationResult)
        {
            AddressLines = response.Result.AddressLines is not null
                               ? response.Result.AddressLines.ToFrozenSet(StringComparer.OrdinalIgnoreCase)
                               : FrozenSet<string>.Empty,
            CityOrTown = response.Result.City,
            StateOrProvince = response.Result.State,
            PostalCode = response.Result.PostalCode,
            CustomResponseData = response.GetCustomResponseData(),
        };
    }
}
Note

GetCustomResponseData() is generated at compile time by the source generator bundled in VisusIO.AddressValidation for every contract type that has at least one [CustomResponseDataProperty]-decorated property. It returns IReadOnlyDictionary<string, object?>. See Data Models for how to annotate contract properties.

When custom data is spread across multiple nested contract types, merge the dictionaries before assigning:

Dictionary<string, object?> customResponseData = new(StringComparer.OrdinalIgnoreCase);
customResponseData.Merge(response.GetCustomResponseData());
customResponseData.Merge(response.Result.GetCustomResponseData());

return new AddressValidationResponse(response, validationResult)
{
    // ...
    CustomResponseData = customResponseData.AsReadOnly(),
};
Note

The concrete return type — AddressValidationResponse in the example above — should extend AbstractAddressValidationResponse<TResponse> where TResponse is the provider DTO (ApiResponse). The base class automatically populates the Errors and Warnings collections from the validationResult.

internal sealed class AddressValidationResponse : AbstractAddressValidationResponse<ApiResponse>
{
    public AddressValidationResponse(ApiResponse response, IValidationResult? validationResult = null)
        : base(response, validationResult)
    {
    }
}
Note

It is not necessary for the response mapper to be internal, but it is strongly recommended if redistributing as a library.