use std::ffi::{CStr, CString, NulError};
use std::slice::Iter;
use crate::ffi;
#[derive(Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct AddressParserResponse {
tokens: Vec<String>,
labels: Vec<String>,
}
impl AddressParserResponse {
pub fn new() -> AddressParserResponse {
Default::default()
}
}
impl<'a> IntoIterator for &'a AddressParserResponse {
type Item = (&'a String, &'a String);
type IntoIter = std::iter::Zip<Iter<'a, String>, Iter<'a, String>>;
fn into_iter(self) -> Self::IntoIter {
self.labels[..].iter().zip(self.tokens[..].iter())
}
}
#[derive(Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct AddressParserOptions {
language: Option<CString>,
country: Option<CString>,
}
impl AddressParserOptions {
pub fn new(
language: Option<&str>,
country: Option<&str>,
) -> Result<AddressParserOptions, NulError> {
let mut options = AddressParserOptions::default();
if let Some(s) = language {
let c_lang = CString::new(s)?;
options.language = Some(c_lang);
}
if let Some(s) = country {
let c_country = CString::new(s)?;
options.country = Some(c_country);
}
Ok(options)
}
pub fn language(&self) -> Option<&str> {
if let Some(language) = &self.language {
return Some(language.to_str().unwrap());
}
None
}
pub fn country(&self) -> Option<&str> {
if let Some(country) = &self.country {
return Some(country.to_str().unwrap());
}
None
}
fn update_ffi_language(&self, options: &mut ffi::libpostal_address_parser_options) {
if let Some(language) = &self.language {
options.language = language.as_ptr();
};
}
fn update_ffi_country(&self, options: &mut ffi::libpostal_address_parser_options) {
if let Some(country) = &self.country {
options.country = country.as_ptr();
};
}
pub fn parse<'b>(&self, address: &'b str) -> Result<AddressParserResponse, NulError> {
let c_address = CString::new(address)?;
let mut response = AddressParserResponse::new();
let ptr = c_address.into_raw();
let mut ffi_options = unsafe { ffi::libpostal_get_address_parser_default_options() };
self.update_ffi_language(&mut ffi_options);
self.update_ffi_country(&mut ffi_options);
let raw = unsafe { ffi::libpostal_parse_address(ptr, ffi_options) };
if let Some(parsed) = unsafe { raw.as_ref() } {
for i in 0..parsed.num_components {
let component = unsafe { CStr::from_ptr(*parsed.components.add(i)) };
let label = unsafe { CStr::from_ptr(*parsed.labels.add(i)) };
response
.tokens
.push(String::from(component.to_str().unwrap()));
response.labels.push(String::from(label.to_str().unwrap()));
}
};
unsafe {
ffi::libpostal_address_parser_response_destroy(raw);
}
let _c_address = unsafe { CString::from_raw(ptr) };
Ok(response)
}
}
pub fn parse_address(
address: &str,
language: Option<&str>,
country: Option<&str>,
) -> Result<AddressParserResponse, NulError> {
let options = AddressParserOptions::new(language, country)?;
options.parse(address)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::RuntimeError;
use crate::LibModules;
#[test]
fn default_address_parser_options() -> Result<(), NulError> {
let options = AddressParserOptions::new(None, None)?;
assert_eq!(options.language(), None);
assert_eq!(options.country(), None);
let options = AddressParserOptions::new(None, Some("EN"))?;
assert_eq!(options.language(), None);
assert_eq!(options.country(), Some("EN"));
Ok(())
}
#[test]
fn address_parser_options_parse() -> Result<(), RuntimeError> {
let postal_module = LibModules::Address;
postal_module.setup()?;
let options = AddressParserOptions::new(None, None)?;
let address = "St Johns Centre, Rope Walk, Bedford, Bedfordshire, MK42 0XE, United Kingdom";
let labeled_tokens = options.parse(address)?;
for (label, token) in &labeled_tokens {
println!("{}: {}", label, token);
}
Ok(())
}
}