Address Validation US 4 C# Rest Code Snippet

using System;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;

namespace address_validation_us_4_dot_net.REST
{
    /// <summary>
    /// Provides functionality to call the ServiceObjects AV4 REST API's ValidateAddress endpoint.
    /// Retrieves parsed and validated address elements including Delivery Point Validation (DPV),
    /// Residential Delivery Indicator (RDI), and Suite data with fallback to a backup endpoint for reliability in live mode.
    /// </summary>
    public class ValidateAddressClient
    {
        private const string LiveBaseUrl = "https://strial.serviceobjects.com/AV4/";
        private const string BackupBaseUrl = "https://strialbackup.serviceobjects.com/AV4/";
        private const string TrialBaseUrl = "https://trial.serviceobjects.com/AV4/";

        /// <summary>
        /// Synchronously calls the ValidateAddress REST endpoint to retrieve validated address information.
        /// Validates input parameters and returns an AV4Response with an Error object if validation fails.
        /// Attempts the primary endpoint first, falling back to the backup if the response is invalid (Error.Status == "500") in live mode.
        /// </summary>
        /// <param name="input">The input parameters including address components, mode, and authentication details.</param>
        /// <returns>Deserialized <see cref="AV4Response"/> containing validated address details or an error if validation fails.</returns>
        public static AV4ResponseWrapper Invoke(ValidateAddressInput input)
        {

            // Build URL with query string parameters to avoid issues with missing/optional fields
            string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            string jsonResponse = Helper.HttpGet(url, input.TimeoutSeconds);
            bool IsValid = true;
            AV4ResponseWrapper response = new();
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };
            if (jsonResponse.Replace(" ", "").Contains("\"status\":4"))
            {
                response.ProblemDetails = JsonSerializer.Deserialize<ProblemDetails>(jsonResponse, options);
            }
            else if (jsonResponse.Replace(" ", "").Contains("\"status\":5"))
            {
                IsValid = false;
            }
            else
            {
                response.ValidateAddressResponse = JsonSerializer.Deserialize<ValidateAddressResponse>(jsonResponse, options);
            }
            // Fallback on error in live mode 
            if (input.IsLive && !IsValid)
            {
                string fallbackUrl = BuildUrl(input, BackupBaseUrl);
                string fallbackJsonResponse = Helper.HttpGet(fallbackUrl, input.TimeoutSeconds);
                response = new();
                if (fallbackJsonResponse.Replace(" ", "").Contains("\"status\":4") || fallbackJsonResponse.Replace(" ", "").Contains("\"status\":5"))
                {
                    response.ProblemDetails = JsonSerializer.Deserialize<ProblemDetails>(jsonResponse, options);
                }
                else
                {
                    response.ValidateAddressResponse = JsonSerializer.Deserialize<ValidateAddressResponse>(jsonResponse, options);
                }
                return response;
            }
            return response;
        }

        /// <summary>
        /// Asynchronously calls the ValidateAddress REST endpoint to retrieve validated address information.
        /// Validates input parameters and returns an AV4Response with an Error object if validation fails.
        /// Attempts the primary endpoint first, falling back to the backup if the response is invalid (Error.Status == "500") in live mode.
        /// </summary>
        /// <param name="input">The input parameters including address components, mode, and authentication details.</param>
        /// <returns>Deserialized <see cref="AV4Response"/> containing validated address details or an error if validation fails.</returns>
        public static async Task<AV4ResponseWrapper> InvokeAsync(ValidateAddressInput input)
        {

            // Build URL with query string parameters to avoid issues with missing/optional fields
            string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            string jsonResponse = await Helper.HttpGetAsync(url, input.TimeoutSeconds).ConfigureAwait(false);

            bool IsValid = true;
            AV4ResponseWrapper response = new();
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };
            if (jsonResponse.Replace(" ", "").Contains("\"status\":4"))
            {
                response.ProblemDetails = JsonSerializer.Deserialize<ProblemDetails>(jsonResponse, options);
            }
            else if (jsonResponse.Replace(" ", "").Contains("\"status\":5"))
            {
                IsValid = false;
            }
            else
            {
                response.ValidateAddressResponse = JsonSerializer.Deserialize<ValidateAddressResponse>(jsonResponse, options);
            }

