usps-api 0.0.1

A wrapper around the USPS WebTools API
Documentation
//! # Address API
//! This module holds the Address struct and implementations for all address related APIs. See the USPS API documentation for more details: https://www.usps.com/business/web-tools-apis/address-information-api.pdf
#![deny(missing_docs,
        missing_debug_implementations, missing_copy_implementations,
        trivial_casts, trivial_numeric_casts,
        unstable_features, unsafe_code,
        unused_import_braces, unused_qualifications)]

/* Third Party Libraries */
use roxmltree;

/// This struct represents a complete US address. None of the fields are required, howver a blank address is not particularly useful.
#[derive(Debug, PartialEq)]
pub struct USPSAddress {
    /// The name of the business at the location
    pub firmname: Option<String>,
    /// The first line of an address. e.g. 123 Main St
    pub address1: Option<String>,
    /// The second line of an address. e.g. Apt 123 
    pub address2: Option<String>,
    /// US City 
    pub city:     Option<String>,
    /// US State. Preferably a two letter abbreviation.
    pub state:    Option<String>,
    /// For use in Puerto Rico only
    pub urbanization: Option<String>,
    /// 5 digit zip code e.g. 12345
    pub zip5:     Option<String>,
    /// 4 digit zip code extension. e.g. 12345-XXXX
    pub zip4:     Option<String>,

    /// Set upon return from verify_address
    pub city_abbreviation: Option<String>,
    /// Set upon return from verify_address
    pub delivery_point: Option<String>,
    /// Set upon return from verify_address
    pub carrier_route: Option<String>,
    /// Set upon return from verify_address
    pub footnotes: Option<String>,
    /// Set upon return from verify_address
    pub dpv_cmra: Option<String>,
    /// Set upon return from verify_address
    pub dpv_confirmation: Option<String>,
    /// Set upon return from verify_address
    pub dpv_footnotes: Option<String>,
    /// Set upon return from verify_address
    pub business: Option<String>,
    /// Set upon return from verify_address
    pub central_delivery_point: Option<String>,
    /// Set upon return from verify_address
    pub vacant: Option<String>,
}

/* Traits */
impl Default for USPSAddress {
    fn default() -> Self {
        USPSAddress {
            firmname: None,
            address1: None,
            address2: None,
            city: None,
            state: None,
            urbanization: None,
            zip5: None,
            zip4: None,

            city_abbreviation: None,
            delivery_point: None,
            carrier_route: None,
            footnotes: None,
            dpv_confirmation: None,
            dpv_cmra: None,
            dpv_footnotes: None,
            business: None,
            central_delivery_point: None,
            vacant: None,
        }
    }
}

impl USPSAddress {
    /// Convenience function to save having to write Some(String::from()) over and over again. 
    /// # Example
    /// ```
    /// # use usps_api::address::USPSAddress;
    /// let address = USPSAddress::init("", "123 Main St.", "", "Big Town", "Washington", "", "99999", "");
    /// ```
    pub fn init(firmname: &str,
               address_1: &str,
               address_2: &str,
               city: &str,
               state: &str,
               urbanization: &str,
               zip5: &str,
               zip4: &str) -> Self {

        let firmname = match firmname { "" => None, _ => Some(firmname.into()) };
        let address1 = match address_1 { "" => None, _ => Some(address_1.into()) };
        let address2 = match address_2 { "" => None, _ => Some(address_2.into()) };
        let city = match city { "" => None, _ => Some(city.into()) };
        let state = match state { "" => None, _ => Some(state.into()) };
        let urbanization = match urbanization { "" => None, _ => Some(urbanization.into()) };
        let zip5 = match zip5 { "" => None, _ => Some(zip5.into()) };
        let zip4 = match zip4 { "" => None, _ => Some(zip4.into()) };

        USPSAddress {
            firmname, 
            address1,
            address2,
            city,
            state,
            urbanization,
            zip5,
            zip4,
            ..Default::default()
       }
    }

    /// Convenience function that only requires common fields to be passed. 
    /// # Example
    /// ```
    /// # use usps_api::address::USPSAddress;
    /// let address = USPSAddress::quick("123 Main St.", "Apt 1", "Seattle", "Washington", "99999");
    /// ```
    pub fn quick(address_1: &str,
               address_2: &str,
               city: &str,
               state: &str,
               zip5: &str) -> Self {

        let address1 = match address_1 { "" => None, _ => Some(address_1.into()) };
        let address2 = match address_2 { "" => None, _ => Some(address_2.into()) };
        let city = match city { "" => None, _ => Some(city.into()) };
        let state = match state { "" => None, _ => Some(state.into()) };
        let zip5 = match zip5 { "" => None, _ => Some(zip5.into()) };

        USPSAddress {
            firmname: None,
            address1,
            address2,
            city,
            state,
            urbanization: None,
            zip5,
            zip4: None,
            ..Default::default()
       }
    }

