1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
//! # USPS WebTools
//! A Rust library for interfacing with the USPS Web API 
#![deny(missing_docs,
        missing_debug_implementations, missing_copy_implementations,
        trivial_casts, trivial_numeric_casts,
        unstable_features, unsafe_code,
        unused_import_braces, unused_qualifications)]

/* Standard Libary */
use std::env;

/* Third Party Libraries */
use reqwest;
use roxmltree;

/* Modules */
pub mod address;

/* Static Strings */
const SECURE_ENDPOINT:   &'static str = "https://secure.shippingapis.com/ShippingAPI.dll?API=";
const UNSECURE_ENDPOINT: &'static str = "http://production.shippingapis.com/ShippingAPI.dll?API=";

const ENV_USER_ID: &'static str = "USPS_USER_ID";
const ENV_PASSWORD: &'static str = "USPS_PASSWORD";

/// This struct is how you will make API calls to the USPS. It can be initialized with a hardcoded user_id and password using the 'init' contructor or it can attempt to capture these values from the environment (USPS_USER_ID and USPS_PASSWORD respectively) using the 'new' constructor.
#[derive(Debug, PartialEq)]
pub struct USPSWebTool {
    secure: bool,
    user_id: String,
    password: String,
}

/* Constructor and Setter Implementations */
impl USPSWebTool {
    /// Used to build a new USPSWebTool struct by specifying the USPS authentication credentials directly."
	/// # Example
	/// ```
	/// # use usps_api::USPSWebTool;
    /// let usps_api = USPSWebTool::init("XXXX", "YYYY");
	/// ```
    pub fn init(user_id: &str, password: &str) -> Self {
        USPSWebTool {
            secure: true,
            user_id: String::from(user_id),
            password: String::from(password)
        }
    }

    /// Used to build a new USPSWebTool struct by looking at the environment variables "USPS_USER_ID" and "USPS_PASSWORD to specify the USPS authentication credentials." 
	/// # Example
	/// ```
	/// # use usps_api::USPSWebTool;
    /// # use std::env;
    /// # fn main() -> Result<(), env::VarError> {
    /// env::set_var("USPS_USER_ID", "XXXX");
    /// env::set_var("USPS_PASSWORD", "YYYY");
    /// let usps_api = USPSWebTool::new()?;
    /// # Ok(())
    /// # }
	/// ```
    pub fn new() -> Result<Self, env::VarError> {
        let user_id = env::var(ENV_USER_ID)?;
        let password = env::var(ENV_PASSWORD)?;

        Ok(USPSWebTool {
            secure: true,
            user_id,
            password,
        })
    }

    /* Disables security */
    /// Will use the unsecured endpoint for communication with the USPS API. This feature is **not** recommended.
    ///
	/// # Example
	/// ```
	/// # use usps_api::USPSWebTool;
    /// let usps_api = USPSWebTool::init("XXXX", "YYYY").use_http();
	/// ```
    pub fn use_http(mut self) -> Self {
       self.secure = false;
       self
    }

    /// Accepts a USPS Address and returns a result with the 'correct' form of the address.
    pub fn verify_address(&self, address: address::USPSAddress) -> Result<address::USPSAddress, Box<dyn std::error::Error>> {
        let mut req;
        if self.secure {
            req = String::from(SECURE_ENDPOINT);
        } else {
            req = String::from(UNSECURE_ENDPOINT);
        }
        
        /* Construct the query URL */
        req.push_str("Verify&XML=");
        req.push_str(&format!("<AddressValidateRequest USERID=\"{}\"><Revision>1</Revision>", self.user_id));
        req.push_str(&address.xml());
        req.push_str("</AddressValidateRequest>"); 
        
        /* Fetch the result */
        let body = reqwest::get(&req)?.text()?;
        println!("{:?}", body);

        /* Check if the XML is an error */
        let xml = roxmltree::Document::parse(&body)?;
        /* if xml.root_element().tag_name().name() == "Error" { */ 
        /* } */
        
        Ok(address::USPSAddress::from_xml(xml))
    }
}

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

    const DUMMY_UN: &'static str = "USERNAME";
    const DUMMY_PW: &'static str = "PASSWORD";

    #[test]
    fn init_constructor() {
        let dummy_struct = USPSWebTool {
            secure: true,
            user_id: String::from(DUMMY_UN), 
            password: String::from(DUMMY_PW),
        };

        assert_eq!(USPSWebTool::init(DUMMY_UN, DUMMY_PW), dummy_struct);
    }

    #[test]
    fn new_constructor() {
        env::set_var(ENV_USER_ID, DUMMY_UN);
        env::set_var(ENV_PASSWORD, DUMMY_PW);

        let dummy_struct = USPSWebTool {
            secure: true,
            user_id: String::from(DUMMY_UN), 
            password: String::from(DUMMY_PW),
        };

        assert_eq!(USPSWebTool::new().unwrap(), dummy_struct);
    }

    #[test]
    #[should_panic]
    fn new_contructor_no_env() {
        env::remove_var(ENV_USER_ID);
        env::remove_var(ENV_PASSWORD);

        let dummy_struct = USPSWebTool {
            secure: true,
            user_id: String::from(DUMMY_UN), 
            password: String::from(DUMMY_PW),
        };

        assert_eq!(USPSWebTool::new().unwrap(), dummy_struct);
    }

    #[test]
    fn use_http() {
        let dummy_struct = USPSWebTool {
            secure: false,
            user_id: String::from(DUMMY_UN), 
            password: String::from(DUMMY_PW),
        };

        assert_eq!(USPSWebTool::init(DUMMY_UN, DUMMY_PW).use_http(), 
                   dummy_struct);
    }

    mod address_test {
        use super::*;

        #[test]
        fn test_address_1() {

            let api = USPSWebTool::init("348XVRQT1293", "088NI00MI876");
            let home = address::USPSAddress::quick("429 2nd Ave W", "104", "Seattle", "WA", "98119");
            api.verify_address(home).unwrap();
            

        }
    }
}