            if (input.IsLive && !IsValid)
            {
                string fallbackUrl = BuildUrl(input, BackupBaseUrl);
                string fallbackJsonResponse = await Helper.HttpGetAsync(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false);
                response = new();
                if (fallbackJsonResponse.Replace(" ", "").Contains("\"status\":4") || fallbackJsonResponse.Replace(" ", "").Contains("\"status\":5"))
                {
                    response.ProblemDetails = JsonSerializer.Deserialize<ProblemDetails>(jsonResponse, options);
                }
                else
                {
                    response.ValidateAddressResponse = JsonSerializer.Deserialize<ValidateAddressResponse>(jsonResponse, options);
                }
                return response;
            }

            return response;
        }

        /// <summary>
        /// Builds the full request URL with URL-encoded query string parameters.
        /// </summary>
        /// <param name="input">The input parameters to include in the query string.</param>
        /// <param name="baseUrl">The base URL (live, backup, or trial).</param>
        /// <returns>The complete URL with query string.</returns>
        public static string BuildUrl(ValidateAddressInput input, string baseUrl)
        {
            string qs = $"ValidateAddress?" +
                     $"Mode={Helper.UrlEncode(input.Mode)}" +
                     $"&Address1={Helper.UrlEncode(input.Address1)}" +
                     $"&Address2={Helper.UrlEncode(input.Address2)}" +
                     $"&City={Helper.UrlEncode(input.City)}" +
                     $"&State={Helper.UrlEncode(input.State)}" +
                     $"&ZIP={Helper.UrlEncode(input.ZIP)}" +
                     $"&BusinessName={Helper.UrlEncode(input.BusinessName)}" +
                     $"&FirstName={Helper.UrlEncode(input.FirstName)}" +
                     $"&MiddleName={Helper.UrlEncode(input.MiddleName)}" +
                     $"&LastName={Helper.UrlEncode(input.LastName)}" +
                     $"&PhoneNumber={Helper.UrlEncode(input.PhoneNumber)}" +
                     $"&Options={Helper.UrlEncode(input.Options)}" +
                     $"&AuthID={Helper.UrlEncode(input.AuthID)}";
            return baseUrl + qs;
        }

        /// <summary>
        /// Input parameters for the ValidateAddress operation, which validates an address against a CASS-approved engine
        /// and supplemental data sources depending on the mode.
        /// - Mode 1 uses standard USPS datasets.
        /// - Mode 2 includes aggregated non-USPS datasets.
        /// - Mode 3 enhances validation with name and phone-assisted nudging using proprietary data.
        /// Returns parsed and validated address elements including Delivery Point Validation (DPV), Residential Delivery Indicator (RDI),
        /// and Suite data. In cases of ambiguity, multiple address matches may be returned.
        /// </summary>
        /// <param name="Mode">Required. Values are "1", "2", or "3". Specifies the validation mode.</param>
        /// <param name="Address1">Required. The primary address line or single-line address.</param>
        /// <param name="Address2">Optional. The secondary address line (e.g., suite or apartment number).</param>
        /// <param name="City">The city of the address. Required if ZIP is not provided.</param>
        /// <param name="State">The state of the address. Required if ZIP is not provided.</param>
        /// <param name="ZIP">The ZIP code of the address. Required if City and State are not provided.</param>
        /// <param name="BusinessName">Optional. Company name for business addresses, may append SuiteLink data.</param>
        /// <param name="FirstName">Optional. First name of the contact.</param>
        /// <param name="MiddleName">Optional. Middle name of the contact.</param>
        /// <param name="LastName">Optional. Last name of the contact.</param>
        /// <param name="PhoneNumber">Optional. Phone number for the contact, enables features like PhoneLink.</param>
        /// <param name="Options">Optional. Reserved for future use.</param>
        /// <param name="AuthID">Required. Authentication ID provided by Service Objects.</param>
        /// <param name="IsLive">Optional. True for live service, false for trial service. Defaults to true.</param>
        /// <param name="TimeoutSeconds">Optional. Timeout for the HTTP request in seconds. Defaults to 15.</param>
        public record ValidateAddressInput(
            string Mode = "",
            string Address1 = "",
            string Address2 = "",
            string City = "",
            string State = "",
            string ZIP = "",
            string BusinessName = "",
            string FirstName = "",
            string MiddleName = "",
            string LastName = "",
            string PhoneNumber = "",
            string Options = "",
            string AuthID = "",
            bool IsLive = true,
            int TimeoutSeconds = 15
        );
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace address_validation_us_4_dot_net.REST
{
    public class AV4ResponseWrapper
    {
        public ValidateAddressResponse? ValidateAddressResponse;
        public ProblemDetails? ProblemDetails;
        public AV4ResponseWrapper() { }
    }
}


