lodestone-proto 0.0.26

Versioned protocol envelopes and transport-neutral requests for Lodestone
Documentation
//! Canonical projection from the generated relationship wire into the
//! transport-neutral mapping-owned model.

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(())
    }
}