Skip to main content

ave_common/bridge/
conversions.rs

1//! Conversions between bridge models and internal domain models.
2
3use std::collections::BTreeSet;
4use std::str::FromStr;
5
6use ave_identity::{DigestIdentifier, PublicKey, Signed};
7
8use crate::{
9    Namespace, SchemaType, ValueWrapper,
10    bridge::request::{
11        BridgeConfirmRequest, BridgeCreateRequest, BridgeEOLRequest,
12        BridgeEventRequest, BridgeFactRequest, BridgeRejectRequest,
13        BridgeSignedEventRequest, BridgeTransferRequest,
14    },
15    error::ConversionError,
16    request::{
17        ConfirmRequest, CreateRequest, EOLRequest, EventRequest, FactRequest,
18        RejectRequest, TransferRequest,
19    },
20    signature::BridgeSignature,
21};
22
23// ============================================================================
24// EventRequest conversions
25// ============================================================================
26
27impl From<Signed<EventRequest>> for BridgeSignedEventRequest {
28    fn from(value: Signed<EventRequest>) -> Self {
29        let request = BridgeEventRequest::from(value.content().clone());
30        let signature = Some(BridgeSignature::from(value.signature().clone()));
31
32        Self { request, signature }
33    }
34}
35
36impl From<EventRequest> for BridgeSignedEventRequest {
37    fn from(value: EventRequest) -> Self {
38        let request = BridgeEventRequest::from(value);
39        let signature = None;
40
41        Self { request, signature }
42    }
43}
44
45impl From<EventRequest> for BridgeEventRequest {
46    fn from(request: EventRequest) -> Self {
47        match request {
48            EventRequest::Create(req) => Self::Create(req.into()),
49            EventRequest::Fact(req) => Self::Fact(req.into()),
50            EventRequest::Transfer(req) => Self::Transfer(req.into()),
51            EventRequest::EOL(req) => Self::Eol(req.into()),
52            EventRequest::Confirm(req) => Self::Confirm(req.into()),
53            EventRequest::Reject(req) => Self::Reject(req.into()),
54        }
55    }
56}
57
58impl TryFrom<BridgeEventRequest> for EventRequest {
59    type Error = ConversionError;
60
61    fn try_from(request: BridgeEventRequest) -> Result<Self, Self::Error> {
62        match request {
63            BridgeEventRequest::Create(req) => {
64                Ok(Self::Create(req.try_into()?))
65            }
66            BridgeEventRequest::Fact(req) => Ok(Self::Fact(req.try_into()?)),
67            BridgeEventRequest::Transfer(req) => {
68                Ok(Self::Transfer(req.try_into()?))
69            }
70            BridgeEventRequest::Eol(req) => Ok(Self::EOL(req.try_into()?)),
71            BridgeEventRequest::Confirm(req) => {
72                Ok(Self::Confirm(req.try_into()?))
73            }
74            BridgeEventRequest::Reject(req) => {
75                Ok(Self::Reject(req.try_into()?))
76            }
77        }
78    }
79}
80
81// ============================================================================
82// CreateRequest conversions
83// ============================================================================
84
85impl From<CreateRequest> for BridgeCreateRequest {
86    fn from(request: CreateRequest) -> Self {
87        Self {
88            name: request.name,
89            description: request.description,
90            governance_id: Some(request.governance_id.to_string()),
91            schema_id: request.schema_id.to_string(),
92            namespace: Some(request.namespace.to_string()),
93        }
94    }
95}
96
97impl TryFrom<BridgeCreateRequest> for CreateRequest {
98    type Error = ConversionError;
99
100    fn try_from(request: BridgeCreateRequest) -> Result<Self, Self::Error> {
101        let governance_id = if let Some(governance_id) = request.governance_id {
102            DigestIdentifier::from_str(&governance_id).map_err(|e| {
103                ConversionError::InvalidGovernanceId(e.to_string())
104            })?
105        } else {
106            DigestIdentifier::default()
107        };
108
109        let schema_id = SchemaType::from_str(&request.schema_id)
110            .map_err(ConversionError::InvalidSchemaId)?;
111
112        let namespace = request
113            .namespace
114            .map_or_else(Namespace::new, Namespace::from);
115
116        Ok(Self {
117            name: request.name,
118            description: request.description,
119            governance_id,
120            schema_id,
121            namespace,
122        })
123    }
124}
125
126// ============================================================================
127// FactRequest conversions
128// ============================================================================
129
130impl From<FactRequest> for BridgeFactRequest {
131    fn from(request: FactRequest) -> Self {
132        Self {
133            subject_id: request.subject_id.to_string(),
134            payload: request.payload.0,
135            viewpoints: request.viewpoints.into_iter().collect(),
136        }
137    }
138}
139
140impl TryFrom<BridgeFactRequest> for FactRequest {
141    type Error = ConversionError;
142
143    fn try_from(request: BridgeFactRequest) -> Result<Self, Self::Error> {
144        let subject_id = DigestIdentifier::from_str(&request.subject_id)
145            .map_err(|e| ConversionError::InvalidSubjectId(e.to_string()))?;
146
147        let mut viewpoints = BTreeSet::new();
148        for viewpoint in request.viewpoints {
149            if !viewpoints.insert(viewpoint.clone()) {
150                return Err(ConversionError::InvalidViewpoints(format!(
151                    "duplicated viewpoint '{viewpoint}'"
152                )));
153            }
154        }
155
156        Ok(Self {
157            subject_id,
158            payload: ValueWrapper(request.payload),
159            viewpoints,
160        })
161    }
162}
163
164// ============================================================================
165// TransferRequest conversions
166// ============================================================================
167
168impl From<TransferRequest> for BridgeTransferRequest {
169    fn from(request: TransferRequest) -> Self {
170        Self {
171            subject_id: request.subject_id.to_string(),
172            new_owner: request.new_owner.to_string(),
173        }
174    }
175}
176
177impl TryFrom<BridgeTransferRequest> for TransferRequest {
178    type Error = ConversionError;
179
180    fn try_from(request: BridgeTransferRequest) -> Result<Self, Self::Error> {
181        let subject_id = DigestIdentifier::from_str(&request.subject_id)
182            .map_err(|e| ConversionError::InvalidSubjectId(e.to_string()))?;
183
184        let new_owner = PublicKey::from_str(&request.new_owner)
185            .map_err(|e| ConversionError::InvalidPublicKey(e.to_string()))?;
186
187        Ok(Self {
188            subject_id,
189            new_owner,
190        })
191    }
192}
193
194// ============================================================================
195// EOLRequest conversions
196// ============================================================================
197
198impl From<EOLRequest> for BridgeEOLRequest {
199    fn from(request: EOLRequest) -> Self {
200        Self {
201            subject_id: request.subject_id.to_string(),
202        }
203    }
204}
205
206impl TryFrom<BridgeEOLRequest> for EOLRequest {
207    type Error = ConversionError;
208
209    fn try_from(request: BridgeEOLRequest) -> Result<Self, Self::Error> {
210        let subject_id = DigestIdentifier::from_str(&request.subject_id)
211            .map_err(|e| ConversionError::InvalidSubjectId(e.to_string()))?;
212
213        Ok(Self { subject_id })
214    }
215}
216
217// ============================================================================
218// ConfirmRequest conversions
219// ============================================================================
220
221impl From<ConfirmRequest> for BridgeConfirmRequest {
222    fn from(request: ConfirmRequest) -> Self {
223        Self {
224            subject_id: request.subject_id.to_string(),
225            name_old_owner: request.name_old_owner,
226        }
227    }
228}
229
230impl TryFrom<BridgeConfirmRequest> for ConfirmRequest {
231    type Error = ConversionError;
232
233    fn try_from(request: BridgeConfirmRequest) -> Result<Self, Self::Error> {
234        let subject_id = DigestIdentifier::from_str(&request.subject_id)
235            .map_err(|e| ConversionError::InvalidSubjectId(e.to_string()))?;
236
237        Ok(Self {
238            subject_id,
239            name_old_owner: request.name_old_owner,
240        })
241    }
242}
243
244// ============================================================================
245// RejectRequest conversions
246// ============================================================================
247
248impl From<RejectRequest> for BridgeRejectRequest {
249    fn from(request: RejectRequest) -> Self {
250        Self {
251            subject_id: request.subject_id.to_string(),
252        }
253    }
254}
255
256impl TryFrom<BridgeRejectRequest> for RejectRequest {
257    type Error = ConversionError;
258
259    fn try_from(request: BridgeRejectRequest) -> Result<Self, Self::Error> {
260        let subject_id = DigestIdentifier::from_str(&request.subject_id)
261            .map_err(|e| ConversionError::InvalidSubjectId(e.to_string()))?;
262
263        Ok(Self { subject_id })
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270    use serde_json::json;
271
272    #[test]
273    fn test_fact_request_conversion() {
274        let bridge_fact = BridgeFactRequest {
275            subject_id: "BKZgYibuHNJjiNS179FUDpLGgdLq0C04TZRGb6AXMd1s"
276                .to_string(),
277            payload: json!({"test": "value"}),
278            viewpoints: vec![],
279        };
280
281        let fact: Result<FactRequest, _> = bridge_fact.clone().try_into();
282        assert!(fact.is_ok());
283
284        let fact = fact.unwrap();
285        let bridge_back: BridgeFactRequest = fact.into();
286        assert_eq!(bridge_back.subject_id, bridge_fact.subject_id);
287    }
288
289    #[test]
290    fn test_bridge_fact_request_defaults_missing_viewpoints_to_empty() {
291        let bridge_fact = serde_json::from_value::<BridgeFactRequest>(json!({
292            "subject_id": "BKZgYibuHNJjiNS179FUDpLGgdLq0C04TZRGb6AXMd1s",
293            "payload": {"test": "value"}
294        }))
295        .unwrap();
296
297        assert!(bridge_fact.viewpoints.is_empty());
298    }
299
300    #[test]
301    fn test_create_request_conversion() {
302        let bridge_create = BridgeCreateRequest {
303            name: Some("Test".to_string()),
304            description: Some("Test description".to_string()),
305            governance_id: Some(
306                "BKZgYibuHNJjiNS179FUDpLGgdLq0C04TZRGb6AXMd1s".to_string(),
307            ),
308            schema_id: "governance".to_string(),
309            namespace: Some("test.namespace".to_string()),
310        };
311
312        let create: Result<CreateRequest, _> = bridge_create.try_into();
313        assert!(create.is_ok());
314    }
315
316    #[test]
317    fn test_create_request_missing_governance_id() {
318        let bridge_create = BridgeCreateRequest {
319            name: Some("Test".to_string()),
320            description: Some("Test description".to_string()),
321            governance_id: None,
322            schema_id: "governance".to_string(),
323            namespace: Some("test.namespace".to_string()),
324        };
325
326        let create: Result<CreateRequest, _> = bridge_create.try_into();
327        assert!(create.is_ok());
328    }
329
330    #[test]
331    fn test_invalid_subject_id() {
332        let bridge_fact = BridgeFactRequest {
333            subject_id: "invalid_id".to_string(),
334            payload: json!({"test": "value"}),
335            viewpoints: vec![],
336        };
337
338        let fact: Result<FactRequest, _> = bridge_fact.try_into();
339        assert!(fact.is_err());
340        assert!(matches!(
341            fact.unwrap_err(),
342            ConversionError::InvalidSubjectId(_)
343        ));
344    }
345
346    #[test]
347    fn test_fact_request_conversion_rejects_duplicated_viewpoints() {
348        let bridge_fact = BridgeFactRequest {
349            subject_id: "BKZgYibuHNJjiNS179FUDpLGgdLq0C04TZRGb6AXMd1s"
350                .to_string(),
351            payload: json!({"test": "value"}),
352            viewpoints: vec!["agua".to_string(), "agua".to_string()],
353        };
354
355        let fact: Result<FactRequest, _> = bridge_fact.try_into();
356        assert!(matches!(
357            fact.unwrap_err(),
358            ConversionError::InvalidViewpoints(_)
359        ));
360    }
361}