Phone Exchange C# Rest Code Snippet


using System.Web;

namespace phone_exchange_2_dot_net.REST
{
    /// <summary>
    /// Provides functionality to call the ServiceObjects Phone Exchange (PE2) REST API's GetExchangeInfo endpoint,
    /// retrieving phone exchange information (e.g., carrier, line type, ported status) for a given phone number
    /// with fallback to a backup endpoint for reliability in live mode.
    /// </summary>
    public static class GetExchangeInfoClient
    {
        // Base URL constants: production, backup, and trial
        private const string LiveBaseUrl = "https://sws.serviceobjects.com/pe2/web.svc/json/";
        private const string BackupBaseUrl = "https://swsbackup.serviceobjects.com/pe2/web.svc/json/";
        private const string TrialBaseUrl = "https://trial.serviceobjects.com/pe2/web.svc/json/";

        /// <summary>
        /// Synchronously calls the GetExchangeInfo REST endpoint to retrieve phone exchange information,
        /// attempting the primary endpoint first and falling back to the backup if the response is invalid
        /// (Error.TypeCode == "3") in live mode.
        /// </summary>
        /// <param name="input">The input parameters including phone number, country code, and license key.</param>
        /// <returns>Deserialized <see cref="PE2Response"/> containing phone exchange data or an error.</returns>
        public static PE2Response Invoke(GetExchangeInfoInput input)
        {
            // Use query string parameters so missing/optional fields don't break the URL
            string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            PE2Response response = Helper.HttpGet<PE2Response>(url, input.TimeoutSeconds);

            // Fallback on error in live mode
            if (input.IsLive && !IsValid(response))
            {
                string fallbackUrl = BuildUrl(input, BackupBaseUrl);
                PE2Response fallbackResponse = Helper.HttpGet<PE2Response>(fallbackUrl, input.TimeoutSeconds);
                return fallbackResponse;
            }

            return response;
        }

        /// <summary>
        /// Asynchronously calls the GetExchangeInfo REST endpoint to retrieve phone exchange information,
        /// attempting the primary endpoint first and falling back to the backup if the response is invalid
        /// (Error.TypeCode == "3") in live mode.
        /// </summary>
        /// <param name="input">The input parameters including phone number, country code, and license key.</param>
        /// <returns>Deserialized <see cref="PE2Response"/> containing phone exchange data or an error.</returns>
        public static async Task<PE2Response> InvokeAsync(GetExchangeInfoInput input)
        {
            // Use query string parameters so missing/optional fields don't break the URL
            string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            PE2Response response = await Helper.HttpGetAsync<PE2Response>(url, input.TimeoutSeconds).ConfigureAwait(false);

            // Fallback on error in live mode
            if (input.IsLive && !IsValid(response))
            {
                string fallbackUrl = BuildUrl(input, BackupBaseUrl);
                PE2Response fallbackResponse = await Helper.HttpGetAsync<PE2Response>(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false);
                return fallbackResponse;
            }

            return response;
        }

        // Build the full request URL, including URL-encoded query string
        public static string BuildUrl(GetExchangeInfoInput input, string baseUrl)
        {
            // Construct query string with URL-encoded parameters
            string qs = $"GetExchangeInfo?" +
                        $"PhoneNumber={Helper.UrlEncode(input.PhoneNumber)}" +
                        $"&LicenseKey={Helper.UrlEncode(input.LicenseKey)}";
            return baseUrl + qs;
        }

        private static bool IsValid(PE2Response response) => response?.Error == null || response.Error.TypeCode != "3";

