buildkit_frontend/options/
deserializer.rs

1use std::io::Cursor;
2use std::iter::empty;
3
4use failure::Error;
5use serde::de::value::{MapDeserializer, SeqDeserializer};
6use serde::de::{self, DeserializeOwned, IntoDeserializer, Visitor};
7use serde::forward_to_deserialize_any;
8
9pub fn from_env<T, I>(pairs: I) -> Result<T, Error>
10where
11    T: DeserializeOwned,
12    I: IntoIterator<Item = (String, String)>,
13{
14    let owned_pairs = pairs.into_iter().collect::<Vec<_>>();
15    let pairs = {
16        owned_pairs.iter().filter_map(|(name, value)| {
17            if name.starts_with("BUILDKIT_FRONTEND_OPT_") {
18                Some(value)
19            } else {
20                None
21            }
22        })
23    };
24
25    let deserializer = EnvDeserializer {
26        vals: pairs.map(|value| extract_name_and_value(&value)),
27    };
28
29    T::deserialize(deserializer).map_err(Error::from)
30}
31
32#[derive(Debug)]
33struct EnvDeserializer<P> {
34    vals: P,
35}
36
37#[derive(Debug)]
38enum EnvValue<'de> {
39    Flag,
40    Json(&'de str),
41    Text(&'de str),
42}
43
44#[derive(Debug)]
45struct EnvItem<'de>(&'de str);
46
47fn extract_name_and_value(mut raw_value: &str) -> (&str, EnvValue) {
48    if raw_value.starts_with("build-arg:") {
49        raw_value = raw_value.trim_start_matches("build-arg:");
50    }
51
52    let mut parts = raw_value.splitn(2, '=');
53    let name = parts.next().unwrap();
54
55    match parts.next() {
56        None => (name, EnvValue::Flag),
57        Some(text) if text.is_empty() => (name, EnvValue::Flag),
58        Some(text) if &text[0..1] == "[" || &text[0..1] == "{" => (name, EnvValue::Json(text)),
59        Some(text) => (name, EnvValue::Text(text)),
60    }
61}
62
63impl<'de> IntoDeserializer<'de, serde::de::value::Error> for EnvValue<'de> {
64    type Deserializer = Self;
65
66    fn into_deserializer(self) -> Self::Deserializer {
67        self
68    }
69}
70
71impl<'de> IntoDeserializer<'de, serde::de::value::Error> for EnvItem<'de> {
72    type Deserializer = Self;
73
74    fn into_deserializer(self) -> Self::Deserializer {
75        self
76    }
77}
78
79impl<'de> EnvItem<'de> {
80    fn infer<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, serde::de::value::Error> {
81        match self.0 {
82            "true" => visitor.visit_bool(true),
83            "false" => visitor.visit_bool(false),
84
85            _ => visitor.visit_str(self.0),
86        }
87    }
88
89    fn json<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, serde::de::value::Error> {
90        use serde::de::Deserializer;
91        use serde::de::Error;
92
93        serde_json::Deserializer::from_reader(Cursor::new(self.0))
94            .deserialize_any(visitor)
95            .map_err(serde::de::value::Error::custom)
96    }
97}
98
99impl<'de, P> de::Deserializer<'de> for EnvDeserializer<P>
100where
101    P: Iterator<Item = (&'de str, EnvValue<'de>)>,
102{
103    type Error = serde::de::value::Error;
104
105    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
106        visitor.visit_map(MapDeserializer::new(self.vals))
107    }
108
109    forward_to_deserialize_any! {
110        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
111        bytes byte_buf option unit unit_struct newtype_struct seq tuple
112        tuple_struct map struct enum identifier ignored_any
113    }
114}
115
116// The approach is shamelessly borrowed from https://github.com/softprops/envy/blob/master/src/lib.rs#L113
117macro_rules! forward_parsed_values_env_value {
118    ($($ty:ident => $method:ident,)*) => {
119        $(
120            fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error>
121                where V: de::Visitor<'de>
122            {
123                match self {
124                    EnvValue::Flag => self.deserialize_any(visitor),
125                    EnvValue::Json(_) => self.deserialize_any(visitor),
126                    EnvValue::Text(contents) => {
127                        match contents.parse::<$ty>() {
128                            Ok(val) => val.into_deserializer().$method(visitor),
129                            Err(e) => Err(de::Error::custom(format_args!("{} while parsing value '{}'", e, contents)))
130                        }
131                    }
132                }
133            }
134        )*
135    }
136}
137
138macro_rules! forward_parsed_values_env_item {
139    ($($ty:ident => $method:ident,)*) => {
140        $(
141            fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error>
142                where V: de::Visitor<'de>
143            {
144                match self.0.parse::<$ty>() {
145                    Ok(val) => val.into_deserializer().$method(visitor),
146                    Err(e) => Err(de::Error::custom(format_args!("{} while parsing value '{}'", e, self.0)))
147                }
148            }
149        )*
150    }
151}
152
153impl<'de> de::Deserializer<'de> for EnvValue<'de> {
154    type Error = serde::de::value::Error;
155
156    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
157        match self {
158            EnvValue::Flag => visitor.visit_bool(true),
159            EnvValue::Json(contents) => EnvItem(contents).json(visitor),
160            EnvValue::Text(contents) => {
161                if !contents.contains(',') {
162                    EnvItem(contents).infer(visitor)
163                } else {
164                    SeqDeserializer::new(contents.split(',')).deserialize_seq(visitor)
165                }
166            }
167        }
168    }
169
170    fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
171        match self {
172            EnvValue::Flag => SeqDeserializer::new(empty::<&'de str>()).deserialize_seq(visitor),
173            EnvValue::Json(contents) => EnvItem(contents).json(visitor),
174            EnvValue::Text(contents) => {
175                SeqDeserializer::new(contents.split(',')).deserialize_seq(visitor)
176            }
177        }
178    }
179
180    fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
181        visitor.visit_some(self)
182    }
183
184    forward_parsed_values_env_value! {
185        bool => deserialize_bool,
186        u8 => deserialize_u8,
187        u16 => deserialize_u16,
188        u32 => deserialize_u32,
189        u64 => deserialize_u64,
190        u128 => deserialize_u128,
191        i8 => deserialize_i8,
192        i16 => deserialize_i16,
193        i32 => deserialize_i32,
194        i64 => deserialize_i64,
195        i128 => deserialize_i128,
196        f32 => deserialize_f32,
197        f64 => deserialize_f64,
198    }
199
200    forward_to_deserialize_any! {
201        byte_buf
202        bytes
203        char
204        enum
205        identifier
206        ignored_any
207        map
208        newtype_struct
209        str
210        string
211        struct
212        tuple
213        tuple_struct
214        unit
215        unit_struct
216    }
217}
218
219impl<'de> de::Deserializer<'de> for EnvItem<'de> {
220    type Error = serde::de::value::Error;
221
222    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
223        self.0.into_deserializer().deserialize_any(visitor)
224    }
225
226    fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
227        self.json(visitor)
228    }
229
230    fn deserialize_struct<V: Visitor<'de>>(
231        self,
232        _: &'static str,
233        _: &'static [&'static str],
234        visitor: V,
235    ) -> Result<V::Value, Self::Error> {
236        self.json(visitor)
237    }
238
239    forward_parsed_values_env_item! {
240        bool => deserialize_bool,
241        u8 => deserialize_u8,
242        u16 => deserialize_u16,
243        u32 => deserialize_u32,
244        u64 => deserialize_u64,
245        u128 => deserialize_u128,
246        i8 => deserialize_i8,
247        i16 => deserialize_i16,
248        i32 => deserialize_i32,
249        i64 => deserialize_i64,
250        i128 => deserialize_i128,
251        f32 => deserialize_f32,
252        f64 => deserialize_f64,
253    }
254
255    forward_to_deserialize_any! {
256        byte_buf
257        bytes
258        char
259        enum
260        identifier
261        ignored_any
262        newtype_struct
263        option
264        seq
265        str
266        string
267        tuple
268        tuple_struct
269        unit
270        unit_struct
271    }
272}