1#![allow(clippy::doc_markdown)]
2
3use std::collections::BTreeMap;
10
11use thiserror::Error;
12
13#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct SourceIdentity(String);
16
17impl SourceIdentity {
18 #[must_use]
20 pub fn new(value: impl Into<String>) -> Self {
21 Self(value.into())
22 }
23
24 #[must_use]
26 pub fn as_str(&self) -> &str {
27 &self.0
28 }
29}
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub enum Exactness {
34 Exact,
36 Approximate,
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct GeometryEvidence {
43 subject_id: String,
44 exactness: Exactness,
45 provenance: String,
46}
47
48impl GeometryEvidence {
49 #[must_use]
51 pub fn new(
52 subject_id: impl Into<String>,
53 exactness: Exactness,
54 provenance: impl Into<String>,
55 ) -> Self {
56 Self {
57 subject_id: subject_id.into(),
58 exactness,
59 provenance: provenance.into(),
60 }
61 }
62
63 #[must_use]
65 pub fn subject_id(&self) -> &str {
66 &self.subject_id
67 }
68
69 #[must_use]
71 pub fn exactness(&self) -> Exactness {
72 self.exactness
73 }
74
75 #[must_use]
77 pub fn provenance(&self) -> &str {
78 &self.provenance
79 }
80}
81
82pub trait GeometrySource {
84 fn source_identity(&self) -> &SourceIdentity;
86 fn geometry_for(&self, local_id: &str) -> Option<&GeometryEvidence>;
88}
89
90#[derive(Clone, Debug)]
92pub struct InMemoryGeometrySource {
93 identity: SourceIdentity,
94 evidence: BTreeMap<String, GeometryEvidence>,
95}
96
97impl InMemoryGeometrySource {
98 #[must_use]
100 pub fn new(identity: SourceIdentity) -> Self {
101 Self {
102 identity,
103 evidence: BTreeMap::new(),
104 }
105 }
106
107 pub fn insert(&mut self, mut evidence: GeometryEvidence) -> Result<(), AxiolidError> {
114 let prefix = format!("{}:", self.identity.as_str());
115 if evidence.subject_id.contains(':') && !evidence.subject_id.starts_with(&prefix) {
116 return Err(AxiolidError::ForeignSubject {
117 subject_id: evidence.subject_id,
118 });
119 }
120 let local_id = evidence
121 .subject_id
122 .strip_prefix(&prefix)
123 .unwrap_or(&evidence.subject_id)
124 .to_owned();
125 if local_id.is_empty() {
126 return Err(AxiolidError::EmptySubjectId);
127 }
128 evidence.subject_id = format!("{prefix}{local_id}");
129 self.evidence.insert(local_id, evidence);
130 Ok(())
131 }
132}
133
134impl GeometrySource for InMemoryGeometrySource {
135 fn source_identity(&self) -> &SourceIdentity {
136 &self.identity
137 }
138 fn geometry_for(&self, local_id: &str) -> Option<&GeometryEvidence> {
139 self.evidence.get(local_id)
140 }
141}
142
143pub trait GeometryBackend {
145 fn resolve(&self, subject_id: &str) -> Result<GeometryEvidence, AxiolidError>;
151}
152
153#[derive(Clone, Debug, Default)]
155pub struct UnavailableGeometryBackend;
156
157impl GeometryBackend for UnavailableGeometryBackend {
158 fn resolve(&self, _subject_id: &str) -> Result<GeometryEvidence, AxiolidError> {
159 Err(AxiolidError::IntegrationUnavailable {
160 integration: "Axiolid geometry kernel",
161 })
162 }
163}
164
165#[derive(Debug, Error, Eq, PartialEq)]
167pub enum AxiolidError {
168 #[error("geometry subject ID must not be empty")]
170 EmptySubjectId,
171 #[error("geometry evidence belongs to a foreign subject: {subject_id}")]
173 ForeignSubject {
174 subject_id: String,
176 },
177 #[error("external integration unavailable: {integration}")]
179 IntegrationUnavailable {
180 integration: &'static str,
182 },
183}