using System.Text.Json;
using System.Web;

namespace address_validation_us_4_dot_net.REST
{
    public static class Helper
    {
        private static readonly HttpClient _client = new HttpClient();

        public static async Task<string> HttpGetAsync(string url, int timeoutSeconds)
        {
           
            _client.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
            HttpResponseMessage response = await _client.GetAsync(url).ConfigureAwait(false);
            string jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            return jsonResponse;
        }

        public static string HttpGet(string url, int timeoutSeconds)
        {
            _client.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
            HttpResponseMessage response = _client.GetAsync(url).GetAwaiter().GetResult();
            string jsonResponse = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
            return jsonResponse;
        }

        public static string UrlEncode(string value) => HttpUtility.UrlEncode(value ?? string.Empty);
    }
}

Address Validation US 4 Python Code Snippet

import requests
import json
from av4_response import AV4Response, AddressInfo, ParsedInputInfo, ProblemDetails

# Endpoint URLs for Address Validation US 4 ValidateAddress REST API
primary_url = 'https://strial.serviceobjects.com/AV4/ValidateAddress?'
backup_url = 'https://strialbackup.serviceobjects.com/AV4/ValidateAddress?'
trial_url = 'https://trial.serviceobjects.com/AV4/ValidateAddress?'

def validate_address(
    mode: str,
    address1: str,
    address2: str = "",
    city: str = "",
    state: str = "",
    zip_code: str = "",
    business_name: str = "",
    first_name: str = "",
    middle_name: str = "",
    last_name: str = "",
    phone_number: str = "",
    options: str = "",
    auth_id: str = "",
    is_live: bool = True,
    timeout_seconds: int = 15
) -> AV4Response:
    """
    Calls the Address Validation US 4 ValidateAddress API to retrieve parsed and validated address elements,
    including Delivery Point Validation (DPV), Residential Delivery Indicator (RDI), and Suite data.
    Validates input parameters and returns a ProblemDetails response if invalid. Uses a backup endpoint for reliability in live mode.

    Args:
        mode (str): Required. Values are "1", "2", or "3". Specifies the validation mode (1: USPS datasets, 2: non-USPS datasets, 3: name/phone-assisted).
        address1 (str): Required. The primary address line or single-line address.
        address2 (str, optional): Optional. The secondary address line (e.g., suite or apartment number).
        city (str, optional): The city of the address. Required if ZIP is not provided.
        state (str, optional): The state of the address. Required if ZIP is not provided.
        zip_code (str, optional): The ZIP code of the address. Required if City and State are not provided.
        business_name (str, optional): Optional. Company name for business addresses, may append SuiteLink data.
        first_name (str, optional):Optional. First name of the contact, used in Mode 3.
        middle_name (str, optional): Optional. Middle name of the contact, used in Mode 3.
        last_name (str, optional): Optional. Last name of the contact, used in Mode 3.
        phone_number (str, optional): Optional. Phone number for the contact, enables features like PhoneLink.
        options (str, optional): Optional. Reserved for future use.
        auth_id (str): Required. Required. Authentication ID provided by Service Objects.
        is_live (bool, optional): Option to use live service (true) or trial service (false).
        timeout_seconds (int, optional): Timeout in seconds for the HTTP request. Defaults to 15.

    Returns:
        AV4Response: Parsed JSON response with validated address details or a ProblemDetails if validation fails or the API call fails.
    """

    params = {
        'Mode': mode,
        'Address1': address1,
        'Address2': address2,
        'City': city,
        'State': state,
        'ZIP': zip_code,
        'BusinessName': business_name,
        'FirstName': first_name,
        'MiddleName': middle_name,
        'LastName': last_name,
        'PhoneNumber': phone_number,
        'Options': options,
        'AuthID': auth_id
    }

    url = primary_url if is_live else trial_url

    try:
        response = requests.get(url, params=params, timeout=timeout_seconds)
        response.raise_for_status()
        data = response.json()

        # If API returned an error in JSON payload, trigger fallback
        problem_details = data.get('ProblemDetails')
        if problem_details:
            if is_live:
                # Try backup URL
                response = requests.get(backup_url, params=params, timeout=timeout_seconds)
                response.raise_for_status()
                data = response.json()
                problem_details = data.get('ProblemDetails')
                if problem_details:
                    return AV4Response(
                        Status=None,
                        Addresses=None,
                        ParsedInput=None,
                        ProblemDetails=ProblemDetails(**problem_details)
                    )
            else:
                # Trial mode error is terminal
                return AV4Response(
                    Status=None,
                    Addresses=None,
                    ParsedInput=None,
                    ProblemDetails=ProblemDetails(**problem_details)
                )

        addresses = [AddressInfo(**addr) for addr in data.get('addresses', [])] if data.get('addresses') else None
        parsed_input = ParsedInputInfo(**data.get('parsedInput', {})) if data.get('parsedInput') else None

        return AV4Response(
            Status=data.get('status'),
            Addresses=addresses,
            ParsedInput=parsed_input,
            ProblemDetails=None
        )

    except requests.RequestException as req_exc:
        # Network or HTTP-level error occurred
        if is_live:
            try:
                # Fallback to backup URL
                response = requests.get(backup_url, params=params, timeout=timeout_seconds)
                response.raise_for_status()
                data = response.json()
                problem_details = data.get('ProblemDetails')
                if problem_details:
                    return AV4Response(
                        Status=None,
                        Addresses=None,
                        ParsedInput=None,
                        ProblemDetails=ProblemDetails(**problem_details)
                    )

                addresses = [AddressInfo(**addr) for addr in data.get('addresses', [])] if data.get('addresses') else None
                parsed_input = ParsedInputInfo(**data.get('parsedInput', {})) if data.get('parsedInput') else None

                return AV4Response(
                    Status=data.get('status'),
                    Addresses=addresses,
                    ParsedInput=parsed_input,
                    ProblemDetails=None
                )
            except Exception as backup_exc:
                data = json.loads(backup_exc.response.content)
                problem_details = ProblemDetails(
                    Type=data["type"],
                    Title=data["title"],
                    Status=data["status"]
                )
                return AV4Response(
                    Status=None,
                    Addresses=None,
                    ParsedInput=None,
                    ProblemDetails=problem_details
                )
        else:
            data = json.loads(req_exc.response.content)
            problem_details = ProblemDetails(
                Type=data["type"],
                Title=data["title"],
                Status=data["status"]
            )
            return AV4Response(
                Status=None,
                Addresses=None,
                ParsedInput=None,
                ProblemDetails=problem_details
            )




