1use chrono::{DateTime, Utc};
2use gluex_core::{
3 constants::{MAX_RUN_NUMBER, MIN_RUN_NUMBER},
4 parsers::parse_timestamp,
5 run_periods::{RESTVersionSelection, RunPeriod},
6 RunNumber,
7};
8use std::{ops::Bound, str::FromStr};
9
10use crate::{CCDBError, CCDBResult};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct NamePath(pub String);
15impl FromStr for NamePath {
16 type Err = CCDBError;
17
18 fn from_str(s: &str) -> Result<Self, Self::Err> {
19 if !s.starts_with('/') {
20 return Err(CCDBError::NotAbsolutePath(s.to_string()));
21 }
22 if !s
23 .chars()
24 .all(|c| c.is_ascii_alphanumeric() || c == '/' || c == '_' || c == '-')
25 {
26 return Err(CCDBError::IllegalCharacter(s.to_string()));
27 }
28 Ok(Self(s.to_string()))
29 }
30}
31impl NamePath {
32 #[must_use]
34 pub fn full_path(&self) -> &str {
35 &self.0
36 }
37 #[must_use]
39 pub fn name(&self) -> &str {
40 self.0.rsplit('/').next().unwrap_or("")
41 }
42 #[must_use]
44 pub fn parent(&self) -> Option<NamePath> {
45 if self.is_root() {
46 return None;
47 }
48 let mut parts: Vec<&str> = self.0.split('/').collect();
49 parts.pop();
50 Some(NamePath(format!("/{}", parts.join("/"))))
51 }
52 #[must_use]
54 pub fn is_root(&self) -> bool {
55 self.0 == "/"
56 }
57}
58const DEFAULT_VARIATION: &str = "default";
59const DEFAULT_RUN_NUMBER: RunNumber = 0;
60
61#[derive(Debug, Clone)]
63pub struct CCDBContext {
64 pub runs: Vec<RunNumber>,
66 pub variation: String,
68 pub timestamp: DateTime<Utc>,
70}
71impl Default for CCDBContext {
72 fn default() -> Self {
73 Self {
74 runs: vec![DEFAULT_RUN_NUMBER],
75 variation: DEFAULT_VARIATION.to_string(),
76 timestamp: Utc::now(),
77 }
78 }
79}
80impl CCDBContext {
81 #[must_use]
83 pub fn new(
84 runs: Option<Vec<RunNumber>>,
85 variation: Option<String>,
86 timestamp: Option<DateTime<Utc>>,
87 ) -> Self {
88 let mut context = Self::default();
89 if let Some(runs) = runs {
90 context.runs = runs;
91 }
92 if let Some(variation) = variation {
93 context.variation = variation;
94 }
95 if let Some(timestamp) = timestamp {
96 context.timestamp = timestamp;
97 }
98 context
99 }
100 pub fn with_run_period(
108 mut self,
109 run_period: RunPeriod,
110 rest_version: RESTVersionSelection,
111 ) -> CCDBResult<Self> {
112 self.runs = run_period.run_range().collect();
113 self.timestamp = rest_version.resolve_timestamp(run_period)?;
114 Ok(self)
115 }
116 #[must_use]
118 pub fn with_run(mut self, run: RunNumber) -> Self {
119 self.runs = vec![run.clamp(MIN_RUN_NUMBER, MAX_RUN_NUMBER)];
120 self
121 }
122 #[must_use]
124 pub fn with_runs(mut self, iter: impl IntoIterator<Item = RunNumber>) -> Self {
125 self.runs = iter
126 .into_iter()
127 .map(|r| r.clamp(MIN_RUN_NUMBER, MAX_RUN_NUMBER))
128 .collect();
129 self
130 }
131 #[must_use]
133 pub fn with_run_range(mut self, run_range: impl std::ops::RangeBounds<RunNumber>) -> Self {
134 let start = match run_range.start_bound() {
135 Bound::Included(&s) => s,
136 Bound::Excluded(&s) => s.saturating_add(1),
137 Bound::Unbounded => MIN_RUN_NUMBER,
138 }
139 .max(MIN_RUN_NUMBER);
140 let end = match run_range.end_bound() {
141 Bound::Included(&e) => e,
142 Bound::Excluded(&e) => e.saturating_sub(1),
143 Bound::Unbounded => MAX_RUN_NUMBER,
144 }
145 .min(MAX_RUN_NUMBER);
146 self.runs = if start > end {
147 Vec::new()
148 } else {
149 (start..=end).collect()
150 };
151 self
152 }
153 #[must_use]
155 pub fn with_variation(mut self, variation: &str) -> Self {
156 self.variation = variation.to_string();
157 self
158 }
159 #[must_use]
161 pub fn with_timestamp(mut self, timestamp: DateTime<Utc>) -> Self {
162 self.timestamp = timestamp;
163 self
164 }
165 pub fn with_timestamp_string(mut self, timestamp: &str) -> CCDBResult<Self> {
171 self.timestamp = parse_timestamp(timestamp)?;
172 Ok(self)
173 }
174}
175
176#[derive(Debug, Clone)]
178pub struct Request {
179 pub path: NamePath,
181 pub context: CCDBContext,
183}
184impl FromStr for Request {
185 type Err = CCDBError;
186
187 fn from_str(s: &str) -> Result<Self, Self::Err> {
188 let (path_str, rest) = s.split_once(':').map_or((s, None), |(p, r)| (p, Some(r)));
189 let path = NamePath::from_str(path_str)?;
190 let mut run: Option<RunNumber> = None;
191 let mut variation: Option<String> = None;
192 let mut timestamp: Option<DateTime<Utc>> = None;
193 if let Some(rest) = rest {
194 let mut parts: Vec<&str> = rest.splitn(3, ':').collect();
195 while parts.len() < 3 {
196 parts.push("");
197 }
198 let (run_s, var_s, time_s) = (parts[0], parts[1], parts[2]);
199 if !run_s.is_empty() {
200 run = Some(
201 run_s
202 .parse::<RunNumber>()
203 .map_err(|_| CCDBError::InvalidRunNumberError(run_s.to_string()))?,
204 );
205 }
206 if !var_s.is_empty() {
207 variation = Some(var_s.to_string());
208 }
209 if !time_s.is_empty() {
210 timestamp = Some(parse_timestamp(time_s)?);
211 }
212 }
213 Ok(Request {
214 path,
215 context: CCDBContext::new(run.map(|r| vec![r]), variation, timestamp),
216 })
217 }
218}