        /// <summary>
        /// Input parameters for the GetExchangeInfo API call. Represents a phone number to retrieve exchange information.
        /// </summary>
        /// <param name="PhoneNumber">The phone number to validate (e.g., "1234567890").</param>
        /// <param name="CountryCode">1-3 digit country calling code (e.g., "1"). Optional.</param>
        /// <param name="Country">ISO2, ISO3, or country name (e.g., "US"). Optional.</param>
        /// <param name="IPAddress">IPv4 address. Optional.</param>
        /// <param name="CallerCountry">ISO2 or ISO3 code representing the caller's country. Optional.</param>
        /// <param name="Extras">Comma-separated list of possible options. Optional.</param>
        /// <param name="LicenseKey">The license key to authenticate the API request.</param>
        /// <param name="IsLive">Indicates whether to use the live service (true) or trial service (false).</param>
        /// <param name="TimeoutSeconds">Timeout duration for the API call, in seconds.</param>
        public record GetExchangeInfoInput(
            string PhoneNumber = "",
            string CountryCode = "",
            string Country = "",
            string IPAddress = "",
            string CallerCountry = "",
            string Extras = "",
            string LicenseKey = "",
            string Token = "",
            bool IsLive = true,
            int TimeoutSeconds = 15
        );
    }
}


using System.Runtime.Serialization;
using System.Linq;

namespace phone_exchange_2_dot_net.REST
{
    /// <summary>
    /// Response from PE2 GetExchangeInfo and GetInternationalExchangeInfo APIs, containing phone exchange information.
    /// </summary>
    [DataContract]
    public class PE2Response
    {
        public ExchangeInfo[] ExchangeInfoResults { get; set; }
        public InternationalExchangeInfo InternationalExchangeInfo { get; set; }
        public Error Error { get; set; }

        public override string ToString()
        {
            string exchangeInfoStr = ExchangeInfoResults != null
                ? string.Join("\n", ExchangeInfoResults.Select(r => r.ToString()))
                : "null";
            string internationalInfoStr = InternationalExchangeInfo != null
                ? InternationalExchangeInfo.ToString()
                : "";
            return $"PE2Response:\n" +
                   $"ExchangeInfoResults:\n{exchangeInfoStr}\n" +
                   $"InternationalExchangeInfo:\n{internationalInfoStr}\n" +
                   $"Error: {(Error != null ? Error.ToString() : "null")}";
        }
    }

    /// <summary>
    /// Phone exchange information for a validated phone number (USA/Canada).
    /// </summary>
    [DataContract]
    public class ExchangeInfo
    {
        public string PhoneNumber { get; set; }
        public string Name { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
        public string LineType { get; set; }
        public string TimeZone { get; set; }
        public string Latitude { get; set; }
        public string Longitude { get; set; }
        public string SMSAddress { get; set; }
        public string MMSAddress { get; set; }
        public PortedInfo PortedInfo { get; set; }
        public string NoteCodes { get; set; }
        public string NoteDescriptions { get; set; }

        public override string ToString()
        {
            string portedInfoStr = PortedInfo != null
                ? PortedInfo.ToString()
                : "";
            return $"ExchangeInfo:\n" +
                   $"PhoneNumber: {PhoneNumber}\n" +
                   $"Name: {Name}\n" +
                   $"City: {City}\n" +
                   $"State: {State}\n" +
                   $"Country: {Country}\n" +
                   $"LineType: {LineType}\n" +
                   $"TimeZone: {TimeZone}\n" +
                   $"Latitude: {Latitude}\n" +
                   $"Longitude: {Longitude}\n" +
                   $"SMSAddress: {SMSAddress}\n" +
                   $"MMSAddress: {MMSAddress}\n" +
                   $"PortedInfo: {portedInfoStr}\n" +
                   $"NoteCodes: {NoteCodes}\n" +
                   $"NoteDescriptions: {NoteDescriptions}";
        }
    }

    /// <summary>
    /// Ported information for a phone number.
    /// </summary>
    [DataContract]
    public class PortedInfo
    {
        public string OriginalName { get; set; }
        public string OriginalLineType { get; set; }
        public string PortedDate { get; set; }
        public string LATA { get; set; }

        public override string ToString()
        {
            return $"OriginalName: {OriginalName}, OriginalLineType: {OriginalLineType}, PortedDate: {PortedDate}, LATA: {LATA}";
        }
    }

