1use thiserror::Error;
9
10use crate::{BackendId, CapabilityId, Operation};
11
12pub const INTEGRATION_API_VERSION: ApiVersion = ApiVersion::new(0, 4, 0);
14pub const MINIMUM_RUST_VERSION: &str = "1.88";
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct ApiVersion {
20 pub major: u16,
21 pub minor: u16,
22 pub patch: u16,
23}
24
25impl ApiVersion {
26 pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
27 Self {
28 major,
29 minor,
30 patch,
31 }
32 }
33
34 pub const fn supports(self, requested: Self) -> bool {
36 self.major == requested.major
37 && (self.minor > requested.minor
38 || (self.minor == requested.minor && self.patch >= requested.patch))
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub enum IntegrationProfile {
45 RustLeaf,
47 RustFacade,
49 NativeC,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
55pub enum Representation {
56 Scalar,
57 Linear,
58 Profile2d,
59 AnalyticCurve,
60 AnalyticSurface,
61 Topology,
62 ExactBrep,
63 TriangleMesh,
64 MeshHealth,
65 Measurements,
66 RayHit,
67 ModelGraph,
68 SampledField,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub enum Exactness {
74 Exact,
76 ToleranceBounded,
78}
79
80impl Exactness {
81 const fn satisfies(self, required: Self) -> bool {
82 matches!(
83 (self, required),
84 (Self::Exact, _) | (Self::ToleranceBounded, Self::ToleranceBounded)
85 )
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
91pub enum Ownership {
92 RustValues,
94 OpaqueOwnedHandles,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub enum ThreadSafety {
101 SendSyncValues,
103 ContextSendNotSync,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub struct BoundaryContract {
110 pub right_handed_cartesian_f64: bool,
112 pub caller_defined_consistent_units: bool,
114 pub explicit_tolerance: bool,
116 pub ownership: Ownership,
117 pub thread_safety: ThreadSafety,
118}
119
120impl BoundaryContract {
121 pub const fn rust() -> Self {
122 Self {
123 right_handed_cartesian_f64: true,
124 caller_defined_consistent_units: true,
125 explicit_tolerance: true,
126 ownership: Ownership::RustValues,
127 thread_safety: ThreadSafety::SendSyncValues,
128 }
129 }
130
131 pub const fn native() -> Self {
132 Self {
133 ownership: Ownership::OpaqueOwnedHandles,
134 thread_safety: ThreadSafety::ContextSendNotSync,
135 ..Self::rust()
136 }
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
142pub struct CapabilityDescriptor {
143 pub id: CapabilityId,
144 pub operation: Operation,
145 pub provider: BackendId,
146 pub required_feature: &'static str,
148 pub inputs: &'static [Representation],
149 pub output: Representation,
150 pub exactness: Exactness,
151 pub deterministic: bool,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
156pub struct CapabilityRequirement {
157 pub id: CapabilityId,
158 pub output: Representation,
159 pub exactness: Exactness,
160 pub deterministic: bool,
161}
162
163#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
165pub enum RequirementRefusal {
166 #[error("integration API {requested:?} is incompatible with advertised {advertised:?}")]
167 ApiVersionUnavailable {
168 requested: ApiVersion,
169 advertised: ApiVersion,
170 },
171 #[error("capability `{capability}` is not available")]
172 CapabilityUnavailable { capability: CapabilityId },
173 #[error("capability `{capability}` returns {advertised:?}, not required {required:?}")]
174 RepresentationUnavailable {
175 capability: CapabilityId,
176 required: Representation,
177 advertised: Representation,
178 },
179 #[error("capability `{capability}` advertises {advertised:?}, not required {required:?}")]
180 ExactnessUnavailable {
181 capability: CapabilityId,
182 required: Exactness,
183 advertised: Exactness,
184 },
185 #[error("capability `{capability}` is not deterministic in this build")]
186 DeterminismUnavailable { capability: CapabilityId },
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct IntegrationDescriptor {
192 pub api_version: ApiVersion,
193 pub abi_version: Option<ApiVersion>,
194 pub profile: IntegrationProfile,
195 pub minimum_rust_version: Option<&'static str>,
196 pub enabled_features: Vec<&'static str>,
197 pub representations: Vec<Representation>,
198 pub capabilities: Vec<CapabilityDescriptor>,
199 pub boundary: BoundaryContract,
200}
201
202impl IntegrationDescriptor {
203 pub fn empty(profile: IntegrationProfile) -> Self {
205 let native = matches!(profile, IntegrationProfile::NativeC);
206 Self {
207 api_version: INTEGRATION_API_VERSION,
208 abi_version: native.then_some(INTEGRATION_API_VERSION),
209 profile,
210 minimum_rust_version: (!native).then_some(MINIMUM_RUST_VERSION),
211 enabled_features: Vec::new(),
212 representations: Vec::new(),
213 capabilities: Vec::new(),
214 boundary: if native {
215 BoundaryContract::native()
216 } else {
217 BoundaryContract::rust()
218 },
219 }
220 }
221
222 pub fn supports_api(&self, requested: ApiVersion) -> bool {
223 self.api_version.supports(requested)
224 }
225
226 pub fn require_api(&self, requested: ApiVersion) -> Result<(), RequirementRefusal> {
227 if self.supports_api(requested) {
228 Ok(())
229 } else {
230 Err(RequirementRefusal::ApiVersionUnavailable {
231 requested,
232 advertised: self.api_version,
233 })
234 }
235 }
236
237 pub fn require(
239 &self,
240 requirement: CapabilityRequirement,
241 ) -> Result<&CapabilityDescriptor, RequirementRefusal> {
242 let Some(capability) = self
243 .capabilities
244 .iter()
245 .find(|candidate| candidate.id == requirement.id)
246 else {
247 return Err(RequirementRefusal::CapabilityUnavailable {
248 capability: requirement.id,
249 });
250 };
251
252 if capability.output != requirement.output {
253 return Err(RequirementRefusal::RepresentationUnavailable {
254 capability: requirement.id,
255 required: requirement.output,
256 advertised: capability.output,
257 });
258 }
259 if !capability.exactness.satisfies(requirement.exactness) {
260 return Err(RequirementRefusal::ExactnessUnavailable {
261 capability: requirement.id,
262 required: requirement.exactness,
263 advertised: capability.exactness,
264 });
265 }
266 if requirement.deterministic && !capability.deterministic {
267 return Err(RequirementRefusal::DeterminismUnavailable {
268 capability: requirement.id,
269 });
270 }
271 Ok(capability)
272 }
273}