use std::borrow::Cow;
use deser_core::__format::{Float, format_finite};
use deser_core::ext::Number;
use deser_core::ser::SerializeDriver;
use deser_core::{Atom, BytesFormat, Error, ErrorKind, Event, Serialize, State};
use crate::Case;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SerializerConfig {
separator: &'static str,
case: Case,
bytes: BytesFormat,
}
impl Default for SerializerConfig {
fn default() -> SerializerConfig {
SerializerConfig::new()
}
}
impl SerializerConfig {
pub const fn new() -> SerializerConfig {
SerializerConfig {
separator: "__",
case: Case::Upper,
bytes: BytesFormat::BASE64,
}
}
pub const fn separator(mut self, separator: &'static str) -> SerializerConfig {
self.separator = separator;
self
}
pub const fn case(mut self, case: Case) -> SerializerConfig {
self.case = case;
self
}
pub const fn bytes(mut self, format: BytesFormat) -> SerializerConfig {
self.bytes = format;
self
}
pub fn to_vars(
&self,
prefix: &str,
value: &dyn Serialize,
) -> Result<Vec<(String, String)>, Error> {
self.to_vars_with(prefix, value, |_| {})
}
pub fn to_vars_with<F>(
&self,
prefix: &str,
value: &dyn Serialize,
setup: F,
) -> Result<Vec<(String, String)>, Error>
where
F: FnOnce(&mut SerializeDriver<'_>),
{
let mut driver = SerializeDriver::new(value);
setup(&mut driver);
let mut writer = Writer {
config: self,
out: Vec::new(),
name: prefix.to_string(),
stack: Vec::new(),
};
driver.drive(|event, state| writer.event(event, state))?;
Ok(writer.out)
}
}
pub fn to_vars(prefix: &str, value: &dyn Serialize) -> Result<Vec<(String, String)>, Error> {
SerializerConfig::new().to_vars(prefix, value)
}
enum Frame {
Map { prefix: usize },
Seq { prefix: usize, index: usize },
}
struct Writer<'c> {
config: &'c SerializerConfig,
out: Vec<(String, String)>,
name: String,
stack: Vec<Frame>,
}
impl Writer<'_> {
fn event(&mut self, event: Event, state: &State) -> Result<(), Error> {
match (self.stack.last_mut(), event) {
(None, Event::MapStart(_)) => self.stack.push(Frame::Map {
prefix: self.name.len(),
}),
(None, Event::Atom(Atom::Null)) => {}
(None, _) => {
return Err(Error::new(
ErrorKind::UnsupportedType,
"environment variables hold maps (like structs)",
));
}
(Some(Frame::Map { .. }), Event::MapEnd) => {
self.stack.pop();
}
(Some(&mut Frame::Map { prefix }), event) => {
if state.is_map_key() {
let key = match event {
Event::Atom(ref atom) => key_text(atom)?,
_ => return Err(unsupported_key()),
};
if key.is_empty() {
return Err(Error::new(
ErrorKind::UnsupportedType,
"keys of environment variables must not be empty",
));
}
if !self.config.separator.is_empty() && key.contains(self.config.separator) {
return Err(Error::new(
ErrorKind::UnsupportedType,
format!("key {:?} contains the separator", key),
));
}
self.name.truncate(prefix);
if self.stack.len() > 1 {
self.name.push_str(self.config.separator);
}
self.push_key(&key);
} else {
self.value(event, false)?;
}
}
(Some(Frame::Seq { .. }), Event::SeqEnd) => {
self.stack.pop();
}
(
Some(&mut Frame::Seq {
prefix,
ref mut index,
}),
event,
) => {
let element = *index;
*index += 1;
self.name.truncate(prefix);
self.name.push_str(self.config.separator);
self.name.push_str(&element.to_string());
self.value(event, true)?;
}
}
Ok(())
}
fn push_key(&mut self, key: &str) {
match self.config.case {
Case::Upper => self
.name
.extend(key.chars().map(|c| c.to_ascii_uppercase())),
Case::Preserve => self.name.push_str(key),
}
}
fn value(&mut self, event: Event, in_seq: bool) -> Result<(), Error> {
match event {
Event::Atom(atom) => {
let value = match value_text(&atom, self.config.bytes)? {
Some(value) => value,
None if in_seq => Cow::Borrowed(""),
None => return Ok(()),
};
self.out.push((self.name.clone(), value.into_owned()));
}
Event::MapStart(_) | Event::SeqStart(_) if self.config.separator.is_empty() => {
return Err(Error::new(
ErrorKind::UnsupportedType,
"nested maps and sequences require a separator",
));
}
Event::MapStart(_) => self.stack.push(Frame::Map {
prefix: self.name.len(),
}),
Event::SeqStart(_) => self.stack.push(Frame::Seq {
prefix: self.name.len(),
index: 0,
}),
Event::MapEnd | Event::SeqEnd => unreachable!("ends are handled by the frames"),
}
Ok(())
}
}
fn key_text<'a>(atom: &'a Atom<'_>) -> Result<Cow<'a, str>, Error> {
match atom {
Atom::Null | Atom::Bytes(_) => Err(unsupported_key()),
atom => value_text(atom, BytesFormat::BASE64)?.ok_or_else(unsupported_key),
}
}
fn value_text<'a>(atom: &'a Atom<'_>, bytes: BytesFormat) -> Result<Option<Cow<'a, str>>, Error> {
Ok(Some(match *atom {
Atom::Null => return Ok(None),
Atom::Implicit(ref value) => {
return Ok(value_text(&value.value().to_atom(), bytes)?
.map(|text| Cow::Owned(text.into_owned())));
}
Atom::Bool(value) => Cow::Borrowed(if value { "true" } else { "false" }),
Atom::Str(ref value) | Atom::Lexical(ref value) => Cow::Borrowed(&**value),
Atom::Char(value) => Cow::Owned(value.to_string()),
Atom::U64(value) => Cow::Owned(value.to_string()),
Atom::I64(value) => Cow::Owned(value.to_string()),
Atom::F32(value) => Cow::Owned(float_text(value)),
Atom::F64(value) => Cow::Owned(float_text(value)),
Atom::Bytes(ref value) => {
let format = value.fallback.copied().unwrap_or(bytes);
Cow::Owned(
format
.encode(value)
.or_else(|| BytesFormat::BASE64.encode(value))
.unwrap_or_default(),
)
}
Atom::Ext(ref ext) => {
if let Some(number) = ext.downcast_value_ref::<Number>() {
Cow::Owned(number.as_str().to_string())
} else if let Some(value) = ext.downcast_ref::<u128>() {
Cow::Owned(value.to_string())
} else if let Some(value) = ext.downcast_ref::<i128>() {
Cow::Owned(value.to_string())
} else {
match ext.fallback() {
Atom::Ext(_) => {
return Err(Error::new(
ErrorKind::UnsupportedType,
format!("environment variables do not support {}", ext.name()),
));
}
fallback => match value_text(&fallback, bytes)? {
Some(text) => Cow::Owned(text.into_owned()),
None => return Ok(None),
},
}
}
}
_ => {
return Err(Error::new(
ErrorKind::UnsupportedType,
format!("environment variables do not support {}", atom.name()),
));
}
}))
}
#[cold]
fn unsupported_key() -> Error {
Error::new(
ErrorKind::UnsupportedType,
"keys of environment variables must be strings, numbers or booleans",
)
}
fn float_text<F: Float>(value: F) -> String {
if value.is_finite() {
format_finite(value)
} else {
value.to_f64().to_string()
}
}