    /// <summary>
    /// International phone exchange information for a validated phone number.
    /// </summary>
    [DataContract]
    public class InternationalExchangeInfo
    {
        public string PhoneNumberIn { get; set; }
        public string CountryCode { get; set; }
        public string FormatNational { get; set; }
        public string Extension { get; set; }
        public string Locality { get; set; }
        public string LocalityMatchLevel { get; set; }
        public string TimeZone { get; set; }
        public string Latitude { get; set; }
        public string Longitude { get; set; }
        public string Country { get; set; }
        public string CountryISO2 { get; set; }
        public string CountryISO3 { get; set; }
        public string FormatInternational { get; set; }
        public string FormatE164 { get; set; }
        public string Carrier { get; set; }
        public string LineType { get; set; }
        public string SMSAddress { get; set; }
        public string MMSAddress { get; set; }
        public bool IsValid { get; set; }
        public bool IsValidForRegion { get; set; }
        public string NoteCodes { get; set; }
        public string NoteDescriptions { get; set; }

        public override string ToString()
        {
            return $"InternationalExchangeInfo:\n" +
                   $"PhoneNumberIn: {PhoneNumberIn}\n" +
                   $"CountryCode: {CountryCode}\n" +
                   $"FormatNational: {FormatNational}\n" +
                   $"Extension: {Extension}\n" +
                   $"Locality: {Locality}\n" +
                   $"LocalityMatchLevel: {LocalityMatchLevel}\n" +
                   $"TimeZone: {TimeZone}\n" +
                   $"Latitude: {Latitude}\n" +
                   $"Longitude: {Longitude}\n" +
                   $"Country: {Country}\n" +
                   $"CountryISO2: {CountryISO2}\n" +
                   $"CountryISO3: {CountryISO3}\n" +
                   $"FormatInternational: {FormatInternational}\n" +
                   $"FormatE164: {FormatE164}\n" +
                   $"Carrier: {Carrier}\n" +
                   $"LineType: {LineType}\n" +
                   $"SMSAddress: {SMSAddress}\n" +
                   $"MMSAddress: {MMSAddress}\n" +
                   $"IsValid: {IsValid}\n" +
                   $"IsValidForRegion: {IsValidForRegion}\n" +
                   $"NoteCodes: {NoteCodes}\n" +
                   $"NoteDescriptions: {NoteDescriptions}";
        }
    }

    /// <summary>
    /// Error object for PE2 API responses.
    /// </summary>
    [DataContract]
    public class Error
    {
        public string Type { get; set; }
        public string Desc { get; set; }
        public string TypeCode { get; set; }
        public string DescCode { get; set; }

        public override string ToString()
        {
            return $"Desc: {Desc}, TypeCode: {TypeCode}, DescCode: {DescCode}, Type: {Type}";
        }
    }
}


using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Web;

namespace phone_exchange_2_dot_net.REST
{
    public static class Helper
    {
        public static async Task<T> HttpGetAsync<T>(string url, int timeoutSeconds)
        {
            HttpClient HttpClient = new HttpClient();
            HttpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
            using var httpResponse = await HttpClient.GetAsync(url).ConfigureAwait(false);
            httpResponse.EnsureSuccessStatusCode();
            var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
            return JsonSerializer.Deserialize<T>(stream)!;
        }

