use std::fmt;
use serde::de::{
self, Deserializer, IntoDeserializer, MapAccess, SeqAccess, Visitor,
value::{MapAccessDeserializer, SeqAccessDeserializer},
};
use serde::{Deserialize, Serialize};
use crate::validation::{
ProtocolLimits, Validate, ValidationError, add_to_total, check_count, check_finite,
check_finite_slice, check_opt_string, check_string,
};
use super::de::{
de_config_map, de_config_string, de_finite_f32, de_float_array, de_opt_session_id, prefix_path,
};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(try_from = "ConfigPayloadWire")]
pub struct ConfigPayload {
pub session_id: Option<String>,
pub config: std::collections::HashMap<String, ConfigValue>,
}
#[derive(Deserialize)]
struct ConfigPayloadWire {
#[serde(deserialize_with = "de_opt_session_id")]
session_id: Option<String>,
#[serde(deserialize_with = "de_config_map")]
config: std::collections::HashMap<String, ConfigValue>,
}
impl TryFrom<ConfigPayloadWire> for ConfigPayload {
type Error = ValidationError;
fn try_from(wire: ConfigPayloadWire) -> Result<Self, Self::Error> {
let payload = ConfigPayload {
session_id: wire.session_id,
config: wire.config,
};
payload.validate()?;
Ok(payload)
}
}
impl Validate for ConfigPayload {
fn validate_with(&self, limits: ProtocolLimits) -> Result<(), ValidationError> {
check_opt_string("session_id", self.session_id.as_deref(), limits)?;
check_count("config", self.config.len(), limits.max_metadata_entries)?;
let mut total = 0;
add_to_total(&mut total, self.config.len(), limits, "aggregate")?;
validate_config_entries(&self.config, limits, &mut total)
}
}
fn validate_config_entries(
config: &std::collections::HashMap<String, ConfigValue>,
limits: ProtocolLimits,
total: &mut usize,
) -> Result<(), ValidationError> {
for (key, value) in config {
if key.is_empty() {
return Err(ValidationError::nested_metadata("config.<empty>"));
}
check_string("config.<key>", key, limits)?;
value
.validate_with(limits)
.map_err(|err| prefix_path(err, &format!("config.{key}")))?;
if let ConfigValue::FloatArray(values) = value {
add_to_total(total, values.len(), limits, "aggregate")?;
}
}
Ok(())
}
#[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum ConfigValue {
Float(f32),
Integer(u64),
String(String),
Boolean(bool),
FloatArray(Vec<f32>),
}
impl<'de> Deserialize<'de> for ConfigValue {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct ConfigValueVisitor;
impl<'de> Visitor<'de> for ConfigValueVisitor {
type Value = ConfigValue;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a JSON number, string, boolean, or array of finite floats")
}
fn visit_bool<E: de::Error>(self, value: bool) -> Result<Self::Value, E> {
Ok(ConfigValue::Boolean(value))
}
fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
de_finite_f32(value.into_deserializer()).map(ConfigValue::Float)
}
fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
de_finite_f32(value.into_deserializer()).map(ConfigValue::Float)
}
fn visit_f32<E: de::Error>(self, value: f32) -> Result<Self::Value, E> {
de_finite_f32(value.into_deserializer()).map(ConfigValue::Float)
}
fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
de_finite_f32(value.into_deserializer()).map(ConfigValue::Float)
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
de_config_string(value.into_deserializer()).map(ConfigValue::String)
}
fn visit_string<E: de::Error>(self, value: String) -> Result<Self::Value, E> {
de_config_string(value.into_deserializer()).map(ConfigValue::String)
}
fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
de_float_array(SeqAccessDeserializer::new(seq)).map(ConfigValue::FloatArray)
}
fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
match serde_json::Value::deserialize(MapAccessDeserializer::new(map))? {
serde_json::Value::Number(number) => {
let Ok(parsed) = number.to_string().parse::<f32>() else {
return Err(de::Error::custom(ValidationError::non_finite(
"value",
f32::NAN,
)));
};
de_finite_f32(parsed.into_deserializer()).map(ConfigValue::Float)
}
_ => Err(de::Error::custom(ValidationError::nested_metadata("value"))),
}
}
}
deserializer.deserialize_any(ConfigValueVisitor)
}
}
impl Validate for ConfigValue {
fn validate_with(&self, limits: ProtocolLimits) -> Result<(), ValidationError> {
match self {
Self::Float(value) => check_finite("value", *value),
Self::Integer(_) | Self::Boolean(_) => Ok(()),
Self::String(value) => check_string("value", value, limits),
Self::FloatArray(values) => {
check_count("value", values.len(), limits.max_channel_values)?;
check_finite_slice("value", values)
}
}
}
}