buildkit_frontend/options/
default.rs

1use std::collections::BTreeMap;
2use std::iter::once;
3
4use either::Either;
5use serde::Deserialize;
6
7#[derive(Debug, PartialEq, Deserialize)]
8#[serde(transparent)]
9pub struct Options {
10    inner: BTreeMap<String, OptionValue>,
11}
12
13#[derive(Debug, PartialEq, Deserialize)]
14#[serde(untagged)]
15enum OptionValue {
16    Flag(bool),
17    Single(String),
18    Multiple(Vec<String>),
19}
20
21impl Options {
22    pub fn has<S>(&self, name: S) -> bool
23    where
24        S: AsRef<str>,
25    {
26        match self.inner.get(name.as_ref()) {
27            Some(container) => match container {
28                OptionValue::Flag(exists) => *exists,
29                OptionValue::Single(_) => true,
30                OptionValue::Multiple(_) => true,
31            },
32
33            None => false,
34        }
35    }
36
37    pub fn is_flag_set<S>(&self, name: S) -> bool
38    where
39        S: AsRef<str>,
40    {
41        match self.inner.get(name.as_ref()) {
42            Some(container) => match container {
43                OptionValue::Flag(flag) => *flag,
44                OptionValue::Single(_) => false,
45                OptionValue::Multiple(_) => false,
46            },
47
48            None => false,
49        }
50    }
51
52    pub fn has_value<S1, S2>(&self, name: S1, value: S2) -> bool
53    where
54        S1: AsRef<str>,
55        S2: AsRef<str>,
56    {
57        match self.inner.get(name.as_ref()) {
58            Some(container) => match container {
59                OptionValue::Flag(_) => false,
60                OptionValue::Single(single) => single == value.as_ref(),
61                OptionValue::Multiple(values) => values.iter().any(|item| item == value.as_ref()),
62            },
63
64            None => false,
65        }
66    }
67
68    pub fn get<S>(&self, name: S) -> Option<&str>
69    where
70        S: AsRef<str>,
71    {
72        match self.inner.get(name.as_ref()) {
73            Some(container) => match container {
74                OptionValue::Flag(_) => None,
75                OptionValue::Single(value) => Some(value.as_str()),
76                OptionValue::Multiple(values) => values.iter().map(String::as_str).next(),
77            },
78
79            None => None,
80        }
81    }
82
83    pub fn iter<S>(&self, name: S) -> Option<impl Iterator<Item = &str>>
84    where
85        S: AsRef<str>,
86    {
87        match self.inner.get(name.as_ref()) {
88            Some(container) => match container {
89                OptionValue::Flag(_) => None,
90                OptionValue::Single(value) => Some(Either::Left(once(value.as_str()))),
91                OptionValue::Multiple(values) => {
92                    Some(Either::Right(values.iter().map(String::as_str)))
93                }
94            },
95
96            None => None,
97        }
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::super::from_env;
104    use super::*;
105
106    #[test]
107    fn options_parsing() {
108        let options = from_env::<Options, _>(into_env(vec![
109            "name1",
110            "name2=true",
111            "name3=false",
112            "name4=",
113            "name5=value",
114            "name6=de=limiter",
115            "name7=false,true",
116            "name8=value1,value2,value3",
117            "name9=value1,val=ue2,value3",
118            "build-arg:name10",
119            "build-arg:name11=value",
120        ]))
121        .unwrap();
122
123        assert_eq!(options.inner["name1"], OptionValue::Flag(true));
124        assert_eq!(options.inner["name2"], OptionValue::Flag(true));
125        assert_eq!(options.inner["name3"], OptionValue::Flag(false));
126        assert_eq!(options.inner["name4"], OptionValue::Flag(true));
127
128        assert_eq!(options.inner["name5"], OptionValue::Single("value".into()));
129        assert_eq!(
130            options.inner["name6"],
131            OptionValue::Single("de=limiter".into())
132        );
133        assert_eq!(
134            options.inner["name7"],
135            OptionValue::Multiple(vec!["false".into(), "true".into()])
136        );
137        assert_eq!(
138            options.inner["name8"],
139            OptionValue::Multiple(vec!["value1".into(), "value2".into(), "value3".into()])
140        );
141        assert_eq!(
142            options.inner["name9"],
143            OptionValue::Multiple(vec!["value1".into(), "val=ue2".into(), "value3".into()])
144        );
145
146        assert_eq!(options.inner["name10"], OptionValue::Flag(true));
147        assert_eq!(options.inner["name11"], OptionValue::Single("value".into()));
148    }
149
150    #[test]
151    fn has_method() {
152        let options = from_env::<Options, _>(into_env(vec![
153            "option1",
154            "option2=true",
155            "option3=false",
156            "option4=true,false",
157        ]))
158        .unwrap();
159
160        assert_eq!(options.has("option1"), true);
161        assert_eq!(options.has("option2"), true);
162        assert_eq!(options.has("option3"), false);
163        assert_eq!(options.has("option4"), true);
164    }
165
166    #[test]
167    fn has_value_method() {
168        let options = from_env::<Options, _>(into_env(vec![
169            "option1",
170            "option2=true",
171            "option3=true,false,any_other",
172        ]))
173        .unwrap();
174
175        assert_eq!(options.has_value("option1", ""), false);
176        assert_eq!(options.has_value("option1", "any_other"), false);
177        assert_eq!(options.has_value("option2", ""), false);
178        assert_eq!(options.has_value("option2", "any_other"), false);
179        assert_eq!(options.has_value("option3", "true"), true);
180        assert_eq!(options.has_value("option3", "false"), true);
181        assert_eq!(options.has_value("option3", "any_other"), true);
182        assert_eq!(options.has_value("option3", "missing"), false);
183    }
184
185    #[test]
186    fn iter_method() {
187        let options = from_env::<Options, _>(into_env(vec![
188            "option1",
189            "option2=true",
190            "option3=true,false,any_other",
191        ]))
192        .unwrap();
193
194        assert!(options.iter("option1").is_none());
195        assert!(options.iter("option2").is_none());
196        assert!(options.iter("option4").is_none());
197
198        assert!(options.iter("option3").is_some());
199        assert_eq!(
200            options.iter("option3").unwrap().collect::<Vec<_>>(),
201            vec!["true", "false", "any_other"]
202        );
203    }
204
205    fn into_env(args: Vec<&'static str>) -> Vec<(String, String)> {
206        args.into_iter()
207            .enumerate()
208            .map(|(index, option)| {
209                (
210                    format!("BUILDKIT_FRONTEND_OPT_{}", index),
211                    String::from(option),
212                )
213            })
214            .collect()
215    }
216}