You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
7.7 KiB
Rust
273 lines
7.7 KiB
Rust
use std::io::Cursor;
|
|
use std::iter::empty;
|
|
|
|
use anyhow::Result;
|
|
use serde::de::value::{MapDeserializer, SeqDeserializer};
|
|
use serde::de::{self, DeserializeOwned, IntoDeserializer, Visitor};
|
|
use serde::forward_to_deserialize_any;
|
|
|
|
pub fn from_env<T, I>(pairs: I) -> Result<T>
|
|
where
|
|
T: DeserializeOwned,
|
|
I: IntoIterator<Item = (String, String)>,
|
|
{
|
|
let owned_pairs = pairs.into_iter().collect::<Vec<_>>();
|
|
let pairs = {
|
|
owned_pairs.iter().filter_map(|(name, value)| {
|
|
if name.starts_with("BUILDKIT_FRONTEND_OPT_") {
|
|
Some(value)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
};
|
|
|
|
let deserializer = EnvDeserializer {
|
|
vals: pairs.map(|value| extract_name_and_value(&value)),
|
|
};
|
|
|
|
Ok(T::deserialize(deserializer)?)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct EnvDeserializer<P> {
|
|
vals: P,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum EnvValue<'de> {
|
|
Flag,
|
|
Json(&'de str),
|
|
Text(&'de str),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct EnvItem<'de>(&'de str);
|
|
|
|
fn extract_name_and_value(mut raw_value: &str) -> (&str, EnvValue) {
|
|
if raw_value.starts_with("build-arg:") {
|
|
raw_value = raw_value.trim_start_matches("build-arg:");
|
|
}
|
|
|
|
let mut parts = raw_value.splitn(2, '=');
|
|
let name = parts.next().unwrap();
|
|
|
|
match parts.next() {
|
|
None => (name, EnvValue::Flag),
|
|
Some(text) if text.is_empty() => (name, EnvValue::Flag),
|
|
Some(text) if &text[0..1] == "[" || &text[0..1] == "{" => (name, EnvValue::Json(text)),
|
|
Some(text) => (name, EnvValue::Text(text)),
|
|
}
|
|
}
|
|
|
|
impl<'de> IntoDeserializer<'de, serde::de::value::Error> for EnvValue<'de> {
|
|
type Deserializer = Self;
|
|
|
|
fn into_deserializer(self) -> Self::Deserializer {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<'de> IntoDeserializer<'de, serde::de::value::Error> for EnvItem<'de> {
|
|
type Deserializer = Self;
|
|
|
|
fn into_deserializer(self) -> Self::Deserializer {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<'de> EnvItem<'de> {
|
|
fn infer<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, serde::de::value::Error> {
|
|
match self.0 {
|
|
"true" => visitor.visit_bool(true),
|
|
"false" => visitor.visit_bool(false),
|
|
|
|
_ => visitor.visit_str(self.0),
|
|
}
|
|
}
|
|
|
|
fn json<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, serde::de::value::Error> {
|
|
use serde::de::Deserializer;
|
|
use serde::de::Error;
|
|
|
|
serde_json::Deserializer::from_reader(Cursor::new(self.0))
|
|
.deserialize_any(visitor)
|
|
.map_err(serde::de::value::Error::custom)
|
|
}
|
|
}
|
|
|
|
impl<'de, P> de::Deserializer<'de> for EnvDeserializer<P>
|
|
where
|
|
P: Iterator<Item = (&'de str, EnvValue<'de>)>,
|
|
{
|
|
type Error = serde::de::value::Error;
|
|
|
|
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
visitor.visit_map(MapDeserializer::new(self.vals))
|
|
}
|
|
|
|
forward_to_deserialize_any! {
|
|
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
|
|
bytes byte_buf option unit unit_struct newtype_struct seq tuple
|
|
tuple_struct map struct enum identifier ignored_any
|
|
}
|
|
}
|
|
|
|
// The approach is shamelessly borrowed from https://github.com/softprops/envy/blob/master/src/lib.rs#L113
|
|
macro_rules! forward_parsed_values_env_value {
|
|
($($ty:ident => $method:ident,)*) => {
|
|
$(
|
|
fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
where V: de::Visitor<'de>
|
|
{
|
|
match self {
|
|
EnvValue::Flag => self.deserialize_any(visitor),
|
|
EnvValue::Json(_) => self.deserialize_any(visitor),
|
|
EnvValue::Text(contents) => {
|
|
match contents.parse::<$ty>() {
|
|
Ok(val) => val.into_deserializer().$method(visitor),
|
|
Err(e) => Err(de::Error::custom(format_args!("{} while parsing value '{}'", e, contents)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
)*
|
|
}
|
|
}
|
|
|
|
macro_rules! forward_parsed_values_env_item {
|
|
($($ty:ident => $method:ident,)*) => {
|
|
$(
|
|
fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
where V: de::Visitor<'de>
|
|
{
|
|
match self.0.parse::<$ty>() {
|
|
Ok(val) => val.into_deserializer().$method(visitor),
|
|
Err(e) => Err(de::Error::custom(format_args!("{} while parsing value '{}'", e, self.0)))
|
|
}
|
|
}
|
|
)*
|
|
}
|
|
}
|
|
|
|
impl<'de> de::Deserializer<'de> for EnvValue<'de> {
|
|
type Error = serde::de::value::Error;
|
|
|
|
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
match self {
|
|
EnvValue::Flag => visitor.visit_bool(true),
|
|
EnvValue::Json(contents) => EnvItem(contents).json(visitor),
|
|
EnvValue::Text(contents) => {
|
|
if !contents.contains(',') {
|
|
EnvItem(contents).infer(visitor)
|
|
} else {
|
|
SeqDeserializer::new(contents.split(',')).deserialize_seq(visitor)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
match self {
|
|
EnvValue::Flag => SeqDeserializer::new(empty::<&'de str>()).deserialize_seq(visitor),
|
|
EnvValue::Json(contents) => EnvItem(contents).json(visitor),
|
|
EnvValue::Text(contents) => {
|
|
SeqDeserializer::new(contents.split(',')).deserialize_seq(visitor)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
visitor.visit_some(self)
|
|
}
|
|
|
|
forward_parsed_values_env_value! {
|
|
bool => deserialize_bool,
|
|
u8 => deserialize_u8,
|
|
u16 => deserialize_u16,
|
|
u32 => deserialize_u32,
|
|
u64 => deserialize_u64,
|
|
u128 => deserialize_u128,
|
|
i8 => deserialize_i8,
|
|
i16 => deserialize_i16,
|
|
i32 => deserialize_i32,
|
|
i64 => deserialize_i64,
|
|
i128 => deserialize_i128,
|
|
f32 => deserialize_f32,
|
|
f64 => deserialize_f64,
|
|
}
|
|
|
|
forward_to_deserialize_any! {
|
|
byte_buf
|
|
bytes
|
|
char
|
|
enum
|
|
identifier
|
|
ignored_any
|
|
map
|
|
newtype_struct
|
|
str
|
|
string
|
|
struct
|
|
tuple
|
|
tuple_struct
|
|
unit
|
|
unit_struct
|
|
}
|
|
}
|
|
|
|
impl<'de> de::Deserializer<'de> for EnvItem<'de> {
|
|
type Error = serde::de::value::Error;
|
|
|
|
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
self.0.into_deserializer().deserialize_any(visitor)
|
|
}
|
|
|
|
fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
|
|
self.json(visitor)
|
|
}
|
|
|
|
fn deserialize_struct<V: Visitor<'de>>(
|
|
self,
|
|
_: &'static str,
|
|
_: &'static [&'static str],
|
|
visitor: V,
|
|
) -> Result<V::Value, Self::Error> {
|
|
self.json(visitor)
|
|
}
|
|
|
|
forward_parsed_values_env_item! {
|
|
bool => deserialize_bool,
|
|
u8 => deserialize_u8,
|
|
u16 => deserialize_u16,
|
|
u32 => deserialize_u32,
|
|
u64 => deserialize_u64,
|
|
u128 => deserialize_u128,
|
|
i8 => deserialize_i8,
|
|
i16 => deserialize_i16,
|
|
i32 => deserialize_i32,
|
|
i64 => deserialize_i64,
|
|
i128 => deserialize_i128,
|
|
f32 => deserialize_f32,
|
|
f64 => deserialize_f64,
|
|
}
|
|
|
|
forward_to_deserialize_any! {
|
|
byte_buf
|
|
bytes
|
|
char
|
|
enum
|
|
identifier
|
|
ignored_any
|
|
newtype_struct
|
|
option
|
|
seq
|
|
str
|
|
string
|
|
tuple
|
|
tuple_struct
|
|
unit
|
|
unit_struct
|
|
}
|
|
}
|