diff --git a/Makefile b/Makefile index c136138..7680958 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,9 @@ logger: us_autocomplete_pro_api: RUST_LOG=trace cargo run --example us_autocomplete_pro_api +us_autocomplete_api: + RUST_LOG=trace cargo run --example us_autocomplete_api + us_extract_api: RUST_LOG=trace cargo run --example us_extract_api @@ -71,9 +74,6 @@ us_enrichment_business_name_search_api: integration: cargo test --test us_street_integration -- --ignored -examples: international_autocomplete_api international_postal_code_api international_street_api logger us_autocomplete_pro_api us_extract_api us_reverse_geo_api us_street_api us_street_match_strategy us_zipcode_api us_enrichment_api us_enrichment_address_search_api us_enrichment_business_api - -.PHONY: clean test dependencies package examples clippy integration international_autocomplete_api international_postal_code_api international_street_api logger us_autocomplete_pro_api us_extract_api us_reverse_geo_api us_street_api us_street_match_strategy us_street_iana_timezone us_zipcode_api us_enrichment_api us_enrichment_address_search_api us_enrichment_business_api -examples: international_autocomplete_api international_postal_code_api international_street_api logger us_autocomplete_pro_api us_extract_api us_reverse_geo_api us_street_api us_zipcode_api us_enrichment_api us_enrichment_address_search_api us_enrichment_business_api us_enrichment_business_name_search_api +examples: international_autocomplete_api international_postal_code_api international_street_api logger us_autocomplete_pro_api us_autocomplete_api us_extract_api us_reverse_geo_api us_street_api us_street_match_strategy us_zipcode_api us_enrichment_api us_enrichment_address_search_api us_enrichment_business_api us_enrichment_business_name_search_api -.PHONY: clean test dependencies package examples clippy integration international_autocomplete_api international_postal_code_api international_street_api logger us_autocomplete_pro_api us_extract_api us_reverse_geo_api us_street_api us_street_iana_timezone us_zipcode_api us_enrichment_api us_enrichment_address_search_api us_enrichment_business_api us_enrichment_business_name_search_api +.PHONY: clean test dependencies package examples clippy integration international_autocomplete_api international_postal_code_api international_street_api logger us_autocomplete_pro_api us_autocomplete_api us_extract_api us_reverse_geo_api us_street_api us_street_match_strategy us_street_iana_timezone us_zipcode_api us_enrichment_api us_enrichment_address_search_api us_enrichment_business_api us_enrichment_business_name_search_api diff --git a/smarty-rust-sdk/Cargo.toml b/smarty-rust-sdk/Cargo.toml index 1c3a913..2339ed0 100644 --- a/smarty-rust-sdk/Cargo.toml +++ b/smarty-rust-sdk/Cargo.toml @@ -49,6 +49,9 @@ name = "us_extract_api" [[example]] name = "us_autocomplete_pro_api" +[[example]] +name = "us_autocomplete_api" + [[example]] name = "international_street_api" diff --git a/smarty-rust-sdk/examples/us_autocomplete_api.rs b/smarty-rust-sdk/examples/us_autocomplete_api.rs new file mode 100644 index 0000000..a85ed9c --- /dev/null +++ b/smarty-rust-sdk/examples/us_autocomplete_api.rs @@ -0,0 +1,62 @@ +extern crate serde_json; +extern crate smarty_rust_sdk; +extern crate tokio; + +// This example is for US Autocomplete (V2). It has the same name as a previous product +// which has been deprecated since 2022, which we refer to as US Autocomplete Basic. +// If you are still using US Autocomplete Basic, this SDK will not work. + +use smarty_rust_sdk::sdk::authentication::BasicAuthCredential; +use smarty_rust_sdk::sdk::options::OptionsBuilder; +use smarty_rust_sdk::us_autocomplete_api::client::USAutocompleteClient; +use smarty_rust_sdk::us_autocomplete_api::lookup::{Lookup, PreferGeolocation, Source}; +use std::error::Error; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Documentation for input fields can be found at: + // https://www.smarty.com/docs/apis/us-autocomplete-v2/reference#http-request-input-fields + + let lookup = &mut Lookup { + search: "1042 W Center".to_string(), + max_results: 5, + city_filter: vec!["Orem,UT".to_string(), "Madisonville,KY".to_string()], + state_filter: vec!["UT".to_string(), "KY".to_string()], + prefer_state: vec!["KY".to_string()], + prefer_ratio: 3, + prefer_geolocation: PreferGeolocation::GeolocateCity, + source: Some(Source::All), + ..Default::default() + }; + + let authentication = BasicAuthCredential::new( + std::env::var("SMARTY_AUTH_ID").expect("Missing SMARTY_AUTH_ID env variable"), + std::env::var("SMARTY_AUTH_TOKEN").expect("Missing SMARTY_AUTH_TOKEN env variable"), + ); + + let options = OptionsBuilder::new(Some(authentication)) + .with_logging() + .build(); + + let client = USAutocompleteClient::new(options)?; + client.send(lookup).await?; + + println!("{}", serde_json::to_string_pretty(&lookup.results)?); + + // Expand the secondaries of a result that has an entry_id by passing it back as the selected address. + if let Some(entry_id) = lookup + .results + .suggestions + .iter() + .find(|suggestion| !suggestion.entry_id.is_empty()) + .map(|suggestion| suggestion.entry_id.clone()) + { + lookup.selected = entry_id; + client.send(lookup).await?; + + println!("Secondaries:"); + println!("{}", serde_json::to_string_pretty(&lookup.results)?); + } + + Ok(()) +} diff --git a/smarty-rust-sdk/src/lib.rs b/smarty-rust-sdk/src/lib.rs index 5a6ffa3..a5287ec 100644 --- a/smarty-rust-sdk/src/lib.rs +++ b/smarty-rust-sdk/src/lib.rs @@ -2,6 +2,7 @@ pub mod international_autocomplete_api; pub mod international_postal_code_api; pub mod international_street_api; pub mod sdk; +pub mod us_autocomplete_api; pub mod us_autocomplete_pro_api; pub mod us_enrichment_api; pub mod us_extract_api; diff --git a/smarty-rust-sdk/src/us_autocomplete_api/client.rs b/smarty-rust-sdk/src/us_autocomplete_api/client.rs new file mode 100644 index 0000000..af587b6 --- /dev/null +++ b/smarty-rust-sdk/src/us_autocomplete_api/client.rs @@ -0,0 +1,17 @@ +use crate::sdk::client::Client; +use crate::sdk::error::SmartyError; +use crate::sdk::options::Options; +use crate::sdk::send_request; +use crate::us_autocomplete_api::lookup::Lookup; +use crate::us_autocomplete_api::suggestion::SuggestionListing; +use reqwest::Method; +use smarty_rust_proc_macro::smarty_api; + +#[smarty_api( + api_path = "v2/lookup", + default_url = "https://us-autocomplete.api.smarty.com/", + lookup_style(lookup), + lookup_type = "Lookup", + result_type = "SuggestionListing" +)] +pub struct USAutocompleteClient; diff --git a/smarty-rust-sdk/src/us_autocomplete_api/lookup.rs b/smarty-rust-sdk/src/us_autocomplete_api/lookup.rs new file mode 100644 index 0000000..e87ba6d --- /dev/null +++ b/smarty-rust-sdk/src/us_autocomplete_api/lookup.rs @@ -0,0 +1,118 @@ +use crate::sdk::{has_param, has_vec_param}; +use crate::us_autocomplete_api::suggestion::SuggestionListing; +use serde::Serialize; +use std::fmt::{Display, Formatter}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Lookup { + pub search: String, + pub source: Option, + pub max_results: i32, + pub city_filter: Vec, + pub state_filter: Vec, + pub zip_filter: Vec, + pub exclude_states: Vec, + pub prefer_city: Vec, + pub prefer_state: Vec, + pub prefer_zip: Vec, + pub prefer_ratio: i32, + pub prefer_geolocation: PreferGeolocation, + pub selected: String, + pub exclude: Vec, + + pub results: SuggestionListing, +} + +impl Default for Lookup { + fn default() -> Self { + Lookup { + search: String::default(), + source: None, + max_results: 0, + city_filter: vec![], + state_filter: vec![], + zip_filter: vec![], + exclude_states: vec![], + prefer_city: vec![], + prefer_state: vec![], + prefer_zip: vec![], + prefer_ratio: 0, + prefer_geolocation: PreferGeolocation::default(), + selected: String::default(), + exclude: vec![], + + results: SuggestionListing { + suggestions: vec![], + }, + } + } +} + +impl Lookup { + pub(crate) fn into_param_array(self) -> Vec<(String, String)> { + let geolocation = self.geolocation_param(); + let mut result: Vec<(String, String)> = [ + has_param("search".to_string(), self.search), + has_param("max_results".to_string(), self.max_results), + has_vec_param("include_only_cities".to_string(), ";", self.city_filter), + has_vec_param("include_only_states".to_string(), ";", self.state_filter), + has_vec_param("include_only_zip_codes".to_string(), ";", self.zip_filter), + has_vec_param("exclude_states".to_string(), ";", self.exclude_states), + has_vec_param("prefer_cities".to_string(), ";", self.prefer_city), + has_vec_param("prefer_states".to_string(), ";", self.prefer_state), + has_vec_param("prefer_zip_codes".to_string(), ";", self.prefer_zip), + has_param("prefer_ratio".to_string(), self.prefer_ratio), + geolocation, + has_param("selected".to_string(), self.selected), + has_vec_param("exclude".to_string(), ",", self.exclude), + ] + .iter() + .filter_map(Option::clone) + .collect::>(); + + if let Some(source) = self.source { + result.push(("source".to_string(), source.to_string())); + } + + result + } + + fn geolocation_param(&self) -> Option<(String, String)> { + if !self.zip_filter.is_empty() || !self.prefer_zip.is_empty() { + return Some(("prefer_geolocation".to_string(), "none".to_string())); + } + + match self.prefer_geolocation { + PreferGeolocation::GeolocateCity => { + Some(("prefer_geolocation".to_string(), "city".to_string())) + } + PreferGeolocation::GeolocateNone => { + Some(("prefer_geolocation".to_string(), "none".to_string())) + } + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize)] +pub enum PreferGeolocation { + #[default] + #[serde(rename = "none")] + GeolocateNone, + #[serde(rename = "city")] + GeolocateCity, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Source { + All, + Postal, +} + +impl Display for Source { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Source::All => write!(f, "all"), + Source::Postal => write!(f, "postal"), + } + } +} diff --git a/smarty-rust-sdk/src/us_autocomplete_api/mod.rs b/smarty-rust-sdk/src/us_autocomplete_api/mod.rs new file mode 100644 index 0000000..4a5f964 --- /dev/null +++ b/smarty-rust-sdk/src/us_autocomplete_api/mod.rs @@ -0,0 +1,86 @@ +pub mod client; +pub mod lookup; +pub mod suggestion; + +#[cfg(test)] +mod tests { + use crate::sdk::options::OptionsBuilder; + use crate::us_autocomplete_api::client::USAutocompleteClient; + use crate::us_autocomplete_api::lookup::{Lookup, PreferGeolocation, Source}; + + #[test] + fn client_test() { + let client = USAutocompleteClient::new(OptionsBuilder::new(None).build()).unwrap(); + + assert_eq!( + client.client.url.to_string(), + "https://us-autocomplete.api.smarty.com/v2/lookup".to_string() + ) + } + + #[test] + fn lookup_test() { + let lookup = Lookup { + search: "1042 W Center".to_string(), + max_results: 5, + city_filter: vec!["Denver,CO".to_string(), "Orem,UT".to_string()], + state_filter: vec!["CO".to_string(), "UT".to_string()], + prefer_city: vec!["Denver".to_string()], + prefer_state: vec!["CO".to_string()], + prefer_ratio: 3, + prefer_geolocation: PreferGeolocation::GeolocateCity, + source: Some(Source::All), + selected: "selectedAddress".to_string(), + exclude: vec![ + "excludedAddress".to_string(), + "excludedAddress2".to_string(), + ], + ..Default::default() + }; + + let expected_results = vec![ + ("search".to_string(), "1042 W Center".to_string()), + ("max_results".to_string(), "5".to_string()), + ( + "include_only_cities".to_string(), + "Denver,CO;Orem,UT".to_string(), + ), + ("include_only_states".to_string(), "CO;UT".to_string()), + ("prefer_cities".to_string(), "Denver".to_string()), + ("prefer_states".to_string(), "CO".to_string()), + ("prefer_ratio".to_string(), "3".to_string()), + ("prefer_geolocation".to_string(), "city".to_string()), + ("selected".to_string(), "selectedAddress".to_string()), + ( + "exclude".to_string(), + "excludedAddress,excludedAddress2".to_string(), + ), + ("source".to_string(), "all".to_string()), + ]; + + assert_eq!(lookup.into_param_array(), expected_results) + } + + #[test] + fn lookup_excludes_source_when_not_specified() { + let lookup = Lookup { + search: "1042 W Center".to_string(), + ..Default::default() + }; + + let params = lookup.into_param_array(); + assert!(!params.iter().any(|(k, _)| k == "source")); + } + + #[test] + fn lookup_includes_source_postal() { + let lookup = Lookup { + search: "1042 W Center".to_string(), + source: Some(Source::Postal), + ..Default::default() + }; + + let params = lookup.into_param_array(); + assert!(params.iter().any(|(k, v)| k == "source" && v == "postal")); + } +} diff --git a/smarty-rust-sdk/src/us_autocomplete_api/suggestion.rs b/smarty-rust-sdk/src/us_autocomplete_api/suggestion.rs new file mode 100644 index 0000000..d480668 --- /dev/null +++ b/smarty-rust-sdk/src/us_autocomplete_api/suggestion.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct SuggestionListing { + pub suggestions: Vec, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct Suggestion { + pub smarty_key: String, + pub entry_id: String, + pub street_line: String, + pub secondary: String, + pub city: String, + pub state: String, + pub zipcode: String, + pub entries: i32, + pub source: String, +} diff --git a/smarty-rust-sdk/src/us_autocomplete_pro_api/lookup.rs b/smarty-rust-sdk/src/us_autocomplete_pro_api/lookup.rs index 432881b..3701511 100644 --- a/smarty-rust-sdk/src/us_autocomplete_pro_api/lookup.rs +++ b/smarty-rust-sdk/src/us_autocomplete_pro_api/lookup.rs @@ -54,6 +54,7 @@ impl Lookup { has_vec_param("include_only_states".to_string(), ";", self.state_filter), has_vec_param("include_only_zip_codes".to_string(), ";", self.zip_filter), has_vec_param("exclude_states".to_string(), ";", self.exclude_states), + has_vec_param("prefer_cities".to_string(), ";", self.prefer_city), has_vec_param("prefer_states".to_string(), ";", self.prefer_state), has_vec_param("prefer_zip_codes".to_string(), ";", self.prefer_zip), has_param("prefer_ratio".to_string(), self.prefer_ratio), diff --git a/smarty-rust-sdk/src/us_autocomplete_pro_api/mod.rs b/smarty-rust-sdk/src/us_autocomplete_pro_api/mod.rs index 9eafb35..7a8a06c 100644 --- a/smarty-rust-sdk/src/us_autocomplete_pro_api/mod.rs +++ b/smarty-rust-sdk/src/us_autocomplete_pro_api/mod.rs @@ -25,6 +25,7 @@ mod tests { max_results: 5, city_filter: vec!["Denver,CO".to_string(), "Orem,UT".to_string()], state_filter: vec!["CO".to_string(), "UT".to_string()], + prefer_city: vec!["Denver".to_string()], prefer_state: vec!["CO".to_string()], prefer_ratio: 3, geolocation: Geolocation::GeolocateCity, @@ -40,6 +41,7 @@ mod tests { "Denver,CO;Orem,UT".to_string(), ), ("include_only_states".to_string(), "CO;UT".to_string()), + ("prefer_cities".to_string(), "Denver".to_string()), ("prefer_states".to_string(), "CO".to_string()), ("prefer_ratio".to_string(), "3".to_string()), ("prefer_geolocation".to_string(), "city".to_string()),