p2panda_rs/operation/
operation_value.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3use serde::Serialize;
4
5use crate::document::{DocumentId, DocumentViewId};
6use crate::operation::{PinnedRelation, PinnedRelationList, Relation, RelationList};
7
8/// Enum of possible data types which can be added to the operations fields as values.
9#[derive(Clone, Debug, PartialEq, Serialize)]
10pub enum OperationValue {
11    /// Boolean value.
12    Boolean(bool),
13
14    /// Bytes value.
15    #[serde(with = "serde_bytes")]
16    Bytes(Vec<u8>),
17
18    /// Signed integer value.
19    Integer(i64),
20
21    /// Floating point value.
22    Float(f64),
23
24    /// String value.
25    String(String),
26
27    /// Reference to a document.
28    Relation(Relation),
29
30    /// Reference to a list of documents.
31    RelationList(RelationList),
32
33    /// Reference to a document view.
34    PinnedRelation(PinnedRelation),
35
36    /// Reference to a list of document views.
37    PinnedRelationList(PinnedRelationList),
38}
39
40impl OperationValue {
41    /// Return the field type for this operation value as a string
42    pub fn field_type(&self) -> &str {
43        match self {
44            OperationValue::Boolean(_) => "bool",
45            OperationValue::Bytes(_) => "bytes",
46            OperationValue::Integer(_) => "int",
47            OperationValue::Float(_) => "float",
48            OperationValue::String(_) => "str",
49            OperationValue::Relation(_) => "relation",
50            OperationValue::RelationList(_) => "relation_list",
51            OperationValue::PinnedRelation(_) => "pinned_relation",
52            OperationValue::PinnedRelationList(_) => "pinned_relation_list",
53        }
54    }
55}
56
57impl From<bool> for OperationValue {
58    fn from(value: bool) -> Self {
59        OperationValue::Boolean(value)
60    }
61}
62
63impl From<f64> for OperationValue {
64    fn from(value: f64) -> Self {
65        OperationValue::Float(value)
66    }
67}
68
69impl From<i64> for OperationValue {
70    fn from(value: i64) -> Self {
71        OperationValue::Integer(value)
72    }
73}
74
75impl From<String> for OperationValue {
76    fn from(value: String) -> Self {
77        OperationValue::String(value)
78    }
79}
80
81impl From<&str> for OperationValue {
82    fn from(value: &str) -> Self {
83        OperationValue::String(value.to_string())
84    }
85}
86
87impl From<&[u8]> for OperationValue {
88    fn from(value: &[u8]) -> Self {
89        OperationValue::Bytes(value.to_owned())
90    }
91}
92
93impl From<DocumentId> for OperationValue {
94    fn from(value: DocumentId) -> Self {
95        OperationValue::Relation(Relation::new(value))
96    }
97}
98
99impl From<Vec<DocumentId>> for OperationValue {
100    fn from(value: Vec<DocumentId>) -> Self {
101        OperationValue::RelationList(RelationList::new(value))
102    }
103}
104
105impl From<DocumentViewId> for OperationValue {
106    fn from(value: DocumentViewId) -> Self {
107        OperationValue::PinnedRelation(PinnedRelation::new(value))
108    }
109}
110
111impl From<Vec<DocumentViewId>> for OperationValue {
112    fn from(value: Vec<DocumentViewId>) -> Self {
113        OperationValue::PinnedRelationList(PinnedRelationList::new(value))
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use rstest::rstest;
120
121    use crate::document::{DocumentId, DocumentViewId};
122    use crate::operation::{
123        OperationId, PinnedRelation, PinnedRelationList, Relation, RelationList,
124    };
125    use crate::test_utils::fixtures::{document_id, document_view_id, random_operation_id};
126
127    use super::OperationValue;
128
129    #[rstest]
130    fn to_field_type(#[from(random_operation_id)] operation_id: OperationId) {
131        let bool = OperationValue::Boolean(true);
132        assert_eq!(bool.field_type(), "bool");
133
134        let int = OperationValue::Integer(1);
135        assert_eq!(int.field_type(), "int");
136
137        let float = OperationValue::Float(0.1);
138        assert_eq!(float.field_type(), "float");
139
140        let text = OperationValue::String("Hello".to_string());
141        assert_eq!(text.field_type(), "str");
142
143        let relation = OperationValue::Relation(Relation::new(DocumentId::new(&operation_id)));
144        assert_eq!(relation.field_type(), "relation");
145
146        let pinned_relation =
147            OperationValue::PinnedRelation(PinnedRelation::new(DocumentViewId::new(&[
148                operation_id.clone(),
149            ])));
150        assert_eq!(pinned_relation.field_type(), "pinned_relation");
151
152        let relation_list =
153            OperationValue::RelationList(RelationList::new(vec![DocumentId::new(&operation_id)]));
154        assert_eq!(relation_list.field_type(), "relation_list");
155
156        let pinned_relation_list = OperationValue::PinnedRelationList(PinnedRelationList::new(
157            vec![DocumentViewId::new(&[operation_id])],
158        ));
159        assert_eq!(pinned_relation_list.field_type(), "pinned_relation_list");
160    }
161
162    #[rstest]
163    fn from_primitives(document_id: DocumentId, document_view_id: DocumentViewId) {
164        // Scalar types
165        assert_eq!(OperationValue::Boolean(true), true.into());
166        assert_eq!(OperationValue::Float(1.5), 1.5.into());
167        assert_eq!(OperationValue::Integer(3), 3.into());
168        assert_eq!(OperationValue::String("hellö".to_string()), "hellö".into());
169
170        // Relation types
171        assert_eq!(
172            OperationValue::Relation(Relation::new(document_id.clone())),
173            document_id.clone().into()
174        );
175        assert_eq!(
176            OperationValue::RelationList(RelationList::new(vec![document_id.clone()])),
177            vec![document_id].into()
178        );
179        assert_eq!(
180            OperationValue::PinnedRelation(PinnedRelation::new(document_view_id.clone())),
181            document_view_id.clone().into()
182        );
183        assert_eq!(
184            OperationValue::PinnedRelationList(PinnedRelationList::new(vec![
185                document_view_id.clone()
186            ])),
187            vec![document_view_id].into()
188        );
189    }
190}