compose_lens/model/
pull.rs1use super::Located;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct PullPolicy {
8 raw: Located<String>,
9 kind: PullPolicyKind,
10}
11
12impl PullPolicy {
13 pub(crate) fn parse(raw: Located<String>) -> Self {
14 let kind = match raw.value().as_str() {
15 "always" => PullPolicyKind::Always,
16 "never" => PullPolicyKind::Never,
17 "missing" => PullPolicyKind::Missing,
18 "if_not_present" => PullPolicyKind::IfNotPresentAlias,
19 "build" => PullPolicyKind::Build,
20 "daily" => PullPolicyKind::Daily,
21 "weekly" => PullPolicyKind::Weekly,
22 "refresh" => PullPolicyKind::RefreshSchemaOnly,
23 value if value.contains('$') => PullPolicyKind::Expression,
24 value => parse_every_duration(value).map_or(PullPolicyKind::Other, |duration| PullPolicyKind::Every {
25 duration: duration.to_owned(),
26 }),
27 };
28 Self { raw, kind }
29 }
30
31 #[must_use]
33 pub const fn raw(&self) -> &Located<String> {
34 &self.raw
35 }
36
37 #[must_use]
39 pub const fn kind(&self) -> &PullPolicyKind {
40 &self.kind
41 }
42
43 #[must_use]
45 pub const fn is_recognized(&self) -> bool {
46 !matches!(self.kind, PullPolicyKind::Other)
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52#[non_exhaustive]
53pub enum PullPolicyKind {
54 Always,
56 Never,
58 Missing,
60 IfNotPresentAlias,
62 Build,
64 Daily,
66 Weekly,
68 Every {
70 duration: String,
72 },
73 RefreshSchemaOnly,
75 Expression,
77 Other,
79}
80
81pub(crate) fn valid_pull_policy_duration(value: &str) -> bool {
82 parse_duration(value)
83}
84
85fn parse_every_duration(value: &str) -> Option<&str> {
86 let duration = value.strip_prefix("every_")?;
87 valid_pull_policy_duration(duration).then_some(duration)
88}
89
90fn parse_duration(mut value: &str) -> bool {
91 let mut found = false;
92 while !value.is_empty() {
93 let number_end = value.bytes().take_while(u8::is_ascii_digit).count();
94 if number_end == 0 {
95 return false;
96 }
97 value = &value[number_end..];
98 let Some(unit) = value.bytes().next() else {
99 return false;
100 };
101 if !matches!(unit, b'w' | b'd' | b'h' | b'm' | b's') {
102 return false;
103 }
104 value = &value[1..];
105 found = true;
106 }
107 found
108}
109
110#[cfg(test)]
111mod tests {
112 use super::{PullPolicy, PullPolicyKind, valid_pull_policy_duration};
113 use crate::model::Located;
114 use crate::source::{SourceId, SourceSpan};
115
116 #[test]
117 fn classifies_documented_schema_only_deferred_and_other_values() -> Result<(), &'static str> {
118 for (value, kind) in [
119 ("always", PullPolicyKind::Always),
120 ("missing", PullPolicyKind::Missing),
121 ("if_not_present", PullPolicyKind::IfNotPresentAlias),
122 ("refresh", PullPolicyKind::RefreshSchemaOnly),
123 ("${PULL_POLICY:-missing}", PullPolicyKind::Expression),
124 ("provider-newest", PullPolicyKind::Other),
125 ] {
126 let span = SourceSpan::new(SourceId::new(1), 0, value.len()).ok_or("valid test span expected")?;
127 assert_eq!(PullPolicy::parse(Located::new(value.to_owned(), span)).kind(), &kind);
128 }
129 Ok(())
130 }
131
132 #[test]
133 fn accepts_documented_compose_duration_units_without_normalizing_spelling() {
134 for value in ["1w", "2d", "3h", "4m", "5s", "1w2d3h4m5s", "0s", "01h30m"] {
135 assert!(valid_pull_policy_duration(value), "expected valid duration {value}");
136 }
137 for value in ["", "0", "1", "1us", "1ms", "1.5h", ".5h", "1h30", "1x", "h", "1hh"] {
138 assert!(!valid_pull_policy_duration(value), "expected invalid duration {value}");
139 }
140 }
141}