from dataclasses import dataclass
from typing import Optional, List

@dataclass
class AddressInfo:
    rating: Optional[str] = None
    validationType: Optional[str] = None
    validationTypeValue: Optional[int] = None
    address: Optional[str] = None
    addressExtra: Optional[str] = None
    city: Optional[str] = None
    state: Optional[str] = None
    zip: Optional[str] = None
    countyName: Optional[str] = None
    primaryNumber: Optional[str] = None
    preDirectional: Optional[str] = None
    postDirectional: Optional[str] = None
    streetName: Optional[str] = None
    streetSuffix: Optional[str] = None
    secondaryType: Optional[str] = None
    secondaryNumber: Optional[str] = None
    pmbType: Optional[str] = None
    pmbNumber: Optional[str] = None
    barcodeDigits: Optional[str] = None
    carrierRoute: Optional[str] = None
    congressCode: Optional[str] = None
    addressNotes: Optional[List[str]] = None

@dataclass
class ParsedInputInfo:
    address: Optional[str] = None
    addressExtra: Optional[str] = None
    city: Optional[str] = None
    state: Optional[str] = None
    zip: Optional[str] = None
    primaryNumber: Optional[str] = None
    preDirectional: Optional[str] = None
    postDirectional: Optional[str] = None
    streetName: Optional[str] = None
    streetSuffix: Optional[str] = None
    secondaryType: Optional[str] = None
    secondaryNumber: Optional[str] = None
    phoneNumber: Optional[str] = None
    firstName: Optional[str] = None
    middleName: Optional[str] = None
    lastName: Optional[str] = None

