1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
//! Core reusable functionality related to [WebAssembly Interface types ("WIT")](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md)
use std::collections::HashMap;
use anyhow::Context;
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize, Serializer};
use crate::{WitFunction, WitInterface, WitNamespace, WitPackage};
// I don't know if these would be generated or if we'd just include them in the library and then use them in the generated code, but they work around the lack of a map type in wit
/// Representation of maps (AKA associative arrays) that are usable from WIT
///
/// This representation is required because WIT does not natively
/// have support for a map type, so we must use a list of tuples
pub type WitMap<T> = Vec<(String, T)>;
pub(crate) fn serialize_wit_map<S: Serializer, T>(
map: &WitMap<T>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
T: Serialize,
{
let mut seq = serializer.serialize_map(Some(map.len()))?;
for (key, val) in map {
seq.serialize_entry(key, val)?;
}
seq.end()
}
pub(crate) fn deserialize_wit_map<'de, D: serde::Deserializer<'de>, T>(
deserializer: D,
) -> Result<WitMap<T>, D::Error>
where
T: Deserialize<'de>,
{
let values = HashMap::<String, T>::deserialize(deserializer)?;
Ok(values.into_iter().collect())
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
/// Call target identifier, which is equivalent to a WIT specification, which
/// can identify an interface being called and optionally a specific function on that interface.
pub struct CallTargetInterface {
/// WIT namespace (ex. `wasi` in `wasi:keyvalue/readwrite.get`)
pub namespace: String,
/// WIT package name (ex. `keyvalue` in `wasi:keyvalue/readwrite.get`)
pub package: String,
/// WIT interface (ex. `readwrite` in `wasi:keyvalue/readwrite.get`)
pub interface: String,
// TODO(brooksmtownsend): I'm almost certain we do not need this.
/// WIT package name (ex. `get` in `wasi:keyvalue/readwrite.get`)
pub function: Option<String>,
}
impl CallTargetInterface {
/// Returns the 3-tuple of (namespace, package, interface) for this interface
#[must_use]
pub fn as_parts(&self) -> (&str, &str, &str, Option<&str>) {
(
&self.namespace,
&self.package,
&self.interface,
self.function.as_deref(),
)
}
/// Build a [`TargetInterface`] from constituent parts
#[must_use]
pub fn from_parts(parts: (&str, &str, &str, Option<&str>)) -> Self {
let (ns, pkg, iface, function) = parts;
Self {
namespace: ns.into(),
package: pkg.into(),
interface: iface.into(),
function: function.map(String::from),
}
}
/// Build a target interface from a given operation
pub fn from_operation(operation: impl AsRef<str>) -> anyhow::Result<Self> {
let operation = operation.as_ref();
let (wit_ns, wit_pkg, wit_iface, wit_fn) = parse_wit_meta_from_operation(operation)?;
Ok(CallTargetInterface::from_parts((
&wit_ns,
&wit_pkg,
&wit_iface,
wit_fn.as_deref(),
)))
}
}
/// Parse a sufficiently specified WIT operation/method into constituent parts.
///
///
/// # Errors
///
/// Returns `Err` if the operation is not of the form "<package>:<ns>/<interface>.<function>"
///
/// # Example
///
/// ```
/// let (wit_ns, wit_pkg, wit_iface, wit_fn) = parse_wit_meta_from_operation(("wasmcloud:bus/guest-config"));
/// #assert_eq!(wit_ns, "wasmcloud")
/// #assert_eq!(wit_pkg, "bus")
/// #assert_eq!(wit_iface, "iface")
/// #assert_eq!(wit_fn, None)
/// let (wit_ns, wit_pkg, wit_iface, wit_fn) = parse_wit_meta_from_operation(("wasmcloud:bus/guest-config.get"));
/// #assert_eq!(wit_ns, "wasmcloud")
/// #assert_eq!(wit_pkg, "bus")
/// #assert_eq!(wit_iface, "iface")
/// #assert_eq!(wit_fn, Some("get"))
/// ```
pub fn parse_wit_meta_from_operation(
operation: impl AsRef<str>,
) -> anyhow::Result<(WitNamespace, WitPackage, WitInterface, Option<WitFunction>)> {
let operation = operation.as_ref();
let (ns_and_pkg, interface_and_func) = operation
.rsplit_once('/')
.context("failed to parse operation")?;
let (wit_iface, wit_fn) = interface_and_func
.split_once('.')
.context("interface and function should be specified")?;
let (wit_ns, wit_pkg) = ns_and_pkg
.rsplit_once(':')
.context("failed to parse operation for WIT ns/pkg")?;
Ok((
wit_ns.into(),
wit_pkg.into(),
wit_iface.into(),
if wit_fn.is_empty() {
None
} else {
Some(wit_fn.into())
},
))
}