1use std::error::Error;
4use std::fmt;
5
6use crate::{BoxId, CapabilityId, ContractDescriptor, ContractRevision};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct ImportDescriptor {
11 slot_id: BoxId,
12 expected_revision: ContractRevision,
13 capabilities: Vec<CapabilityId>,
14}
15
16impl ImportDescriptor {
17 pub fn new(
19 slot_id: BoxId,
20 expected_revision: ContractRevision,
21 capabilities: impl IntoIterator<Item = CapabilityId>,
22 ) -> Result<Self, ImportDescriptorError> {
23 let mut accepted: Vec<CapabilityId> = Vec::new();
24 for capability in capabilities {
25 if capability.box_id() != &slot_id {
26 return Err(ImportDescriptorError::CapabilityPackageMismatch {
27 slot_id,
28 capability,
29 });
30 }
31 if accepted.iter().any(|known| known == &capability) {
32 return Err(ImportDescriptorError::DuplicateCapability { capability });
33 }
34 accepted.push(capability);
35 }
36 Ok(Self {
37 slot_id,
38 expected_revision,
39 capabilities: accepted,
40 })
41 }
42
43 pub fn slot_id(&self) -> &BoxId {
45 &self.slot_id
46 }
47
48 pub fn expected_revision(&self) -> &ContractRevision {
50 &self.expected_revision
51 }
52
53 pub fn capabilities(&self) -> &[CapabilityId] {
55 &self.capabilities
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61#[non_exhaustive]
62pub enum ImportDescriptorError {
63 CapabilityPackageMismatch {
65 slot_id: BoxId,
67 capability: CapabilityId,
69 },
70 DuplicateCapability {
72 capability: CapabilityId,
74 },
75}
76
77impl fmt::Display for ImportDescriptorError {
78 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 Self::CapabilityPackageMismatch {
81 slot_id,
82 capability,
83 } => write!(
84 formatter,
85 "capability {capability} does not belong to import slot {slot_id}"
86 ),
87 Self::DuplicateCapability { capability } => {
88 write!(formatter, "duplicate imported capability: {capability}")
89 }
90 }
91 }
92}
93
94impl Error for ImportDescriptorError {}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct ImplementationDescriptor {
102 contract: &'static ContractDescriptor,
103 imports: Vec<ImportDescriptor>,
104}
105
106impl ImplementationDescriptor {
107 pub fn new(
109 contract: &'static ContractDescriptor,
110 imports: impl IntoIterator<Item = ImportDescriptor>,
111 ) -> Result<Self, ImplementationDescriptorError> {
112 let mut accepted: Vec<ImportDescriptor> = Vec::new();
113 for import in imports {
114 if accepted
115 .iter()
116 .any(|known| known.slot_id() == import.slot_id())
117 {
118 return Err(ImplementationDescriptorError::DuplicateImportSlot {
119 slot_id: import.slot_id,
120 });
121 }
122 accepted.push(import);
123 }
124 Ok(Self {
125 contract,
126 imports: accepted,
127 })
128 }
129
130 pub fn contract(&self) -> &'static ContractDescriptor {
132 self.contract
133 }
134
135 pub fn imports(&self) -> &[ImportDescriptor] {
137 &self.imports
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
143#[non_exhaustive]
144pub enum ImplementationDescriptorError {
145 DuplicateImportSlot {
147 slot_id: BoxId,
149 },
150}
151
152impl fmt::Display for ImplementationDescriptorError {
153 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 Self::DuplicateImportSlot { slot_id } => {
156 write!(formatter, "duplicate import slot: {slot_id}")
157 }
158 }
159 }
160}
161
162impl Error for ImplementationDescriptorError {}
163
164#[cfg(test)]
165mod tests {
166 use std::sync::LazyLock;
167
168 use super::*;
169 use crate::{
170 CapabilityDescriptor, CapabilityName, CapabilityShape, ExposureLevel, Idempotency,
171 TypeDescriptor,
172 };
173
174 fn box_id(value: &str) -> BoxId {
175 BoxId::new(value).unwrap()
176 }
177
178 fn capability(package: &str, name: &str) -> CapabilityId {
179 CapabilityId::new(box_id(package), CapabilityName::new(name).unwrap())
180 }
181
182 fn import(package: &str, revision: &str, names: &[&str]) -> ImportDescriptor {
183 ImportDescriptor::new(
184 box_id(package),
185 ContractRevision::new(revision).unwrap(),
186 names.iter().map(|name| capability(package, name)),
187 )
188 .unwrap()
189 }
190
191 static CONTRACT: LazyLock<ContractDescriptor> = LazyLock::new(|| {
192 let id = capability("greeter", "greet");
193 let descriptor = CapabilityDescriptor::new(
194 id,
195 TypeDescriptor::string(),
196 TypeDescriptor::string(),
197 TypeDescriptor::string(),
198 CapabilityShape::Unary,
199 ExposureLevel::External,
200 Idempotency::None,
201 None,
202 );
203 ContractDescriptor::new(
204 box_id("greeter"),
205 [descriptor],
206 ContractRevision::new("greeter-r1").unwrap(),
207 )
208 .unwrap()
209 });
210
211 #[test]
212 fn import_view_preserves_revision_and_capability_order() {
213 let descriptor = import("hello", "hello-r7", &["health", "greet"]);
214
215 assert_eq!(descriptor.slot_id(), &box_id("hello"));
216 assert_eq!(descriptor.expected_revision().as_str(), "hello-r7");
217 assert_eq!(
218 descriptor.capabilities(),
219 &[capability("hello", "health"), capability("hello", "greet")]
220 );
221 }
222
223 #[test]
224 fn import_rejects_cross_package_capability_exactly() {
225 let error = ImportDescriptor::new(
226 box_id("hello"),
227 ContractRevision::new("r1").unwrap(),
228 [capability("other", "greet")],
229 )
230 .unwrap_err();
231 assert_eq!(
232 error,
233 ImportDescriptorError::CapabilityPackageMismatch {
234 slot_id: box_id("hello"),
235 capability: capability("other", "greet"),
236 }
237 );
238 assert_eq!(
239 error.to_string(),
240 "capability other.greet does not belong to import slot hello"
241 );
242 }
243
244 #[test]
245 fn import_rejects_duplicate_capability_exactly() {
246 let repeated = capability("hello", "greet");
247 let error = ImportDescriptor::new(
248 box_id("hello"),
249 ContractRevision::new("r1").unwrap(),
250 [repeated.clone(), repeated],
251 )
252 .unwrap_err();
253 assert_eq!(
254 error,
255 ImportDescriptorError::DuplicateCapability {
256 capability: capability("hello", "greet"),
257 }
258 );
259 assert_eq!(
260 error.to_string(),
261 "duplicate imported capability: hello.greet"
262 );
263 }
264
265 #[test]
266 fn implementation_shares_contract_and_preserves_import_order() {
267 let hello = import("hello", "r1", &["greet"]);
268 let audit = import("audit", "r2", &["record"]);
269 let descriptor =
270 ImplementationDescriptor::new(&CONTRACT, [hello.clone(), audit.clone()]).unwrap();
271
272 assert!(std::ptr::eq(descriptor.contract(), &*CONTRACT));
273 assert_eq!(descriptor.imports(), &[hello, audit]);
274 }
275
276 #[test]
277 fn implementation_rejects_duplicate_slots_exactly() {
278 let first = import("hello", "r1", &["greet"]);
279 let second = import("hello", "r2", &["health"]);
280 let error = ImplementationDescriptor::new(&CONTRACT, [first, second]).unwrap_err();
281
282 assert_eq!(
283 error,
284 ImplementationDescriptorError::DuplicateImportSlot {
285 slot_id: box_id("hello"),
286 }
287 );
288 assert_eq!(error.to_string(), "duplicate import slot: hello");
289 }
290
291 #[test]
292 fn private_import_changes_leave_outward_contract_unchanged() {
293 let without_imports = ImplementationDescriptor::new(&CONTRACT, []).unwrap();
294 let with_imports =
295 ImplementationDescriptor::new(&CONTRACT, [import("hello", "r1", &["greet"])]).unwrap();
296
297 assert!(std::ptr::eq(
298 without_imports.contract(),
299 with_imports.contract()
300 ));
301 assert_eq!(
302 without_imports.contract().revision(),
303 with_imports.contract().revision()
304 );
305 assert_ne!(without_imports.imports(), with_imports.imports());
306 }
307
308 #[test]
309 fn implementation_descriptors_are_structural_static_plain_data() {
310 fn assert_bounds<T: Send + Sync + 'static>() {}
311
312 let build = || {
313 ImplementationDescriptor::new(&CONTRACT, [import("hello", "r1", &["greet"])]).unwrap()
314 };
315 assert_eq!(build(), build());
316 assert_eq!(build(), build().clone());
317 assert_bounds::<ImportDescriptor>();
318 assert_bounds::<ImportDescriptorError>();
319 assert_bounds::<ImplementationDescriptor>();
320 assert_bounds::<ImplementationDescriptorError>();
321 }
322}