use std::sync::Arc;
use axioval_ir::{Evidence, ObjectId};
use thiserror::Error;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum RelationshipSelectionError {
#[error("relationship selection request is invalid")]
InvalidRequest,
#[error("relationship selection contains a duplicate candidate")]
DuplicateCandidate,
#[error("relationship selection contains duplicate evidence")]
DuplicateEvidence,
#[error("relationship selection response does not match its request")]
ResponseRequestMismatch,
#[error("relationship selection evidence is not exact and reviewable")]
InexactEvidence,
#[error("relationship selection unavailable: {0}")]
Unavailable(String),
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SemanticRelationship(String);
impl SemanticRelationship {
pub fn try_new(value: impl Into<String>) -> Result<Self, RelationshipSelectionError> {
let value = value.into();
if value.trim().is_empty() {
return Err(RelationshipSelectionError::InvalidRequest);
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TraversalDirection {
Forward,
Backward,
Either,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RelationshipQuery {
SharedGroup {
relationship: SemanticRelationship,
},
Related {
relationship: SemanticRelationship,
direction: TraversalDirection,
follow_chain: bool,
},
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct RelationshipSelectionRequest {
anchor: ObjectId,
candidate_universe: Vec<ObjectId>,
query: RelationshipQuery,
}
impl RelationshipSelectionRequest {
pub fn try_new(
anchor: ObjectId,
mut candidate_universe: Vec<ObjectId>,
query: RelationshipQuery,
) -> Result<Self, RelationshipSelectionError> {
candidate_universe.sort();
if candidate_universe.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(RelationshipSelectionError::DuplicateCandidate);
}
Ok(Self {
anchor,
candidate_universe,
query,
})
}
#[must_use]
pub fn anchor(&self) -> &ObjectId {
&self.anchor
}
#[must_use]
pub fn candidate_universe(&self) -> &[ObjectId] {
&self.candidate_universe
}
#[must_use]
pub fn query(&self) -> &RelationshipQuery {
&self.query
}
fn contains_candidate(&self, candidate: &ObjectId) -> bool {
self.candidate_universe.binary_search(candidate).is_ok()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CompleteRelationshipSelection {
request: RelationshipSelectionRequest,
candidates: Vec<ObjectId>,
evidence: Vec<Evidence>,
}
impl CompleteRelationshipSelection {
pub fn try_new(
request: RelationshipSelectionRequest,
mut candidates: Vec<ObjectId>,
mut evidence: Vec<Evidence>,
) -> Result<Self, RelationshipSelectionError> {
candidates.sort();
if candidates.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(RelationshipSelectionError::DuplicateCandidate);
}
if candidates
.iter()
.any(|candidate| !request.contains_candidate(candidate))
{
return Err(RelationshipSelectionError::ResponseRequestMismatch);
}
if evidence.is_empty() || evidence.iter().any(|item| !reviewable(item)) {
return Err(RelationshipSelectionError::InexactEvidence);
}
evidence.sort_by(|left, right| {
(&left.source, &left.locator).cmp(&(&right.source, &right.locator))
});
if evidence.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(RelationshipSelectionError::DuplicateEvidence);
}
Ok(Self {
request,
candidates,
evidence,
})
}
#[must_use]
pub fn request(&self) -> &RelationshipSelectionRequest {
&self.request
}
#[must_use]
pub fn candidates(&self) -> &[ObjectId] {
&self.candidates
}
#[must_use]
pub fn evidence(&self) -> &[Evidence] {
&self.evidence
}
}
pub trait RelationshipSelectionService: Send + Sync {
fn select(
&self,
request: &RelationshipSelectionRequest,
) -> Result<CompleteRelationshipSelection, RelationshipSelectionError>;
}
#[derive(Clone)]
pub struct RelationshipSelectionServiceHandle(Arc<dyn RelationshipSelectionService>);
impl RelationshipSelectionServiceHandle {
#[must_use]
pub fn new(service: Arc<dyn RelationshipSelectionService>) -> Self {
Self(service)
}
pub fn select(
&self,
request: &RelationshipSelectionRequest,
) -> Result<CompleteRelationshipSelection, RelationshipSelectionError> {
let selection = self.0.select(request)?;
if selection.request() != request
|| selection
.candidates()
.iter()
.any(|candidate| !request.contains_candidate(candidate))
{
return Err(RelationshipSelectionError::ResponseRequestMismatch);
}
if selection
.candidates()
.windows(2)
.any(|pair| pair[0] >= pair[1])
{
return Err(RelationshipSelectionError::DuplicateCandidate);
}
if selection.evidence().is_empty()
|| selection.evidence().iter().any(|item| !reviewable(item))
{
return Err(RelationshipSelectionError::InexactEvidence);
}
Ok(selection)
}
}
fn reviewable(evidence: &Evidence) -> bool {
evidence.exact && !evidence.locator.trim().is_empty()
}