use regex::Regex;
use thiserror::Error;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Symbol(String);
impl Symbol {
pub fn new(s: String) -> Result<Self, ParseSymbolError> {
if Self::is_valid_symbol(&s) {
Ok(Self(s))
} else {
Err(ParseSymbolError::from_string(s))
}
}
pub fn from_encoded_json_string(
json_string: &str,
) -> Result<Self, ParseSymbolError> {
Self::new(json_string.replacen("y:", "^", 1))
}
pub fn to_encoded_json_string(&self) -> String {
self.0.replacen('^', "y:", 1)
}
pub fn into_string(self) -> String {
self.0
}
pub fn to_axon_code(&self) -> &str {
self.as_ref()
}
pub(crate) fn is_valid_symbol(s: &str) -> bool {
if !s.starts_with('^') {
return false;
}
let s = &s[1..];
let sections = s.split(':').collect::<Vec<_>>();
if sections.len() > 2 {
return false;
}
let first_section = sections[0];
let second_section = sections.get(1);
let re =
Regex::new(r"[a-z][a-zA-Z0-9_]*(-[a-z][a-zA-Z0-9_])*").unwrap();
if !re.is_match(first_section) {
return false;
};
if let Some(second_section) = second_section {
if !re.is_match(second_section) {
return false;
};
};
true
}
}
impl std::str::FromStr for Symbol {
type Err = ParseSymbolError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if Self::is_valid_symbol(s) {
Ok(Self(s.to_owned()))
} else {
let unparsable_symbol = s.to_owned();
Err(ParseSymbolError { unparsable_symbol })
}
}
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_axon_code())
}
}
impl std::convert::AsRef<str> for Symbol {
fn as_ref(&self) -> &str {
&self.0
}
}
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("Could not parse a Symbol from the string {unparsable_symbol}")]
pub struct ParseSymbolError {
unparsable_symbol: String,
}
impl ParseSymbolError {
pub(crate) fn from_string(s: String) -> Self {
Self {
unparsable_symbol: s,
}
}
}
#[cfg(test)]
mod test {
use super::Symbol;
#[test]
fn parse_symbol() {
assert_eq!(Symbol::is_valid_symbol("^steam"), true);
assert_eq!(Symbol::is_valid_symbol("^steam-boiler"), true);
assert_eq!(Symbol::is_valid_symbol("^azAZ09"), true);
assert_eq!(Symbol::is_valid_symbol("^azAZ09-azAZ09"), true);
assert_eq!(Symbol::is_valid_symbol("^"), false);
assert_eq!(Symbol::is_valid_symbol(""), false);
assert_eq!(Symbol::is_valid_symbol("^steam:boiler"), true);
assert_eq!(Symbol::is_valid_symbol("^steam-boiler:boiler-steam"), true);
assert_eq!(Symbol::is_valid_symbol("^az0-az0-az0:az0-az0"), true);
assert_eq!(Symbol::is_valid_symbol("^steam:boiler:another"), false);
assert_eq!(Symbol::is_valid_symbol("^steam_-__boil_er"), true);
}
#[test]
fn to_json_works() {
let sym = Symbol::new("^steam-boiler".to_owned()).unwrap();
assert_eq!(sym.to_encoded_json_string(), "y:steam-boiler");
}
#[test]
fn from_json_works() {
let sym = Symbol::new("^steam-boiler".to_owned()).unwrap();
assert_eq!(
Symbol::from_encoded_json_string("y:steam-boiler").unwrap(),
sym
);
}
#[test]
fn to_str_works() {
let sym = Symbol::new("^steam-boiler".to_owned()).unwrap();
assert_eq!(format!("{}", sym), "^steam-boiler");
}
}