@dataclass
class ProblemDetails:
    title: Optional[str] = None
    status: Optional[str] = None
    detail: Optional[str] = None

@dataclass
class AV4Response:
    Status: Optional[str] = None
    Addresses: Optional[List[AddressInfo]] = None
    ParsedInput: Optional[ParsedInputInfo] = None
    ProblemDetails: Optional[ProblemDetails] = None

Address Validation US 4 NodeJS Code Snippet

import axios from 'axios';
import querystring from 'querystring';
import { AV4Response } from './av4_response.js';

/**
 * @constant
 * @type {string}
 * @description The base URL for the live ServiceObjects Address Validation US 4 API service.
 */
const LiveBaseUrl = 'https://sws.serviceobjects.com/AV4/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the backup ServiceObjects Address Validation US 4 API service.
 */
const BackupBaseUrl = 'https://swsbackup.serviceobjects.com/AV4/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the trial ServiceObjects Address Validation US 4 API service.
 */
const TrialBaseUrl = 'https://trial.serviceobjects.com/AV4/';

/**
 * Checks if a response from the API is valid by verifying that it either has no Error object
 * or the Error object does not have Status "500" (server error).
 * @param {AV4Response} response - The API response object to validate.
 * @returns {boolean} True if the response is valid, false otherwise.
 */
const isValid = (response) => !response?.ProblemDetails || response.ProblemDetails.status !== 500;

/**
 * Constructs a full URL for the ValidateAddress API endpoint by combining the base URL
 * with URL-encoded query parameters derived from the input parameters.
 * @param {Object} params - An object containing all the input parameters.
 * @param {string} baseUrl - The base URL for the API service (live, backup, or trial).
 * @returns {string} The constructed URL with query parameters.
 */
const buildUrl = (params, baseUrl) =>
    `${baseUrl}ValidateAddress?${querystring.stringify(params)}`;

/**
 * Performs an HTTP GET request to the specified URL with a given timeout.
 * @param {string} url - The URL to send the GET request to.
 * @param {number} timeoutSeconds - The timeout duration in seconds for the request.
 * @returns {Promise<AV4Response>} A promise that resolves to an AV4Response object containing the API response data.
 * @throws {Error} If the HTTP request fails, with a message detailing the error.
 */
export const httpGet = async (url, timeoutSeconds) => {
    let result = new AV4Response();
    try {
        const response = await axios.get(url, { timeout: timeoutSeconds * 1000 });

        if (response.status === 200) {
            result.Status = response.data.status ?? null;
            result.Addresses = response.data.addresses ?? null;
            result.ParsedInput = response.data.parsedInput ?? null;
            result.ProblemDetails = null;
        } else {
            result.ProblemDetails = {
                Title: "Service Objects Error",
                Status: response.status,
                Detail: response.statusText
            };
            result.Status = null;
            result.Addresses = null;
            result.ParsedInput = null;
        }
    } catch (error) {
        result.ProblemDetails = {
            type: error.response.data.type,
            title: error.response.data.title,
            status: error.response.data.status,
            detail: error.response.data.detail
        };
        result.Status = null;
        result.Addresses = null;
        result.ParsedInput = null;
    }
    return result;
};

/**
 * Provides functionality to call the ServiceObjects Address Validation US 4 API's ValidateAddress endpoint,
 * retrieving parsed and validated address elements including Delivery Point Validation (DPV),
 * Residential Delivery Indicator (RDI), and Suite data with fallback to a backup endpoint for reliability in live mode.
 */
