Skip to main content

wash_runtime/
wit.rs

1//! WebAssembly Interface Types (WIT) support for wasmcloud.
2//!
3//! This module provides types and utilities for working with WIT interface
4//! specifications. WIT is used to describe the capabilities that components
5//! require and that plugins provide.
6//!
7//! # Key Types
8//!
9//! - [`WitWorld`] - A collection of imports and exports representing a WIT world
10//! - [`WitInterface`] - A specific interface specification with namespace, package, and version
11//!
12//! # Interface Matching
13//!
14//! The [`WitInterface::contains`] method is used to determine if one interface
15//! specification can satisfy another. This is crucial for matching component
16//! requirements with plugin capabilities.
17
18use std::{
19    collections::{HashMap, HashSet},
20    fmt::Display,
21};
22
23/// A collection of WIT interfaces representing a world definition.
24///
25/// A WIT world describes the imports and exports that a component or
26/// plugin provides. This is used for capability matching between
27/// workloads and plugins.
28#[derive(Clone, Debug, Default, PartialEq, Eq)]
29pub struct WitWorld {
30    /// The interfaces that this world imports (requires from the host)
31    pub imports: HashSet<WitInterface>,
32    /// The interfaces that this world exports (provides to the host)
33    pub exports: HashSet<WitInterface>,
34}
35
36impl WitWorld {
37    /// This function checks if the world includes a specific interface. This is
38    /// slightly different than checking directly against the imports or exports
39    /// because it takes into account the possibility of interface nesting and
40    /// versioning. See [`WitInterface::contains`] for more details.
41    pub fn includes(&self, interface: &WitInterface) -> bool {
42        self.imports.iter().any(|i| i.contains(interface))
43            || self.exports.iter().any(|e| e.contains(interface))
44    }
45
46    /// This function checks if the world includes a specific interface. This is
47    /// different than [`WitWorld::includes`] because it considers that in one
48    /// [`WitInterface`] there may be both imports and exports.
49    pub fn includes_bidirectional(&self, interface: &WitInterface) -> bool {
50        let import_match = self.imports.iter().find(|i| {
51            if let Some(v) = &interface.version
52                && let Some(ov) = &i.version
53                && v != ov
54            {
55                return false;
56            }
57            i.namespace == interface.namespace && i.package == interface.package
58        });
59
60        let export_match = self.exports.iter().find(|e| {
61            // If both interfaces specify a version, they must match
62            if let Some(v) = &interface.version
63                && let Some(ov) = &e.version
64                && v != ov
65            {
66                return false;
67            }
68            e.namespace == interface.namespace && e.package == interface.package
69        });
70
71        // Ensure the interfaces are covered by either the import or export match
72        for i in &interface.interfaces {
73            // Interface satisfied by import
74            if let Some(im) = &import_match
75                && im.interfaces.contains(i)
76            {
77                continue;
78            }
79            // Interface satisfied by export
80            if let Some(em) = &export_match
81                && em.interfaces.contains(i)
82            {
83                continue;
84            }
85
86            return false;
87        }
88
89        true
90    }
91
92    /// Checks if a guest world (imports) can be satisfied by a host world (exports).
93    ///
94    /// A host world satisfies a guest world if all interfaces required by the guest
95    /// are provided by the host, considering interface containment (subset/superset)
96    /// and ignoring any additional interfaces or versions in the host.
97    ///
98    /// # Arguments
99    /// * `guest` - The guest world to check
100    ///
101    /// # Returns
102    /// `true` if the host world (self) provides all interfaces required by the guest world
103    /// through direct exports or through superset relationships.
104    pub fn satisfies(&self, guest: &WitWorld) -> bool {
105        // For each interface that the guest imports, find a matching export in the host
106        for required in &guest.imports {
107            // Check if there's a direct match or a superset match
108            let matched: Vec<_> = self
109                .exports
110                .iter()
111                .filter(|provided| provided.contains(required))
112                .collect();
113
114            // If no matches found, guest cannot be satisfied
115            if matched.is_empty() {
116                return false;
117            }
118
119            // If there's more than one match, we need to ensure it's not a version conflict
120            if matched.len() > 1 {
121                // All matched exports must have the same version (if versioned)
122                let versions: HashSet<_> =
123                    matched.iter().filter_map(|m| m.version.as_ref()).collect();
124                if versions.len() > 1 {
125                    return false; // Conflicting versions
126                }
127            }
128        }
129
130        true
131    }
132}
133
134/// Represents a WIT interface specification with namespace, package, and optional version.
135///
136/// A `WitInterface` identifies a specific set of interfaces from a WIT package.
137/// It follows the format: `namespace:package/interface1,interface2@version`
138/// where interfaces and version are optional.
139///
140/// # Examples
141/// - `wasi:http` - Just namespace and package
142/// - `wasi:http/incoming-handler` - With a single interface
143/// - `wasi:http/incoming-handler,outgoing-handler@0.2.0` - Multiple interfaces with version
144#[derive(Clone, Debug, PartialEq, Eq)]
145pub struct WitInterface {
146    /// The namespace of the interface (e.g., "wasi")
147    pub namespace: String,
148    /// The package name (e.g., "http", "blobstore")
149    pub package: String,
150    /// The specific interfaces within the package (e.g., "incoming-handler", "types")
151    pub interfaces: HashSet<String>,
152    // TODO: This is a nice way to represent a version, but it doesn't account for
153    // compatible versions. We should revisit this and implement https://docs.rs/semver/1.0.27/semver/struct.VersionReq.html
154    /// Optional semantic version for the interface
155    pub version: Option<semver::Version>,
156    /// Additional configuration parameters for this interface
157    pub config: HashMap<String, String>,
158}
159
160impl WitInterface {
161    /// Returns the instance name of this WitInterface, aka the namespace:package@version
162    /// identifier without the interfaces or config.
163    pub fn instance(&self) -> String {
164        if let Some(v) = &self.version {
165            format!("{}:{}@{v}", self.namespace, self.package)
166        } else {
167            format!("{}:{}", self.namespace, self.package)
168        }
169    }
170
171    /// Merges another WitInterface into this one, returning a boolean
172    /// indicating whether the merge was successful (aka if the [`WitInterface::instance`]s matched).
173    pub fn merge(&mut self, other: &WitInterface) -> bool {
174        if self.instance() != other.instance() {
175            return false;
176        }
177
178        self.interfaces.extend(other.interfaces.clone());
179        self.config.extend(other.config.clone());
180        true
181    }
182
183    /// Checks if this interface contains (is a superset of) another interface.
184    ///
185    /// This method is used to determine if a plugin or component that provides
186    /// this interface can satisfy a requirement for the other interface.
187    ///
188    /// # Arguments
189    /// * `other` - The interface to check against
190    ///
191    /// # Returns
192    /// `true` if:
193    /// - The namespace and package match exactly
194    /// - If this interface has a version, it must match the other's version
195    /// - The other's interfaces are a subset of this interface's interfaces
196    pub fn contains(&self, other: &WitInterface) -> bool {
197        // Namespace and package must match
198        if self.namespace != other.namespace || self.package != other.package {
199            return false;
200        }
201
202        // If both interfaces specify a version, they must match
203        if let Some(v) = &self.version
204            && let Some(ov) = &other.version
205            && v != ov
206        {
207            return false;
208        }
209
210        self.interfaces.is_superset(&other.interfaces)
211    }
212}
213
214impl Display for WitInterface {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        write!(f, "{}:{}", self.namespace, self.package)?;
217        if !self.interfaces.is_empty() && !self.interfaces.is_empty() {
218            write!(f, "/")?;
219            let interfaces: Vec<_> = self.interfaces.clone().into_iter().collect();
220            write!(f, "{}", interfaces.join(","))?;
221        }
222        if let Some(v) = &self.version {
223            write!(f, "@{}", v)?;
224        }
225        Ok(())
226    }
227}
228
229impl std::hash::Hash for WitInterface {
230    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
231        self.namespace.hash(state);
232        self.package.hash(state);
233        for iface in &self.interfaces {
234            iface.hash(state);
235        }
236        self.version.hash(state);
237        for (k, v) in &self.config {
238            k.hash(state);
239            v.hash(state);
240        }
241    }
242}
243
244impl From<&str> for WitInterface {
245    fn from(s: &str) -> Self {
246        // Expected format: namespace:package/interface@version
247        // Also supports for convenience: namespace:package/interface,interface2,interface3@version
248        // interface and version are optional
249
250        let (main, version) = match s.split_once('@') {
251            Some((m, v)) => (m, Some(v)),
252            None => (s, None),
253        };
254        let (namespace_package, interface) = match main.split_once('/') {
255            Some((np, iface)) => (np, Some(iface)),
256            None => (main, None),
257        };
258        let (namespace, package) = match namespace_package.split_once(':') {
259            Some((ns, pkg)) => (ns, pkg),
260            None => ("", namespace_package),
261        };
262        let interfaces = match interface {
263            Some(iface) => iface
264                .split(',')
265                .map(|s| s.trim().to_string())
266                .filter(|s| !s.is_empty())
267                .collect(),
268            None => HashSet::new(),
269        };
270        let version = version.and_then(|v| semver::Version::parse(v).ok());
271
272        WitInterface {
273            namespace: namespace.to_string(),
274            package: package.to_string(),
275            interfaces,
276            version,
277            config: HashMap::new(),
278        }
279    }
280}
281
282impl From<String> for WitInterface {
283    fn from(s: String) -> Self {
284        WitInterface::from(s.as_str())
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use std::collections::HashSet;
292
293    fn create_interface(namespace: &str, package: &str, interfaces: &[&str]) -> WitInterface {
294        WitInterface {
295            namespace: namespace.to_string(),
296            package: package.to_string(),
297            interfaces: interfaces.iter().map(|s| s.to_string()).collect(),
298            version: None,
299            config: HashMap::new(),
300        }
301    }
302
303    fn create_interface_with_version(
304        namespace: &str,
305        package: &str,
306        interfaces: &[&str],
307        version: &str,
308    ) -> WitInterface {
309        WitInterface {
310            namespace: namespace.to_string(),
311            package: package.to_string(),
312            interfaces: interfaces.iter().map(|s| s.to_string()).collect(),
313            version: Some(semver::Version::parse(version).unwrap()),
314            config: HashMap::new(),
315        }
316    }
317
318    #[test]
319    fn test_contains_basic() {
320        let interface_a = create_interface("wasi", "logging", &["log", "error", "debug"]);
321        let interface_b = create_interface("wasi", "logging", &["log", "error"]);
322        let interface_c = create_interface("wasi", "logging", &["log", "trace"]);
323
324        // interface_a contains interface_b (superset)
325        assert!(interface_a.contains(&interface_b));
326        // interface_b does not contain interface_a (not a superset)
327        assert!(!interface_b.contains(&interface_a));
328        // interface_a does not contain interface_c (not a superset - missing "trace")
329        assert!(!interface_a.contains(&interface_c));
330        // An interface contains itself
331        assert!(interface_a.contains(&interface_a));
332    }
333
334    #[test]
335    fn test_contains_namespace_and_package_matching() {
336        // Different namespaces should not match
337        let wit1 = WitInterface::from("wasi:blobstore");
338        let wit2 = WitInterface::from("custom:blobstore");
339        assert!(!wit1.contains(&wit2));
340
341        // Different packages should not match
342        let wit3 = WitInterface::from("wasi:blobstore");
343        let wit4 = WitInterface::from("wasi:keyvalue");
344        assert!(!wit3.contains(&wit4));
345
346        // Same namespace and package should match
347        let wit5 = WitInterface::from("wasi:blobstore");
348        let wit6 = WitInterface::from("wasi:blobstore");
349        assert!(wit5.contains(&wit6));
350
351        // Empty namespace cases
352        let wit7 = WitInterface::from("blobstore/types");
353        let wit8 = WitInterface::from("blobstore/types");
354        assert!(wit7.contains(&wit8));
355
356        // Mixed empty namespace should not match
357        let wit9 = WitInterface::from("wasi:blobstore/types");
358        let wit10 = WitInterface::from("blobstore/types");
359        assert!(!wit9.contains(&wit10));
360    }
361
362    #[test]
363    fn test_contains_interface_subsets() {
364        // Subset relationship
365        let wit1 = WitInterface::from("wasi:blobstore/types,container,blobstore");
366        let wit2 = WitInterface::from("wasi:blobstore/types,container");
367        assert!(wit1.contains(&wit2));
368
369        // Not a subset - wit2 has interfaces wit1 doesn't have
370        let wit3 = WitInterface::from("wasi:blobstore/types");
371        let wit4 = WitInterface::from("wasi:blobstore/types,container");
372        assert!(!wit3.contains(&wit4));
373
374        // Empty set is a subset of any set
375        let wit5 = WitInterface::from("wasi:blobstore/types,container");
376        let wit6 = WitInterface::from("wasi:blobstore");
377        assert!(wit5.contains(&wit6));
378
379        // Overlapping but not subset
380        let wit7 = WitInterface::from("wasi:cli/stdin,stdout");
381        let wit8 = WitInterface::from("wasi:cli/stdout,stderr");
382        assert!(!wit7.contains(&wit8));
383
384        // Single interface vs multiple
385        let wit9 = WitInterface::from("wasi:cli/environment,exit,stdin,stdout");
386        let wit10 = WitInterface::from("wasi:cli/environment");
387        assert!(wit9.contains(&wit10));
388    }
389
390    #[test]
391    fn test_contains_version_handling() {
392        // Same versions should match
393        let wit1 = WitInterface::from("wasi:blobstore/types@0.2.0");
394        let wit2 = WitInterface::from("wasi:blobstore/types@0.2.0");
395        assert!(wit1.contains(&wit2));
396
397        // Different versions should not match
398        let wit3 = WitInterface::from("wasi:blobstore/types@0.2.0");
399        let wit4 = WitInterface::from("wasi:blobstore/types@0.3.0");
400        assert!(!wit3.contains(&wit4));
401
402        // wit1 has version, wit2 doesn't - still matches (wit2 is less restrictive)
403        let wit5 = WitInterface::from("wasi:blobstore/types@0.2.0");
404        let wit6 = WitInterface::from("wasi:blobstore/types");
405        assert!(wit5.contains(&wit6));
406
407        // wit1 has no version requirement, wit2 can have any version
408        let wit7 = WitInterface::from("wasi:blobstore/types");
409        let wit8 = WitInterface::from("wasi:blobstore/types@0.2.0");
410        assert!(wit7.contains(&wit8));
411
412        // Complex scenario with version
413        let wit9 = WitInterface::from("wasi:http/types,incoming-handler,outgoing-handler@0.2.0");
414        let wit10 = WitInterface::from("wasi:http/types,incoming-handler@0.2.0");
415        assert!(wit9.contains(&wit10));
416    }
417
418    #[test]
419    fn test_contains_with_version() {
420        let interface_a = create_interface_with_version("wasi", "http", &["handler"], "0.2.0");
421        let interface_b = create_interface_with_version("wasi", "http", &["handler"], "0.2.0");
422        let interface_c = create_interface_with_version("wasi", "http", &["handler"], "0.3.0");
423
424        // Same versions should match
425        assert!(interface_a.contains(&interface_b));
426        // Different versions should not match
427        assert!(!interface_a.contains(&interface_c));
428    }
429
430    #[test]
431    fn test_contains_config_ignored() {
432        // Config doesn't affect contains logic, only namespace, package, interfaces, and version matter
433        let mut wit1 = WitInterface::from("wasi:blobstore/types");
434        wit1.config.insert("key".to_string(), "value1".to_string());
435
436        let mut wit2 = WitInterface::from("wasi:blobstore/types");
437        wit2.config.insert("key".to_string(), "value2".to_string());
438
439        // Config differences are ignored
440        assert!(wit1.contains(&wit2));
441    }
442
443    #[test]
444    fn test_world_includes() {
445        let required_interface = create_interface("wasi", "keyvalue", &["get"]);
446        let broader_interface = create_interface("wasi", "keyvalue", &["get", "set"]);
447        let different_interface = create_interface("wasi", "logging", &["log"]);
448
449        // World that imports the exact interface
450        let world1 = WitWorld {
451            imports: [required_interface.clone()].iter().cloned().collect(),
452            exports: HashSet::new(),
453        };
454        assert!(world1.includes(&required_interface));
455
456        // World that imports a broader interface
457        let world2 = WitWorld {
458            imports: [broader_interface.clone()].iter().cloned().collect(),
459            exports: HashSet::new(),
460        };
461        assert!(world2.includes(&required_interface));
462        assert!(!world1.includes(&broader_interface));
463
464        // World that exports the interface
465        let world3 = WitWorld {
466            imports: HashSet::new(),
467            exports: [broader_interface.clone()].iter().cloned().collect(),
468        };
469        assert!(world3.includes(&required_interface));
470
471        // World with a non-matching interface
472        let world4 = WitWorld {
473            imports: [different_interface].iter().cloned().collect(),
474            exports: HashSet::new(),
475        };
476        assert!(!world4.includes(&required_interface));
477    }
478
479    #[test]
480    fn test_world_satisfies() {
481        // Guest requires 'logging' and 'keyvalue-read'
482        let guest_world = WitWorld {
483            imports: [
484                create_interface("wasi", "logging", &["log"]),
485                create_interface("wasi", "keyvalue", &["get", "exists"]),
486            ]
487            .iter()
488            .cloned()
489            .collect(),
490            exports: HashSet::new(),
491        };
492
493        // Host that provides exactly what's needed
494        let host_world_exact = WitWorld {
495            imports: HashSet::new(),
496            exports: [
497                create_interface("wasi", "logging", &["log"]),
498                create_interface("wasi", "keyvalue", &["get", "exists"]),
499            ]
500            .iter()
501            .cloned()
502            .collect(),
503        };
504        assert!(host_world_exact.satisfies(&guest_world));
505
506        // Host that provides superset interfaces
507        let host_world_superset = WitWorld {
508            imports: HashSet::new(),
509            exports: [
510                create_interface("wasi", "logging", &["log", "error"]),
511                create_interface("wasi", "keyvalue", &["get", "exists", "set", "delete"]),
512            ]
513            .iter()
514            .cloned()
515            .collect(),
516        };
517        assert!(host_world_superset.satisfies(&guest_world));
518
519        // Host that is missing one of the required interfaces
520        let host_world_missing = WitWorld {
521            imports: HashSet::new(),
522            exports: [create_interface("wasi", "logging", &["log"])]
523                .iter()
524                .cloned()
525                .collect(),
526        };
527        assert!(!host_world_missing.satisfies(&guest_world));
528
529        // Host that provides a subset of a required interface (not enough)
530        let host_world_subset = WitWorld {
531            imports: HashSet::new(),
532            exports: [
533                create_interface("wasi", "logging", &["log"]),
534                create_interface("wasi", "keyvalue", &["get"]), // Missing "exists"
535            ]
536            .iter()
537            .cloned()
538            .collect(),
539        };
540        assert!(!host_world_subset.satisfies(&guest_world));
541    }
542
543    #[test]
544    fn test_parse_basic_formats() {
545        // Basic namespace:package
546        let wit1 = WitInterface::from("wasi:blobstore");
547        assert_eq!(wit1.namespace, "wasi");
548        assert_eq!(wit1.package, "blobstore");
549        assert!(wit1.interfaces.is_empty());
550        assert!(wit1.version.is_none());
551
552        // Single interface
553        let wit2 = WitInterface::from("wasi:http/incoming-handler");
554        assert_eq!(wit2.namespace, "wasi");
555        assert_eq!(wit2.package, "http");
556        assert_eq!(wit2.interfaces.len(), 1);
557        assert!(wit2.interfaces.contains("incoming-handler"));
558
559        // Multiple interfaces
560        let wit3 = WitInterface::from("wasi:http/incoming-handler,outgoing-handler,types");
561        assert_eq!(wit3.interfaces.len(), 3);
562        assert!(wit3.interfaces.contains("incoming-handler"));
563        assert!(wit3.interfaces.contains("outgoing-handler"));
564        assert!(wit3.interfaces.contains("types"));
565
566        // Just package (no namespace)
567        let wit4 = WitInterface::from("mypackage");
568        assert_eq!(wit4.namespace, "");
569        assert_eq!(wit4.package, "mypackage");
570        assert!(wit4.interfaces.is_empty());
571
572        // No namespace with interface
573        let wit5 = WitInterface::from("blobstore/types");
574        assert_eq!(wit5.namespace, "");
575        assert_eq!(wit5.package, "blobstore");
576        assert!(wit5.interfaces.contains("types"));
577    }
578
579    #[test]
580    fn test_parse_with_versions() {
581        // Basic version
582        let wit1 = WitInterface::from("wasi:blobstore/types@0.2.0");
583        assert_eq!(wit1.version, Some(semver::Version::parse("0.2.0").unwrap()));
584
585        // Multiple interfaces with version
586        let wit2 = WitInterface::from("wasi:keyvalue/store,atomics,batch@0.2.0-draft");
587        assert_eq!(wit2.interfaces.len(), 3);
588        assert_eq!(
589            wit2.version,
590            Some(semver::Version::parse("0.2.0-draft").unwrap())
591        );
592
593        // No namespace with version
594        let wit3 = WitInterface::from("mypackage/interface1,interface2@1.0.0");
595        assert_eq!(wit3.namespace, "");
596        assert_eq!(wit3.version, Some(semver::Version::parse("1.0.0").unwrap()));
597
598        // Just package with version
599        let wit4 = WitInterface::from("mypackage@1.0.0");
600        assert!(wit4.interfaces.is_empty());
601        assert_eq!(wit4.version, Some(semver::Version::parse("1.0.0").unwrap()));
602
603        // Prerelease version
604        let wit5 = WitInterface::from("wasi:logging/logging@0.1.0-draft");
605        assert_eq!(
606            wit5.version,
607            Some(semver::Version::parse("0.1.0-draft").unwrap())
608        );
609
610        // Complex version
611        let wit6 = WitInterface::from("wasi:cli/environment@0.2.0-rc.2024-12-05");
612        assert_eq!(
613            wit6.version,
614            Some(semver::Version::parse("0.2.0-rc.2024-12-05").unwrap())
615        );
616
617        // Invalid version is ignored
618        let wit7 = WitInterface::from("wasi:blobstore/types@invalid-version");
619        assert!(wit7.version.is_none());
620    }
621
622    #[test]
623    fn test_parse_edge_cases() {
624        // Spaces in interfaces
625        let wit1 = WitInterface::from("wasi:http/incoming-handler, outgoing-handler , types");
626        assert_eq!(wit1.interfaces.len(), 3);
627        assert!(wit1.interfaces.contains("incoming-handler"));
628        assert!(wit1.interfaces.contains("outgoing-handler"));
629        assert!(wit1.interfaces.contains("types"));
630
631        // Empty interface segments (trailing comma)
632        let wit2 = WitInterface::from("wasi:http/incoming-handler,");
633        assert_eq!(wit2.interfaces.len(), 1);
634        assert!(wit2.interfaces.contains("incoming-handler"));
635
636        // Leading comma
637        let wit3 = WitInterface::from("wasi:http/,incoming-handler");
638        assert_eq!(wit3.interfaces.len(), 1);
639        assert!(wit3.interfaces.contains("incoming-handler"));
640
641        // Double comma
642        let wit4 = WitInterface::from("wasi:http/incoming-handler,,outgoing-handler");
643        assert_eq!(wit4.interfaces.len(), 2);
644
645        // Colon in package name (edge case)
646        let wit5 = WitInterface::from("foo:bar:baz/interface");
647        assert_eq!(wit5.namespace, "foo");
648        assert_eq!(wit5.package, "bar:baz");
649        assert!(wit5.interfaces.contains("interface"));
650    }
651
652    #[test]
653    fn test_parse_from_string() {
654        let iface: WitInterface = "wasi:http/incoming-handler@0.2.0".into();
655        assert_eq!(iface.namespace, "wasi");
656        assert_eq!(iface.package, "http");
657        assert_eq!(iface.interfaces.len(), 1);
658        assert!(iface.interfaces.contains("incoming-handler"));
659        assert_eq!(
660            iface.version,
661            Some(semver::Version::parse("0.2.0").unwrap())
662        );
663
664        let iface2: WitInterface = "wasmcloud:messaging".into();
665        assert_eq!(iface2.namespace, "wasmcloud");
666        assert_eq!(iface2.package, "messaging");
667        assert!(iface2.interfaces.is_empty());
668        assert_eq!(iface2.version, None);
669
670        let iface3: WitInterface = "wasi:keyvalue/store,atomic@0.1.0".into();
671        assert_eq!(iface3.namespace, "wasi");
672        assert_eq!(iface3.package, "keyvalue");
673        assert_eq!(iface3.interfaces.len(), 2);
674        assert!(iface3.interfaces.contains("store"));
675        assert!(iface3.interfaces.contains("atomic"));
676    }
677
678    #[test]
679    fn test_display() {
680        let iface = create_interface("wasi", "http", &["incoming-handler"]);
681        assert_eq!(format!("{}", iface), "wasi:http/incoming-handler");
682
683        let iface_with_version =
684            create_interface_with_version("wasi", "http", &["incoming-handler"], "0.2.0");
685        assert_eq!(
686            format!("{}", iface_with_version),
687            "wasi:http/incoming-handler@0.2.0"
688        );
689
690        let iface_no_interfaces = create_interface("wasi", "logging", &[]);
691        assert_eq!(format!("{}", iface_no_interfaces), "wasi:logging");
692    }
693}