use anyhow::Result;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use std::hash::Hash;
pub trait Validate: Sized {
type Unvalidated;
fn validate(unvalidated: &Self::Unvalidated) -> Result<Self>;
fn to_unvalidated(&self) -> Self::Unvalidated;
}
#[derive(Clone, Debug)]
pub struct Validatable<V: Validate> {
unvalidated: V::Unvalidated,
maybe_valid: OnceCell<V>,
}
impl<V: Validate> Validatable<V> {
pub fn new_valid(valid: V) -> Self {
let unvalidated = valid.to_unvalidated();
let maybe_valid = OnceCell::new();
maybe_valid.set(valid).unwrap_or_else(|_| unreachable!());
Self {
unvalidated,
maybe_valid,
}
}
pub fn new_unvalidated(unvalidated: V::Unvalidated) -> Self {
Self {
unvalidated,
maybe_valid: OnceCell::new(),
}
}
pub fn unvalidated(&self) -> &V::Unvalidated {
&self.unvalidated
}
pub fn valid(&self) -> Option<&V> {
self.validate().ok()
}
pub fn validate(&self) -> Result<&V> {
self.maybe_valid
.get_or_try_init(|| V::validate(&self.unvalidated))
}
}
impl<V> Serialize for Validatable<V>
where
V: Validate + Serialize,
V::Unvalidated: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.unvalidated.serialize(serializer)
}
}
impl<'de, V> Deserialize<'de> for Validatable<V>
where
V: Validate,
V::Unvalidated: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let unvalidated = <V::Unvalidated>::deserialize(deserializer)?;
Ok(Self::new_unvalidated(unvalidated))
}
}
impl<V> PartialEq for Validatable<V>
where
V: Validate,
V::Unvalidated: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.unvalidated == other.unvalidated
}
}
impl<V> Eq for Validatable<V>
where
V: Validate,
V::Unvalidated: Eq,
{
}
impl<V> Hash for Validatable<V>
where
V: Validate,
V::Unvalidated: Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.unvalidated.hash(state);
}
}