//! Conversion functions between core types and DTOs. use stemedb_core::types::{Assertion, LifecycleStage, ObjectValue, SignatureEntry, SourceClass}; use super::enums::{LifecycleDto, ObjectValueDto, SignatureDto, SourceClassDto}; use super::requests::{CreateAssertionRequest, HlcTimestampDto}; /// Convert an Assertion to a CreateAssertionRequest. /// /// This is an infallible conversion that hex-encodes all binary fields. /// Includes timestamp and HLC timestamp for v2 signature verification. pub fn assertion_to_request(assertion: &Assertion) -> CreateAssertionRequest { CreateAssertionRequest { subject: assertion.subject.clone(), predicate: assertion.predicate.clone(), object: object_value_to_dto(&assertion.object), confidence: assertion.confidence, signatures: assertion.signatures.iter().map(signature_to_dto).collect(), source_hash: hex::encode(assertion.source_hash), parent_hash: assertion.parent_hash.map(hex::encode), source_class: Some(source_class_to_dto(assertion.source_class)), lifecycle: Some(lifecycle_to_dto(assertion.lifecycle)), source_metadata: assertion .source_metadata .as_ref() .map(|b| String::from_utf8_lossy(b).into_owned()), // Include timestamps for v2 signature verification timestamp: Some(assertion.timestamp), hlc_timestamp: Some(HlcTimestampDto { time_ntp64: assertion.hlc_timestamp.time_ntp64, node_id: hex::encode(assertion.hlc_timestamp.node_id), }), } } /// Convert ObjectValue to DTO. pub fn object_value_to_dto(value: &ObjectValue) -> ObjectValueDto { match value { ObjectValue::Text(s) => ObjectValueDto::Text(s.clone()), ObjectValue::Number(n) => ObjectValueDto::Number(*n), ObjectValue::Boolean(b) => ObjectValueDto::Boolean(*b), ObjectValue::Reference(r) => ObjectValueDto::Reference(r.clone()), } } /// Convert SignatureEntry to DTO. pub fn signature_to_dto(entry: &SignatureEntry) -> SignatureDto { SignatureDto { agent_id: hex::encode(entry.agent_id), signature: hex::encode(entry.signature), timestamp: entry.timestamp, version: Some(entry.version), } } /// Convert SourceClass to DTO. pub fn source_class_to_dto(sc: SourceClass) -> SourceClassDto { match sc { SourceClass::Regulatory => SourceClassDto::Regulatory, SourceClass::Clinical => SourceClassDto::Clinical, SourceClass::Observational => SourceClassDto::Observational, SourceClass::Expert => SourceClassDto::Expert, SourceClass::Community => SourceClassDto::Community, SourceClass::Anecdotal => SourceClassDto::Anecdotal, } } /// Convert LifecycleStage to DTO. pub fn lifecycle_to_dto(stage: LifecycleStage) -> LifecycleDto { match stage { LifecycleStage::Proposed => LifecycleDto::Proposed, LifecycleStage::UnderReview => LifecycleDto::UnderReview, LifecycleStage::Approved => LifecycleDto::Approved, LifecycleStage::Deprecated => LifecycleDto::Deprecated, LifecycleStage::Rejected => LifecycleDto::Rejected, } } #[cfg(test)] mod tests { use super::*; use stemedb_core::types::HlcTimestamp; #[test] fn test_assertion_to_request_basic() { let assertion = Assertion { subject: "Semaglutide:Type2Diabetes".to_string(), predicate: "hba1c_change_percent".to_string(), object: ObjectValue::Number(-1.2), parent_hash: None, source_hash: [0u8; 32], source_class: SourceClass::Regulatory, visual_hash: None, epoch: None, source_metadata: None, lifecycle: LifecycleStage::Approved, signatures: vec![SignatureEntry { agent_id: [1u8; 32], signature: [2u8; 64], timestamp: 1234567890, version: 2, }], confidence: 0.95, timestamp: 1234567890, hlc_timestamp: HlcTimestamp::default(), vector: None, }; let request = assertion_to_request(&assertion); assert_eq!(request.subject, "Semaglutide:Type2Diabetes"); assert_eq!(request.predicate, "hba1c_change_percent"); assert_eq!(request.confidence, 0.95); assert_eq!(request.signatures.len(), 1); assert_eq!(request.signatures[0].version, Some(2)); assert!(matches!(request.source_class, Some(SourceClassDto::Regulatory))); assert!(matches!(request.lifecycle, Some(LifecycleDto::Approved))); } #[test] fn test_object_value_conversion() { assert!(matches!( object_value_to_dto(&ObjectValue::Text("test".to_string())), ObjectValueDto::Text(s) if s == "test" )); assert!(matches!( object_value_to_dto(&ObjectValue::Number(42.5)), ObjectValueDto::Number(n) if (n - 42.5).abs() < f64::EPSILON )); assert!(matches!( object_value_to_dto(&ObjectValue::Boolean(true)), ObjectValueDto::Boolean(true) )); assert!(matches!( object_value_to_dto(&ObjectValue::Reference("ref".to_string())), ObjectValueDto::Reference(r) if r == "ref" )); } }