use std::borrow::Cow;
use std::collections::HashMap;
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,
context: deser_core::Context,
}
impl Default for SerializerConfig {
fn default() -> SerializerConfig {
SerializerConfig::new()
}
}
impl SerializerConfig {
pub const fn new() -> SerializerConfig {
SerializerConfig {
separator: "__",
case: Case::Upper,
context: deser_core::Context::new(),
}
}
pub const fn builder() -> SerializerConfigBuilder {
SerializerConfigBuilder::new()
}
pub const fn into_builder(self) -> SerializerConfigBuilder {
SerializerConfigBuilder { value: self }
}
pub fn set_context(&mut self, context: deser_core::Context) {
self.context = context;
}
pub fn context(&self) -> &deser_core::Context {
&self.context
}
#[inline]
fn apply_context(&self, driver: &mut SerializeDriver<'_>) {
if !self.context.is_empty() {
driver.set_default_context(self.context.clone());
}
}
pub const fn set_separator(&mut self, separator: &'static str) {
self.separator = separator;
}
pub const fn set_case(&mut self, case: Case) {
self.case = case;
}
pub fn to_vars<T: Serialize + ?Sized>(
&self,
prefix: &str,
value: &T,
) -> Result<Vec<(String, String)>, Error> {
self.to_vars_with(prefix, value, |_| {})
}
pub fn to_vars_with<F, T: Serialize + ?Sized>(
&self,
prefix: &str,
value: &T,
setup: F,
) -> Result<Vec<(String, String)>, Error>
where
F: FnOnce(&mut SerializeDriver<'_>),
{
let mut driver = SerializeDriver::new(&value);
setup(&mut driver);
self.apply_context(&mut driver);
let mut writer = Writer {
config: self,
bytes: BytesFormat::of(driver.state()),
out: Vec::new(),
name: prefix.to_string(),
stack: Vec::new(),
};
driver.drive(|event, state| writer.event(event, state))?;
writer.check_names(prefix.len())?;
Ok(writer.out)
}
}
#[derive(Debug, Clone)]
#[must_use]
pub struct SerializerConfigBuilder {
value: SerializerConfig,
}
impl SerializerConfigBuilder {
pub const fn new() -> SerializerConfigBuilder {
SerializerConfigBuilder {
value: SerializerConfig::new(),
}
}
pub const fn separator(mut self, separator: &'static str) -> SerializerConfigBuilder {
self.value.set_separator(separator);
self
}
pub const fn case(mut self, case: Case) -> SerializerConfigBuilder {
self.value.set_case(case);
self
}
pub fn context(mut self, context: deser_core::Context) -> SerializerConfigBuilder {
self.value.set_context(context);
self
}
pub const fn build(self) -> SerializerConfig {
let value = unsafe { core::ptr::read(&self.value) };
core::mem::forget(self);
value
}
}
impl Default for SerializerConfigBuilder {
fn default() -> SerializerConfigBuilder {
SerializerConfigBuilder::new()
}
}
pub fn to_vars<T: Serialize + ?Sized>(
prefix: &str,
value: &T,
) -> Result<Vec<(String, String)>, Error> {
SerializerConfig::new().to_vars(prefix, value)
}
enum Frame {
Map { prefix: usize },
Seq { prefix: usize, index: usize },
}
impl Frame {
fn prefix(&self) -> usize {
match *self {
Frame::Map { prefix } | Frame::Seq { prefix, .. } => prefix,
}
}
}
struct Writer<'c> {
config: &'c SerializerConfig,
bytes: BytesFormat,
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 check_names(&self, prefix: usize) -> Result<(), Error> {
let normalize = |name: &str| match self.config.case {
Case::Upper => name.to_ascii_lowercase(),
Case::Preserve => name.to_string(),
};
let names: HashMap<String, &str> = self
.out
.iter()
.map(|(name, _)| (normalize(name), name.as_str()))
.collect();
if names.len() != self.out.len() {
return Err(Error::new(
ErrorKind::UnsupportedType,
"different keys have the same name",
));
}
let mut segments = Vec::new();
for name in names.keys() {
segments.clear();
crate::de::split_name(&name[prefix..], self.config.separator, &mut segments);
for &(_, end) in &segments[..segments.len() - 1] {
if let Some(parent) = names.get(&name[..prefix + end]) {
return Err(Error::new(
ErrorKind::UnsupportedType,
format!("the variable {parent:?} has a value and nested variables"),
));
}
}
}
Ok(())
}
fn check_name(&self) -> Result<(), Error> {
let Some(base) = self.stack.first().map(Frame::prefix) else {
return Ok(());
};
let separator = self.config.separator;
let mut segments = Vec::new();
crate::de::split_name(&self.name[base..], separator, &mut segments);
let expected = self.stack.iter().enumerate().map(|(index, frame)| {
let start = match index {
0 => frame.prefix(),
_ => frame.prefix() + separator.len(),
};
let end = match self.stack.get(index + 1) {
Some(next) => next.prefix(),
None => self.name.len(),
};
(start - base, end - base)
});
if segments.iter().copied().eq(expected) {
Ok(())
} else {
Err(Error::new(
ErrorKind::UnsupportedType,
format!(
"the name {:?} does not split into its keys at the separator",
self.name
),
))
}
}
fn value(&mut self, event: Event, in_seq: bool) -> Result<(), Error> {
match event {
Event::Atom(atom) => {
let value = match value_text(&atom, self.bytes)? {
Some(value) => value,
None if in_seq => Cow::Borrowed(""),
None => return Ok(()),
};
self.check_name()?;
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(zmij::Buffer::new().format(value).into()),
Atom::F64(value) => Cow::Owned(zmij::Buffer::new().format(value).into()),
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",
)
}