Skip to main content

axiolid_contracts/
capability_id.rs

1//! Stable, transport-independent capability identifiers.
2
3use core::fmt;
4
5/// Versioned semantic operation identity, independent of providers and wire encoding.
6#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct CapabilityId(&'static str);
8
9impl CapabilityId {
10    pub const fn from_static(value: &'static str) -> Self {
11        Self(value)
12    }
13    pub const fn as_str(self) -> &'static str {
14        self.0
15    }
16}
17
18impl fmt::Debug for CapabilityId {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        f.debug_tuple("CapabilityId").field(&self.0).finish()
21    }
22}
23impl fmt::Display for CapabilityId {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        f.write_str(self.0)
26    }
27}
28
29pub mod capability_ids {
30    use super::CapabilityId;
31    pub const TESSELLATE: CapabilityId =
32        CapabilityId::from_static("org.axiolid.geometry.tessellate.v1");
33    pub const MESH_BOOLEAN: CapabilityId =
34        CapabilityId::from_static("org.axiolid.geometry.mesh-boolean.v1");
35    pub const MESH_SECTION: CapabilityId =
36        CapabilityId::from_static("org.axiolid.geometry.mesh-section.v1");
37    /// Orthographic hidden-line drawing of triangle meshes.
38    ///
39    /// Visibility is decided exactly in the view's own frame; the
40    /// operation draws edges, it never rasterises (ADR 0090).
41    pub const MESH_HIDDEN_LINES: CapabilityId =
42        CapabilityId::from_static("org.axiolid.geometry.mesh-hidden-lines.v1");
43    /// Visible face regions of triangle meshes in an orthographic view.
44    ///
45    /// Distinct from [`MESH_HIDDEN_LINES`]: a provider that draws edges does
46    /// not thereby claim filled regions. Both decide visibility exactly in
47    /// the view's own frame (ADR 0094).
48    pub const MESH_VISIBLE_FACES: CapabilityId =
49        CapabilityId::from_static("org.axiolid.geometry.mesh-visible-faces.v1");
50    /// Point-sampled geometry reconstructed into a discrete surface.
51    ///
52    /// Distinct from [`MESH_BOOLEAN`] and friends because the input is not a
53    /// mesh at all: a provider advertising this claims it can turn an
54    /// unstructured point set into a surface, which is a different
55    /// obligation from operating on one that already exists.
56    pub const POINTCLOUD_RECONSTRUCTION: CapabilityId =
57        CapabilityId::from_static("org.axiolid.geometry.pointcloud-reconstruction.v1");
58    pub const MESH_VALIDATE: CapabilityId =
59        CapabilityId::from_static("org.axiolid.geometry.mesh-validate.v1");
60    pub const MESH_MEASURE: CapabilityId =
61        CapabilityId::from_static("org.axiolid.geometry.mesh-measure.v1");
62    pub const RAY_MESH: CapabilityId =
63        CapabilityId::from_static("org.axiolid.geometry.ray-mesh.v1");
64    pub const EXACT_EXTRUDE: CapabilityId =
65        CapabilityId::from_static("org.axiolid.geometry.exact-extrude.v1");
66    pub const GRAPH_TO_MESH: CapabilityId =
67        CapabilityId::from_static("org.axiolid.geometry.graph-to-mesh.v1");
68    /// Graph lowered to an exact B-rep, or refused.
69    ///
70    /// Deliberately distinct from [`GRAPH_TO_MESH`]: a backend advertising this
71    /// promises analytic supports and trims survive, never triangles.
72    pub const GRAPH_TO_EXACT_BREP: CapabilityId =
73        CapabilityId::from_static("org.axiolid.geometry.graph-to-exact-brep.v1");
74    /// Point, tangent and oriented frame at a distance along a curve.
75    ///
76    /// Distinct from tessellation: this is exact evaluation of an
77    /// analytic curve, not a sampled approximation of it.
78    pub const CURVE_EVALUATE: CapabilityId =
79        CapabilityId::from_static("org.axiolid.geometry.curve-evaluate.v1");
80    /// Every identifier this vocabulary defines.
81    ///
82    /// A SLICE rather than a fixed-size array on purpose: with
83    /// `[CapabilityId; N]` the length is part of the public type, so
84    /// registering a capability changed `N` and was technically a breaking
85    /// change for every consumer. Capabilities are added as the kernel
86    /// grows, and taxing each addition with a major bump would either
87    /// throttle them or push the break through silently. A slice makes
88    /// every future addition additive.
89    pub const ALL: &[CapabilityId] = &[
90        TESSELLATE,
91        MESH_BOOLEAN,
92        MESH_SECTION,
93        MESH_HIDDEN_LINES,
94        MESH_VISIBLE_FACES,
95        MESH_VALIDATE,
96        MESH_MEASURE,
97        RAY_MESH,
98        EXACT_EXTRUDE,
99        GRAPH_TO_MESH,
100        GRAPH_TO_EXACT_BREP,
101        CURVE_EVALUATE,
102    ];
103}
104
105#[cfg(test)]
106mod tests {
107    use super::capability_ids::ALL;
108    use std::collections::BTreeSet;
109
110    /// Registering a capability must stay an ADDITIVE change.
111    ///
112    /// `ALL` was `[CapabilityId; N]`, where the count sat in the public
113    /// type: adding an identifier changed the type and broke every
114    /// consumer, so a routine addition demanded a major bump. Binding it
115    /// as a slice here fails to compile if it is ever narrowed back to a
116    /// fixed-size array.
117    #[test]
118    fn the_vocabulary_can_grow_without_a_breaking_change() {
119        let ids: &[super::CapabilityId] = super::capability_ids::ALL;
120        assert!(ids.len() >= 10, "vocabulary should not shrink");
121    }
122    #[test]
123    fn ids_are_unique_versioned_ascii_tokens() {
124        let unique: BTreeSet<_> = ALL.iter().map(|id| id.as_str()).collect();
125        assert_eq!(unique.len(), ALL.len());
126        for id in ALL {
127            let text = id.as_str();
128            assert!(text.ends_with(".v1"));
129            assert!(text
130                .bytes()
131                .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'.' | b'-')));
132        }
133    }
134}