cortexdb-sdk 0.2.0-beta.2

Rust HTTP client SDK for CortexDB
Documentation
use crate::aql::{Aql, AqlBuildError};
use crate::http::path;
use crate::types::{EvidenceResponse, NumericConflictResponse, VerificationReportResponse};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VerifyOutputFormat {
    Json,
    Markdown,
    Audit,
}

impl VerifyOutputFormat {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Json => "json",
            Self::Markdown => "markdown",
            Self::Audit => "audit",
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VerifyRequest {
    scope: String,
    statement: String,
    format: VerifyOutputFormat,
}

impl VerifyRequest {
    pub fn new(scope: impl Into<String>, statement: impl Into<String>) -> Self {
        Self {
            scope: scope.into(),
            statement: statement.into(),
            format: VerifyOutputFormat::Json,
        }
    }

    pub fn fact(
        scope: impl Into<String>,
        fact: impl Into<String>,
        brain: impl Into<String>,
    ) -> Result<Self, AqlBuildError> {
        let statement = Aql::verify_fact(fact, brain).build()?;
        Ok(Self::new(scope, statement))
    }

    pub fn json(mut self) -> Self {
        self.format = VerifyOutputFormat::Json;
        self
    }

    pub fn markdown(mut self) -> Self {
        self.format = VerifyOutputFormat::Markdown;
        self
    }

    pub fn audit(mut self) -> Self {
        self.format = VerifyOutputFormat::Audit;
        self
    }

    pub fn with_format(mut self, format: VerifyOutputFormat) -> Self {
        self.format = format;
        self
    }

    pub fn scope(&self) -> &str {
        &self.scope
    }

    pub fn statement(&self) -> &str {
        &self.statement
    }

    pub fn format(&self) -> VerifyOutputFormat {
        self.format
    }

    pub fn path(&self) -> String {
        path(
            "/v1/verify",
            &[
                ("scope", self.scope.as_str()),
                ("format", self.format.as_str()),
            ],
        )
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VerifyResult {
    Supported,
    Insufficient,
    Contradicted,
    MixedEvidence,
    Unknown(String),
}

impl VerifyResult {
    pub fn from_wire(value: &str) -> Self {
        match value {
            "supported" => Self::Supported,
            "insufficient" => Self::Insufficient,
            "contradicted" => Self::Contradicted,
            "mixed" | "mixed_evidence" => Self::MixedEvidence,
            other => Self::Unknown(other.to_owned()),
        }
    }

    pub fn as_str(&self) -> &str {
        match self {
            Self::Supported => "supported",
            Self::Insufficient => "insufficient",
            Self::Contradicted => "contradicted",
            Self::MixedEvidence => "mixed_evidence",
            Self::Unknown(value) => value.as_str(),
        }
    }

    pub fn is_actionable_conflict(&self) -> bool {
        matches!(self, Self::Contradicted | Self::MixedEvidence)
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VerifyConflict {
    ContradictingEvidence(VerifyEvidenceConflict),
    Numeric(VerifyNumericConflict),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VerifyEvidenceConflict {
    pub cell_id: u64,
    pub matched_terms: u32,
    pub match_score_q16: u16,
    pub match_kind: String,
    pub source_trust_q16: u16,
    pub source_trust_category: String,
    pub citation: Option<String>,
    pub payload_text: String,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VerifyNumericConflict {
    pub metric: String,
    pub left: String,
    pub right: String,
}

impl From<EvidenceResponse> for VerifyEvidenceConflict {
    fn from(value: EvidenceResponse) -> Self {
        Self {
            cell_id: value.cell_id,
            matched_terms: value.matched_terms,
            match_score_q16: value.match_score_q16,
            match_kind: value.match_kind,
            source_trust_q16: value.source_trust_q16,
            source_trust_category: value.source_trust_category,
            citation: value.citation,
            payload_text: value.payload_text,
        }
    }
}

impl From<NumericConflictResponse> for VerifyNumericConflict {
    fn from(value: NumericConflictResponse) -> Self {
        Self {
            metric: value.metric,
            left: value.left,
            right: value.right,
        }
    }
}

pub trait VerificationReportExt {
    fn result(&self) -> VerifyResult;
    fn has_conflicts(&self) -> bool;
    fn conflicts(&self) -> Vec<VerifyConflict>;
}

impl VerificationReportExt for VerificationReportResponse {
    fn result(&self) -> VerifyResult {
        let status = VerifyResult::from_wire(&self.status);
        match status {
            VerifyResult::Unknown(_) => VerifyResult::from_wire(&self.verdict),
            known => known,
        }
    }

    fn has_conflicts(&self) -> bool {
        !self.contradicting_evidence.is_empty() || !self.numeric_conflicts.is_empty()
    }

    fn conflicts(&self) -> Vec<VerifyConflict> {
        let evidence = self
            .contradicting_evidence
            .iter()
            .cloned()
            .map(VerifyEvidenceConflict::from)
            .map(VerifyConflict::ContradictingEvidence);
        let numeric = self
            .numeric_conflicts
            .iter()
            .cloned()
            .map(VerifyNumericConflict::from)
            .map(VerifyConflict::Numeric);

        evidence.chain(numeric).collect()
    }
}