Skip to main content

bids_layout/
query.rs

1//! Query types: filters, scopes, return types, and special query values.
2//!
3//! Defines the building blocks used by [`GetBuilder`](crate::GetBuilder) to
4//! express dataset queries: exact matching, regex, existence checks (`Any`/`None`),
5//! scope selection (raw/derivatives/pipeline), and return type (objects/paths/IDs).
6
7/// Special query values for entity filtering.
8///
9/// These correspond to PyBIDS' `Query.NONE`, `Query.ANY`, and `Query.OPTIONAL`
10/// sentinel values that modify how entity filters behave beyond simple value
11/// matching.
12///
13/// # Example
14///
15/// ```no_run
16/// # use bids_layout::{BidsLayout, Query, QueryFilter};
17/// # let layout = BidsLayout::new("/path").unwrap();
18/// // Find files that have a session entity (any value)
19/// let files = layout.get().query_any("session").collect().unwrap();
20///
21/// // Find files that do NOT have a session entity
22/// let files = layout.get().query_none("session").collect().unwrap();
23/// ```
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum Query {
26    /// The entity must **not** be defined on the file. Files that have any
27    /// value for this entity are excluded.
28    None,
29    /// The entity must be defined (with any value). Files missing this entity
30    /// are excluded.
31    Any,
32    /// The entity is optional — no filtering is applied regardless of whether
33    /// the entity is present or absent.
34    Optional,
35}
36
37impl std::fmt::Display for Query {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Query::None => write!(f, "Query::None"),
41            Query::Any => write!(f, "Query::Any"),
42            Query::Optional => write!(f, "Query::Optional"),
43        }
44    }
45}
46
47/// A filter for querying files by entity values.
48#[derive(Debug, Clone)]
49pub struct QueryFilter {
50    pub entity: String,
51    pub values: Vec<String>,
52    pub regex: bool,
53    pub query: Option<Query>,
54}
55
56impl QueryFilter {
57    /// Convert to the internal tuple representation `(entity, values, is_regex)`.
58    #[must_use]
59    pub fn to_tuple(&self) -> (String, Vec<String>, bool) {
60        (self.entity.clone(), self.values.clone(), self.regex)
61    }
62
63    /// Convert a slice of `QueryFilter`s to internal tuple representation.
64    pub fn to_tuples(filters: &[QueryFilter]) -> Vec<(String, Vec<String>, bool)> {
65        filters.iter().map(QueryFilter::to_tuple).collect()
66    }
67
68    #[must_use]
69    pub fn eq(entity: &str, value: &str) -> Self {
70        Self {
71            entity: entity.into(),
72            values: vec![value.into()],
73            regex: false,
74            query: None,
75        }
76    }
77    #[must_use]
78    pub fn one_of(entity: &str, values: &[&str]) -> Self {
79        Self {
80            entity: entity.into(),
81            values: values
82                .iter()
83                .map(std::string::ToString::to_string)
84                .collect(),
85            regex: false,
86            query: None,
87        }
88    }
89    #[must_use]
90    pub fn regex(entity: &str, pattern: &str) -> Self {
91        Self {
92            entity: entity.into(),
93            values: vec![pattern.into()],
94            regex: true,
95            query: None,
96        }
97    }
98    #[must_use]
99    pub fn any(entity: &str) -> Self {
100        Self {
101            entity: entity.into(),
102            values: vec!["__ANY__".into()],
103            regex: false,
104            query: Some(Query::Any),
105        }
106    }
107    #[must_use]
108    pub fn none(entity: &str) -> Self {
109        Self {
110            entity: entity.into(),
111            values: vec!["__NONE__".into()],
112            regex: false,
113            query: Some(Query::None),
114        }
115    }
116    #[must_use]
117    pub fn optional(entity: &str) -> Self {
118        Self {
119            entity: entity.into(),
120            values: vec!["__OPTIONAL__".into()],
121            regex: false,
122            query: Some(Query::Optional),
123        }
124    }
125}
126
127/// Return type for get() queries.
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum ReturnType {
130    Object,
131    Filename,
132    Id,
133    Dir,
134}
135
136/// Scope for queries.
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub enum Scope {
139    All,
140    Raw,
141    Derivatives,
142    Self_,
143    Pipeline(String),
144}
145
146impl Scope {
147    /// Parse scope from a string. Prefer using `str::parse::<Scope>()`.
148    pub fn parse(s: &str) -> Self {
149        match s {
150            "all" => Scope::All,
151            "raw" => Scope::Raw,
152            "derivatives" => Scope::Derivatives,
153            "self" => Scope::Self_,
154            other => Scope::Pipeline(other.to_string()),
155        }
156    }
157}
158
159impl std::str::FromStr for Scope {
160    type Err = std::convert::Infallible;
161    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
162        Ok(Scope::parse(s))
163    }
164}
165
166impl std::fmt::Display for Scope {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        match self {
169            Scope::All => write!(f, "all"),
170            Scope::Raw => write!(f, "raw"),
171            Scope::Derivatives => write!(f, "derivatives"),
172            Scope::Self_ => write!(f, "self"),
173            Scope::Pipeline(name) => write!(f, "pipeline:{name}"),
174        }
175    }
176}
177
178impl From<(String, Vec<String>, bool)> for QueryFilter {
179    fn from((entity, values, regex): (String, Vec<String>, bool)) -> Self {
180        Self {
181            entity,
182            values,
183            regex,
184            query: None,
185        }
186    }
187}
188
189impl From<QueryFilter> for (String, Vec<String>, bool) {
190    fn from(f: QueryFilter) -> Self {
191        (f.entity, f.values, f.regex)
192    }
193}
194
195impl std::fmt::Display for QueryFilter {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        write!(f, "{}=", self.entity)?;
198        if self.regex {
199            write!(f, "/{}/", self.values.join("|"))
200        } else if self.values.len() == 1 {
201            write!(f, "{}", self.values[0])
202        } else {
203            write!(f, "[{}]", self.values.join(", "))
204        }
205    }
206}