        public static T HttpGet<T>(string url, int timeoutSeconds)
        {
            using var httpClient = new HttpClient 
            { 
                Timeout = TimeSpan.FromSeconds(timeoutSeconds) 
            };

            using var request = new HttpRequestMessage(HttpMethod.Get, url);
            using HttpResponseMessage response = httpClient
                .SendAsync(request)
                .GetAwaiter()
                .GetResult();
            response.EnsureSuccessStatusCode();
            using Stream responseStream = response.Content
                .ReadAsStreamAsync()
                .GetAwaiter()
                .GetResult();
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };
            object? obj = JsonSerializer.Deserialize(responseStream, typeof(T), options);
            T result = (T)obj!;
            return result;
        }

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

Phone Exchange Python Rest Code Snippet

from pe2_response import PE2Response, ExchangeInfo, InternationalExchangeInfo, PortedInfo, Error
import requests

# Endpoint URLs for ServiceObjects Phone Exchange (PE2) API
primary_url = "https://sws.serviceobjects.com/pe2/web.svc/json/GetExchangeInfo?"
backup_url = "https://swsbackup.serviceobjects.com/pe2/web.svc/json/GetExchangeInfo?"
trial_url = "https://trial.serviceobjects.com/pe2/web.svc/json/GetExchangeInfo?"

def get_exchange_info(
    phone_number: str,
    license_key: str = None,
    is_live: bool = True
) -> PE2Response:
    """
    Call ServiceObjects Phone Exchange (PE2) API's GetExchangeInfo endpoint
    to retrieve phone exchange information for a given US/Canada phone number.

    Parameters:
        phone_number: The phone number to validate (e.g., "8051234567").
        license_key: Your ServiceObjects license key.
        is_live: Use live or trial servers.

    Returns:
        PE2Response: Parsed JSON response with phone exchange results or error details.

    Raises:
        RuntimeError: If the API returns an error payload.
        requests.RequestException: On network/HTTP failures (trial mode).
    """
    params = {
        "PhoneNumber": phone_number,
        "LicenseKey": license_key,
    }
    # Select the base URL: production vs trial
    url = primary_url if is_live else trial_url

    try:
        # Attempt primary (or trial) endpoint
        response = requests.get(url, params=params, timeout=10)
        response.raise_for_status()
        data = response.json()

        # If API returned an error in JSON payload, trigger fallback
        error = data.get('Error')
        if not (error is None or error.get('TypeCode') != "3"):
            if is_live:
                # Try backup URL
                response = requests.get(backup_url, params=params, timeout=10)
                response.raise_for_status()
                data = response.json()

                # If still error, propagate exception
                if 'Error' in data:
                    raise RuntimeError(f"Phone Exchange service error: {data['Error']}")
            else:
                # Trial mode error is terminal
                raise RuntimeError(f"Phone Exchange trial error: {data['Error']}")

        # Convert JSON response to PE2Response for structured access
        error = Error(**data.get("Error", {})) if data.get("Error") else None
        exchange_info_results = []
        if "ExchangeInfoResults" in data:
            for ei in data.get("ExchangeInfoResults", []):
                if isinstance(ei, dict):
                    ported_info_list = []
                    if "PortedInfo" in ei and ei.get("PortedInfo"):
                        for pi in ei.get("PortedInfo", []):
                            if isinstance(pi, dict):
                                ported_info_list.append(PortedInfo(
                                    OriginalName=pi.get("OriginalName"),
                                    OriginalLineType=pi.get("OriginalLineType"),
                                    PortedDate=pi.get("PortedDate"),
                                    LATA=pi.get("LATA")
                                ))
                    exchange_info_results.append(ExchangeInfo(
                        PhoneNumber=ei.get("PhoneNumber"),
                        Name=ei.get("Name"),
                        City=ei.get("City"),
                        State=ei.get("State"),
                        Country=ei.get("Country"),
                        LineType=ei.get("LineType"),
                        TimeZone=ei.get("TimeZone"),
                        Latitude=ei.get("Latitude"),
                        Longitude=ei.get("Longitude"),
                        SMSAddress=ei.get("SMSAddress"),
                        MMSAddress=ei.get("MMSAddress"),
                        PortedInfo=ported_info_list,
                        NoteCodes=ei.get("NoteCodes", []),
                        NoteDescriptions=ei.get("NoteDescriptions", [])
                    ))

        return PE2Response(
            ExchangeInfoResults=exchange_info_results,
            Error=error
        )

    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=10)
                response.raise_for_status()
                data = response.json()
                if "Error" in data:
                    raise RuntimeError(f"Phone Exchange backup error: {data['Error']}") from req_exc

