use std::collections::HashMap;
use conformal_component::parameters::{TypeSpecificInfoRef, Value as ParameterValue};
use serde::{Deserialize, Serialize};
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
enum Value {
Numeric(f32),
Enum(String),
Switch(bool),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Snapshot {
values: HashMap<String, Value>,
}
pub enum WriteInfoRef<I> {
Numeric {},
Enum { values: I },
Switch {},
}
impl<'a, S: AsRef<str>> From<TypeSpecificInfoRef<'a, S>> for WriteInfoRef<&'a [S]> {
fn from(info: TypeSpecificInfoRef<'a, S>) -> Self {
match info {
TypeSpecificInfoRef::Numeric { .. } => Self::Numeric {},
TypeSpecificInfoRef::Enum { values, .. } => Self::Enum { values },
TypeSpecificInfoRef::Switch { .. } => Self::Switch {},
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ReadInfoRef<I> {
Numeric {
default: f32,
valid_range: std::ops::RangeInclusive<f32>,
},
Enum {
default: u32,
values: I,
},
Switch {
default: bool,
},
}
impl<'a, S: AsRef<str>> From<TypeSpecificInfoRef<'a, S>> for ReadInfoRef<std::slice::Iter<'a, S>> {
fn from(info: TypeSpecificInfoRef<'a, S>) -> Self {
match info {
TypeSpecificInfoRef::Numeric {
default,
valid_range,
..
} => Self::Numeric {
default,
valid_range,
},
TypeSpecificInfoRef::Enum {
default, values, ..
} => Self::Enum {
default,
values: values.iter(),
},
TypeSpecificInfoRef::Switch { default, .. } => Self::Switch { default },
}
}
}
impl super::Snapshot {
pub fn into_serialize<'a, I: IntoIterator<Item = &'a str>>(
self,
lookup: impl Fn(&str) -> Option<WriteInfoRef<I>>,
) -> Option<Snapshot> {
let mut values = HashMap::new();
values.reserve(self.values.len());
for (id, value) in self.values {
let info = lookup(id.as_str())?;
let serialized_value = match (info, value) {
(WriteInfoRef::Numeric {}, ParameterValue::Numeric(value)) => {
Some(Value::Numeric(value))
}
(WriteInfoRef::Enum { .. }, ParameterValue::Enum(value)) => {
Some(Value::Enum(value))
}
(WriteInfoRef::Switch {}, ParameterValue::Switch(value)) => {
Some(Value::Switch(value))
}
_ => None,
}?;
values.insert(id.clone(), serialized_value);
}
Some(Snapshot { values })
}
pub fn into_serialize_no_enum(
self,
lookup: impl Fn(&str) -> Option<WriteInfoRef<std::iter::Empty<&'static str>>>,
) -> Option<Snapshot> {
self.into_serialize(lookup)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SnapshotCorruptionError {
IncompatibleType(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum DeserializationError {
VersionTooNew(),
Corrupted(SnapshotCorruptionError),
}
impl Snapshot {
pub fn into_snapshot<'a, I: IntoIterator<Item = &'a str> + Clone>(
mut self,
all_params: impl IntoIterator<Item = (&'a str, ReadInfoRef<I>)>,
) -> Result<super::Snapshot, DeserializationError> {
let mut values = HashMap::new();
for (id, info) in all_params {
let serialized_value = self.values.get_mut(id);
let value = match (info, serialized_value) {
(ReadInfoRef::Numeric { valid_range, .. }, Some(Value::Numeric(value))) => {
if valid_range.contains(value) {
Ok(ParameterValue::Numeric(*value))
} else {
Err(DeserializationError::VersionTooNew())
}
}
(ReadInfoRef::Numeric { default, .. }, None) => {
Ok(ParameterValue::Numeric(default))
}
(ReadInfoRef::Enum { values, .. }, Some(Value::Enum(value))) => {
if values.into_iter().any(|v| v == value.as_str()) {
Ok(ParameterValue::Enum(std::mem::take(value)))
} else {
Err(DeserializationError::VersionTooNew())
}
}
(ReadInfoRef::Enum { default, values }, None) => Ok(ParameterValue::Enum(
values
.clone()
.into_iter()
.nth(default as usize)
.unwrap()
.to_string(),
)),
(ReadInfoRef::Switch { .. }, Some(Value::Switch(value))) => {
Ok(ParameterValue::Switch(*value))
}
(ReadInfoRef::Switch { default, .. }, None) => Ok(ParameterValue::Switch(default)),
_ => Err(DeserializationError::Corrupted(
SnapshotCorruptionError::IncompatibleType(id.to_string()),
)),
}?;
values.insert(id.to_owned(), value);
}
Ok(super::Snapshot { values })
}
#[cfg(test)]
fn into_snapshot_no_enums<'a>(
self,
all_params: impl IntoIterator<Item = (&'a str, ReadInfoRef<std::iter::Empty<&'a str>>)>,
) -> Result<super::Snapshot, DeserializationError> {
self.into_snapshot(all_params)
}
}