use std::sync::Arc;
use axioval_ir::{Evidence, ObjectId};
use crate::services::reviewable_exact_evidence;
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum EnvelopeMembershipError {
#[error("envelope membership evidence must be exact and reviewable")]
InexactEvidence,
#[error("envelope membership is unavailable for the requested derivation")]
Unavailable,
#[error("requested envelope derivation is not supported by this source")]
UnsupportedDerivation,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum EnvelopeDerivation {
AllSpaces,
GrossAreaGroups,
}
impl EnvelopeDerivation {
pub fn as_str(self) -> &'static str {
match self {
EnvelopeDerivation::AllSpaces => "all-spaces",
EnvelopeDerivation::GrossAreaGroups => "gross-area-groups",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EnvelopeMembershipRequest {
derivation: EnvelopeDerivation,
}
impl EnvelopeMembershipRequest {
pub fn new(derivation: EnvelopeDerivation) -> Self {
Self { derivation }
}
pub fn derivation(&self) -> EnvelopeDerivation {
self.derivation
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct EnvelopeMembershipEvidence {
request: EnvelopeMembershipRequest,
declared: Vec<ObjectId>,
derived: Vec<ObjectId>,
evaluated_objects: usize,
evidence: Evidence,
}
impl EnvelopeMembershipEvidence {
pub fn try_new(
request: EnvelopeMembershipRequest,
mut declared: Vec<ObjectId>,
mut derived: Vec<ObjectId>,
evaluated_objects: usize,
evidence: Evidence,
) -> Result<Self, EnvelopeMembershipError> {
if !reviewable_exact_evidence(&evidence) {
return Err(EnvelopeMembershipError::InexactEvidence);
}
declared.sort();
declared.dedup();
derived.sort();
derived.dedup();
Ok(Self {
request,
declared,
derived,
evaluated_objects,
evidence,
})
}
pub fn request(&self) -> EnvelopeMembershipRequest {
self.request
}
pub fn declared(&self) -> &[ObjectId] {
&self.declared
}
pub fn derived(&self) -> &[ObjectId] {
&self.derived
}
pub fn evaluated_objects(&self) -> usize {
self.evaluated_objects
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
pub fn agrees(&self) -> bool {
self.declared == self.derived
}
pub fn declared_only(&self) -> Vec<ObjectId> {
difference(&self.declared, &self.derived)
}
pub fn derived_only(&self) -> Vec<ObjectId> {
difference(&self.derived, &self.declared)
}
}
fn difference(left: &[ObjectId], right: &[ObjectId]) -> Vec<ObjectId> {
let mut out = Vec::new();
let (mut i, mut j) = (0, 0);
while i < left.len() {
match right.get(j) {
Some(candidate) if candidate < &left[i] => j += 1,
Some(candidate) if candidate == &left[i] => {
i += 1;
j += 1;
}
_ => {
out.push(left[i].clone());
i += 1;
}
}
}
out
}
pub trait EnvelopeMembershipService: Send + Sync + 'static {
fn measure_envelope_membership(
&self,
request: &EnvelopeMembershipRequest,
) -> Result<EnvelopeMembershipEvidence, EnvelopeMembershipError>;
}
#[derive(Clone)]
pub struct EnvelopeMembershipServiceHandle(Arc<dyn EnvelopeMembershipService>);
impl EnvelopeMembershipServiceHandle {
pub fn new(service: Arc<dyn EnvelopeMembershipService>) -> Self {
Self(service)
}
pub fn measure_envelope_membership(
&self,
request: &EnvelopeMembershipRequest,
) -> Result<EnvelopeMembershipEvidence, EnvelopeMembershipError> {
self.0.measure_envelope_membership(request)
}
}
#[cfg(test)]
mod tests {
use super::*;
use axioval_ir::SourceId;
fn source() -> SourceId {
SourceId::new("cad", "m").unwrap()
}
fn oid(local: &str) -> ObjectId {
ObjectId::new(source(), local).unwrap()
}
fn build(declared: &[&str], derived: &[&str]) -> EnvelopeMembershipEvidence {
EnvelopeMembershipEvidence::try_new(
EnvelopeMembershipRequest::new(EnvelopeDerivation::AllSpaces),
declared.iter().map(|s| oid(s)).collect(),
derived.iter().map(|s| oid(s)).collect(),
3,
Evidence::exact(source(), "envelope:all-spaces"),
)
.unwrap()
}
#[test]
fn agreement_ignores_order_and_duplicates() {
assert!(build(&["w2", "w1", "w2"], &["w1", "w2"]).agrees());
}
#[test]
fn differences_are_reported_in_both_directions() {
let measured = build(&["w1", "w2"], &["w2", "w3"]);
assert!(!measured.agrees());
assert_eq!(measured.declared_only(), vec![oid("w1")]);
assert_eq!(measured.derived_only(), vec![oid("w3")]);
}
#[test]
fn empty_sets_agree_and_have_no_differences() {
let measured = build(&[], &[]);
assert!(measured.agrees());
assert!(measured.declared_only().is_empty());
assert!(measured.derived_only().is_empty());
}
#[test]
fn nothing_declared_against_derived_walls_is_a_difference() {
let measured = build(&[], &["w1", "w2"]);
assert!(!measured.agrees());
assert_eq!(measured.derived_only(), vec![oid("w1"), oid("w2")]);
assert!(measured.declared_only().is_empty());
}
#[test]
fn inexact_evidence_is_refused() {
let result = EnvelopeMembershipEvidence::try_new(
EnvelopeMembershipRequest::new(EnvelopeDerivation::AllSpaces),
Vec::new(),
Vec::new(),
0,
Evidence {
source: source(),
locator: "envelope:estimate".into(),
exact: false,
},
);
assert_eq!(result, Err(EnvelopeMembershipError::InexactEvidence));
}
}