use thiserror::Error;
use crate::{BackendId, CapabilityId, Operation};
pub const INTEGRATION_API_VERSION: ApiVersion = ApiVersion::new(0, 4, 0);
pub const MINIMUM_RUST_VERSION: &str = "1.88";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ApiVersion {
pub major: u16,
pub minor: u16,
pub patch: u16,
}
impl ApiVersion {
pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
Self {
major,
minor,
patch,
}
}
pub const fn supports(self, requested: Self) -> bool {
self.major == requested.major
&& (self.minor > requested.minor
|| (self.minor == requested.minor && self.patch >= requested.patch))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IntegrationProfile {
RustLeaf,
RustFacade,
NativeC,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Representation {
Scalar,
Linear,
Profile2d,
AnalyticCurve,
AnalyticSurface,
Topology,
ExactBrep,
TriangleMesh,
MeshHealth,
Measurements,
RayHit,
ModelGraph,
SampledField,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Exactness {
Exact,
ToleranceBounded,
}
impl Exactness {
const fn satisfies(self, required: Self) -> bool {
matches!(
(self, required),
(Self::Exact, _) | (Self::ToleranceBounded, Self::ToleranceBounded)
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Ownership {
RustValues,
OpaqueOwnedHandles,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ThreadSafety {
SendSyncValues,
ContextSendNotSync,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BoundaryContract {
pub right_handed_cartesian_f64: bool,
pub caller_defined_consistent_units: bool,
pub explicit_tolerance: bool,
pub ownership: Ownership,
pub thread_safety: ThreadSafety,
}
impl BoundaryContract {
pub const fn rust() -> Self {
Self {
right_handed_cartesian_f64: true,
caller_defined_consistent_units: true,
explicit_tolerance: true,
ownership: Ownership::RustValues,
thread_safety: ThreadSafety::SendSyncValues,
}
}
pub const fn native() -> Self {
Self {
ownership: Ownership::OpaqueOwnedHandles,
thread_safety: ThreadSafety::ContextSendNotSync,
..Self::rust()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CapabilityDescriptor {
pub id: CapabilityId,
pub operation: Operation,
pub provider: BackendId,
pub required_feature: &'static str,
pub inputs: &'static [Representation],
pub output: Representation,
pub exactness: Exactness,
pub deterministic: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CapabilityRequirement {
pub id: CapabilityId,
pub output: Representation,
pub exactness: Exactness,
pub deterministic: bool,
}
#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
pub enum RequirementRefusal {
#[error("integration API {requested:?} is incompatible with advertised {advertised:?}")]
ApiVersionUnavailable {
requested: ApiVersion,
advertised: ApiVersion,
},
#[error("capability `{capability}` is not available")]
CapabilityUnavailable { capability: CapabilityId },
#[error("capability `{capability}` returns {advertised:?}, not required {required:?}")]
RepresentationUnavailable {
capability: CapabilityId,
required: Representation,
advertised: Representation,
},
#[error("capability `{capability}` advertises {advertised:?}, not required {required:?}")]
ExactnessUnavailable {
capability: CapabilityId,
required: Exactness,
advertised: Exactness,
},
#[error("capability `{capability}` is not deterministic in this build")]
DeterminismUnavailable { capability: CapabilityId },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IntegrationDescriptor {
pub api_version: ApiVersion,
pub abi_version: Option<ApiVersion>,
pub profile: IntegrationProfile,
pub minimum_rust_version: Option<&'static str>,
pub enabled_features: Vec<&'static str>,
pub representations: Vec<Representation>,
pub capabilities: Vec<CapabilityDescriptor>,
pub boundary: BoundaryContract,
}
impl IntegrationDescriptor {
pub fn empty(profile: IntegrationProfile) -> Self {
let native = matches!(profile, IntegrationProfile::NativeC);
Self {
api_version: INTEGRATION_API_VERSION,
abi_version: native.then_some(INTEGRATION_API_VERSION),
profile,
minimum_rust_version: (!native).then_some(MINIMUM_RUST_VERSION),
enabled_features: Vec::new(),
representations: Vec::new(),
capabilities: Vec::new(),
boundary: if native {
BoundaryContract::native()
} else {
BoundaryContract::rust()
},
}
}
pub fn supports_api(&self, requested: ApiVersion) -> bool {
self.api_version.supports(requested)
}
pub fn require_api(&self, requested: ApiVersion) -> Result<(), RequirementRefusal> {
if self.supports_api(requested) {
Ok(())
} else {
Err(RequirementRefusal::ApiVersionUnavailable {
requested,
advertised: self.api_version,
})
}
}
pub fn require(
&self,
requirement: CapabilityRequirement,
) -> Result<&CapabilityDescriptor, RequirementRefusal> {
let Some(capability) = self
.capabilities
.iter()
.find(|candidate| candidate.id == requirement.id)
else {
return Err(RequirementRefusal::CapabilityUnavailable {
capability: requirement.id,
});
};
if capability.output != requirement.output {
return Err(RequirementRefusal::RepresentationUnavailable {
capability: requirement.id,
required: requirement.output,
advertised: capability.output,
});
}
if !capability.exactness.satisfies(requirement.exactness) {
return Err(RequirementRefusal::ExactnessUnavailable {
capability: requirement.id,
required: requirement.exactness,
advertised: capability.exactness,
});
}
if requirement.deterministic && !capability.deterministic {
return Err(RequirementRefusal::DeterminismUnavailable {
capability: requirement.id,
});
}
Ok(capability)
}
}