1use std::fmt;
2use std::str::FromStr;
3
4use serde::{Deserialize, Serialize};
5
6pub type TypeId = String;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum TypeKind {
12 File,
13 Directory,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct TypeDef {
19 pub description: String,
20 pub kind: Option<TypeKind>,
22 pub parent: Option<TypeId>,
24 #[serde(default)]
25 pub mime: Option<String>,
26 #[serde(default)]
28 pub parameters: Vec<String>,
29 #[serde(default)]
31 pub required_files: Vec<String>,
32 #[serde(default)]
34 pub columns: Option<String>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct TypeRef {
41 pub id: TypeId,
42 pub params: Vec<String>,
43}
44
45impl TypeRef {
46 pub fn base_id(&self) -> &str {
47 &self.id
48 }
49}
50
51impl fmt::Display for TypeRef {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 if self.params.is_empty() {
54 write!(f, "{}", self.id)
55 } else {
56 write!(f, "{}[{}]", self.id, self.params.join(","))
57 }
58 }
59}
60
61impl FromStr for TypeRef {
62 type Err = String;
63
64 fn from_str(s: &str) -> Result<Self, Self::Err> {
65 if let Some((id, rest)) = s.split_once('[') {
66 let params_str = rest
67 .strip_suffix(']')
68 .ok_or_else(|| format!("unclosed '[' in type ref '{s}'"))?;
69 let params = params_str
70 .split(',')
71 .map(|p| p.trim().to_string())
72 .filter(|p| !p.is_empty())
73 .collect();
74 Ok(TypeRef {
75 id: id.to_string(),
76 params,
77 })
78 } else {
79 Ok(TypeRef {
80 id: s.to_string(),
81 params: vec![],
82 })
83 }
84 }
85}
86
87impl Serialize for TypeRef {
88 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
89 s.serialize_str(&self.to_string())
90 }
91}
92
93impl<'de> Deserialize<'de> for TypeRef {
94 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
95 let s = String::deserialize(d)?;
96 s.parse().map_err(serde::de::Error::custom)
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
102#[serde(rename_all = "snake_case")]
103pub enum Cardinality {
104 #[default]
106 One,
107 Many,
109 Optional,
111}
112
113impl fmt::Display for Cardinality {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 match self {
116 Cardinality::One => write!(f, "one"),
117 Cardinality::Many => write!(f, "many"),
118 Cardinality::Optional => write!(f, "optional"),
119 }
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn typeref_no_params() {
129 let r: TypeRef = "fasta".parse().unwrap();
130 assert_eq!(r.id, "fasta");
131 assert!(r.params.is_empty());
132 assert_eq!(r.to_string(), "fasta");
133 }
134
135 #[test]
136 fn typeref_with_params() {
137 let r: TypeRef = "fasta[protein]".parse().unwrap();
138 assert_eq!(r.id, "fasta");
139 assert_eq!(r.params, vec!["protein"]);
140 assert_eq!(r.to_string(), "fasta[protein]");
141 }
142
143 #[test]
144 fn typeref_multi_params() {
145 let r: TypeRef = "msa[stockholm,nucleotide]".parse().unwrap();
146 assert_eq!(r.id, "msa");
147 assert_eq!(r.params, vec!["stockholm", "nucleotide"]);
148 }
149
150 #[test]
151 fn typeref_unclosed_bracket() {
152 assert!("fasta[protein".parse::<TypeRef>().is_err());
153 }
154}