Skip to content

Commit

Permalink
[#2] Refactor address module
Browse files Browse the repository at this point in the history
  • Loading branch information
kodemartin committed Jul 18, 2021
1 parent 70c5dcd commit e0570ef
Showing 1 changed file with 153 additions and 87 deletions.
240 changes: 153 additions & 87 deletions src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,152 +2,218 @@
//!
//! # Examples
//!
//! ## Parsing
//!
//! ```
//! use rustpostal::LibModules;
//! use rustpostal::address;
//! use rustpostal::{address, LibModules};
//! use rustpostal::error::RuntimeError;
//!
//! fn main() {
//! unsafe { rustpostal::setup(LibModules::Address) }
//! fn main() -> Result<(), RuntimeError> {
//! let postal_module = LibModules::Address;
//! postal_module.setup()?;
//!
//! let address = "St Johns Centre, Rope Walk, Bedford, Bedfordshire, MK42 0XE, United Kingdom";
//!
//! let labeled_tokens = address::parse_address(address, None, None);
//!
//! for (label, token) in labeled_tokens.into_iter() {
//! for (label, token) in &labeled_tokens {
//! println!("{}: {}", label, token);
//! }
//!
//! unsafe { rustpostal::teardown(LibModules::Address) }
//! Ok(())
//! }
//! ```
use std::ffi::{CStr, CString};
use std::ffi::{CStr, CString, NulError};
use std::slice::Iter;

use crate::ffi;

/// Represents the parsing result.
#[derive(Clone, Default, Debug, Hash)]
pub struct AddressParserResponse {
pub tokens: Vec<String>,
pub labels: Vec<String>,
tokens: Vec<String>,
labels: Vec<String>,
}

impl AddressParserResponse {
/// Create a new value.
pub fn new() -> AddressParserResponse {
AddressParserResponse {
tokens: Vec::new(),
labels: Vec::new(),
}
Default::default()
}
}

impl IntoIterator for AddressParserResponse {
type Item = (String, String);
type IntoIter = std::iter::Zip<std::vec::IntoIter<String>, std::vec::IntoIter<String>>;
impl<'a> IntoIterator for &'a AddressParserResponse {
type Item = (&'a String, &'a String);
type IntoIter = std::iter::Zip<Iter<'a, String>, Iter<'a, String>>;

/// Iterates over `(label, token)` pairs.
fn into_iter(self) -> Self::IntoIter {
self.labels.into_iter().zip(self.tokens)
self.labels[..].iter().zip(self.tokens[..].iter())
}
}

/// Parsing options.
pub struct AddressParserOptions<'a> {
language: Option<&'a str>,
country: Option<&'a str>,
#[derive(Default)]
pub struct AddressParserOptions {
language: Option<CString>,
country: Option<CString>,
}

impl<'a> AddressParserOptions<'a> {
/// Create a new instance with the default options from the library.
impl AddressParserOptions {
/// Create options for the address parser.
///
/// ## Examples
///
/// `language`, and `country` if given override the default values.
pub fn new(language: Option<&'a str>, country: Option<&'a str>) -> AddressParserOptions<'a> {
let (mut default_l, mut default_c) = (None, None);
if language.is_none() || country.is_none() {
let (l, c) = Self::get_default_options();
default_l = l;
default_c = c;
/// ```
/// use rustpostal::address;
///
/// fn main() -> Result<(), std::ffi::NulError> {
/// let options = AddressParserOptions("en", "en")?;
/// Ok(())
/// }
/// ```
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);
}
let language = language.or(default_l);
let country = country.or(default_c);
AddressParserOptions { language, country }
Ok(options)
}

fn get_default_options() -> (Option<&'a str>, Option<&'a str>) {
let (mut language, mut country) = (None, None);
unsafe {
let options = ffi::libpostal_get_address_parser_default_options();
if !options.language.is_null() {
language = Some(CStr::from_ptr(options.language).to_str().unwrap());
}
if !options.country.is_null() {
country = Some(CStr::from_ptr(options.language).to_str().unwrap());
}
/// Get the language option.
pub fn language(&self) -> Option<&str> {
if let Some(language) = &self.language {
return Some(language.to_str().unwrap());
}
(language, country)
None
}

unsafe fn as_ffi_options(&self) -> ffi::libpostal_address_parser_options {
let (language, country): (*const libc::c_char, *const libc::c_char);
if self.language.is_none() {
language = std::ptr::null();
} else {
language = self.language.unwrap().as_ptr() as *const libc::c_char;
/// Get the country option.
pub fn country(&self) -> Option<&str> {
if let Some(country) = &self.country {
return Some(country.to_str().unwrap());
}
if self.country.is_none() {
country = std::ptr::null();
} else {
country = self.country.unwrap().as_ptr() as *const libc::c_char;
}
ffi::libpostal_address_parser_options { language, country }
None
}
}

/// Analyze address into labeled tokens.
///
/// * `address`: The postal address to parse.
/// * `language`: A language code.
/// * `country`: A country code.
pub fn parse_address(
address: &str,
language: Option<&str>,
country: Option<&str>,
) -> AddressParserResponse {
let address = CString::new(address).unwrap();
let mut response = AddressParserResponse::new();
fn update_ffi_language(&self, options: &mut ffi::libpostal_address_parser_options) {
if let Some(language) = &self.language {
options.language = language.as_ptr();
};
}

let options = AddressParserOptions::new(language, country);
fn update_ffi_country(&self, options: &mut ffi::libpostal_address_parser_options) {
if let Some(country) = &self.country {
options.country = country.as_ptr();
};
}

unsafe {
let options = options.as_ffi_options();
let raw = ffi::libpostal_parse_address(address.as_ptr() as *const libc::c_char, options);
if let Some(parsed) = raw.as_ref() {
/// Parse a postal address and derive labeled tokens using `libpostal`.
///
/// # Examples
///
/// ```
/// use rustpostal::error::RuntimeError;
/// use rustpostal::{address, LibModules};
///
/// fn main() -> Result<(), RuntimeError> {
/// let postal_module = LibModules::Address;
/// postal_module.setup()?;
///
/// let options = AddressParserOptions::new();
/// let address = "St Johns Centre, Rope Walk, Bedford, Bedfordshire, MK42 0XE, United Kingdom";
/// let labels_tokens = options.parse(address)?;
///
/// for (label, token) in &labels_tokens {
/// println!("{}: {}", label, token);
/// }
///
/// Ok(())
/// }
/// ```
///
/// # Errors
///
/// It will return an error if the address contains an internal null byte.
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 = CStr::from_ptr(*parsed.components.add(i));
let label = CStr::from_ptr(*parsed.labels.add(i));
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()));
}
};
ffi::libpostal_address_parser_response_destroy(raw);
unsafe {
ffi::libpostal_address_parser_response_destroy(raw);
}
let _c_address = unsafe { CString::from_raw(ptr) };
Ok(response)
}
}

response
/// Analyze address into labeled tokens.
///
/// * `address`: The postal address to parse.
/// * `language`: A language code.
/// * `country`: A country code.
///
/// The function wraps [`AddressParserOptions::parse`].
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() {
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"));
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(())
}
}

0 comments on commit e0570ef

Please sign in to comment.