Skip to main content

bv_types/
types.rs

1use std::fmt;
2use std::str::FromStr;
3
4use serde::{Deserialize, Serialize};
5
6pub type TypeId = String;
7
8/// Whether a type node is a plain file or a directory layout.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum TypeKind {
12    File,
13    Directory,
14}
15
16/// One entry in the type vocabulary (from `types.toml`).
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct TypeDef {
19    pub description: String,
20    /// Set on root types (`file`, `dir`, `blast_db`, ...); mutually exclusive with `parent`.
21    pub kind: Option<TypeKind>,
22    /// Parent type id; mutually exclusive with `kind`.
23    pub parent: Option<TypeId>,
24    #[serde(default)]
25    pub mime: Option<String>,
26    /// Named parameters this type accepts (e.g. `["alphabet"]` for `fasta`).
27    #[serde(default)]
28    pub parameters: Vec<String>,
29    /// Required file globs for composite directory types.
30    #[serde(default)]
31    pub required_files: Vec<String>,
32    /// Semantic column layout for tabular subtypes.
33    #[serde(default)]
34    pub columns: Option<String>,
35}
36
37/// A resolved type reference: base id plus optional parameter values.
38/// Parses `"fasta"` and `"fasta[protein]"`.
39#[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/// How many values an I/O port accepts.
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
102#[serde(rename_all = "snake_case")]
103pub enum Cardinality {
104    /// Exactly one value; required.
105    #[default]
106    One,
107    /// One or more values.
108    Many,
109    /// Zero or one value; optional.
110    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}