Skip to main content

ave_common/
request.rs

1//! Ledger event request types.
2//!
3//! These structures describe the input accepted by the core ledger flow before
4//! it is wrapped in transport-specific formats.
5
6use ave_identity::{DigestIdentifier, PublicKey};
7use borsh::{BorshDeserialize, BorshSerialize};
8use serde::{Deserialize, Deserializer, Serialize};
9use std::collections::BTreeSet;
10
11use crate::{Namespace, SchemaType, ValueWrapper};
12
13/// Event request accepted by the ledger.
14#[derive(
15    Debug,
16    Clone,
17    Serialize,
18    Deserialize,
19    Eq,
20    PartialEq,
21    BorshSerialize,
22    BorshDeserialize,
23)]
24pub enum EventRequest {
25    /// Creates a new subject.
26    Create(CreateRequest),
27    /// Appends a fact to an existing subject.
28    Fact(FactRequest),
29    /// Transfers subject ownership.
30    Transfer(TransferRequest),
31    /// Confirms a transfer.
32    Confirm(ConfirmRequest),
33    /// Rejects a transfer.
34    Reject(RejectRequest),
35    /// Marks a subject as end-of-life.
36    EOL(EOLRequest),
37}
38
39impl EventRequest {
40    /// Returns `true` when `signer` is allowed to sign this request.
41    pub fn check_request_signature(
42        &self,
43        signer: &PublicKey,
44        owner: &PublicKey,
45        new_owner: &Option<PublicKey>,
46    ) -> bool {
47        match self {
48            Self::Create(..) | Self::Transfer(..) | Self::EOL(..) => {
49                signer == owner
50            }
51            Self::Confirm(..) | Self::Reject(..) => {
52                new_owner.as_ref() == Some(signer)
53            }
54            Self::Fact(..) => true,
55        }
56    }
57
58    /// Returns `true` when the request is a `Create`.
59    pub const fn is_create_event(&self) -> bool {
60        matches!(self, Self::Create(_create_request))
61    }
62
63    /// Returns `true` when the request is a `Fact`.
64    pub const fn is_fact_event(&self) -> bool {
65        matches!(self, Self::Fact(_fact_request))
66    }
67
68    /// Returns the subject identifier affected by the request.
69    ///
70    /// `Create` requests do not have a subject id yet, so they return the empty
71    /// digest placeholder used by the rest of the workspace.
72    pub fn get_subject_id(&self) -> DigestIdentifier {
73        match self {
74            Self::Create(_create_request) => DigestIdentifier::default(),
75            Self::Fact(fact_request) => fact_request.subject_id.clone(),
76            Self::Transfer(transfer_request) => {
77                transfer_request.subject_id.clone()
78            }
79            Self::Confirm(confirm_request) => {
80                confirm_request.subject_id.clone()
81            }
82            Self::Reject(reject_request) => reject_request.subject_id.clone(),
83            Self::EOL(eolrequest) => eolrequest.subject_id.clone(),
84        }
85    }
86}
87
88/// Payload for a `Create` event.
89#[derive(
90    Debug,
91    Clone,
92    Serialize,
93    Deserialize,
94    Eq,
95    PartialEq,
96    BorshSerialize,
97    BorshDeserialize,
98)]
99pub struct CreateRequest {
100    /// Optional subject name.
101    pub name: Option<String>,
102    /// Optional subject description.
103    pub description: Option<String>,
104    /// Governance identifier.
105    pub governance_id: DigestIdentifier,
106    /// Schema used to validate the initial state.
107    pub schema_id: SchemaType,
108    /// Subject namespace.
109    pub namespace: Namespace,
110}
111
112/// Payload for a `Fact` event.
113#[derive(
114    Debug,
115    Clone,
116    Serialize,
117    Deserialize,
118    Eq,
119    PartialEq,
120    BorshSerialize,
121    BorshDeserialize,
122)]
123pub struct FactRequest {
124    /// Subject identifier.
125    pub subject_id: DigestIdentifier,
126    /// JSON payload to append to the subject state.
127    pub payload: ValueWrapper,
128    /// Optional viewpoints targeted by this fact.
129    ///
130    /// An empty set means the event is not segmented by viewpoints.
131    #[serde(default)]
132    #[serde(deserialize_with = "deserialize_unique_viewpoints")]
133    pub viewpoints: BTreeSet<String>,
134}
135
136fn deserialize_unique_viewpoints<'de, D>(
137    deserializer: D,
138) -> Result<BTreeSet<String>, D::Error>
139where
140    D: Deserializer<'de>,
141{
142    let viewpoints =
143        <Vec<String> as serde::Deserialize>::deserialize(deserializer)?;
144    let mut unique: BTreeSet<String> = BTreeSet::new();
145
146    for viewpoint in viewpoints {
147        if !unique.insert(viewpoint.clone()) {
148            return Err(serde::de::Error::custom(format!(
149                "duplicated viewpoint '{viewpoint}'"
150            )));
151        }
152    }
153
154    Ok(unique)
155}
156
157/// Payload for a `Transfer` event.
158#[derive(
159    Debug,
160    Clone,
161    Serialize,
162    Deserialize,
163    Eq,
164    PartialEq,
165    BorshSerialize,
166    BorshDeserialize,
167)]
168pub struct TransferRequest {
169    /// Subject identifier.
170    pub subject_id: DigestIdentifier,
171    /// Public key of the new owner.
172    pub new_owner: PublicKey,
173}
174
175/// Payload for a `Confirm` event.
176#[derive(
177    Debug,
178    Clone,
179    Serialize,
180    Deserialize,
181    Eq,
182    PartialEq,
183    BorshSerialize,
184    BorshDeserialize,
185)]
186pub struct ConfirmRequest {
187    pub subject_id: DigestIdentifier,
188    /// Optional name for the previous owner in governance updates.
189    pub name_old_owner: Option<String>,
190}
191
192/// Payload for an `EOL` event.
193#[derive(
194    Debug,
195    Clone,
196    Serialize,
197    Deserialize,
198    Eq,
199    PartialEq,
200    BorshSerialize,
201    BorshDeserialize,
202)]
203pub struct EOLRequest {
204    /// Subject identifier.
205    pub subject_id: DigestIdentifier,
206}
207
208/// Payload for a `Reject` event.
209#[derive(
210    Debug,
211    Clone,
212    Serialize,
213    Deserialize,
214    Eq,
215    PartialEq,
216    BorshSerialize,
217    BorshDeserialize,
218)]
219pub struct RejectRequest {
220    pub subject_id: DigestIdentifier,
221}
222
223#[cfg(test)]
224mod tests {
225    use super::FactRequest;
226    use ave_identity::DigestIdentifier;
227    use serde_json::json;
228    use std::collections::BTreeSet;
229
230    #[test]
231    fn test_fact_request_defaults_missing_viewpoints_to_empty() {
232        let subject_id = DigestIdentifier::default().to_string();
233        let request = serde_json::from_value::<FactRequest>(json!({
234            "subject_id": subject_id,
235            "payload": { "ModOne": { "data": 1 } }
236        }))
237        .unwrap();
238
239        assert_eq!(request.viewpoints, BTreeSet::new());
240    }
241
242    #[test]
243    fn test_fact_request_rejects_duplicated_viewpoints() {
244        let subject_id = DigestIdentifier::default().to_string();
245        let error = serde_json::from_value::<FactRequest>(json!({
246            "subject_id": subject_id,
247            "payload": { "ModOne": { "data": 1 } },
248            "viewpoints": ["agua", "agua"]
249        }))
250        .unwrap_err();
251
252        assert!(error.to_string().contains("duplicated viewpoint"));
253    }
254}