1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// Adapted from Aptos::storage::schemadb;
// While most of the Sovereign SDK will be available under both
// MIT and APACHE 2.0 licenses, this file is
// licensed under APACHE 2.0 only.

//! A type-safe interface over [`DB`](crate::DB) column families.

use std::fmt::Debug;

use crate::CodecError;

/// Crate users are expected to know [column
/// family](https://github.com/EighteenZi/rocksdb_wiki/blob/master/Column-Families.md)
/// names beforehand, so they can have `static` lifetimes.
pub type ColumnFamilyName = &'static str;

/// A [`Schema`] is a type-safe interface over a specific column family in a
/// [`DB`](crate::DB). It always a key type ([`KeyCodec`]) and a value type ([`ValueCodec`]).
pub trait Schema: Debug + Send + Sync + 'static + Sized {
    /// The column family name associated with this struct.
    /// Note: all schemas within the same SchemaDB must have distinct column family names.
    const COLUMN_FAMILY_NAME: ColumnFamilyName;

    /// Type of the key.
    type Key: KeyCodec<Self>;

    /// Type of the value.
    type Value: ValueCodec<Self>;
}

/// A [`core::result::Result`] alias with [`CodecError`] as the error type.
pub type Result<T, E = CodecError> = core::result::Result<T, E>;

/// This trait defines a type that can serve as a [`Schema::Key`].
///
/// [`KeyCodec`] is a marker trait with a blaket implementation for all types
/// that are both [`KeyEncoder`] and [`KeyDecoder`]. Having [`KeyEncoder`] and
/// [`KeyDecoder`] as two standalone traits on top of [`KeyCodec`] may seem
/// superflous, but it allows for zero-copy key encoding under specific
/// circumstances. E.g.:
///
/// ```rust
/// use anyhow::Context;
///
/// use sov_schema_db::define_schema;
/// use sov_schema_db::schema::{
///     Schema, KeyEncoder, KeyDecoder, ValueCodec, Result,
/// };
///
/// define_schema!(PersonAgeByName, String, u32, "person_age_by_name");
///
/// impl KeyEncoder<PersonAgeByName> for String {
///     fn encode_key(&self) -> Result<Vec<u8>> {
///         Ok(self.as_bytes().to_vec())
///     }
/// }
///
/// /// What about encoding a `&str`, though? We'd have to copy it into a
/// /// `String` first, which is not ideal. But we can do better:
/// impl<'a> KeyEncoder<PersonAgeByName> for &'a str {
///     fn encode_key(&self) -> Result<Vec<u8>> {
///         Ok(self.as_bytes().to_vec())
///     }
/// }
///
/// impl KeyDecoder<PersonAgeByName> for String {
///     fn decode_key(data: &[u8]) -> Result<Self> {
///         Ok(String::from_utf8(data.to_vec()).context("Can't read key")?)
///     }
/// }
///
/// impl ValueCodec<PersonAgeByName> for u32 {
///     fn encode_value(&self) -> Result<Vec<u8>> {
///         Ok(self.to_le_bytes().to_vec())
///     }
///
///     fn decode_value(data: &[u8]) -> Result<Self> {
///         let mut buf = [0u8; 4];
///         buf.copy_from_slice(data);
///         Ok(u32::from_le_bytes(buf))
///     }
/// }
/// ```
pub trait KeyCodec<S: Schema + ?Sized>: KeyEncoder<S> + KeyDecoder<S> {}

impl<T, S: Schema + ?Sized> KeyCodec<S> for T where T: KeyEncoder<S> + KeyDecoder<S> {}

/// Implementors of this trait can be used to encode keys in the given [`Schema`].
pub trait KeyEncoder<S: Schema + ?Sized>: Sized + PartialEq + Debug {
    /// Converts `self` to bytes to be stored in RocksDB.
    fn encode_key(&self) -> Result<Vec<u8>>;
}

/// Implementors of this trait can be used to decode keys in the given [`Schema`].
pub trait KeyDecoder<S: Schema + ?Sized>: Sized + PartialEq + Debug {
    /// Converts bytes fetched from RocksDB to `Self`.
    fn decode_key(data: &[u8]) -> Result<Self>;
}