                error = Error(**data.get("Error", {})) if data.get("Error") else None
                exchange_info_results = []
                if "ExchangeInfoResults" in data:
                    for ei in data.get("ExchangeInfoResults", []):
                        if isinstance(ei, dict):
                            ported_info_list = []
                            if "PortedInfo" in ei and ei.get("PortedInfo"):
                                for pi in ei.get("PortedInfo", []):
                                    if isinstance(pi, dict):
                                        ported_info_list.append(PortedInfo(
                                            OriginalName=pi.get("OriginalName"),
                                            OriginalLineType=pi.get("OriginalLineType"),
                                            PortedDate=pi.get("PortedDate"),
                                            LATA=pi.get("LATA")
                                        ))
                            exchange_info_results.append(ExchangeInfo(
                                PhoneNumber=ei.get("PhoneNumber"),
                                Name=ei.get("Name"),
                                City=ei.get("City"),
                                State=ei.get("State"),
                                Country=ei.get("Country"),
                                LineType=ei.get("LineType"),
                                TimeZone=ei.get("TimeZone"),
                                Latitude=ei.get("Latitude"),
                                Longitude=ei.get("Longitude"),
                                SMSAddress=ei.get("SMSAddress"),
                                MMSAddress=ei.get("MMSAddress"),
                                PortedInfo=ported_info_list,
                                NoteCodes=ei.get("NoteCodes", []),
                                NoteDescriptions=ei.get("NoteDescriptions", [])
                            ))
                return PE2Response(
                    ExchangeInfoResults=exchange_info_results,
                    Error=error
                )
            except Exception as backup_exc:
                raise RuntimeError("Phone Exchange service unreachable on both endpoints") from backup_exc
        else:
            raise RuntimeError(f"Phone Exchange trial error: {str(req_exc)}") from req_exc



from asyncio.windows_events import NULL
from dataclasses import dataclass
from typing import Optional, List, Type

@dataclass
class Error:
    Desc: Optional[str] = None
    TypeCode: Optional[str] = None
    DescCode: Optional[str] = None
    Type: Optional[str] = None

    def __str__(self) -> str:
        return f"Error: Desc={self.Desc}, TypeCode={self.TypeCode}, DescCode={self.DescCode}, Type={self.Type}"

@dataclass
class PortedInfo:
    OriginalName: Optional[str] = None
    OriginalLineType: Optional[str] = None
    PortedDate: Optional[str] = None
    LATA: Optional[str] = None

    def __str__(self) -> str:
        return (f"PortedInfo: OriginalName={self.OriginalName}, OriginalLineType={self.OriginalLineType}, "
                f"PortedDate={self.PortedDate}, LATA={self.LATA}")

@dataclass
class ExchangeInfo:
    PhoneNumber: Optional[str] = None
    Name: Optional[str] = None
    City: Optional[str] = None
    State: Optional[str] = None
    Country: Optional[str] = None
    LineType: Optional[str] = None
    TimeZone: Optional[str] = None
    Latitude: Optional[str] = None
    Longitude: Optional[str] = None
    SMSAddress: Optional[str] = None
    MMSAddress: Optional[str] = None
    PortedInfo: Optional['PortedInfo'] = None
    NoteCodes: Optional[str] = None
    NoteDescriptions: Optional[str] = None

    def __post_init__(self):
        if self.NoteCodes is None:
            self.NoteCodes = ""
        if self.NoteDescriptions is None:
            self.NoteDescriptions = ""

    def __str__(self) -> str:
        return (f"ExchangeInfo: PhoneNumber={self.PhoneNumber}, Name={self.Name}, City={self.City}, "
                f"State={self.State}, Country={self.Country}, LineType={self.LineType}, "
                f"TimeZone={self.TimeZone}, Latitude={self.Latitude}, Longitude={self.Longitude}, "
                f"SMSAddress={self.SMSAddress}, MMSAddress={self.MMSAddress}, "
                f"PortedInfo={self.PortedInfo}, NoteCodes=[{self.NoteCodes}], "
                f"NoteDescriptions={self.NoteDescriptions}")

@dataclass
class InternationalExchangeInfo:
    PhoneNumberIn: Optional[str] = None
    CountryCode: Optional[str] = None
    FormatNational: Optional[str] = None
    Extension: Optional[str] = None
    Locality: Optional[str] = None
    LocalityMatchLevel: Optional[str] = None
    TimeZone: Optional[str] = None
    Latitude: Optional[str] = None
    Longitude: Optional[str] = None
    Country: Optional[str] = None
    CountryISO2: Optional[str] = None
    CountryISO3: Optional[str] = None
    FormatInternational: Optional[str] = None
    FormatE164: Optional[str] = None
    Carrier: Optional[str] = None
    LineType: Optional[str] = None
    SMSAddress: Optional[str] = None
    MMSAddress: Optional[str] = None
    IsValid: bool = False
    IsValidForRegion: bool = False
    NoteCodes: Optional[str] = None
    NoteDescriptions: Optional[str] = None

