Skip to main content

ave_common/bridge/
conversions.rs

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