Skip to main content

bids_core/
config.rs

1//! Layout configuration: entity patterns and path templates.
2//!
3//! A [`Config`] defines the entities a layout recognizes and the path patterns
4//! used to build BIDS-compliant filenames. Two built-in configs ship with the
5//! crate (`bids` and `derivatives`); custom configs can be loaded from JSON.
6
7use serde::{Deserialize, Serialize};
8use std::path::Path;
9
10use crate::entities::Entity;
11use crate::error::{BidsError, Result};
12
13/// Configuration for a BIDS layout, defining entities and path patterns.
14///
15/// Corresponds to PyBIDS' `Config` class. Can be loaded from JSON config files
16/// (like `bids.json`) or constructed programmatically.
17///
18/// # Example
19///
20/// ```
21/// use bids_core::Config;
22///
23/// let config = Config::bids();
24/// assert_eq!(config.name, "bids");
25/// assert!(config.entity_count() > 20);
26/// assert!(config.get_entity("subject").is_some());
27/// ```
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct Config {
30    pub name: String,
31    #[serde(default)]
32    pub entities: Vec<Entity>,
33    #[serde(default)]
34    pub default_path_patterns: Option<Vec<String>>,
35}
36
37impl Config {
38    /// Load a config from a JSON file.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if the file cannot be read or contains invalid JSON.
43    pub fn from_file(path: &Path) -> Result<Self> {
44        let contents = std::fs::read_to_string(path)?;
45        let config: Self = serde_json::from_str(&contents)?;
46        Ok(config)
47    }
48
49    /// Load the built-in BIDS config.
50    ///
51    /// # Panics
52    ///
53    /// Panics if the embedded `bids.json` is malformed (should never happen).
54    #[must_use]
55    pub fn bids() -> Self {
56        serde_json::from_str(include_str!("configs/bids.json"))
57            .expect("Built-in bids.json config should be valid")
58    }
59
60    /// Load the built-in derivatives config.
61    ///
62    /// # Panics
63    ///
64    /// Panics if the embedded `derivatives.json` is malformed (should never happen).
65    #[must_use]
66    pub fn derivatives() -> Self {
67        serde_json::from_str(include_str!("configs/derivatives.json"))
68            .expect("Built-in derivatives.json config should be valid")
69    }
70
71    /// Load a named config (`"bids"`, `"derivatives"`) or from a file path.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error if the name is unrecognized and the path doesn't
76    /// exist or contains invalid JSON.
77    pub fn load(name_or_path: &str) -> Result<Self> {
78        match name_or_path {
79            "bids" => Ok(Self::bids()),
80            "derivatives" => Ok(Self::derivatives()),
81            path => {
82                let p = Path::new(path);
83                if p.exists() {
84                    Self::from_file(p)
85                } else {
86                    Err(BidsError::Config(format!(
87                        "'{name_or_path}' is not a valid config name or path"
88                    )))
89                }
90            }
91        }
92    }
93
94    /// Get mutable references to all entity definitions.
95    pub fn entities_mut(&mut self) -> &mut Vec<Entity> {
96        &mut self.entities
97    }
98
99    /// Look up an entity definition by name.
100    #[must_use]
101    pub fn get_entity(&self, name: &str) -> Option<&Entity> {
102        self.entities.iter().find(|e| e.name == name)
103    }
104
105    /// Returns the number of entity definitions in this config.
106    #[must_use]
107    pub fn entity_count(&self) -> usize {
108        self.entities.len()
109    }
110}
111
112impl std::fmt::Display for Config {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        write!(
115            f,
116            "Config('{}', {} entities",
117            self.name,
118            self.entities.len()
119        )?;
120        if let Some(patterns) = &self.default_path_patterns {
121            write!(f, ", {} patterns", patterns.len())?;
122        }
123        write!(f, ")")
124    }
125}