bids_core/
dataset_description.rs1use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::path::Path;
11
12use crate::error::{BidsError, Result};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(rename_all = "PascalCase")]
30pub struct DatasetDescription {
31 pub name: String,
33 #[serde(rename = "BIDSVersion")]
35 pub bids_version: String,
36 #[serde(default)]
38 pub license: Option<String>,
39 #[serde(default)]
41 pub authors: Option<Vec<String>>,
42 #[serde(default)]
44 pub acknowledgements: Option<String>,
45 #[serde(default)]
47 pub how_to_acknowledge: Option<String>,
48 #[serde(default)]
50 pub funding: Option<Vec<String>>,
51 #[serde(default)]
53 pub ethics_approvals: Option<Vec<String>>,
54 #[serde(default)]
56 pub references_and_links: Option<Vec<String>>,
57 #[serde(rename = "DatasetDOI", default)]
59 pub dataset_doi: Option<String>,
60 #[serde(default)]
62 pub dataset_type: Option<String>,
63 #[serde(default)]
65 pub generated_by: Option<Vec<GeneratedBy>>,
66 #[serde(default)]
68 pub source_datasets: Option<Vec<Value>>,
69 #[serde(default)]
71 pub pipeline_description: Option<PipelineDescription>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76#[serde(rename_all = "PascalCase")]
77pub struct GeneratedBy {
78 pub name: String,
79 #[serde(default)]
80 pub version: Option<String>,
81 #[serde(default)]
82 pub description: Option<String>,
83 #[serde(default)]
84 pub code_url: Option<String>,
85 #[serde(default)]
86 pub container: Option<Value>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91#[serde(rename_all = "PascalCase")]
92pub struct PipelineDescription {
93 pub name: String,
94 #[serde(default)]
95 pub version: Option<String>,
96 #[serde(default)]
97 pub description: Option<String>,
98}
99
100impl DatasetDescription {
101 pub fn from_dir(dir: &Path) -> Result<Self> {
108 let path = dir.join("dataset_description.json");
109 if !path.exists() {
110 return Err(BidsError::MissingDatasetDescription);
111 }
112 let contents = std::fs::read_to_string(&path)?;
113 let desc: Self = serde_json::from_str(&contents)?;
114 Ok(desc)
115 }
116
117 pub fn validate(&self) -> Result<()> {
124 if self.name.is_empty() {
125 return Err(BidsError::MissingMandatoryField {
126 field: "Name".into(),
127 });
128 }
129 if self.bids_version.is_empty() {
130 return Err(BidsError::MissingMandatoryField {
131 field: "BIDSVersion".into(),
132 });
133 }
134 Ok(())
135 }
136
137 #[must_use]
139 pub fn is_derivative(&self) -> bool {
140 self.dataset_type.as_deref() == Some("derivative")
141 }
142
143 #[must_use]
145 pub fn pipeline_name(&self) -> Option<&str> {
146 if let Some(generated_by) = &self.generated_by
148 && let Some(first) = generated_by.first()
149 {
150 return Some(&first.name);
151 }
152 if let Some(pd) = &self.pipeline_description {
154 return Some(&pd.name);
155 }
156 None
157 }
158
159 pub fn save_to(&self, dir: &Path) -> Result<()> {
166 let path = dir.join("dataset_description.json");
167 let json = serde_json::to_string_pretty(self)?;
168 std::fs::write(path, json)?;
169 Ok(())
170 }
171}
172
173impl std::fmt::Display for DatasetDescription {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 write!(f, "{} (BIDS {})", self.name, self.bids_version)?;
176 if self.is_derivative() {
177 if let Some(pipeline) = self.pipeline_name() {
178 write!(f, " [derivative: {pipeline}]")?;
179 } else {
180 write!(f, " [derivative]")?;
181 }
182 }
183 Ok(())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_parse_description() {
193 let json = r#"{
194 "Name": "Test Dataset",
195 "BIDSVersion": "1.6.0",
196 "License": "CC0",
197 "Authors": ["Test Author"]
198 }"#;
199 let desc: DatasetDescription = serde_json::from_str(json).unwrap();
200 assert_eq!(desc.name, "Test Dataset");
201 assert_eq!(desc.bids_version, "1.6.0");
202 assert!(!desc.is_derivative());
203 }
204
205 #[test]
206 fn test_derivative_description() {
207 let json = r#"{
208 "Name": "fmriprep",
209 "BIDSVersion": "1.6.0",
210 "DatasetType": "derivative",
211 "GeneratedBy": [{"Name": "fmriprep", "Version": "20.2.0"}]
212 }"#;
213 let desc: DatasetDescription = serde_json::from_str(json).unwrap();
214 assert!(desc.is_derivative());
215 assert_eq!(desc.pipeline_name(), Some("fmriprep"));
216 }
217}