1use super::{Located, MemLimitUnit};
4use crate::source::SourceSpan;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct InvalidServiceStringItem {
9 span: SourceSpan,
10}
11impl InvalidServiceStringItem {
12 pub(crate) const fn new(span: SourceSpan) -> Self {
13 Self { span }
14 }
15 #[must_use]
17 pub const fn span(self) -> SourceSpan {
18 self.span
19 }
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24#[non_exhaustive]
25pub enum ServiceInteger {
26 Valid(String),
28 OutOfRange(String),
30 Other(String),
32}
33
34impl ServiceInteger {
35 pub(crate) fn parse(value: String, min: i128, max: i128) -> Self {
36 match value.parse::<i128>() {
37 Ok(number) if (min..=max).contains(&number) => Self::Valid(value),
38 Ok(_) => Self::OutOfRange(value),
39 Err(_) => Self::Other(value),
40 }
41 }
42
43 #[must_use]
45 pub const fn is_valid(&self) -> bool {
46 matches!(self, Self::Valid(_))
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct MemswapLimit {
53 raw: Located<String>,
54 scalar_kind: MemswapLimitScalarKind,
55 kind: MemswapLimitKind,
56}
57
58impl MemswapLimit {
59 pub(crate) fn parse(raw: Located<String>, scalar_kind: MemswapLimitScalarKind) -> Self {
60 let value = raw.value();
61 let kind = if value == "-1" {
62 MemswapLimitKind::Unlimited
63 } else if matches!(scalar_kind, MemswapLimitScalarKind::String) && value.contains('$') {
64 MemswapLimitKind::Expression
65 } else if let Some((amount_raw, unit)) = quantity_parts(value) {
66 if amount_raw.bytes().all(|byte| byte == b'0') {
67 MemswapLimitKind::Zero {
68 amount_raw: amount_raw.to_owned(),
69 unit,
70 }
71 } else {
72 MemswapLimitKind::Positive {
73 amount_raw: amount_raw.to_owned(),
74 unit,
75 }
76 }
77 } else {
78 MemswapLimitKind::Other(value.to_owned())
79 };
80 Self { raw, scalar_kind, kind }
81 }
82
83 #[must_use]
85 pub const fn raw(&self) -> &Located<String> {
86 &self.raw
87 }
88
89 #[must_use]
91 pub const fn scalar_kind(&self) -> MemswapLimitScalarKind {
92 self.scalar_kind
93 }
94
95 #[must_use]
97 pub const fn kind(&self) -> &MemswapLimitKind {
98 &self.kind
99 }
100
101 pub(crate) const fn is_positive(&self) -> bool {
102 matches!(self.kind, MemswapLimitKind::Positive { .. })
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
108#[non_exhaustive]
109pub enum MemswapLimitScalarKind {
110 Number,
112 String,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
118#[non_exhaustive]
119pub enum MemswapLimitKind {
120 Unlimited,
122 Zero {
124 amount_raw: String,
126 unit: Option<MemLimitUnit>,
128 },
129 Positive {
131 amount_raw: String,
133 unit: Option<MemLimitUnit>,
135 },
136 Expression,
138 Other(String),
140}
141
142fn quantity_parts(value: &str) -> Option<(&str, Option<MemLimitUnit>)> {
143 let with_unit = [
144 ("kb", MemLimitUnit::Kb),
145 ("mb", MemLimitUnit::Mb),
146 ("gb", MemLimitUnit::Gb),
147 ("b", MemLimitUnit::B),
148 ("k", MemLimitUnit::K),
149 ("m", MemLimitUnit::M),
150 ("g", MemLimitUnit::G),
151 ]
152 .into_iter()
153 .find_map(|(suffix, unit)| value.strip_suffix(suffix).map(|amount| (amount, Some(unit))));
154 let (amount, unit) = with_unit.unwrap_or((value, None));
155 (!amount.is_empty() && amount.bytes().all(|byte| byte.is_ascii_digit())).then_some((amount, unit))
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
160#[non_exhaustive]
161pub enum Cpus {
162 Decimal(String),
164 Expression(String),
166 Other(String),
168}
169
170impl Cpus {
171 pub(crate) fn parse(value: String) -> Self {
172 if value.contains('$') {
173 return Self::Expression(value);
174 }
175 if !value.is_empty()
176 && value.bytes().all(|byte| byte.is_ascii_digit() || byte == b'.')
177 && value.bytes().filter(|byte| *byte == b'.').count() <= 1
178 {
179 Self::Decimal(value)
180 } else {
181 Self::Other(value)
182 }
183 }
184 #[must_use]
186 pub const fn is_valid(&self) -> bool {
187 !matches!(self, Self::Other(_))
188 }
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
193#[non_exhaustive]
194pub enum IpcMode {
195 Shareable,
197 Service(String),
199 Raw(String),
201}
202impl IpcMode {
203 pub(crate) fn parse(value: String) -> Self {
204 if value == "shareable" {
205 Self::Shareable
206 } else if let Some(name) = value.strip_prefix("service:") {
207 Self::Service(name.to_owned())
208 } else {
209 Self::Raw(value)
210 }
211 }
212}
213
214#[derive(Debug, Clone, PartialEq, Eq)]
216#[non_exhaustive]
217pub enum NetworkMode {
218 None,
220 Host,
222 Service(String),
224 Container(String),
226 Raw(String),
228}
229impl NetworkMode {
230 pub(crate) fn parse(value: String) -> Self {
231 match value.as_str() {
232 "none" => Self::None,
233 "host" => Self::Host,
234 _ if value.starts_with("service:") => Self::Service(value[8..].to_owned()),
235 _ if value.starts_with("container:") => Self::Container(value[10..].to_owned()),
236 _ => Self::Raw(value),
237 }
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq)]
243#[non_exhaustive]
244pub enum PidMode {
245 Service(String),
247 Container(String),
249 Raw(String),
251}
252impl PidMode {
253 pub(crate) fn parse(value: String) -> Self {
254 if let Some(name) = value.strip_prefix("service:") {
255 Self::Service(name.to_owned())
256 } else if let Some(name) = value.strip_prefix("container:") {
257 Self::Container(name.to_owned())
258 } else {
259 Self::Raw(value)
260 }
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Eq)]
266pub struct VolumesFrom {
267 raw: Located<String>,
268 source: String,
269 read_only: bool,
270}
271impl VolumesFrom {
272 pub(crate) fn parse(raw: Located<String>) -> Self {
273 let value = raw.value();
274 let (source, read_only) = match value.rsplit_once(':') {
275 Some((source, "ro")) => (source.to_owned(), true),
276 Some((source, "rw")) => (source.to_owned(), false),
277 _ => (value.to_owned(), false),
278 };
279 Self { raw, source, read_only }
280 }
281 #[must_use]
283 pub const fn raw(&self) -> &Located<String> {
284 &self.raw
285 }
286 #[must_use]
288 pub fn source(&self) -> &str {
289 &self.source
290 }
291 #[must_use]
293 pub const fn read_only(&self) -> bool {
294 self.read_only
295 }
296}