use std::error::Error;
use std::fmt::{self, Display, Formatter};
use lodestone_types::{
CollectionId, CollectionIndexRef, DocumentId, EntityRefV1, IndexId, IndexName, RelationshipDirectionalityV1,
RelationshipEvidenceRefV1, RelationshipRecordV1,
};
use crate::{RelationshipDirectionality, RelationshipRecord};
pub fn relationship_record_from_wire(
value: &RelationshipRecord,
collection_id: CollectionId,
) -> Result<RelationshipRecordV1, NativeRelationshipWireError> {
Ok(RelationshipRecordV1 {
format_version: u16::try_from(value.format_version).map_err(|_| NativeRelationshipWireError::Integer)?,
relationship_id: value.relationship_id.clone(),
source: EntityRefV1 {
entity_kind: value.source.entity_kind.clone(),
entity_id: value.source.entity_id.clone(),
},
target: EntityRefV1 {
entity_kind: value.target.entity_kind.clone(),
entity_id: value.target.entity_id.clone(),
},
relationship_type: value.relationship_type.clone(),
directionality: match value.directionality {
RelationshipDirectionality::Directed => RelationshipDirectionalityV1::Directed,
RelationshipDirectionality::Undirected => RelationshipDirectionalityV1::Undirected,
},
valid_from_unix_nanos: value.valid_from_unix_nanos.as_ref().map(parse_u64).transpose()?,
valid_through_unix_nanos: value.valid_through_unix_nanos.as_ref().map(parse_u64).transpose()?,
provenance: value.provenance.clone(),
evidence: value
.evidence
.iter()
.map(|reference| {
if reference.index.kind != "index" {
return Err(NativeRelationshipWireError::Resource);
}
let raw = u128::from_str_radix(&reference.index.incarnation.value, 16)
.map_err(|_| NativeRelationshipWireError::Integer)?;
Ok(RelationshipEvidenceRefV1 {
index: CollectionIndexRef::new(
collection_id,
IndexId::new(raw).map_err(|_| NativeRelationshipWireError::Resource)?,
),
index_name: IndexName::new(reference.index.name.clone())
.map_err(|_| NativeRelationshipWireError::Resource)?,
record_id: DocumentId::new(reference.record_id.clone())
.map_err(|_| NativeRelationshipWireError::Resource)?,
})
})
.collect::<Result<Vec<_>, _>>()?,
assertion_class: value.assertion_class.clone(),
})
}
fn parse_u64(value: &crate::U64) -> Result<u64, NativeRelationshipWireError> {
value.value.parse().map_err(|_| NativeRelationshipWireError::Integer)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NativeRelationshipWireError {
Integer,
Resource,
}
impl Display for NativeRelationshipWireError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("native relationship wire is noncanonical")
}
}
impl Error for NativeRelationshipWireError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::{EntityRef, Identity, RelationshipEvidenceRef, Resource};
use lodestone_types::RELATIONSHIP_RECORD_FORMAT_VERSION;
#[test]
fn evidence_index_incarnation_is_the_canonical_hex_identity() -> Result<(), Box<dyn Error>> {
let collection = CollectionId::new(1)?;
let relationship = RelationshipRecord {
format_version: RELATIONSHIP_RECORD_FORMAT_VERSION.into(),
relationship_id: "edge-1".to_owned(),
source: EntityRef {
entity_kind: "service".to_owned(),
entity_id: "api".to_owned(),
},
target: EntityRef {
entity_kind: "service".to_owned(),
entity_id: "database".to_owned(),
},
relationship_type: "calls".to_owned(),
directionality: RelationshipDirectionality::Directed,
valid_from_unix_nanos: None,
valid_through_unix_nanos: None,
provenance: Vec::new(),
evidence: vec![RelationshipEvidenceRef {
index: Resource {
kind: "index".to_owned(),
name: "spans".to_owned(),
incarnation: Identity {
value: "000000000000000000000000000000af".to_owned(),
},
},
record_id: "span-1".to_owned(),
}],
assertion_class: "observed".to_owned(),
};
let projected = relationship_record_from_wire(&relationship, collection)?;
assert_eq!(projected.evidence[0].index.index_id().get(), 0xaf);
Ok(())
}
}