    def __post_init__(self):
        if self.NoteCodes is None:
            self.NoteCodes = ""
        if self.NoteDescriptions is None:
            self.NoteDescriptions = ""

    def __str__(self) -> str:
        return (f"InternationalExchangeInfo: PhoneNumberIn={self.PhoneNumberIn}, CountryCode={self.CountryCode}, "
                f"FormatNational={self.FormatNational}, Extension={self.Extension}, Locality={self.Locality}, "
                f"LocalityMatchLevel={self.LocalityMatchLevel}, TimeZone={self.TimeZone}, "
                f"Latitude={self.Latitude}, Longitude={self.Longitude}, Country={self.Country}, "
                f"CountryISO2={self.CountryISO2}, CountryISO3={self.CountryISO3}, "
                f"FormatInternational={self.FormatInternational}, FormatE164={self.FormatE164}, "
                f"Carrier={self.Carrier}, LineType={self.LineType}, SMSAddress={self.SMSAddress}, "
                f"MMSAddress={self.MMSAddress}, IsValid={self.IsValid}, IsValidForRegion={self.IsValidForRegion}, "
                f"NoteCodes={self.NoteCodes}, NoteDescriptions={self.NoteDescriptions}")

@dataclass
class PE2Response:
    ExchangeInfoResults: Optional[List['ExchangeInfo']] = None
    InternationalExchangeInfo: Optional['InternationalExchangeInfo'] = None
    Error: Optional['Error'] = None

    def __post_init__(self):
        if self.ExchangeInfoResults is None:
            self.ExchangeInfoResults = []

