pub mod providers;
pub mod common;
use std::any::TypeId;
use std::collections::BTreeMap;
use std::str::FromStr;
use std::fmt;
use regex::Regex;
use serde::Deserialize;
use strum_macros::{AsRefStr, EnumString};
use self::common::dataitems::{DataItem, DataItemIndex};
use self::providers::Provider;
use super::category::CategoryKey;
use super::category;
#[derive(Clone)]
pub struct UAPDefinition {
pub category: CategoryKey,
pub provider: Provider,
pub uap: UAP
}
#[derive(Clone)]
pub struct UAPDefinitions {
pub category_uap_map: BTreeMap<CategoryKey, Vec<UAPDefinition>>
}
impl UAPDefinitions {
pub fn add_definition(&mut self, category_key: category::CategoryKey, uap_definitions: Vec<UAPDefinition>) {
self.category_uap_map.entry(category_key)
.or_insert_with(|| uap_definitions);
}
}
#[derive(Deserialize)]
pub struct UAPCsvRecord {
_frn: String,
_code: String,
_description: String,
_length: String,
_unit: String,
}
#[derive(Debug, Clone)]
pub struct UAPLoadError {
message: String
}
impl fmt::Display for UAPLoadError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
}
#[derive(Clone)]
pub struct UAP {
pub category: u8,
pub message_type: String,
pub data_items: Vec<UAPDataItem>
}
impl UAP {
pub fn new(category: u8, message_type: &str, data_sequence: Vec<UAPDataItem>) -> UAP {
UAP {
category: category,
message_type: message_type.into(),
data_items: data_sequence,
}
}
}
#[derive(Clone)]
pub struct UAPDataItem {
pub frn: u8,
pub data_item: DataItem,
}
#[derive(Clone)]
pub struct UAPBlock {
pub uap_data_item_bits: Vec<UAPDataItem>,
}
#[derive(AsRefStr, Clone, Debug, PartialEq, EnumString)]
pub enum UAPEntryLength {
Fixed { size: u8 },
Variable { size_first: u8 },
FixedRepeatable { size_first: u8, size_repetitive: u8 },
CompoundField { size_first: u8, size_second: u8 }
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomUAPEntryError {
message: String
}
impl fmt::Display for CustomUAPEntryError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
}
pub struct UAPEntry {
pub frn: u8,
pub code: DataItemIndex,
pub description: String,
pub type_id: TypeId,
pub length: UAPEntryLength,
}
impl UAPEntry {
pub fn create(
frn: &str,
code: &str,
description: &str,
length: &str,
type_id: TypeId,
)
-> Result<UAPEntry, CustomUAPEntryError> {
let frn_u8: u8 = match frn.trim().parse() {
Ok(u) => u,
Err(_) => return Err(CustomUAPEntryError { message: "bad field number reference (frn) provided".to_string()})
};
let code_str = "_".to_string() + &code;
let dataitemindex = match DataItemIndex::from_str(code_str.as_str()) {
Ok(d) => d,
Err(_) => return Err(CustomUAPEntryError { message: "bad field number reference (frn) provided".to_string()}),
};
let len: UAPEntryLength = match UAPEntry::parse_length(length) {
Ok(u) => u,
Err(e) => return Err(CustomUAPEntryError {message: e.message})
};
Ok(UAPEntry {
frn: frn_u8,
code: dataitemindex,
description: description.to_string(),
type_id,
length: len })
}
fn parse_length(length: &str) -> Result<UAPEntryLength, CustomUAPEntryError> {
let numbers: Vec<&str> = length.split(|c| c == '+' || c == '*').collect();
let pluses: Vec<_> = length.match_indices(|c| c == '+').collect();
let stars: Vec<_> = length.match_indices(|c| c == '*').collect();
let re_fixed: Regex = Regex::new(r"^[1-9][0-9]{0,2}$").unwrap();
let is_fixed_match = re_fixed.is_match(length);
let is_fixed = pluses.len() == 0 && stars.len() == 0 &&
is_fixed_match;
let re_repeat: Regex = Regex::new(r"^[1-9][0-9]{0,2}\+[1-9][0-9]{0,2}\*[a-z]$").unwrap();
let is_repeat_match = re_repeat.is_match(length);
let is_repetitive = pluses.len()>0 && stars.len() > 0 &&
(pluses.len() == 1 || stars.len() == 1) &&
is_repeat_match;
let re_vary: Regex = Regex::new(r"^([0-9][0-9]{0,2}\+){1}$").unwrap();
let is_vary_match = re_vary.is_match(length);
let is_variable = pluses.len() > 0 && stars.len() == 0 &&
pluses.len() < 3 &&
is_vary_match;
let re_compound: Regex = Regex::new(r"^([0-9][0-9]{0,2}\+){2}$").unwrap();
let is_compound_match = re_compound.is_match(length);
let is_compound = pluses.len() > 0 && stars.len() == 0 &&
pluses.len() < 3 &&
is_compound_match;
if is_fixed {
if numbers.len() == 1 {
match numbers[0].trim().parse::<u8>() {
Ok(u) => return Ok(UAPEntryLength::Fixed { size: u }),
Err(_) => {
let msg = "invalid fixed length provided";
return Err(CustomUAPEntryError { message: msg.to_string() });
}
};
}
}
if is_repetitive {
let size_first = match numbers[0].trim().parse::<u8>() {
Ok(u) => u,
Err(_) => {
let msg = "invalid repetitive size first provided";
return Err(CustomUAPEntryError { message: msg.to_string() });
}
};
let size_repetitive = match numbers[1].trim().parse::<u8>() {
Ok(u) => u,
Err(_) => {
let msg = "invalid repetitive size repetitive provided";
return Err(CustomUAPEntryError { message: msg.to_string() });
}
};
return Ok(UAPEntryLength::FixedRepeatable { size_first, size_repetitive })
}
if is_variable {
let size_first = match numbers[0].trim().parse::<u8>() {
Ok(u) => u,
Err(_) => {
let msg = "invalid variable length provided";
return Err(CustomUAPEntryError { message: msg.to_string() });
}
};
return Ok(UAPEntryLength::Variable { size_first })
}
if is_compound {
let size_first = match numbers[0].trim().parse::<u8>() {
Ok(u) => u,
Err(_) => {
let msg = fmt::format(format_args!("invalid length provided for field"));
return Err(CustomUAPEntryError { message: msg });
}
};
let size_second = match numbers[0].trim().parse::<u8>() {
Ok(u) => u,
Err(_) => {
let msg = fmt::format(format_args!("invalid length provided for field"));
return Err(CustomUAPEntryError { message: msg });
}
};
return Ok(UAPEntryLength::CompoundField { size_first, size_second })
}
let msg = "could not parse field length";
return Err(CustomUAPEntryError { message: msg.to_string() })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ut_test_parse_length_happy() {
let mut uap_entry_length: UAPEntryLength = UAPEntry::parse_length("1").unwrap();
assert_eq!(UAPEntryLength::Fixed { size: 1 }, uap_entry_length);
uap_entry_length = UAPEntry::parse_length("8").unwrap();
assert_eq!(UAPEntryLength::Fixed { size: 8 }, uap_entry_length);
uap_entry_length = UAPEntry::parse_length("2+").unwrap();
assert_eq!(UAPEntryLength::Variable { size_first: 2 }, uap_entry_length);
uap_entry_length = UAPEntry::parse_length("1+5*n").unwrap();
assert_eq!(UAPEntryLength::FixedRepeatable { size_first: 1, size_repetitive: 5 }, uap_entry_length);
uap_entry_length = UAPEntry::parse_length("1+1+").unwrap();
assert_eq!(UAPEntryLength::CompoundField { size_first: 1, size_second: 1 }, uap_entry_length);
}
#[test]
fn ut_test_parse_length_sad() {
let mut result = UAPEntry::parse_length("+1");
assert!(result.is_err());
assert_eq!(CustomUAPEntryError { message: "could not parse field length".to_string() }, result.unwrap_err());
result = UAPEntry::parse_length("8-");
assert!(result.is_err());
assert_eq!(CustomUAPEntryError { message: "could not parse field length".to_string() }, result.unwrap_err());
result = UAPEntry::parse_length("-1+");
assert!(result.is_err());
assert_eq!(CustomUAPEntryError { message: "could not parse field length".to_string() }, result.unwrap_err());
result = UAPEntry::parse_length("1+0*n");
assert!(result.is_err());
assert_eq!(CustomUAPEntryError { message: "could not parse field length".to_string() }, result.unwrap_err());
result = UAPEntry::parse_length("1+1x");
assert!(result.is_err());
assert_eq!(CustomUAPEntryError { message: "could not parse field length".to_string() }, result.unwrap_err());
}
#[test]
fn ut_test_playground() {
let re_repeat = Regex::new(r"^[0-9]{1,3}\+[0-9]{1,3}\*[a-z]$").unwrap();
let mut length: &str = "1+8*n";
let mut is_repeat_match = re_repeat.is_match(length);
assert_eq!(true, is_repeat_match);
length = "2+3*n";
is_repeat_match = re_repeat.is_match(length);
assert_eq!(true, is_repeat_match);
let re_vary: Regex = Regex::new(r"^([0-9]{1,3}\+){1,2}$").unwrap();
length = "1+";
let is_vary_match = re_vary.is_match(length);
assert_eq!(true, is_vary_match, "Does not match variable description");
}
}