atlas_local/models/
logs_options.rs1use chrono::{DateTime, Utc};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum Tail {
6 All,
8 Number(u64),
10}
11
12#[derive(Debug, thiserror::Error)]
14pub enum TailParseError {
15 #[error("Invalid tail value: '{0}'. Expected 'all' or a positive number")]
17 InvalidValue(String),
18}
19
20impl From<u64> for Tail {
21 fn from(n: u64) -> Self {
22 Tail::Number(n)
23 }
24}
25
26impl TryFrom<&str> for Tail {
27 type Error = TailParseError;
28
29 fn try_from(s: &str) -> Result<Self, Self::Error> {
30 match s {
31 "all" => Ok(Tail::All),
32 _ => s
33 .parse::<u64>()
34 .map(Tail::Number)
35 .map_err(|_| TailParseError::InvalidValue(s.to_string())),
36 }
37 }
38}
39
40impl TryFrom<String> for Tail {
41 type Error = TailParseError;
42
43 fn try_from(s: String) -> Result<Self, Self::Error> {
44 s.as_str().try_into()
45 }
46}
47
48impl std::fmt::Display for Tail {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 Tail::All => write!(f, "all"),
52 Tail::Number(n) => write!(f, "{}", n),
53 }
54 }
55}
56
57#[derive(Debug, Clone, Default, PartialEq, typed_builder::TypedBuilder)]
76#[builder(doc)]
77pub struct LogsOptions {
78 #[builder(default = false)]
80 pub stdout: bool,
81 #[builder(default = false)]
83 pub stderr: bool,
84 #[builder(default, setter(strip_option))]
86 pub since: Option<DateTime<Utc>>,
87 #[builder(default, setter(strip_option))]
89 pub until: Option<DateTime<Utc>>,
90 #[builder(default = false)]
92 pub timestamps: bool,
93 #[builder(default, setter(strip_option, into))]
95 pub tail: Option<Tail>,
96}
97
98impl From<LogsOptions> for bollard::query_parameters::LogsOptions {
99 fn from(options: LogsOptions) -> Self {
100 bollard::query_parameters::LogsOptions {
101 follow: false,
102 stdout: options.stdout,
103 stderr: options.stderr,
104 since: options.since.map(|t| t.timestamp() as i32).unwrap_or(0),
105 until: options.until.map(|t| t.timestamp() as i32).unwrap_or(0),
106 timestamps: options.timestamps,
107 tail: options.tail.map(|t| t.to_string()).unwrap_or_default(),
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_logs_options_into_bollard() {
118 let options = LogsOptions::builder()
119 .stdout(true)
120 .stderr(true)
121 .since(DateTime::from_timestamp(1234567890, 0).unwrap())
122 .until(DateTime::from_timestamp(1234567900, 0).unwrap())
123 .timestamps(true)
124 .tail(Tail::Number(100))
125 .build();
126
127 let bollard_options: bollard::query_parameters::LogsOptions = options.into();
128
129 assert!(bollard_options.stdout);
130 assert!(bollard_options.stderr);
131 assert_eq!(bollard_options.since, 1234567890);
132 assert_eq!(bollard_options.until, 1234567900);
133 assert!(bollard_options.timestamps);
134 assert_eq!(bollard_options.tail, "100");
135 assert!(!bollard_options.follow);
136 }
137
138 #[test]
139 fn test_tail_display() {
140 assert_eq!(Tail::All.to_string(), "all");
141 assert_eq!(Tail::Number(100).to_string(), "100");
142 assert_eq!(Tail::Number(0).to_string(), "0");
143 }
144
145 #[test]
146 fn test_tail_try_from_str() {
147 assert_eq!(Tail::try_from("all").unwrap(), Tail::All);
149 assert_eq!(Tail::try_from("100").unwrap(), Tail::Number(100));
150 assert_eq!(Tail::try_from("0").unwrap(), Tail::Number(0));
151
152 let result = Tail::try_from("ALL");
154 assert!(result.is_err());
155 assert!(matches!(
156 result.unwrap_err(),
157 TailParseError::InvalidValue(_)
158 ));
159
160 let result = Tail::try_from("invalid");
161 assert!(result.is_err());
162 assert!(matches!(
163 result.unwrap_err(),
164 TailParseError::InvalidValue(_)
165 ));
166
167 let result = Tail::try_from("-1");
168 assert!(result.is_err());
169
170 let result = Tail::try_from("");
171 assert!(result.is_err());
172 }
173
174 #[test]
175 fn test_tail_try_from_string() {
176 assert_eq!(Tail::try_from("all".to_string()).unwrap(), Tail::All);
177 assert_eq!(
178 Tail::try_from("100".to_string()).unwrap(),
179 Tail::Number(100)
180 );
181
182 let result = Tail::try_from("invalid".to_string());
183 assert!(result.is_err());
184 }
185
186 #[test]
187 fn test_tail_from_u64() {
188 assert_eq!(Tail::from(100u64), Tail::Number(100));
189 assert_eq!(Tail::from(0u64), Tail::Number(0));
190 }
191
192 #[test]
193 fn test_logs_options_into_bollard_with_tail_all() {
194 let options = LogsOptions {
195 stdout: true,
196 stderr: false,
197 since: None,
198 until: None,
199 timestamps: false,
200 tail: Some(Tail::All),
201 };
202
203 let bollard_options: bollard::query_parameters::LogsOptions = options.into();
204 assert_eq!(bollard_options.tail, "all");
205 }
206
207 #[test]
208 fn test_logs_options_builder_with_u64_tail() {
209 let options = LogsOptions::builder().stdout(true).tail(100u64).build();
211
212 assert_eq!(options.tail, Some(Tail::Number(100)));
213 }
214
215 #[test]
216 fn test_tail_parse_error_display() {
217 let err = TailParseError::InvalidValue("bad_value".to_string());
218 let error_msg = err.to_string();
219 assert!(error_msg.contains("Invalid tail value"));
220 assert!(error_msg.contains("bad_value"));
221 }
222}