stemedb/crates/stemedb-ontology/src/dto/conversions.rs
jordan 41c676a78e feat: Aphoria enterprise features + ontology SDK + file length compliance
Enterprise Features:
- Hosted mode with remote sync for team pattern aggregation
- Community sharing with privacy-preserving anonymization
- LLM-based semantic claim extraction with Gemini integration
- Pattern learning with promotion to declarative extractors
- High-entropy secrets extractor with configurable thresholds
- Auth bypass and insecure cookies extractors

Module Refactoring:
- Split oversized files to comply with 500-line limit
- Config split: types/core.rs, types/extractors.rs, types/hosted.rs, etc.
- Handlers split: scan.rs, policy.rs, report.rs modules
- Extractors split: declarative/, high_entropy_secrets/, insecure_cookies/
- Learning split: store modules with metrics and persistence

SDK & Ontology:
- stemedb-ontology SDK with fluent builders and StemeDB client
- Pharma domain extractors for FDA Orange Book data
- Consumer health UAT test infrastructure

Code Quality:
- Fixed clippy warnings (needless_borrows_for_generic_args)
- Added KVStore trait imports where needed
- Fixed utoipa path re-exports for OpenAPI docs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 12:55:29 -07:00

140 lines
5.3 KiB
Rust

//! 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"
));
}
}