const ValidateAddressClient = {
    /**
     * Asynchronously invokes the ValidateAddress API endpoint, validating input parameters
     * and attempting the primary endpoint first, falling back to the backup if the response
     * is invalid (Error.TypeCode == '422') in live mode.
     * @param {string} Mode - Required. Values are "1", "2", or "3". Specifies the validation mode (1: USPS datasets, 2: non-USPS datasets, 3: name/phone-assisted).
     * @param {string} Address1 - Required. The primary address line or single-line address.
     * @param {string} [Address2] - Optional. The secondary address line (e.g., suite or apartment number).
     * @param {string} [City] - The city of the address. Required if ZIP is not provided.
     * @param {string} [State] - The state of the address. Required if ZIP is not provided.
     * @param {string} [ZIP] - The ZIP code of the address. Required if City and State are not provided.
     * @param {string} [BusinessName] - Optional. Company name for business addresses, may append SuiteLink data.
     * @param {string} [FirstName] - Optional. First name of the contact, used in Mode 3.
     * @param {string} [MiddleName] - Optional. Middle name of the contact, used in Mode 3.
     * @param {string} [LastName] - Optional. Last name of the contact, used in Mode 3.
     * @param {string} [PhoneNumber] - Optional. Phone number for the contact, enables features like PhoneLink.
     * @param {string} [Options] - Optional. Reserved for future use.
     * @param {string} AuthID - Required. Authentication ID provided by Service Objects.
     * @param {boolean} [isLive=true] - Option to use live service (true) or trial service (false).
     * @param {number} [timeoutSeconds=15] - Timeout in seconds for the HTTP request.
     * @returns {Promise<AV4Response>} A promise that resolves to an AV4Response object with validated address details or an error.
     */
    async invokeAsync(Mode, Address1, Address2, City, State, ZIP,BusinessName, FirstName, MiddleName, LastName,
        PhoneNumber, Options, AuthID, isLive = true, timeoutSeconds = 15) {
        const params = {
            Mode,Address1, Address2, City, State,ZIP, BusinessName, FirstName, MiddleName, LastName,
            PhoneNumber, Options, AuthID
        };

        const url = buildUrl(params, isLive ? LiveBaseUrl : TrialBaseUrl);
        let response = await httpGet(url, timeoutSeconds);
        
        if (isLive && !isValid(response)) {
            const fallbackUrl = buildUrl(params, BackupBaseUrl);
            const fallbackResponse = await httpGet(fallbackUrl, timeoutSeconds);
            return fallbackResponse;
        }

        return response;
    },

    /**
     * Synchronously invokes the ValidateAddress API endpoint by wrapping the async call
     * and awaiting its result immediately. Note: This method should be used cautiously
     * in Node.js as it blocks the event loop.
     * @param {string} Mode - Required. Values are "1", "2", or "3". Specifies the validation mode (1: USPS datasets, 2: non-USPS datasets, 3: name/phone-assisted).
     * @param {string} Address1 - Required. The primary address line or single-line address.
     * @param {string} [Address2] - Optional. The secondary address line (e.g., suite or apartment number).
     * @param {string} [City] - The city of the address. Required if ZIP is not provided.
     * @param {string} [State] - The state of the address. Required if ZIP is not provided.
     * @param {string} [ZIP] - The ZIP code of the address. Required if City and State are not provided.
     * @param {string} [BusinessName] - Optional. Company name for business addresses, may append SuiteLink data.
     * @param {string} [FirstName] - Optional. First name of the contact, used in Mode 3.
     * @param {string} [MiddleName] - Optional. Middle name of the contact, used in Mode 3.
     * @param {string} [LastName] - Optional. Last name of the contact, used in Mode 3.
     * @param {string} [PhoneNumber] - Optional. Phone number for the contact, enables features like PhoneLink.
     * @param {string} [Options] - Optional. Reserved for future use.
     * @param {string} AuthID - Required. Authentication ID provided by Service Objects.
     * * @param {boolean} [isLive=true] - Option to use live service (true) or trial service (false).
     * @param {number} [timeoutSeconds=15] - Timeout in seconds for the HTTP request.
     * @returns {AV4Response} An AV4Response object with validated address details or an error.
     */
    invoke(Mode, Address1, Address2, City, State, ZIP, BusinessName, FirstName, MiddleName, LastName,
        PhoneNumber, Options, AuthID, isLive = true, timeoutSeconds = 15) {
        return (async () => await this.invokeAsync(
            Mode, Address1, Address2, City, State, ZIP, BusinessName, FirstName, MiddleName, LastName,
            PhoneNumber, Options, AuthID, isLive, timeoutSeconds
        ))();
    }
};

export { ValidateAddressClient, AV4Response };