    pub(in crate) fn from_xml(xml: roxmltree::Document) -> Self {
        let mut address = USPSAddress::default();

        for node in xml.descendants() {
            if node.is_element() {
                let text = Some(String::from(node.text().unwrap_or("")));
                println!("{:?}", node.tag_name().name());
                println!("{:?}", node.text().unwrap_or("gay"));
                match node.tag_name().name() {
                    "FirmName" => address.firmname = text,
                    "Address1" => address.address1 = text,
                    "Address2" => address.address2 = text,
                    "City"     => address.city     = text,
                    "CityAbbreviation" => address.city_abbreviation = text,
                    "State" => address.state = text,
                    "Urbanization" => address.urbanization = text,
                    "Zip5" => address.zip5 = text,
                    "Zip4" => address.zip4 = text,
                    "DeliveryPoint" => address.delivery_point = text,
                    "CarrierRoute" => address.carrier_route = text,
                    "Footnotes" => address.footnotes = text,
                    "DPVConfirmation" => address.dpv_confirmation = text,
                    "DPVCMRA" => address.dpv_cmra = text,
                    "DPVFootnotes" => address.dpv_footnotes = text,
                    "Business" => address.business = text,
                    "CentralDeliveryPoint" => address.central_delivery_point = text,
                    "Vacant" => address.vacant = text,
                    _ => ()
                }
            }
        }

        address
    }

    /* Returns an Address as XML for use in API calls */
    pub(in crate) fn xml(&self) -> String {
        let mut s = String::with_capacity(150);
        s.push_str("<Address ID=\"0\">");

            s.push_str("<FirmName>");
            if let Some(ref a) = self.firmname { s.push_str(a); }
            s.push_str("</FirmName>");

            s.push_str("<Address1>");
            if let Some(ref a) = self.address1 { s.push_str(a); }
            s.push_str("</Address1>");

            s.push_str("<Address2>");
            if let Some(ref a) = self.address2 { s.push_str(a); }
            s.push_str("</Address2>");

            s.push_str("<City>");
            if let Some(ref a) = self.city { s.push_str(a); }
            s.push_str("</City>");

            if let Some(ref a) = self.state { 
                s.push_str("<State>");
                s.push_str(a);
                s.push_str("</State>");
            }

            if let Some(ref a) = self.urbanization {
                s.push_str("<Urbanization>");
                s.push_str(a);
                s.push_str("</Urbanization>");
            }

            s.push_str("<Zip5>");
            if let Some(ref a) = self.zip5 { s.push_str(a); }
            s.push_str("</Zip5>");

            s.push_str("<Zip4>");
            if let Some(ref a) = self.zip4 { s.push_str(a); }
            s.push_str("</Zip4>");

        s.push_str("</Address>");
        s
    }
}

#[cfg(test)]
mod address_tests {
    use super::*;

    #[test]
    fn init_constructor() {
        let dummy_struct = USPSAddress {
            firmname: None,
            address1: Some(String::from("123 Main St")),
            address2: None,
            city: Some(String::from("Seattle")),
            state: Some(String::from("WA")),
            urbanization: None,
            zip5: Some(String::from("98119")),
            zip4: None,
            ..Default::default()
        };

        let constructed = USPSAddress::init("", "123 Main St", "", "Seattle", "WA", "", "98119", "");

        assert_eq!(constructed, dummy_struct);
    }

    #[test]
    fn quick_constructor() {
        let dummy_struct = USPSAddress {
            firmname: None,
            address1: Some(String::from("123 Main St")),
            address2: None,
            city: Some(String::from("Seattle")),
            state: Some(String::from("WA")),
            urbanization: None,
            zip5: Some(String::from("98119")),
            zip4: None,
            ..Default::default()
        };

        let constructed = USPSAddress::quick("123 Main St", "", "Seattle", "WA", "98119");

        assert_eq!(constructed, dummy_struct);
    }

    #[test]
    fn to_xml_1() {
        let a = USPSAddress::quick("123 Main St", "", "Seattle", "WA", "98119");
        let s = String::from("<Address ID=\"0\"><FirmName></FirmName><Address1>123 Main St</Address1><Address2></Address2><City>Seattle</City><State>WA</State><Zip5>98119</Zip5><Zip4></Zip4></Address>");
        assert_eq!(a.xml(), s);
    }

    #[test]
    fn to_xml_2() {
        let a = USPSAddress::init("Acme, LLC", "123 Main St", "Apt 456", "Seattle", "WA", "", "98119", "1234");
        let s = String::from("<Address ID=\"0\"><FirmName>Acme, LLC</FirmName><Address1>123 Main St</Address1><Address2>Apt 456</Address2><City>Seattle</City><State>WA</State><Zip5>98119</Zip5><Zip4>1234</Zip4></Address>");
        assert_eq!(a.xml(), s);
    }
}