usps_api/
address.rs

1//! # Address API
2//! 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
3#![deny(missing_docs,
4        missing_debug_implementations, missing_copy_implementations,
5        trivial_casts, trivial_numeric_casts,
6        unstable_features, unsafe_code,
7        unused_import_braces, unused_qualifications)]
8
9/* Third Party Libraries */
10use roxmltree;
11
12/// This struct represents a complete US address. None of the fields are required, howver a blank address is not particularly useful.
13#[derive(Debug, PartialEq)]
14pub struct USPSAddress {
15    /// The name of the business at the location
16    pub firmname: Option<String>,
17    /// The first line of an address. e.g. 123 Main St
18    pub address1: Option<String>,
19    /// The second line of an address. e.g. Apt 123 
20    pub address2: Option<String>,
21    /// US City 
22    pub city:     Option<String>,
23    /// US State. Preferably a two letter abbreviation.
24    pub state:    Option<String>,
25    /// For use in Puerto Rico only
26    pub urbanization: Option<String>,
27    /// 5 digit zip code e.g. 12345
28    pub zip5:     Option<String>,
29    /// 4 digit zip code extension. e.g. 12345-XXXX
30    pub zip4:     Option<String>,
31
32    /// Set upon return from verify_address
33    pub city_abbreviation: Option<String>,
34    /// Set upon return from verify_address
35    pub delivery_point: Option<String>,
36    /// Set upon return from verify_address
37    pub carrier_route: Option<String>,
38    /// Set upon return from verify_address
39    pub footnotes: Option<String>,
40    /// Set upon return from verify_address
41    pub dpv_cmra: Option<String>,
42    /// Set upon return from verify_address
43    pub dpv_confirmation: Option<String>,
44    /// Set upon return from verify_address
45    pub dpv_footnotes: Option<String>,
46    /// Set upon return from verify_address
47    pub business: Option<String>,
48    /// Set upon return from verify_address
49    pub central_delivery_point: Option<String>,
50    /// Set upon return from verify_address
51    pub vacant: Option<String>,
52}
53
54/* Traits */
55impl Default for USPSAddress {
56    fn default() -> Self {
57        USPSAddress {
58            firmname: None,
59            address1: None,
60            address2: None,
61            city: None,
62            state: None,
63            urbanization: None,
64            zip5: None,
65            zip4: None,
66
67            city_abbreviation: None,
68            delivery_point: None,
69            carrier_route: None,
70            footnotes: None,
71            dpv_confirmation: None,
72            dpv_cmra: None,
73            dpv_footnotes: None,
74            business: None,
75            central_delivery_point: None,
76            vacant: None,
77        }
78    }
79}
80
81impl USPSAddress {
82    /// Convenience function to save having to write Some(String::from()) over and over again. 
83    /// # Example
84    /// ```
85    /// # use usps_api::address::USPSAddress;
86    /// let address = USPSAddress::init("", "123 Main St.", "", "Big Town", "Washington", "", "99999", "");
87    /// ```
88    pub fn init(firmname: &str,
89               address_1: &str,
90               address_2: &str,
91               city: &str,
92               state: &str,
93               urbanization: &str,
94               zip5: &str,
95               zip4: &str) -> Self {
96
97        let firmname = match firmname { "" => None, _ => Some(firmname.into()) };
98        let address1 = match address_1 { "" => None, _ => Some(address_1.into()) };
99        let address2 = match address_2 { "" => None, _ => Some(address_2.into()) };
100        let city = match city { "" => None, _ => Some(city.into()) };
101        let state = match state { "" => None, _ => Some(state.into()) };
102        let urbanization = match urbanization { "" => None, _ => Some(urbanization.into()) };
103        let zip5 = match zip5 { "" => None, _ => Some(zip5.into()) };
104        let zip4 = match zip4 { "" => None, _ => Some(zip4.into()) };
105
106        USPSAddress {
107            firmname, 
108            address1,
109            address2,
110            city,
111            state,
112            urbanization,
113            zip5,
114            zip4,
115            ..Default::default()
116       }
117    }
118
119    /// Convenience function that only requires common fields to be passed. 
120    /// # Example
121    /// ```
122    /// # use usps_api::address::USPSAddress;
123    /// let address = USPSAddress::quick("123 Main St.", "Apt 1", "Seattle", "Washington", "99999");
124    /// ```
125    pub fn quick(address_1: &str,
126               address_2: &str,
127               city: &str,
128               state: &str,
129               zip5: &str) -> Self {
130
131        let address1 = match address_1 { "" => None, _ => Some(address_1.into()) };
132        let address2 = match address_2 { "" => None, _ => Some(address_2.into()) };
133        let city = match city { "" => None, _ => Some(city.into()) };
134        let state = match state { "" => None, _ => Some(state.into()) };
135        let zip5 = match zip5 { "" => None, _ => Some(zip5.into()) };
136
137        USPSAddress {
138            firmname: None,
139            address1,
140            address2,
141            city,
142            state,
143            urbanization: None,
144            zip5,
145            zip4: None,
146            ..Default::default()
147       }
148    }
149
150    pub(in crate) fn from_xml(xml: roxmltree::Document) -> Self {
151        let mut address = USPSAddress::default();
152
153        for node in xml.descendants() {
154            if node.is_element() {
155                let text = Some(String::from(node.text().unwrap_or("")));
156                println!("{:?}", node.tag_name().name());
157                println!("{:?}", node.text().unwrap_or("gay"));
158                match node.tag_name().name() {
159                    "FirmName" => address.firmname = text,
160                    "Address1" => address.address1 = text,
161                    "Address2" => address.address2 = text,
162                    "City"     => address.city     = text,
163                    "CityAbbreviation" => address.city_abbreviation = text,
164                    "State" => address.state = text,
165                    "Urbanization" => address.urbanization = text,
166                    "Zip5" => address.zip5 = text,
167                    "Zip4" => address.zip4 = text,
168                    "DeliveryPoint" => address.delivery_point = text,
169                    "CarrierRoute" => address.carrier_route = text,
170                    "Footnotes" => address.footnotes = text,
171                    "DPVConfirmation" => address.dpv_confirmation = text,
172                    "DPVCMRA" => address.dpv_cmra = text,
173                    "DPVFootnotes" => address.dpv_footnotes = text,
174                    "Business" => address.business = text,
175                    "CentralDeliveryPoint" => address.central_delivery_point = text,
176                    "Vacant" => address.vacant = text,
177                    _ => ()
178                }
179            }
180        }
181
182        address
183    }
184
185    /* Returns an Address as XML for use in API calls */
186    pub(in crate) fn xml(&self) -> String {
187        let mut s = String::with_capacity(150);
188        s.push_str("<Address ID=\"0\">");
189
190            s.push_str("<FirmName>");
191            if let Some(ref a) = self.firmname { s.push_str(a); }
192            s.push_str("</FirmName>");
193
194            s.push_str("<Address1>");
195            if let Some(ref a) = self.address1 { s.push_str(a); }
196            s.push_str("</Address1>");
197
198            s.push_str("<Address2>");
199            if let Some(ref a) = self.address2 { s.push_str(a); }
200            s.push_str("</Address2>");
201
202            s.push_str("<City>");
203            if let Some(ref a) = self.city { s.push_str(a); }
204            s.push_str("</City>");
205
206            if let Some(ref a) = self.state { 
207                s.push_str("<State>");
208                s.push_str(a);
209                s.push_str("</State>");
210            }
211
212            if let Some(ref a) = self.urbanization {
213                s.push_str("<Urbanization>");
214                s.push_str(a);
215                s.push_str("</Urbanization>");
216            }
217
218            s.push_str("<Zip5>");
219            if let Some(ref a) = self.zip5 { s.push_str(a); }
220            s.push_str("</Zip5>");
221
222            s.push_str("<Zip4>");
223            if let Some(ref a) = self.zip4 { s.push_str(a); }
224            s.push_str("</Zip4>");
225
226        s.push_str("</Address>");
227        s
228    }
229}
230
231#[cfg(test)]
232mod address_tests {
233    use super::*;
234
235    #[test]
236    fn init_constructor() {
237        let dummy_struct = USPSAddress {
238            firmname: None,
239            address1: Some(String::from("123 Main St")),
240            address2: None,
241            city: Some(String::from("Seattle")),
242            state: Some(String::from("WA")),
243            urbanization: None,
244            zip5: Some(String::from("98119")),
245            zip4: None,
246            ..Default::default()
247        };
248
249        let constructed = USPSAddress::init("", "123 Main St", "", "Seattle", "WA", "", "98119", "");
250
251        assert_eq!(constructed, dummy_struct);
252    }
253
254    #[test]
255    fn quick_constructor() {
256        let dummy_struct = USPSAddress {
257            firmname: None,
258            address1: Some(String::from("123 Main St")),
259            address2: None,
260            city: Some(String::from("Seattle")),
261            state: Some(String::from("WA")),
262            urbanization: None,
263            zip5: Some(String::from("98119")),
264            zip4: None,
265            ..Default::default()
266        };
267
268        let constructed = USPSAddress::quick("123 Main St", "", "Seattle", "WA", "98119");
269
270        assert_eq!(constructed, dummy_struct);
271    }
272
273    #[test]
274    fn to_xml_1() {
275        let a = USPSAddress::quick("123 Main St", "", "Seattle", "WA", "98119");
276        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>");
277        assert_eq!(a.xml(), s);
278    }
279
280    #[test]
281    fn to_xml_2() {
282        let a = USPSAddress::init("Acme, LLC", "123 Main St", "Apt 456", "Seattle", "WA", "", "98119", "1234");
283        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>");
284        assert_eq!(a.xml(), s);
285    }
286}
287