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