use super::interpolation::KeyPath;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use serde::de::{DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor};
use serde::forward_to_deserialize_any;
fn json_kind_name(v: &serde_json::Value) -> &'static str {
match v {
serde_json::Value::Null => "null",
serde_json::Value::Bool(_) => "bool",
serde_json::Value::Number(_) => "number",
serde_json::Value::String(_) => "string",
serde_json::Value::Array(_) => "array",
serde_json::Value::Object(_) => "object",
}
}
macro_rules! impl_scalar_coerce {
($method:ident, $visit:ident, $ty:ty, $err_ty:ty, $type_name:expr) => {
fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.value {
serde_json::Value::String(s) => {
let parsed: $ty = s.parse().map_err(|e: $err_ty| CoercionError::Parse {
target_type: $type_name.to_string(),
value: s.clone(),
key_path: self.key_path.to_string(),
source: Box::new(e),
})?;
visitor.$visit(parsed)
}
other => other
.clone()
.$method(visitor)
.map_err(|e| CoercionError::Parse {
target_type: $type_name.to_string(),
value: other.to_string(),
key_path: self.key_path.to_string(),
source: Box::new(e),
}),
}
}
};
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CoercionError {
#[error(
"failed to coerce value `{value}` into {target_type} at \
key `{key_path}`: {source}"
)]
Parse {
target_type: String,
value: String,
key_path: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error(
"cannot coerce string into {target} at key `{key_path}`: \
use field-by-field interpolation instead"
)]
UnsupportedShape {
target: &'static str,
key_path: String,
},
#[error(transparent)]
Serde(#[from] serde::de::value::Error),
}
impl serde::de::Error for CoercionError {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
CoercionError::Serde(serde::de::Error::custom(msg))
}
}
pub struct TypedSettingsDeserializer<'a> {
value: &'a serde_json::Value,
key_path: KeyPath,
}
impl<'a> TypedSettingsDeserializer<'a> {
pub fn new(value: &'a serde_json::Value) -> Self {
Self {
value,
key_path: KeyPath::default(),
}
}
}
impl<'de, 'a> Deserializer<'de> for TypedSettingsDeserializer<'a> {
type Error = CoercionError;
forward_to_deserialize_any! {
str string unit unit_struct newtype_struct identifier ignored_any
}
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.value
.clone()
.deserialize_any(visitor)
.map_err(|e| CoercionError::Parse {
target_type: "any".to_string(),
value: self.value.to_string(),
key_path: self.key_path.to_string(),
source: Box::new(e),
})
}
impl_scalar_coerce!(
deserialize_bool,
visit_bool,
bool,
std::str::ParseBoolError,
"bool"
);
impl_scalar_coerce!(deserialize_i8, visit_i8, i8, std::num::ParseIntError, "i8");
impl_scalar_coerce!(
deserialize_i16,
visit_i16,
i16,
std::num::ParseIntError,
"i16"
);
impl_scalar_coerce!(
deserialize_i32,
visit_i32,
i32,
std::num::ParseIntError,
"i32"
);
impl_scalar_coerce!(
deserialize_i64,
visit_i64,
i64,
std::num::ParseIntError,
"i64"
);
impl_scalar_coerce!(
deserialize_i128,
visit_i128,
i128,
std::num::ParseIntError,
"i128"
);
impl_scalar_coerce!(deserialize_u8, visit_u8, u8, std::num::ParseIntError, "u8");
impl_scalar_coerce!(
deserialize_u16,
visit_u16,
u16,
std::num::ParseIntError,
"u16"
);
impl_scalar_coerce!(
deserialize_u32,
visit_u32,
u32,
std::num::ParseIntError,
"u32"
);
impl_scalar_coerce!(
deserialize_u64,
visit_u64,
u64,
std::num::ParseIntError,
"u64"
);
impl_scalar_coerce!(
deserialize_u128,
visit_u128,
u128,
std::num::ParseIntError,
"u128"
);
impl_scalar_coerce!(
deserialize_f32,
visit_f32,
f32,
std::num::ParseFloatError,
"f32"
);
impl_scalar_coerce!(
deserialize_f64,
visit_f64,
f64,
std::num::ParseFloatError,
"f64"
);
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.value {
serde_json::Value::Null => visitor.visit_none(),
serde_json::Value::String(s) if s.is_empty() => visitor.visit_none(),
_ => visitor.visit_some(self),
}
}
fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.value {
serde_json::Value::String(s) => {
let mut iter = s.chars();
let c = iter.next().ok_or_else(|| CoercionError::Parse {
target_type: "char".to_string(),
value: s.clone(),
key_path: self.key_path.to_string(),
source: "expected exactly one char, got empty string".into(),
})?;
if iter.next().is_some() {
return Err(CoercionError::Parse {
target_type: "char".to_string(),
value: s.clone(),
key_path: self.key_path.to_string(),
source: format!(
"expected exactly one char, got {} chars",
s.chars().count()
)
.into(),
});
}
visitor.visit_char(c)
}
other => other
.clone()
.deserialize_char(visitor)
.map_err(|e| CoercionError::Parse {
target_type: "char".to_string(),
value: other.to_string(),
key_path: self.key_path.to_string(),
source: Box::new(e),
}),
}
}
fn deserialize_enum<V>(
self,
name: &'static str,
variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.value
.clone()
.deserialize_enum(name, variants, visitor)
.map_err(|e| CoercionError::Parse {
target_type: format!("enum {name}"),
value: self.value.to_string(),
key_path: self.key_path.to_string(),
source: Box::new(e),
})
}
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
let object: serde_json::Map<String, serde_json::Value> = match self.value {
serde_json::Value::Object(o) => o.clone(),
serde_json::Value::String(s) => {
let parsed: serde_json::Value =
serde_json::from_str(s).map_err(|e| CoercionError::Parse {
target_type: "object".to_string(),
value: s.clone(),
key_path: self.key_path.to_string(),
source: Box::new(e),
})?;
match parsed {
serde_json::Value::Object(o) => o,
other => {
return Err(CoercionError::Parse {
target_type: "object".to_string(),
value: s.clone(),
key_path: self.key_path.to_string(),
source: format!(
"expected JSON object after parsing string, got {}",
json_kind_name(&other)
)
.into(),
});
}
}
}
other => {
return Err(CoercionError::Parse {
target_type: "object".to_string(),
value: other.to_string(),
key_path: self.key_path.to_string(),
source: format!("expected object, got {}", json_kind_name(other)).into(),
});
}
};
visitor.visit_map(TypedMapAccess {
entries: object.into_iter(),
pending_value: None,
pending_key: None,
key_path: self.key_path,
})
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
let array: Vec<serde_json::Value> = match self.value {
serde_json::Value::Array(a) => a.clone(),
serde_json::Value::String(s) => {
let parsed: serde_json::Value =
serde_json::from_str(s).map_err(|e| CoercionError::Parse {
target_type: "array".to_string(),
value: s.clone(),
key_path: self.key_path.to_string(),
source: Box::new(e),
})?;
match parsed {
serde_json::Value::Array(a) => a,
other => {
return Err(CoercionError::Parse {
target_type: "array".to_string(),
value: s.clone(),
key_path: self.key_path.to_string(),
source: format!(
"expected JSON array after parsing string, got {}",
json_kind_name(&other)
)
.into(),
});
}
}
}
other => {
return Err(CoercionError::Parse {
target_type: "array".to_string(),
value: other.to_string(),
key_path: self.key_path.to_string(),
source: format!("expected array, got {}", json_kind_name(other)).into(),
});
}
};
visitor.visit_seq(TypedSeqAccess {
values: array,
index: 0,
key_path: self.key_path,
})
}
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if matches!(self.value, serde_json::Value::String(_)) {
return Err(CoercionError::UnsupportedShape {
target: "struct",
key_path: self.key_path.to_string(),
});
}
self.deserialize_map(visitor)
}
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if matches!(self.value, serde_json::Value::String(_)) {
return Err(CoercionError::UnsupportedShape {
target: "tuple",
key_path: self.key_path.to_string(),
});
}
self.value
.clone()
.deserialize_tuple(len, visitor)
.map_err(|e| CoercionError::Parse {
target_type: "tuple".to_string(),
value: self.value.to_string(),
key_path: self.key_path.to_string(),
source: Box::new(e),
})
}
fn deserialize_tuple_struct<V>(
self,
name: &'static str,
len: usize,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if matches!(self.value, serde_json::Value::String(_)) {
return Err(CoercionError::UnsupportedShape {
target: "tuple struct",
key_path: self.key_path.to_string(),
});
}
self.value
.clone()
.deserialize_tuple_struct(name, len, visitor)
.map_err(|e| CoercionError::Parse {
target_type: format!("tuple struct {name}"),
value: self.value.to_string(),
key_path: self.key_path.to_string(),
source: Box::new(e),
})
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.value {
serde_json::Value::String(s) => {
let decoded = BASE64_STANDARD
.decode(s)
.map_err(|e| CoercionError::Parse {
target_type: "bytes".to_string(),
value: s.clone(),
key_path: self.key_path.to_string(),
source: Box::new(e),
})?;
visitor.visit_byte_buf(decoded)
}
other => other
.clone()
.deserialize_bytes(visitor)
.map_err(|e| CoercionError::Parse {
target_type: "bytes".to_string(),
value: other.to_string(),
key_path: self.key_path.to_string(),
source: Box::new(e),
}),
}
}
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_bytes(visitor)
}
}
struct TypedMapAccess {
entries: serde_json::map::IntoIter,
pending_key: Option<String>,
pending_value: Option<serde_json::Value>,
key_path: KeyPath,
}
impl<'de> MapAccess<'de> for TypedMapAccess {
type Error = CoercionError;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
where
K: DeserializeSeed<'de>,
{
match self.entries.next() {
Some((k, v)) => {
let key = seed
.deserialize(serde::de::value::StringDeserializer::<
serde::de::value::Error,
>::new(k.clone()))
.map_err(|e| CoercionError::Parse {
target_type: "map_key".to_string(),
value: k.clone(),
key_path: self.key_path.to_string(),
source: Box::new(e),
})?;
self.pending_key = Some(k);
self.pending_value = Some(v);
Ok(Some(key))
}
None => Ok(None),
}
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
where
V: DeserializeSeed<'de>,
{
let value = self
.pending_value
.take()
.ok_or_else(|| CoercionError::Parse {
target_type: "map_value".to_string(),
value: String::new(),
key_path: self.key_path.to_string(),
source: "next_value_seed called before next_key_seed".into(),
})?;
let key = self.pending_key.take();
let mut child_path = self.key_path.clone();
if let Some(k) = key.as_deref() {
child_path.push_key(k);
}
let de = TypedSettingsDeserializer {
value: &value,
key_path: child_path,
};
seed.deserialize(de)
}
}
struct TypedSeqAccess {
values: Vec<serde_json::Value>,
index: usize,
key_path: KeyPath,
}
impl<'de> SeqAccess<'de> for TypedSeqAccess {
type Error = CoercionError;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
where
T: DeserializeSeed<'de>,
{
if self.index >= self.values.len() {
return Ok(None);
}
let value = &self.values[self.index];
let mut child_path = self.key_path.clone();
child_path.push_index(self.index);
self.index += 1;
let de = TypedSettingsDeserializer {
value,
key_path: child_path,
};
seed.deserialize(de).map(Some)
}
fn size_hint(&self) -> Option<usize> {
Some(self.values.len().saturating_sub(self.index))
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use serde::Deserialize;
#[rstest]
fn skeleton_passthrough_for_native_int() {
let v = serde_json::json!({ "port": 5432 });
#[derive(Debug, Deserialize, PartialEq)]
struct S {
port: u16,
}
let de = TypedSettingsDeserializer::new(&v);
let got: S = S::deserialize(de).expect("should deserialize");
assert_eq!(got, S { port: 5432 });
}
}