use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
pub type TypeId = String;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TypeKind {
File,
Directory,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeDef {
pub description: String,
pub kind: Option<TypeKind>,
pub parent: Option<TypeId>,
#[serde(default)]
pub mime: Option<String>,
#[serde(default)]
pub parameters: Vec<String>,
#[serde(default)]
pub required_files: Vec<String>,
#[serde(default)]
pub columns: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeRef {
pub id: TypeId,
pub params: Vec<String>,
}
impl TypeRef {
pub fn base_id(&self) -> &str {
&self.id
}
}
impl fmt::Display for TypeRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.params.is_empty() {
write!(f, "{}", self.id)
} else {
write!(f, "{}[{}]", self.id, self.params.join(","))
}
}
}
impl FromStr for TypeRef {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((id, rest)) = s.split_once('[') {
let params_str = rest
.strip_suffix(']')
.ok_or_else(|| format!("unclosed '[' in type ref '{s}'"))?;
let params = params_str
.split(',')
.map(|p| p.trim().to_string())
.filter(|p| !p.is_empty())
.collect();
Ok(TypeRef {
id: id.to_string(),
params,
})
} else {
Ok(TypeRef {
id: s.to_string(),
params: vec![],
})
}
}
}
impl Serialize for TypeRef {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for TypeRef {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum Cardinality {
#[default]
One,
Many,
Optional,
}
impl fmt::Display for Cardinality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Cardinality::One => write!(f, "one"),
Cardinality::Many => write!(f, "many"),
Cardinality::Optional => write!(f, "optional"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn typeref_no_params() {
let r: TypeRef = "fasta".parse().unwrap();
assert_eq!(r.id, "fasta");
assert!(r.params.is_empty());
assert_eq!(r.to_string(), "fasta");
}
#[test]
fn typeref_with_params() {
let r: TypeRef = "fasta[protein]".parse().unwrap();
assert_eq!(r.id, "fasta");
assert_eq!(r.params, vec!["protein"]);
assert_eq!(r.to_string(), "fasta[protein]");
}
#[test]
fn typeref_multi_params() {
let r: TypeRef = "msa[stockholm,nucleotide]".parse().unwrap();
assert_eq!(r.id, "msa");
assert_eq!(r.params, vec!["stockholm", "nucleotide"]);
}
#[test]
fn typeref_unclosed_bracket() {
assert!("fasta[protein".parse::<TypeRef>().is_err());
}
}