use std::{collections::BTreeMap, fmt, iter, mem, sync::Arc};
pub use secrecy::{ExposeSecret, SecretString};
use crate::metadata::BasicTypes;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FileFormat {
Json,
Yaml,
Dotenv,
}
impl fmt::Display for FileFormat {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Json => "JSON",
Self::Yaml => "YAML",
Self::Dotenv => ".env",
})
}
}
#[derive(Debug, Default)]
#[non_exhaustive]
pub enum ValueOrigin {
#[default]
Unknown,
EnvVars,
Fallbacks,
File {
name: String,
format: FileFormat,
},
Path {
source: Arc<Self>,
path: String,
},
Synthetic {
source: Arc<Self>,
transform: String,
},
}
impl fmt::Display for ValueOrigin {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unknown => formatter.write_str("unknown"),
Self::EnvVars => formatter.write_str("env variables"),
Self::Fallbacks => formatter.write_str("fallbacks"),
Self::File { name, format } => {
write!(formatter, "{format} file '{name}'")
}
Self::Path { source, path } => {
if matches!(source.as_ref(), ValueOrigin::EnvVars) {
write!(formatter, "env variable '{path}'")
} else {
write!(formatter, "{source} -> path '{path}'")
}
}
Self::Synthetic { source, transform } => {
write!(formatter, "{source} -> {transform}")
}
}
}
}
#[derive(Clone)]
pub enum StrValue {
Plain(String),
Secret(SecretString),
}
impl StrValue {
pub fn expose(&self) -> &str {
match self {
Self::Plain(s) => s,
Self::Secret(s) => s.expose_secret(),
}
}
pub(crate) fn is_secret(&self) -> bool {
matches!(self, Self::Secret(_))
}
pub(crate) fn make_secret(&mut self) {
match self {
Self::Plain(s) => {
*self = Self::Secret(mem::take(s).into());
}
Self::Secret(_) => { }
}
}
}
impl fmt::Debug for StrValue {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Plain(s) => fmt::Debug::fmt(s, formatter),
Self::Secret(_) => formatter.write_str("[REDACTED]"),
}
}
}
impl fmt::Display for StrValue {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Plain(s) => s,
Self::Secret(_) => "[REDACTED]",
})
}
}
#[derive(Clone, Default)]
pub enum Value {
#[default]
Null,
Bool(bool),
Number(serde_json::Number),
String(StrValue),
Array(Vec<WithOrigin>),
Object(Map),
}
impl fmt::Debug for Value {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => formatter.write_str("null"),
Self::Bool(value) => fmt::Display::fmt(value, formatter),
Self::Number(value) => fmt::Display::fmt(value, formatter),
Self::String(value) => fmt::Debug::fmt(value, formatter),
Self::Array(array) => formatter.debug_list().entries(array).finish(),
Self::Object(map) => formatter.debug_map().entries(map).finish(),
}
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl PartialEq<bool> for Value {
fn eq(&self, other: &bool) -> bool {
match self {
Self::Bool(val) => val == other,
_ => false,
}
}
}
impl From<serde_json::Number> for Value {
fn from(value: serde_json::Number) -> Self {
Self::Number(value)
}
}
impl PartialEq<serde_json::Number> for Value {
fn eq(&self, other: &serde_json::Number) -> bool {
match self {
Self::Number(num) => num == other,
_ => false,
}
}
}
macro_rules! impl_traits_for_number {
($num:ty) => {
impl From<$num> for Value {
fn from(value: $num) -> Self {
Self::Number(value.into())
}
}
impl PartialEq<$num> for Value {
fn eq(&self, other: &$num) -> bool {
match self {
Self::Number(num) => *num == (*other).into(),
_ => false,
}
}
}
};
}
impl_traits_for_number!(u64);
impl_traits_for_number!(i64);
impl From<String> for Value {
fn from(value: String) -> Self {
Self::String(StrValue::Plain(value))
}
}
impl From<Vec<WithOrigin>> for Value {
fn from(array: Vec<WithOrigin>) -> Self {
Self::Array(array)
}
}
impl From<Map> for Value {
fn from(map: Map) -> Self {
Self::Object(map)
}
}
impl Value {
pub(crate) fn is_supported_by(&self, types: BasicTypes) -> bool {
match self {
Self::Null => true,
Self::Bool(_) => types.contains(BasicTypes::BOOL),
Self::Number(number) if number.is_u64() || number.is_i64() => {
types.contains(BasicTypes::INTEGER)
}
Self::Number(_) => types.contains(BasicTypes::FLOAT),
Self::String(_) => {
types.contains(BasicTypes::STRING)
|| types.contains(BasicTypes::INTEGER)
|| types.contains(BasicTypes::BOOL)
}
Self::Array(_) => types.contains(BasicTypes::ARRAY),
Self::Object(_) => types.contains(BasicTypes::OBJECT),
}
}
pub fn as_plain_str(&self) -> Option<&str> {
match self {
Self::String(StrValue::Plain(s)) => Some(s),
_ => None,
}
}
pub fn as_object(&self) -> Option<&Map> {
match self {
Self::Object(map) => Some(map),
_ => None,
}
}
}
pub type Map<V = Value> = BTreeMap<String, WithOrigin<V>>;
#[derive(Debug, Clone, Default)]
pub struct WithOrigin<T = Value> {
pub inner: T,
pub origin: Arc<ValueOrigin>,
}
impl<T> WithOrigin<T> {
pub fn new(inner: T, origin: Arc<ValueOrigin>) -> Self {
Self { inner, origin }
}
pub(crate) fn set_origin_if_unset(mut self, origin: &Arc<ValueOrigin>) -> Self {
if matches!(self.origin.as_ref(), ValueOrigin::Unknown) {
self.origin = origin.clone();
}
self
}
pub(crate) fn map<U>(self, map_fn: impl FnOnce(T) -> U) -> WithOrigin<U> {
WithOrigin {
inner: map_fn(self.inner),
origin: self.origin,
}
}
}
impl WithOrigin {
pub(crate) fn get(&self, pointer: Pointer<'_>) -> Option<&Self> {
pointer
.segments()
.try_fold(self, |ptr, segment| match &ptr.inner {
Value::Object(map) => map.get(segment),
Value::Array(array) => array.get(segment.parse::<usize>().ok()?),
_ => None,
})
}
pub fn pointer(&self, pointer: &str) -> Option<&Self> {
self.get(Pointer(pointer))
}
pub(crate) fn get_mut(&mut self, pointer: Pointer) -> Option<&mut Self> {
pointer
.segments()
.try_fold(self, |ptr, segment| match &mut ptr.inner {
Value::Object(map) => map.get_mut(segment),
Value::Array(array) => array.get_mut(segment.parse::<usize>().ok()?),
_ => None,
})
}
pub(crate) fn ensure_object(
&mut self,
at: Pointer<'_>,
mut create_origin: impl FnMut(Pointer<'_>) -> Arc<ValueOrigin>,
) -> &mut Map {
for ancestor_path in at.with_ancestors() {
self.ensure_object_step(ancestor_path, &mut create_origin);
}
let Value::Object(map) = &mut self.get_mut(at).unwrap().inner else {
unreachable!(); };
map
}
fn ensure_object_step(
&mut self,
at: Pointer<'_>,
mut create_origin: impl FnMut(Pointer<'_>) -> Arc<ValueOrigin>,
) {
let Some((parent, last_segment)) = at.split_last() else {
return;
};
let parent = &mut self.get_mut(parent).unwrap().inner;
if !matches!(parent, Value::Object(_)) {
*parent = Value::Object(Map::new());
}
let Value::Object(parent_object) = parent else {
unreachable!();
};
if !parent_object.contains_key(last_segment) {
parent_object.insert(
last_segment.to_owned(),
WithOrigin {
inner: Value::Object(Map::new()),
origin: create_origin(at),
},
);
}
}
pub(crate) fn deep_merge(&mut self, overrides: Self) {
match (&mut self.inner, overrides.inner) {
(Value::Object(this), Value::Object(other)) => {
Self::deep_merge_into_map(this, other);
}
(this, value) => {
*this = value;
self.origin = overrides.origin;
}
}
}
fn deep_merge_into_map(dest: &mut Map, source: Map) {
for (key, value) in source {
if let Some(existing_value) = dest.get_mut(&key) {
existing_value.deep_merge(value);
} else {
dest.insert(key, value);
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct Pointer<'a>(pub &'a str);
impl fmt::Display for Pointer<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.0)
}
}
impl<'a> Pointer<'a> {
pub(crate) fn segments(self) -> impl Iterator<Item = &'a str> {
self.0
.split('.')
.take(if self.0.is_empty() { 0 } else { usize::MAX })
}
pub(crate) fn split_last(self) -> Option<(Self, &'a str)> {
if self.0.is_empty() {
None
} else if let Some((parent, last_segment)) = self.0.rsplit_once('.') {
Some((Self(parent), last_segment))
} else {
Some((Self(""), self.0))
}
}
pub(crate) fn with_ancestors(self) -> impl Iterator<Item = Self> {
let mut current = self.0;
iter::from_fn(move || {
if current.is_empty() {
None
} else if let Some((_, tail)) = current.split_once('.') {
current = tail;
Some(Self(&self.0[..self.0.len() - tail.len() - 1]))
} else {
current = "";
Some(self)
}
})
}
pub(crate) fn join(self, suffix: &str) -> String {
if suffix.is_empty() {
self.0.to_owned()
} else if self.0.is_empty() {
suffix.to_owned()
} else {
format!("{}.{suffix}", self.0)
}
}
pub(crate) fn join_path(mut self, suffix: Pointer<'_>) -> Option<String> {
let prefix_dots = suffix.0.bytes().take_while(|&ch| ch == b'.').count();
for _ in 0..prefix_dots.saturating_sub(1) {
(self, _) = self.split_last()?;
}
Some(self.join(&suffix.0[prefix_dots..]))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn splitting_pointer() {
let pointer = Pointer("");
assert_eq!(pointer.split_last(), None);
assert_eq!(pointer.segments().collect::<Vec<_>>(), [] as [&str; 0]);
assert_eq!(pointer.with_ancestors().collect::<Vec<_>>(), []);
let pointer = Pointer("test");
assert_eq!(pointer.split_last(), Some((Pointer(""), "test")));
assert_eq!(pointer.segments().collect::<Vec<_>>(), ["test"]);
assert_eq!(
pointer.with_ancestors().collect::<Vec<_>>(),
[Pointer("test")]
);
let pointer = Pointer("test.value");
assert_eq!(pointer.split_last(), Some((Pointer("test"), "value")));
assert_eq!(pointer.segments().collect::<Vec<_>>(), ["test", "value"]);
assert_eq!(
pointer.with_ancestors().collect::<Vec<_>>(),
[Pointer("test"), Pointer("test.value")]
);
}
#[test]
fn joining_pointers() {
let pointer = Pointer("");
let joined = pointer.join("test");
assert_eq!(joined, "test");
let pointer = Pointer("test");
let joined = pointer.join("");
assert_eq!(joined, "test");
let pointer = Pointer("test");
let joined = pointer.join("other");
assert_eq!(joined, "test.other");
}
#[test]
fn joining_pointer_paths() {
let pointer = Pointer("");
let joined = pointer.join_path(Pointer("test")).unwrap();
assert_eq!(joined, "test");
let pointer = Pointer("");
let joined = pointer.join_path(Pointer(".test")).unwrap();
assert_eq!(joined, "test");
let pointer = Pointer("");
let joined = pointer.join_path(Pointer(".test.value")).unwrap();
assert_eq!(joined, "test.value");
let pointer = Pointer("map");
let joined = pointer.join_path(Pointer(".test.value")).unwrap();
assert_eq!(joined, "map.test.value");
let pointer = Pointer("map");
let joined = pointer.join_path(Pointer("..test.value")).unwrap();
assert_eq!(joined, "test.value");
let pointer = Pointer("map.key");
let joined = pointer.join_path(Pointer("..test.value")).unwrap();
assert_eq!(joined, "map.test.value");
let pointer = Pointer("map.key");
let joined = pointer.join_path(Pointer("...test.value")).unwrap();
assert_eq!(joined, "test.value");
}
}