-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CU-1ubvcrq - introduce
Validated
and Validate
for custom codec va…
…lidation (#492) * introduce `Validated` and `Validate` for custom codec validation * add missing Deref/AsRef, use WrapperTypeEncode and introduce `And`
- Loading branch information
1 parent
e2ac78f
commit 885a8ca
Showing
4 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
[package] | ||
name = "composable-support" | ||
version = "0.0.1" | ||
authors = ["Composable Developers"] | ||
homepage = "https://composable.finance" | ||
edition = "2021" | ||
rust-version = "1.56" | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
|
||
[dev-dependencies] | ||
proptest = { version = "1.0" } | ||
|
||
[dependencies] | ||
frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } | ||
frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } | ||
sp-arithmetic = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } | ||
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } | ||
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } | ||
scale-info = { version = "1.0", default-features = false, features = ["derive"] } | ||
|
||
[dependencies.codec] | ||
default-features = false | ||
features = ["derive"] | ||
package = "parity-scale-codec" | ||
version = "2.0.0" | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"codec/std", | ||
"frame-support/std", | ||
"frame-system/std", | ||
"sp-runtime/std", | ||
"sp-std/std", | ||
"scale-info/std", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#![cfg_attr( | ||
not(test), | ||
warn( | ||
clippy::disallowed_method, | ||
clippy::disallowed_type, | ||
clippy::indexing_slicing, | ||
clippy::todo, | ||
clippy::unwrap_used, | ||
clippy::panic | ||
) | ||
)] // allow in tests | ||
#![warn(clippy::unseparated_literal_suffix)] | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
pub mod validation; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
use core::{marker::PhantomData, ops::Deref}; | ||
use scale_info::TypeInfo; | ||
|
||
/// Black box that embbed the validated value. | ||
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, TypeInfo)] | ||
pub struct Validated<T, U> { | ||
value: T, | ||
_marker: PhantomData<U>, | ||
} | ||
|
||
impl<T: Copy, U> Validated<T, U> { | ||
#[inline(always)] | ||
pub fn value(&self) -> T { | ||
self.value | ||
} | ||
} | ||
|
||
impl<T, U> Deref for Validated<T, U> { | ||
type Target = T; | ||
#[inline(always)] | ||
fn deref(&self) -> &Self::Target { | ||
&self.value | ||
} | ||
} | ||
|
||
impl<T, U> AsRef<T> for Validated<T, U> { | ||
#[inline(always)] | ||
fn as_ref(&self) -> &T { | ||
&self.value | ||
} | ||
} | ||
|
||
pub trait Validate<U>: Sized { | ||
fn validate(self) -> Result<Self, &'static str>; | ||
} | ||
|
||
#[derive(Debug, Eq, PartialEq)] | ||
pub struct QED; | ||
|
||
impl<T> Validate<QED> for T { | ||
#[inline(always)] | ||
fn validate(self) -> Result<Self, &'static str> { | ||
Ok(self) | ||
} | ||
} | ||
|
||
impl<T: Validate<U> + Validate<V>, U, V> Validate<(U, V)> for T { | ||
#[inline(always)] | ||
fn validate(self) -> Result<Self, &'static str> { | ||
let value = Validate::<U>::validate(self)?; | ||
let value = Validate::<V>::validate(value)?; | ||
Ok(value) | ||
} | ||
} | ||
|
||
impl<T: codec::Decode + Validate<(U, V)>, U, V> codec::Decode for Validated<T, (U, V)> { | ||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> { | ||
let value = Validate::validate(T::decode(input)?) | ||
.map_err(|desc| Into::<codec::Error>::into(desc))?; | ||
Ok(Validated { value, _marker: PhantomData }) | ||
} | ||
fn skip<I: codec::Input>(input: &mut I) -> Result<(), codec::Error> { | ||
T::skip(input) | ||
} | ||
} | ||
|
||
impl<T: codec::Encode + codec::Decode + Validate<U>, U> codec::WrapperTypeEncode | ||
for Validated<T, U> | ||
{ | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
use codec::{Decode, Encode}; | ||
|
||
#[derive(Debug, Eq, PartialEq)] | ||
struct ValidARange; | ||
#[derive(Debug, Eq, PartialEq)] | ||
struct ValidBRange; | ||
|
||
type CheckARange = (ValidARange, QED); | ||
type CheckBRange = (ValidBRange, QED); | ||
type CheckABRange = (ValidARange, (ValidBRange, QED)); | ||
|
||
#[derive(Debug, Eq, PartialEq, codec::Encode, codec::Decode)] | ||
struct X { | ||
a: u32, | ||
b: u32, | ||
} | ||
|
||
impl Validate<ValidARange> for X { | ||
fn validate(self) -> Result<X, &'static str> { | ||
if self.a > 10 { | ||
Err("Out of range") | ||
} else { | ||
Ok(self) | ||
} | ||
} | ||
} | ||
|
||
impl Validate<ValidBRange> for X { | ||
fn validate(self) -> Result<X, &'static str> { | ||
if self.b > 10 { | ||
Err("Out of range") | ||
} else { | ||
Ok(self) | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_valid_a() { | ||
let valid = X { a: 10, b: 0xCAFEBABE }; | ||
let bytes = valid.encode(); | ||
assert_eq!( | ||
Ok(Validated { value: valid, _marker: PhantomData }), | ||
Validated::<X, CheckARange>::decode(&mut &bytes[..]) | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_invalid_a() { | ||
let invalid = X { a: 0xDEADC0DE, b: 0xCAFEBABE }; | ||
let bytes = invalid.encode(); | ||
assert!(Validated::<X, CheckARange>::decode(&mut &bytes[..]).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_valid_b() { | ||
let valid = X { a: 0xCAFEBABE, b: 10 }; | ||
let bytes = valid.encode(); | ||
assert_eq!( | ||
Ok(Validated { value: valid, _marker: PhantomData }), | ||
Validated::<X, CheckBRange>::decode(&mut &bytes[..]) | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_invalid_b() { | ||
let invalid = X { a: 0xCAFEBABE, b: 0xDEADC0DE }; | ||
let bytes = invalid.encode(); | ||
assert!(Validated::<X, CheckBRange>::decode(&mut &bytes[..]).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_valid_ab() { | ||
let valid = X { a: 10, b: 10 }; | ||
let bytes = valid.encode(); | ||
assert_eq!( | ||
Ok(Validated { value: valid, _marker: PhantomData }), | ||
Validated::<X, CheckABRange>::decode(&mut &bytes[..]) | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_invalid_ab() { | ||
let invalid = X { a: 0xDEADC0DE, b: 0xCAFEBABE }; | ||
let bytes = invalid.encode(); | ||
assert!(Validated::<X, CheckABRange>::decode(&mut &bytes[..]).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_invalid_a_ab() { | ||
let invalid = X { a: 0xDEADC0DE, b: 10 }; | ||
let bytes = invalid.encode(); | ||
assert!(Validated::<X, CheckABRange>::decode(&mut &bytes[..]).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_invalid_b_ab() { | ||
let invalid = X { a: 10, b: 0xDEADC0DE }; | ||
let bytes = invalid.encode(); | ||
assert!(Validated::<X, CheckABRange>::decode(&mut &bytes[..]).is_err()); | ||
} | ||
} |