    def __str__(self) -> str:
        exchange_info_str = '\n'.join(str(r) for r in self.ExchangeInfoResults) if self.ExchangeInfoResults else 'None'
        error_str = str(self.Error) if self.Error else 'None'
        return (f"PE2Response: ExchangeInfoResults=[\n{exchange_info_str}\n], "
                f"InternationalExchangeInfo=\n{self.InternationalExchangeInfo}\n, Error={error_str}")

Phone Exchange NodeJS Rest Code Snippet

import axios from 'axios';
import querystring from 'querystring';
import {PE2Response} from './pe2_response.js';

/**
 * @constant
 * @type {string}
 * @description The base URL for the live ServiceObjects Phone Exchange (PE2) API service.
 */
const LiveBaseUrl = 'https://sws.serviceobjects.com/pe2/web.svc/json/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the backup ServiceObjects Phone Exchange (PE2) API service.
 */
const BackupBaseUrl = 'https://swsbackup.serviceobjects.com/pe2/web.svc/json/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the trial ServiceObjects Phone Exchange (PE2) API service.
 */
const TrialBaseUrl = 'https://trial.serviceobjects.com/pe2/web.svc/json/';

/**
 * <summary>
 * Checks if a response from the API is valid by verifying that it either has no Error object
 * or the Error.TypeCode is not equal to '3'.
 * </summary>
 * <param name="response" type="Object">The API response object to validate.</param>
 * <returns type="boolean">True if the response is valid, false otherwise.</returns>
 */
const isValid = (response) => !response?.Error || response.Error.TypeCode !== '3';

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

/**
 * <summary>
 * Performs an HTTP GET request to the specified URL with a given timeout.
 * </summary>
 * <param name="url" type="string">The URL to send the GET request to.</param>
 * <param name="timeoutSeconds" type="number">The timeout duration in seconds for the request.</param>
 * <returns type="Promise<PE2Response>">A promise that resolves to a PE2Response object containing the API response data.</returns>
 * <exception cref="Error">Thrown if the HTTP request fails, with a message detailing the error.</exception>
 */
const httpGet = async (url, timeoutSeconds) => {
    try {
        const response = await axios.get(url, { timeout: timeoutSeconds * 1000 });
        return new PE2Response(response.data);
    } catch (error) {
        throw new Error(`HTTP request failed: ${error.message}`);
    }
};

/**
 * <summary>
 * Provides functionality to call the ServiceObjects Phone Exchange (PE2) API's GetExchangeInfo endpoint,
 * retrieving phone exchange information (e.g., carrier, line type, ported status) for a given US/Canada phone number
 * with fallback to a backup endpoint for reliability in live mode.
 * </summary>
 */
const GetExchangeInfoClient = {
    /**
     * <summary>
     * Asynchronously invokes the GetExchangeInfo API endpoint, attempting the primary endpoint
     * first and falling back to the backup if the response is invalid (Error.TypeCode == '3') in live mode.
     * </summary>
     * @param {string} PhoneNumber - The phone number to validate (e.g., "1234567890").
     * @param {string} LicenseKey - Your license key to use the service.
     * @param {boolean} isLive - Value to determine whether to use the live or trial servers.
     * @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service.
     * @returns {Promise<PE2Response>} - A promise that resolves to a PE2Response object.
     */
    async invokeAsync(PhoneNumber, LicenseKey, isLive = true, timeoutSeconds = 15) {
        const params = {
            PhoneNumber,
            LicenseKey
        };

        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;
    },

    /**
     * <summary>
     * Synchronously invokes the GetExchangeInfo API endpoint by wrapping the async call
     * and awaiting its result immediately.
     * </summary>
     * @param {string} PhoneNumber - The phone number to validate (e.g., "1234567890").
     * @param {string} LicenseKey - Your license key to use the service.
     * @param {boolean} isLive - Value to determine whether to use the live or trial servers.
     * @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service.
     * @returns {PE2Response} - A PE2Response object with phone exchange details or an error.
     */
    invoke(PhoneNumber, LicenseKey, isLive = true, timeoutSeconds = 15) {
        return (async () => await this.invokeAsync(
            PhoneNumber, LicenseKey, isLive, timeoutSeconds
        ))();
    }
};

export { GetExchangeInfoClient, PE2Response };


export class ExchangeInfo {
    constructor(data = {}) {
        this.PhoneNumber = data.PhoneNumber;
        this.Name = data.Name;
        this.City = data.City;
        this.State = data.State;
        this.Country = data.Country;
        this.LineType = data.LineType;
        this.TimeZone = data.TimeZone;
        this.Latitude = data.Latitude;
        this.Longitude = data.Longitude;
        this.SMSAddress = data.SMSAddress;
        this.MMSAddress = data.MMSAddress;
        this.PortedInfo = data.PortedInfo;
        this.NoteCodes = data.NoteCodes;
        this.NoteDescriptions = data.NoteDescriptions;
    }

    toString() {
        return `ExchangeInfo: PhoneNumber = ${this.PhoneNumber}, Name = ${this.Name}, City = ${this.City}, State = ${this.State}, Country = ${this.Country}, LineType = ${this.LineType}, TimeZone = ${this.TimeZone}, Latitude = ${this.Latitude}, Longitude = ${this.Longitude}, SMSAddress = ${this.SMSAddress}, MMSAddress = ${this.MMSAddress}, PortedInfo = ${this.PortedInfo}, NoteCodes = ${this.NoteCodes}, NoteDescriptions = ${this.NoteDescriptions}`;
    }
}

/**
 * Ported information for a phone number.
 */
export class PortedInfo {
    constructor(data = {}) {
        this.OriginalName = data.OriginalName;
        this.OriginalLineType = data.OriginalLineType;
        this.PortedDate = data.PortedDate;
        this.LATA = data.LATA;
    }

