p2panda_rs/operation/plain/
plain_value.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3use std::convert::TryInto;
4
5use ciborium::Value;
6use serde::{Deserialize, Serialize};
7
8use crate::document::{DocumentId, DocumentViewId};
9use crate::hash::{Hash, HashId};
10use crate::operation::error::PlainValueError;
11
12/// Operation field values which have not been checked against a schema yet.
13///
14/// This enum expresses some operation field types as groups, since "String" or "Relation" are
15/// represented by the same internal data type (a simple string).
16///
17/// Latest when combining the plain values with a schema, the inner types, especially the
18/// relations, get checked against their correct format.
19#[derive(Serialize, Debug, PartialEq, Clone)]
20#[serde(untagged)]
21pub enum PlainValue {
22    /// Boolean value.
23    Boolean(bool),
24
25    /// Integer value.
26    Integer(i64),
27
28    /// Float value.
29    Float(f64),
30
31    /// String value.
32    String(String),
33
34    /// Byte array value which can either represent raw bytes or a relation (document id)
35    /// encoded as bytes.
36    #[serde(with = "serde_bytes")]
37    BytesOrRelation(Vec<u8>),
38
39    /// List of hashes which can either be a pinned relation (list of operation ids), a relation
40    /// list (list of document ids) or an empty pinned relation list.
41    AmbiguousRelation(Vec<Hash>),
42
43    /// List of a list of hashes which is a pinned relation list.
44    PinnedRelationList(Vec<Vec<Hash>>),
45}
46
47impl PlainValue {
48    /// Returns the string representation of these plain values.
49    ///
50    /// This is useful for composing error messages or debug logs.
51    pub fn field_type(&self) -> &str {
52        match self {
53            PlainValue::Boolean(_) => "bool",
54            PlainValue::Integer(_) => "int",
55            PlainValue::Float(_) => "float",
56            PlainValue::String(_) => "str",
57            PlainValue::BytesOrRelation(_) => "bytes",
58            PlainValue::AmbiguousRelation(_) => "hash[]",
59            PlainValue::PinnedRelationList(_) => "hash[][]",
60        }
61    }
62}
63
64impl<'de> Deserialize<'de> for PlainValue {
65    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
66    where
67        D: serde::Deserializer<'de>,
68    {
69        let is_human_readable = deserializer.is_human_readable();
70        let cbor_value: Value = Deserialize::deserialize(deserializer)?;
71
72        to_plain_value(is_human_readable, cbor_value).map_err(|err| {
73            serde::de::Error::custom(format!("error deserializing plain value: {}", err))
74        })
75    }
76}
77
78impl From<bool> for PlainValue {
79    fn from(value: bool) -> Self {
80        PlainValue::Boolean(value)
81    }
82}
83
84impl From<Vec<u8>> for PlainValue {
85    fn from(value: Vec<u8>) -> Self {
86        PlainValue::BytesOrRelation(value)
87    }
88}
89
90impl From<&[u8]> for PlainValue {
91    fn from(value: &[u8]) -> Self {
92        PlainValue::BytesOrRelation(value.to_owned())
93    }
94}
95
96impl From<f64> for PlainValue {
97    fn from(value: f64) -> Self {
98        PlainValue::Float(value)
99    }
100}
101
102impl From<i64> for PlainValue {
103    fn from(value: i64) -> Self {
104        PlainValue::Integer(value)
105    }
106}
107
108impl From<String> for PlainValue {
109    fn from(value: String) -> Self {
110        PlainValue::String(value)
111    }
112}
113
114impl From<Vec<Hash>> for PlainValue {
115    fn from(value: Vec<Hash>) -> Self {
116        PlainValue::AmbiguousRelation(value)
117    }
118}
119
120impl From<&str> for PlainValue {
121    fn from(value: &str) -> Self {
122        PlainValue::String(value.to_owned())
123    }
124}
125
126impl From<DocumentId> for PlainValue {
127    fn from(value: DocumentId) -> Self {
128        PlainValue::BytesOrRelation(hex::decode(value.as_str()).unwrap())
129    }
130}
131
132impl From<Vec<DocumentId>> for PlainValue {
133    fn from(value: Vec<DocumentId>) -> Self {
134        PlainValue::AmbiguousRelation(value.iter().map(HashId::as_hash).cloned().collect())
135    }
136}
137
138impl From<DocumentViewId> for PlainValue {
139    fn from(value: DocumentViewId) -> Self {
140        PlainValue::AmbiguousRelation(value.into())
141    }
142}
143
144impl From<Vec<DocumentViewId>> for PlainValue {
145    fn from(value: Vec<DocumentViewId>) -> Self {
146        PlainValue::PinnedRelationList(value.iter().cloned().map(Into::<Vec<Hash>>::into).collect())
147    }
148}
149
150/// Helper for converting an encoded value into a plain operation value.
151fn to_plain_value(is_human_readable: bool, value: Value) -> Result<PlainValue, PlainValueError> {
152    let result: Result<PlainValue, PlainValueError> = match value {
153        Value::Integer(int) => {
154            let int: i64 = int.try_into()?;
155            Ok(int.into())
156        }
157        Value::Bytes(bytes) => Ok(bytes.into()),
158        Value::Float(float) => Ok(float.into()),
159        Value::Text(text) => Ok(text.into()),
160        Value::Bool(bool) => Ok(bool.into()),
161        Value::Array(array) => to_plain_value_list(is_human_readable, array),
162        _ => return Err(PlainValueError::UnsupportedValue),
163    };
164
165    result
166}
167
168/// Helper for converting an encoded array into a plain operation list value.
169///
170/// This method can fail which means the passed value is not an `AmbiguousRelation` or
171/// `PinnedRelation` plain value variant.
172fn to_plain_value_list(
173    is_human_readable: bool,
174    array: Vec<Value>,
175) -> Result<PlainValue, PlainValueError> {
176    // Helper method to convert the given value to a hexadecimal string.
177    //
178    // If we're working with a human-readable encoding format we can expect the value to already be
179    // a hexadecimal string, for non human-readable formats we need to encode it from the bytes
180    // first.
181    let to_hex_str = |value: &Value| -> Result<String, PlainValueError> {
182        if is_human_readable {
183            match value.as_text() {
184                Some(text) => Ok(text.to_owned()),
185                None => Err(PlainValueError::UnsupportedValue),
186            }
187        } else {
188            match value.as_bytes() {
189                Some(bytes) => Ok(hex::encode(bytes)),
190                None => Err(PlainValueError::UnsupportedValue),
191            }
192        }
193    };
194
195    // First attempt to parse this list of encoded values into a list of hashes. If this succeeds
196    // it means this is an `AmbiguousRelation`
197    let ambiguous_relation: Result<Vec<Hash>, PlainValueError> = array
198        .iter()
199        .map(|value| {
200            let hex_str = to_hex_str(value)?;
201            let hash = Hash::new(&hex_str).map_err(|_| PlainValueError::UnsupportedValue)?;
202            Ok(hash)
203        })
204        .collect();
205
206    // If this was successful we stop here and return the value.
207    if let Ok(hashes) = ambiguous_relation {
208        return Ok(PlainValue::AmbiguousRelation(hashes));
209    };
210
211    // Next we try and parse into a list of `Vec<Hash>` which means this is a `PinnedRelationList`
212    // value
213    let mut pinned_relations = Vec::new();
214    for inner_array in array {
215        let inner_array = match inner_array.as_array() {
216            Some(array) => Ok(array),
217            None => Err(PlainValueError::UnsupportedValue),
218        }?;
219
220        let pinned_relation: Result<Vec<Hash>, PlainValueError> = inner_array
221            .iter()
222            .map(|value| {
223                let hex_str = to_hex_str(value)?;
224                let hash = Hash::new(&hex_str).map_err(|_| PlainValueError::UnsupportedValue)?;
225                Ok(hash)
226            })
227            .collect();
228
229        pinned_relations.push(pinned_relation?);
230    }
231
232    Ok(PlainValue::PinnedRelationList(pinned_relations))
233}
234
235#[cfg(test)]
236mod tests {
237    use ciborium::cbor;
238    use rstest::rstest;
239    use serde_bytes::ByteBuf;
240
241    use crate::document::{DocumentId, DocumentViewId};
242    use crate::hash::{Hash, HashId};
243    use crate::serde::{deserialize_into, hex_string_to_bytes, serialize_from, serialize_value};
244    use crate::test_utils::fixtures::{document_id, document_view_id, random_hash};
245
246    use super::PlainValue;
247
248    #[test]
249    fn field_type_representation() {
250        assert_eq!("int", PlainValue::Integer(5).field_type());
251        assert_eq!("bool", PlainValue::Boolean(false).field_type());
252        assert_eq!(
253            "bytes",
254            PlainValue::BytesOrRelation("test".as_bytes().into()).field_type()
255        );
256        assert_eq!("str", PlainValue::String("test".into()).field_type());
257        assert_eq!(
258            "hash[]",
259            PlainValue::AmbiguousRelation(vec![random_hash()]).field_type()
260        );
261    }
262
263    #[rstest]
264    fn from_primitives(document_id: DocumentId, document_view_id: DocumentViewId) {
265        // Scalar types
266        assert_eq!(PlainValue::Boolean(true), true.into());
267        assert_eq!(PlainValue::Float(1.5), 1.5.into());
268        assert_eq!(PlainValue::Integer(3), 3.into());
269        assert_eq!(
270            PlainValue::BytesOrRelation("hellö".as_bytes().to_vec()),
271            "hellö".as_bytes().into()
272        );
273        assert_eq!(PlainValue::String("hellö".to_string()), "hellö".into());
274
275        // Relation types
276        assert_eq!(
277            PlainValue::BytesOrRelation(document_id.to_bytes()),
278            document_id.clone().into()
279        );
280        assert_eq!(
281            PlainValue::AmbiguousRelation(vec![document_id.clone().into()]),
282            vec![document_id].into()
283        );
284        assert_eq!(
285            PlainValue::AmbiguousRelation(document_view_id.clone().into()),
286            document_view_id.clone().into()
287        );
288        assert_eq!(
289            PlainValue::PinnedRelationList(vec![document_view_id.clone().into()]),
290            vec![document_view_id].into()
291        );
292    }
293
294    #[test]
295    fn serialize() {
296        assert_eq!(
297            serialize_from(PlainValue::Integer(5)),
298            serialize_value(cbor!(5))
299        );
300
301        assert_eq!(
302            serialize_from(PlainValue::AmbiguousRelation(vec![Hash::new(
303                "002089e5c6f0cbc0e8d8c92050dffc60e3217b556d62eace0d2e5d374c70a1d0c2d4"
304            )
305            .unwrap()])),
306            serialize_value(cbor!([hex_string_to_bytes(
307                "002089e5c6f0cbc0e8d8c92050dffc60e3217b556d62eace0d2e5d374c70a1d0c2d4"
308            )]))
309        );
310
311        assert_eq!(
312            serialize_from(PlainValue::PinnedRelationList(vec![vec![Hash::new(
313                "002089e5c6f0cbc0e8d8c92050dffc60e3217b556d62eace0d2e5d374c70a1d0c2d4"
314            )
315            .unwrap()]])),
316            serialize_value(cbor!([[hex_string_to_bytes(
317                "002089e5c6f0cbc0e8d8c92050dffc60e3217b556d62eace0d2e5d374c70a1d0c2d4"
318            )]]))
319        );
320
321        assert_eq!(
322            serialize_from(PlainValue::BytesOrRelation(vec![0, 1, 2, 3])),
323            serialize_value(cbor!(ByteBuf::from(vec![0, 1, 2, 3])))
324        );
325
326        assert_eq!(
327            serialize_from(PlainValue::String("username".to_string())),
328            serialize_value(cbor!("username"))
329        );
330
331        assert_eq!(
332            serialize_from(PlainValue::AmbiguousRelation(vec![])),
333            serialize_value(cbor!([]))
334        );
335    }
336
337    #[test]
338    fn deserialize() {
339        assert_eq!(
340            deserialize_into::<PlainValue>(&serialize_value(cbor!(12))).unwrap(),
341            PlainValue::Integer(12)
342        );
343        assert_eq!(
344            deserialize_into::<PlainValue>(&serialize_value(cbor!(12.0))).unwrap(),
345            PlainValue::Float(12.0)
346        );
347        assert_eq!(
348            deserialize_into::<PlainValue>(&serialize_value(cbor!(ByteBuf::from(vec![
349                0, 1, 2, 3
350            ]))))
351            .unwrap(),
352            PlainValue::BytesOrRelation(vec![0, 1, 2, 3])
353        );
354        assert_eq!(
355            deserialize_into::<PlainValue>(&serialize_value(cbor!("hello"))).unwrap(),
356            PlainValue::String("hello".to_string())
357        );
358        assert_eq!(
359            deserialize_into::<PlainValue>(&serialize_value(cbor!([]))).unwrap(),
360            PlainValue::AmbiguousRelation(vec![])
361        );
362    }
363
364    #[test]
365    fn deserialize_human_readable() {
366        assert_eq!(
367            serde_json::from_str::<PlainValue>("12").unwrap(),
368            PlainValue::Integer(12)
369        );
370        assert_eq!(
371            serde_json::from_str::<PlainValue>("12.0").unwrap(),
372            PlainValue::Float(12.0)
373        );
374        assert_eq!(
375            serde_json::from_str::<PlainValue>("\"hello\"").unwrap(),
376            PlainValue::String("hello".to_string())
377        );
378        assert_eq!(
379            serde_json::from_str::<PlainValue>("[]").unwrap(),
380            PlainValue::AmbiguousRelation(vec![])
381        );
382        assert_eq!(
383            serde_json::from_str::<PlainValue>(
384                "[[\"00200801063d8aaba76c283a2fb63cf5cd4ec86765424452ce7327fda04c5da80d62\"]]"
385            )
386            .unwrap(),
387            PlainValue::PinnedRelationList(vec![vec![Hash::new(
388                "00200801063d8aaba76c283a2fb63cf5cd4ec86765424452ce7327fda04c5da80d62"
389            )
390            .unwrap()]])
391        );
392    }
393
394    #[test]
395    fn large_numbers() {
396        assert_eq!(
397            deserialize_into::<PlainValue>(&serialize_value(cbor!(i64::MAX))).unwrap(),
398            PlainValue::Integer(i64::MAX)
399        );
400        assert_eq!(
401            deserialize_into::<PlainValue>(&serialize_value(cbor!(f64::MAX))).unwrap(),
402            PlainValue::Float(f64::MAX)
403        );
404
405        // It errors when deserializing a too large number
406        let bytes = serialize_value(cbor!(u64::MAX));
407        let value = deserialize_into::<PlainValue>(&bytes);
408        assert!(value.is_err());
409    }
410}