use std::sync::Arc;
use crate::services::reviewable_exact_evidence;
use axioval_ir::{Evidence, ObjectId};
use thiserror::Error;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum MetricRoutingError {
#[error("metric point coordinates must be finite")]
InvalidCoordinate,
#[error("metric length interval is invalid")]
InvalidLengthInterval,
#[error("mobility profile contains an invalid dimension")]
InvalidMobilityProfile,
#[error("metric route evidence is empty")]
EmptyRouteEvidence,
#[error("metric route provenance is not exact and reviewable")]
InexactRouteEvidence,
#[error("metric evidence is incomplete")]
IncompleteMetricEvidence,
#[error("metric routing backend returned mismatched endpoints")]
ResponseEndpointMismatch,
#[error("metric geometry is unavailable for `{0}`")]
MissingGeometry(Box<ObjectId>),
#[error("metric routing query unavailable: {0}")]
Unavailable(String),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ThresholdVerdict {
Satisfied,
Violated,
Indeterminate,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LengthInterval {
lower_metres: f64,
upper_metres: f64,
}
impl LengthInterval {
pub fn try_new(lower_metres: f64, upper_metres: f64) -> Result<Self, MetricRoutingError> {
if !valid_non_negative(lower_metres)
|| !valid_non_negative(upper_metres)
|| lower_metres > upper_metres
{
return Err(MetricRoutingError::InvalidLengthInterval);
}
Ok(Self {
lower_metres,
upper_metres,
})
}
pub fn exact(metres: f64) -> Result<Self, MetricRoutingError> {
Self::try_new(metres, metres)
}
pub fn lower_metres(&self) -> f64 {
self.lower_metres
}
pub fn upper_metres(&self) -> f64 {
self.upper_metres
}
#[allow(clippy::float_cmp)]
pub fn is_exact(&self) -> bool {
self.lower_metres == self.upper_metres
}
pub fn compare_maximum(
&self,
maximum_metres: f64,
) -> Result<ThresholdVerdict, MetricRoutingError> {
if !valid_non_negative(maximum_metres) {
return Err(MetricRoutingError::InvalidLengthInterval);
}
if self.upper_metres <= maximum_metres {
Ok(ThresholdVerdict::Satisfied)
} else if self.lower_metres > maximum_metres {
Ok(ThresholdVerdict::Violated)
} else {
Ok(ThresholdVerdict::Indeterminate)
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MetricPoint {
subject: ObjectId,
coordinates_metres: [f64; 3],
}
impl MetricPoint {
pub fn try_new(
subject: ObjectId,
coordinates_metres: [f64; 3],
) -> Result<Self, MetricRoutingError> {
if !coordinates_metres.iter().all(|value| value.is_finite()) {
return Err(MetricRoutingError::InvalidCoordinate);
}
Ok(Self {
subject,
coordinates_metres,
})
}
pub fn subject(&self) -> &ObjectId {
&self.subject
}
pub fn coordinates_metres(&self) -> [f64; 3] {
self.coordinates_metres
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MobilityProfile {
radius_metres: f64,
height_metres: f64,
maximum_step_metres: f64,
maximum_slope: f64,
}
impl MobilityProfile {
pub fn try_new(
radius_metres: f64,
height_metres: f64,
maximum_step_metres: f64,
maximum_slope: f64,
) -> Result<Self, MetricRoutingError> {
if ![
radius_metres,
height_metres,
maximum_step_metres,
maximum_slope,
]
.into_iter()
.all(valid_non_negative)
{
return Err(MetricRoutingError::InvalidMobilityProfile);
}
Ok(Self {
radius_metres,
height_metres,
maximum_step_metres,
maximum_slope,
})
}
pub fn radius_metres(&self) -> f64 {
self.radius_metres
}
pub fn height_metres(&self) -> f64 {
self.height_metres
}
pub fn maximum_step_metres(&self) -> f64 {
self.maximum_step_metres
}
pub fn maximum_slope(&self) -> f64 {
self.maximum_slope
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MetricRouteRequest {
origin: MetricPoint,
destination: MetricPoint,
profile: MobilityProfile,
}
impl MetricRouteRequest {
pub fn new(origin: MetricPoint, destination: MetricPoint, profile: MobilityProfile) -> Self {
Self {
origin,
destination,
profile,
}
}
pub fn origin(&self) -> &MetricPoint {
&self.origin
}
pub fn destination(&self) -> &MetricPoint {
&self.destination
}
pub fn profile(&self) -> MobilityProfile {
self.profile
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompleteMetricEvidence(Evidence);
impl CompleteMetricEvidence {
pub fn try_new(evidence: Evidence) -> Result<Self, MetricRoutingError> {
if !reviewable_exact_evidence(&evidence) {
return Err(MetricRoutingError::IncompleteMetricEvidence);
}
Ok(Self(evidence))
}
pub fn evidence(&self) -> &Evidence {
&self.0
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BlockedMetricRouteEvidence {
request: MetricRouteRequest,
completeness: CompleteMetricEvidence,
}
impl BlockedMetricRouteEvidence {
pub fn new(request: MetricRouteRequest, completeness: CompleteMetricEvidence) -> Self {
Self {
request,
completeness,
}
}
pub fn request(&self) -> &MetricRouteRequest {
&self.request
}
pub fn completeness(&self) -> &CompleteMetricEvidence {
&self.completeness
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MetricRouteEvidence {
shortest_distance: LengthInterval,
waypoints: Vec<MetricPoint>,
traversed_objects: Vec<ObjectId>,
evidence: Evidence,
}
impl MetricRouteEvidence {
pub fn try_new(
shortest_distance: LengthInterval,
waypoints: Vec<MetricPoint>,
traversed_objects: Vec<ObjectId>,
evidence: Evidence,
) -> Result<Self, MetricRoutingError> {
if waypoints.is_empty() || traversed_objects.is_empty() {
return Err(MetricRoutingError::EmptyRouteEvidence);
}
if !reviewable_exact_evidence(&evidence) {
return Err(MetricRoutingError::InexactRouteEvidence);
}
Ok(Self {
shortest_distance,
waypoints,
traversed_objects,
evidence,
})
}
pub fn shortest_distance(&self) -> &LengthInterval {
&self.shortest_distance
}
pub fn waypoints(&self) -> &[MetricPoint] {
&self.waypoints
}
pub fn traversed_objects(&self) -> &[ObjectId] {
&self.traversed_objects
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum MetricRouteOutcome {
Reachable(MetricRouteEvidence),
Blocked(BlockedMetricRouteEvidence),
}
pub trait MetricRoutingService: Send + Sync + 'static {
fn route(&self, request: &MetricRouteRequest)
-> Result<MetricRouteOutcome, MetricRoutingError>;
}
#[derive(Clone)]
pub struct MetricRoutingServiceHandle(Arc<dyn MetricRoutingService>);
impl MetricRoutingServiceHandle {
pub fn new(service: Arc<dyn MetricRoutingService>) -> Self {
Self(service)
}
pub fn route(
&self,
request: &MetricRouteRequest,
) -> Result<MetricRouteOutcome, MetricRoutingError> {
let outcome = self.0.route(request)?;
if let MetricRouteOutcome::Reachable(route) = &outcome {
let (Some(first), Some(last)) = (route.waypoints.first(), route.waypoints.last())
else {
return Err(MetricRoutingError::EmptyRouteEvidence);
};
if first != request.origin() || last != request.destination() {
return Err(MetricRoutingError::ResponseEndpointMismatch);
}
} else if let MetricRouteOutcome::Blocked(blocked) = &outcome
&& blocked.request() != request
{
return Err(MetricRoutingError::ResponseEndpointMismatch);
}
Ok(outcome)
}
}
fn valid_non_negative(value: f64) -> bool {
value.is_finite() && value >= 0.0
}