use std::{borrow::Cow, fmt::Display, str::FromStr};
use mzdata::{
Param,
params::{
CURIE, CURIEParsingError, ControlledVocabulary, ParamValue, ParamValueParseError, Unit,
Value,
},
};
#[derive(Debug)]
pub enum TermParserError {
CURIEError(CURIEParsingError),
MissingPipe,
UnknownControlledVocabulary,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Term {
pub accession: CURIE,
pub name: Cow<'static, str>, }
impl PartialOrd for Term {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Term {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.accession.accession.cmp(&other.accession.accession)
}
}
#[macro_export]
macro_rules! term {
($ns:ident:$accession:literal|$($name:tt)+) => {
$crate::mzspeclib::Term {
accession: mzdata::curie!($ns:$accession),
name: std::borrow::Cow::Borrowed(stringify!($($name)+))
}
};
}
impl FromStr for Term {
type Err = TermParserError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((curie, name)) = s.split_once('|') {
let curie: CURIE = curie.parse().map_err(TermParserError::CURIEError)?;
if curie.controlled_vocabulary == ControlledVocabulary::Unknown {
Err(TermParserError::UnknownControlledVocabulary)
} else {
Ok(Self::new(curie, name.to_string()))
}
} else {
Err(TermParserError::MissingPipe)
}
}
}
impl TryFrom<Param> for Term {
type Error = ();
fn try_from(value: Param) -> Result<Self, ()> {
Ok(Self {
accession: value.curie().ok_or(())?,
name: value.name.into(),
})
}
}
impl TryFrom<&Param> for Term {
type Error = ();
fn try_from(value: &Param) -> Result<Self, ()> {
Ok(Self {
accession: value.curie().ok_or(())?,
name: value.name.clone().into(),
})
}
}
impl Term {
pub const fn new(accession: CURIE, name: String) -> Self {
Self {
accession,
name: Cow::Owned(name),
}
}
pub fn into_param(self, value: Value, unit: Unit) -> Param {
Param {
name: self.name.to_string(),
value,
accession: Some(self.accession.accession),
controlled_vocabulary: Some(self.accession.controlled_vocabulary),
unit,
}
}
}
impl Display for Term {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.accession.controlled_vocabulary == ControlledVocabulary::Unknown {
write!(f, "??:{}|{}", self.accession.accession, self.name)
} else {
write!(f, "{}|{}", self.accession, self.name)
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AttributeValue {
Scalar(Value),
List(Vec<Value>),
Term(Term),
}
impl AttributeValue {
pub fn scalar(&self) -> Cow<'_, Value> {
match self {
Self::Scalar(value) => Cow::Borrowed(value),
Self::List(_) => Cow::Owned(Value::String(self.to_string())),
Self::Term(term) => Cow::Owned(Value::String(term.to_string())),
}
}
pub fn from_scalar(value: impl Into<Value>) -> Self {
Self::Scalar(value.into())
}
pub(crate) fn equivalent(&self, other: &Self) -> bool {
match (self, other) {
(Self::Term(t), Self::Scalar(Value::String(s)))
| (Self::Scalar(Value::String(s)), Self::Term(t)) => t.to_string() == *s,
(Self::Scalar(Value::Int(0)), Self::Scalar(Value::Float(0.0)))
| (Self::Scalar(Value::Float(0.0)), Self::Scalar(Value::Int(0))) => true,
(a, b) => a == b,
}
}
}
impl FromStr for AttributeValue {
type Err = ParamValueParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(term) = s.parse() {
Ok(Self::Term(term))
} else if s.contains(',') {
let vals: Vec<Value> = s.split(',').flat_map(Value::from_str).collect();
if vals.iter().all(|v| v.is_numeric() || v.is_boolean()) {
Ok(Self::List(vals))
} else {
let v = Value::from_str(s)?;
Ok(Self::Scalar(v))
}
} else {
let v = if s.starts_with('\"') && s.ends_with('\"') && s.len() > 1 {
Value::from_str(&s[1..s.len() - 1])
} else {
Value::from_str(s)
}?;
Ok(Self::Scalar(v))
}
}
}
impl From<Value> for AttributeValue {
fn from(value: Value) -> Self {
Self::Scalar(value)
}
}
impl From<Term> for AttributeValue {
fn from(value: Term) -> Self {
Self::Term(value)
}
}
impl From<AttributeValue> for Value {
fn from(value: AttributeValue) -> Self {
value.scalar().into_owned()
}
}
impl Display for AttributeValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Scalar(value) => write!(f, "{value}"),
Self::List(values) => {
for (i, value) in values.iter().enumerate() {
if i != 0 {
write!(f, ",")?;
}
write!(f, "{value}")?;
}
Ok(())
}
Self::Term(term) => write!(f, "{term}"),
}
}
}
impl From<Vec<Value>> for AttributeValue {
fn from(value: Vec<Value>) -> Self {
Self::List(value)
}
}
#[derive(Debug)]
pub enum AttributeParseError {
TermParserError(TermParserError),
ValueParseError(ParamValueParseError),
GroupIDParseError(std::num::ParseIntError),
MissingValueSeparator,
MissingClosingGroupBracket,
}
impl Display for AttributeParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::TermParserError(TermParserError::CURIEError(
CURIEParsingError::AccessionParsingError(_),
)) => "Accession not numeric",
Self::TermParserError(TermParserError::CURIEError(
CURIEParsingError::MissingNamespaceSeparator,
)) => "There is no namespace",
Self::TermParserError(TermParserError::CURIEError(
CURIEParsingError::UnknownControlledVocabulary(_),
)) => "The controlled vocabulary is unknown",
Self::TermParserError(TermParserError::MissingPipe) =>
"The term pipe symbol is missing, a term should be written like 'MS:1000896|normalized retention time'",
Self::TermParserError(TermParserError::UnknownControlledVocabulary) =>
"An unknown controlled vocabulary was used",
Self::ValueParseError(ParamValueParseError::FailedToExtractBuffer) =>
"Invalid value for the attribute, expected a buffer",
Self::ValueParseError(ParamValueParseError::FailedToExtractFloat(_)) =>
"Invalid value for the attribute, expected a floating point",
Self::ValueParseError(ParamValueParseError::FailedToExtractInt(_)) =>
"Invalid value for the attribute, expected an integer",
Self::ValueParseError(ParamValueParseError::FailedToExtractString) =>
"Invalid value for the attribute, expected a string",
Self::GroupIDParseError(_) => "The group id is not a valid number",
Self::MissingValueSeparator => "The value separator '=' is missing",
Self::MissingClosingGroupBracket => "The group id closing bracket ']' is missing",
}
)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Attribute {
pub name: Term,
pub value: AttributeValue,
}
impl Attribute {
pub fn new(name: Term, value: impl Into<AttributeValue>) -> Self {
Self {
name,
value: value.into(),
}
}
pub fn unit(unit: Unit) -> Option<Self> {
let (_, name) = unit.for_param();
let accession = unit.to_curie()?;
Some(Self::new(
term!(UO:0000000|unit),
Term::new(accession, name.to_string()),
))
}
pub fn parse(
s: &str,
) -> Result<(Option<u32>, Self, std::ops::Range<usize>), AttributeParseError> {
if let Some(s) = s.strip_prefix('[') {
if let Some((group_id, rest)) = s.split_once(']') {
let group_id = match group_id.parse::<u32>() {
Ok(group_id) => group_id,
Err(e) => return Err(AttributeParseError::GroupIDParseError(e)),
};
if let Some((name, val)) = rest.split_once('=') {
let term: Term = name.parse().map_err(AttributeParseError::TermParserError)?;
let value: AttributeValue =
val.parse().map_err(AttributeParseError::ValueParseError)?;
Ok((
Some(group_id),
Self::new(term, value),
s.len() - val.len()..s.len(),
))
} else {
Err(AttributeParseError::MissingValueSeparator)
}
} else {
Err(AttributeParseError::MissingClosingGroupBracket)
}
} else if let Some((name, val)) = s.split_once('=') {
let term: Term = name.parse().map_err(AttributeParseError::TermParserError)?;
let value: AttributeValue =
val.parse().map_err(AttributeParseError::ValueParseError)?;
Ok((None, Self::new(term, value), s.len() - val.len()..s.len()))
} else {
Err(AttributeParseError::MissingValueSeparator)
}
}
}
impl From<Attribute> for Param {
fn from(value: Attribute) -> Self {
Self {
name: value.name.name.to_string(),
value: value.value.into(),
accession: Some(value.name.accession.accession),
controlled_vocabulary: Some(value.name.accession.controlled_vocabulary),
unit: Unit::Unknown,
}
}
}
impl Display for Attribute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}={}", self.name, self.value)
}
}
pub type Attributes = Vec<Vec<Attribute>>;
pub(crate) fn merge_attributes(a: &mut Attributes, b: &Attributes) {
if a.is_empty() {
a.push(Vec::new());
}
if !b.is_empty() {
a[0].extend_from_slice(&b[0]);
a.extend_from_slice(&b[1..]);
}
}