export class AddressInfo {
    constructor(data = {}) {
        this.rating = data.rating;
        this.validationType = data.validationType;
        this.address = data.address;
        this.addressExtra = data.addressExtra;
        this.city = data.city;
        this.state = data.state;
        this.zip = data.zip;
        this.countyName = data.countyName;
        this.primaryNumber = data.primaryNumber;
        this.preDirectional = data.preDirectional;
        this.postDirectional = data.postDirectional;
        this.streetName = data.streetName;
        this.streetSuffix = data.streetSuffix;
        this.secondaryType = data.secondaryType;
        this.secondaryNumber = data.secondaryNumber;
        this.pmbType = data.pmbType;
        this.pmbNumber = data.pmbNumber;
        this.barcodeDigits = data.barcodeDigits;
        this.carrierRoute = data.carrierRoute;
        this.congressCode = data.congressCode;
        this.addressNotes = data.addressNotes || [];
    }

    toString() {
        const addressNotesString = this.addressNotes.length 
            ? this.addressNotes.join(', ') 
            : 'null';
        return `AddressInfo: rating=${this.rating}, validationType=${this.validationType}, ` +
               `address=${this.address}, addressExtra=${this.addressExtra}, city=${this.city}, ` +
               `state=${this.state}, zip=${this.zip}, countyName=${this.countyName}, ` +
               `primaryNumber=${this.primaryNumber}, preDirectional=${this.preDirectional}, ` +
               `postDirectional=${this.postDirectional}, streetName=${this.streetName}, ` +
               `streetSuffix=${this.streetSuffix}, secondaryType=${this.secondaryType}, ` +
               `secondaryNumber=${this.secondaryNumber}, pmbType=${this.pmbType}, ` +
               `pmbNumber=${this.pmbNumber}, barcodeDigits=${this.barcodeDigits}, ` +
               `carrierRoute=${this.carrierRoute}, congressCode=${this.congressCode}, ` +
               `addressNotes=[${addressNotesString}]`;
    }
}

export class ParsedInputInfo {
    constructor(data = {}) {
        this.address = data.address;
        this.addressExtra = data.addressExtra;
        this.city = data.city;
        this.state = data.state;
        this.zip = data.zip;
        this.primaryNumber = data.primaryNumber;
        this.preDirectional = data.preDirectional;
        this.postDirectional = data.postDirectional;
        this.streetName = data.streetName;
        this.streetSuffix = data.streetSuffix;
        this.secondaryType = data.secondaryType;
        this.secondaryNumber = data.secondaryNumber;
        this.phoneNumber = data.phoneNumber;
        this.firstName = data.firstName;
        this.middleName = data.middleName;
        this.lastName = data.lastName;
    }

    toString() {
        return `ParsedInputInfo: address=${this.address}, addressExtra=${this.addressExtra}, ` +
               `city=${this.city}, state=${this.state}, zip=${this.zip}, ` +
               `primaryNumber=${this.primaryNumber}, preDirectional=${this.preDirectional}, ` +
               `postDirectional=${this.postDirectional}, streetName=${this.streetName}, ` +
               `streetSuffix=${this.streetSuffix}, secondaryType=${this.secondaryType}, ` +
               `secondaryNumber=${this.secondaryNumber}, phoneNumber=${this.phoneNumber}, ` +
               `firstName=${this.firstName}, middleName=${this.middleName}, lastName=${this.lastName}`;
    }
}

export class ProblemDetails {
    constructor({ type = null, title = null, status = null, detail = null } = {}) {
        this.type = type;
        this.title = title;
        this.status = status;
        this.detail = detail;
    }

    toString() {
        return `Type: ${this.type} Title: ${this.title} Status: ${this.status} Detail: ${this.detail}`;
    }
}

export class AV4Response {
    constructor(data = {}) {
        this.Status = data.status;
        this.Addresses = (data.addresses  || []).map(addr => new AddressInfo(addr));
        this.ParsedInput = data.parsedInput ? new ParsedInputInfo(data.parsedInput) : null;
        this.ProblemDetails = data.ProblemDetails
            ? new ProblemDetails(
                data.ProblemDetails.Type,
                data.ProblemDetails.Title,
                data.ProblemDetails.Status,
                data.ProblemDetails.Detail
              )
            : null;
    }

    toString() {
        const addressesString = this.addresses.length 
            ? this.addresses.map(addr => addr.toString()).join(', ') 
            : 'null';
        return `ValidateAddressResponse:\nStatus: ${this.status}\nAddresses: ${addressesString}\nParsedInput: ${this.parsedInput ? this.parsedInput.toString() : 'null'}\nProblemDetails: ${this.problemDetails ? this.problemDetails.toString() : 'null'}`;
    }
}

export default AV4Response;