aptos_crypto_link/validatable.rs
1// Copyright (c) Aptos
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module provides the `Validate` trait and `Validatable` type in order to aid in deferred
5//! validation.
6
7use crate::ValidCryptoMaterial;
8use anyhow::Result;
9use once_cell::sync::OnceCell;
10use serde::{Deserialize, Serialize};
11use std::hash::Hash;
12
13/// The `Validate` trait is used in tandem with the `Validatable` type in order to provide deferred
14/// validation for types.
15///
16/// ## Trait Contract
17///
18/// Any type `V` which implement this trait must adhere to the following contract:
19///
20/// * `V` and `V::Unvalidated` are byte-for-byte equivalent.
21/// * `V` and `V::Unvalidated` have equivalent `Hash` implementations.
22/// * `V` and `V::Unvalidated` must have equivalent `Serialize` and `Deserialize` implementation.
23/// This means that `V` and `V:Unvalidated` have equivalent serialized formats and that you can
24/// deserialize a `V::Unvalidated` from a `V` that was previously serialized.
25pub trait Validate: Sized {
26 /// The unvalidated form of some type `V`
27 type Unvalidated: ValidCryptoMaterial;
28
29 /// Attempt to validate a `V::Unvalidated` and returning a validated `V` on success
30 fn validate(unvalidated: &Self::Unvalidated) -> Result<Self>;
31
32 /// Return the unvalidated form of type `V`
33 fn to_unvalidated(&self) -> Self::Unvalidated;
34}
35
36/// Used in connection with the `Validate` trait to be able to represent types which can benefit
37/// from deferred validation as a performance optimization.
38#[derive(Clone, Debug)]
39pub struct Validatable<V: Validate> {
40 unvalidated: V::Unvalidated,
41 maybe_valid: OnceCell<V>,
42}
43
44impl<V: Validate> Validatable<V> {
45 /// Create a new `Validatable` from a validated type. This will assume the input has been validated
46 /// by the caller and as a result `Validatable::<V>::validate().is_ok()` will always return true.
47 pub fn from_validated(valid: V) -> Self {
48 let unvalidated = valid.to_unvalidated();
49
50 let maybe_valid = OnceCell::new();
51 maybe_valid.set(valid).unwrap_or_else(|_| unreachable!());
52
53 Self {
54 unvalidated,
55 maybe_valid,
56 }
57 }
58
59 /// Create a new `Validatable` from an unvalidated type
60 pub fn from_unvalidated(unvalidated: V::Unvalidated) -> Self {
61 Self {
62 unvalidated,
63 maybe_valid: OnceCell::new(),
64 }
65 }
66
67 /// Return a reference to the unvalidated form `V::Unvalidated`
68 pub fn unvalidated(&self) -> &V::Unvalidated {
69 &self.unvalidated
70 }
71
72 /// Try to validate the unvalidated form, returning `Some(&V)` on success and `None` on failure.
73 pub fn valid(&self) -> Option<&V> {
74 self.validate().ok()
75 }
76
77 // TODO maybe optimize to only try once and keep track when we fail. This would avoid multiple calls to validate() by valid() when validation fails
78 /// Attempt to validate `V::Unvalidated` and return a reference to a valid `V`
79 pub fn validate(&self) -> Result<&V> {
80 self.maybe_valid
81 .get_or_try_init(|| V::validate(&self.unvalidated))
82 }
83}
84
85/// Serializes a `Validatable<V>` using the `serde::Serialize` implementation of `V::Unvalidated`
86impl<V> Serialize for Validatable<V>
87where
88 V: Validate + Serialize,
89 V::Unvalidated: Serialize,
90{
91 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
92 where
93 S: serde::Serializer,
94 {
95 self.unvalidated.serialize(serializer)
96 }
97}
98
99/// Deserializes a `Validatable<V>` using the `serde::Deserialize` implementation of `V::Unvalidated`.
100/// Does *not* perform validation on the deserialized `V::Unvalidated` object.
101impl<'de, V> Deserialize<'de> for Validatable<V>
102where
103 V: Validate,
104 V::Unvalidated: Deserialize<'de>,
105{
106 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107 where
108 D: serde::Deserializer<'de>,
109 {
110 let unvalidated = <V::Unvalidated>::deserialize(deserializer)?;
111 Ok(Self::from_unvalidated(unvalidated))
112 }
113}
114
115/// Simply calls the equality operator of `V::Unvalidated`
116impl<V> PartialEq for Validatable<V>
117where
118 V: Validate,
119 V::Unvalidated: PartialEq,
120{
121 fn eq(&self, other: &Self) -> bool {
122 self.unvalidated == other.unvalidated
123 }
124}
125
126impl<V> Eq for Validatable<V>
127where
128 V: Validate,
129 V::Unvalidated: Eq,
130{
131}
132
133/// Simply calls the `Hash` implementation of `V::Unvalidated`
134impl<V> Hash for Validatable<V>
135where
136 V: Validate,
137 V::Unvalidated: Hash,
138{
139 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
140 self.unvalidated.hash(state);
141 }
142}