/// This trait defines a type that can serve as a [`Schema::Value`].
pub trait ValueCodec<S: Schema + ?Sized>: Sized + PartialEq + Debug {
    /// Converts `self` to bytes to be stored in DB.
    fn encode_value(&self) -> Result<Vec<u8>>;
    /// Converts bytes fetched from DB to `Self`.
    fn decode_value(data: &[u8]) -> Result<Self>;
}

/// A utility macro to define [`Schema`] implementors. You must specify the
/// [`Schema`] implementor's name, the key type, the value type, and the column
/// family name.
///
/// # Example
///
/// ```rust
/// use anyhow::Context;
///
/// use sov_schema_db::define_schema;
/// use sov_schema_db::schema::{
///     Schema, KeyEncoder, KeyDecoder, ValueCodec, Result,
/// };
///
/// define_schema!(PersonAgeByName, String, u32, "person_age_by_name");
///
/// impl KeyEncoder<PersonAgeByName> for String {
///     fn encode_key(&self) -> Result<Vec<u8>> {
///         Ok(self.as_bytes().to_vec())
///     }
/// }
///
/// impl KeyDecoder<PersonAgeByName> for String {
///     fn decode_key(data: &[u8]) -> Result<Self> {
///         Ok(String::from_utf8(data.to_vec()).context("Can't read key")?)
///     }
/// }
///
/// impl ValueCodec<PersonAgeByName> for u32 {
///     fn encode_value(&self) -> Result<Vec<u8>> {
///         Ok(self.to_le_bytes().to_vec())
///     }
///
///     fn decode_value(data: &[u8]) -> Result<Self> {
///         let mut buf = [0u8; 4];
///         buf.copy_from_slice(data);
///         Ok(u32::from_le_bytes(buf))
///     }
/// }
/// ```
#[macro_export]
macro_rules! define_schema {
    ($schema_type:ident, $key_type:ty, $value_type:ty, $cf_name:expr) => {
        #[derive(Debug)]
        pub(crate) struct $schema_type;

        impl $crate::schema::Schema for $schema_type {
            type Key = $key_type;
            type Value = $value_type;

            const COLUMN_FAMILY_NAME: $crate::schema::ColumnFamilyName = $cf_name;
        }
    };
}

#[cfg(feature = "fuzzing")]
pub mod fuzzing {
    use proptest::collection::vec;
    use proptest::prelude::*;

    use super::{KeyDecoder, KeyEncoder, Schema, ValueCodec};

    /// Helper used in tests to assert a (key, value) pair for a certain [`Schema`] is able to convert
    /// to bytes and convert back.
    pub fn assert_encode_decode<S: Schema>(key: &S::Key, value: &S::Value) {
        {
            let encoded = key.encode_key().expect("Encoding key should work.");
            let decoded = S::Key::decode_key(&encoded).expect("Decoding key should work.");
            assert_eq!(*key, decoded);
        }
        {
            let encoded = value.encode_value().expect("Encoding value should work.");
            let decoded = S::Value::decode_value(&encoded).expect("Decoding value should work.");
            assert_eq!(*value, decoded);
        }
    }

    /// Helper used in tests and fuzzers to make sure a schema never panics when decoding random bytes.
    #[allow(unused_must_use)]
    pub fn assert_no_panic_decoding<S: Schema>(bytes: &[u8]) {
        S::Key::decode_key(bytes);
        S::Value::decode_value(bytes);
    }

    pub fn arb_small_vec_u8() -> impl Strategy<Value = Vec<u8>> {
        vec(any::<u8>(), 0..2048)
    }

    #[macro_export]
    macro_rules! test_no_panic_decoding {
        ($schema_type:ty) => {
            use proptest::prelude::*;
            use sov_schema_db::schema::fuzzing::{arb_small_vec_u8, assert_no_panic_decoding};

            proptest! {
                #[test]
                fn test_no_panic_decoding(bytes in arb_small_vec_u8()) {
                    assert_no_panic_decoding::<$schema_type>(&bytes)
                }
            }
        };
    }
}