    toString() {
        return `PortedInfo: OriginalName = ${this.OriginalName}, OriginalLineType = ${this.OriginalLineType}, PortedDate = ${this.PortedDate}, LATA = ${this.LATA}`;
    }
}

/**
 * International phone exchange information for a validated phone number.
 */
export class InternationalExchangeInfo {
    constructor(data = {}) {
        this.PhoneNumberIn = data.NumberIn;
        this.CountryCode = data.CountryCode;
        this.FormatNational = data.FormatNational;
        this.Extension = data.Extension;
        this.Locality = data.Locality;
        this.LocalityMatchLevel = data.LocalityMatchLevel;
        this.TimeZone = data.TimeZone;
        this.Latitude = data.Latitude;
        this.Longitude = data.Longitude;
        this.Country = data.Country;
        this.CountryISO2 = data.CountryISO2;
        this.CountryISO3 = data.CountryISO3;
        this.FormatInternational = data.FormatInternational;
        this.FormatE164 = data.FormatE164;
        this.Carrier = data.Carrier;
        this.LineType = data.LineType;
        this.SMSAddress = data.SMSAddress;
        this.MMSAddress = data.MMSAddress;
        this.IsValid = data.IsValid !== undefined ? data.IsValid : null;
        this.IsValidForRegion = data.IsValidForRegion !== undefined ? data.IsValidForRegion : null;
        this.NoteCodes = data.NoteCodes;
        this.NoteDescriptions = data.NoteDescriptions;
    }

    toString() {
        return `InternationalExchangeInfo: PhoneNumberIn = ${this.PhoneNumberIn}, CountryCode = ${this.CountryCode}, FormatNational = ${this.FormatNational}, Extension = ${this.Extension}, Locality = ${this.Locality}, LocalityMatchLevel = ${this.LocalityMatchLevel}, TimeZone = ${this.TimeZone}, Latitude = ${this.Latitude}, Longitude = ${this.Longitude}, Country = ${this.Country}, CountryISO2 = ${this.CountryISO2}, CountryISO3 = ${this.CountryISO3}, FormatInternational = ${this.FormatInternational}, FormatE164 = ${this.FormatE164}, Carrier = ${this.Carrier}, LineType = ${this.LineType}, SMSAddress = ${this.SMSAddress}, MMSAddress = ${this.MMSAddress}, IsValid = ${this.IsValid}, IsValidForRegion = ${this.IsValidForRegion}, NoteCodes = ${this.NoteCodes}, NoteDescriptions = ${this.NoteDescriptions}`;
    }
}

/**
 * Error object for PE2 API responses.
 */
export class Error {
    constructor(data = {}) {
        this.Desc = data.Desc;
        this.TypeCode = data.TypeCode;
        this.DescCode = data.DescCode;
        this.Type = data.Type;
    }

    toString() {
        return `Error: Desc = ${this.Desc}, TypeCode = ${this.TypeCode}, DescCode = ${this.DescCode}, Type= ${this.Type}}`;
    }
}

/**
 * Response from PE2 GetExchangeInfo and GetInternationalExchangeInfo APIs, containing phone exchange information.
 */
export class PE2Response {
    constructor(data = {}) {
        this.ExchangeInfo = Array.isArray(data.ExchangeInfoResults)
            ? data.ExchangeInfoResults.map(info => new ExchangeInfo(info))
            : [];
        this.ExchangeInfoResults = (data.ExchangeInfoResults || []).map(info => new ExchangeInfo(info));
        this.InternationalExchangeInfo = data.InternationalExchangeInfo ? new InternationalExchangeInfo(data.InternationalExchangeInfo) : null;
        this.Error = data.Error ? new Error(data.Error) : null;
    }

    toString() {
        const exchangeInfoString = this.ExchangeInfoResults.length
            ? this.ExchangeInfoResults.map(info => info.toString()).join('; ')
            : 'null';
        const internationalInfoString = this.InternationalExchangeInfo.length
            ? this.InternationalExchangeInfo.map(info => info.toString()).join('; ')
            : 'null';
        return `PE2Response: ExchangeInfoResults = [${exchangeInfoString}], InternationalExchangeInfo = [${internationalInfoString}], Error = ${this.Error ? this.Error.toString() : 'null'}`;
    }
}

export default PE2Response;