use std::collections::HashMap;
use anyhow::{bail, Context as _, Result};
use semver::Version;
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize, Serializer};
use crate::{WitFunction, WitInterface, WitNamespace, WitPackage};
pub type WitMap<T> = Vec<(String, T)>;
pub(crate) fn serialize_wit_map<S: Serializer, T>(
map: &WitMap<T>,
serializer: S,
) -> std::result::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,
) -> std::result::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)]
pub struct CallTargetInterface {
pub namespace: String,
pub package: String,
pub interface: String,
}
impl CallTargetInterface {
#[must_use]
pub fn as_parts(&self) -> (&str, &str, &str) {
(&self.namespace, &self.package, &self.interface)
}
pub fn as_instance(&self) -> String {
format!("{}:{}/{}", self.namespace, self.package, self.interface)
}
#[must_use]
pub fn from_parts((ns, pkg, iface): (&str, &str, &str)) -> Self {
Self {
namespace: ns.into(),
package: pkg.into(),
interface: iface.into(),
}
}
pub fn from_operation(operation: impl AsRef<str>) -> Result<Self> {
let operation = operation.as_ref();
let (wit_ns, wit_pkg, wit_iface, _) = parse_wit_meta_from_operation(operation)?;
Ok(CallTargetInterface::from_parts((
&wit_ns, &wit_pkg, &wit_iface,
)))
}
}
pub fn parse_wit_meta_from_operation(
operation: impl AsRef<str>,
) -> 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_ns, wit_pkg) = ns_and_pkg
.rsplit_once(':')
.context("failed to parse operation for WIT ns/pkg")?;
let (wit_iface, wit_fn) = match interface_and_func.split_once('.') {
Some((iface, func)) => (iface, Some(func.to_string())),
None => (interface_and_func, None),
};
Ok((wit_ns.into(), wit_pkg.into(), wit_iface.into(), wit_fn))
}
type WitInformationTuple = (
WitNamespace,
Vec<WitPackage>,
Option<Vec<WitInterface>>,
Option<WitFunction>,
Option<Version>,
);
pub fn parse_wit_package_name(p: impl AsRef<str>) -> Result<WitInformationTuple> {
let p = p.as_ref();
let (rest, version) = match p.rsplit_once('@') {
Some((rest, version)) => (
rest,
Some(
Version::parse(version)
.map_err(|e| anyhow::anyhow!(e))
.with_context(|| {
format!("failed to parse version from wit package name [{p}]")
})?,
),
),
None => (p, None),
};
let (ns_and_pkg, interface_and_func) = match rest.rsplit_once('/') {
Some((ns_and_pkg, interface_and_func)) => (ns_and_pkg, Some(interface_and_func)),
None => (rest, None),
};
let ns_pkg_split = ns_and_pkg.split(':').collect::<Vec<&str>>();
let (ns, packages) = match ns_pkg_split[..] {
[] => bail!("invalid package name, missing namespace & package"),
[_] => bail!("invalid package name, invalid package"),
[ns, ref packages @ ..] => (ns, packages),
};
let (mut interfaces, iface_with_fn) = match interface_and_func
.unwrap_or_default()
.split('/')
.filter(|v| !v.is_empty())
.collect::<Vec<&str>>()[..]
{
[] => (None, None),
[iface] => (Some(vec![]), Some(iface)),
[iface, f] => (Some(vec![iface]), Some(f)),
[ref ifaces @ .., f] => (Some(Vec::from(ifaces)), Some(f)),
};
let func = match iface_with_fn {
Some(iface_with_fn) => match iface_with_fn.split_once('.') {
Some((iface, f)) => {
if let Some(ref mut interfaces) = interfaces {
interfaces.push(iface);
};
Some(f)
}
None => {
if let Some(ref mut interfaces) = interfaces {
interfaces.push(iface_with_fn);
};
None
}
},
None => None,
};
Ok((
ns.into(),
packages
.iter()
.map(|v| String::from(*v))
.collect::<Vec<_>>(),
interfaces.map(|v| v.into_iter().map(String::from).collect::<Vec<_>>()),
func.map(String::from),
version,
))
}
#[cfg(test)]
mod test {
use semver::Version;
use super::parse_wit_package_name;
#[test]
fn test_parse_wit_package_name() {
let (ns, packages, interfaces, func, version) =
parse_wit_package_name("wasi:http").expect("should have parsed'wasi:http'");
assert_eq!(ns, "wasi".to_string());
assert_eq!(packages, vec!["http".to_string()]);
assert_eq!(interfaces, None);
assert_eq!(func, None);
assert_eq!(version, None);
let (ns, packages, interfaces, func, version) = parse_wit_package_name("wasi:http@0.2.2")
.expect("should have parsed 'wasi:http@0.2.2'");
assert_eq!(ns, "wasi".to_string());
assert_eq!(packages, vec!["http".to_string()]);
assert_eq!(interfaces, None);
assert_eq!(func, None);
assert_eq!(version, Version::parse("0.2.2").ok());
let (ns, packages, interfaces, func, version) =
parse_wit_package_name("wasmcloud:bus/guest-config")
.expect("should have parsed 'wasmcloud:bus/guest-config'");
assert_eq!(ns, "wasmcloud");
assert_eq!(packages, vec!["bus".to_string()]);
assert_eq!(interfaces, Some(vec!["guest-config".to_string()]));
assert_eq!(func, None);
assert_eq!(version, None);
let (ns, packages, interfaces, func, version) =
parse_wit_package_name("wasmcloud:bus/guest-config.get")
.expect("should have parsed 'wasmcloud:bus/guest-config.get'");
assert_eq!(ns, "wasmcloud");
assert_eq!(packages, vec!["bus".to_string()]);
assert_eq!(interfaces, Some(vec!["guest-config".to_string()]));
assert_eq!(func, Some("get".to_string()));
assert_eq!(version, None);
let (ns, packages, interfaces, func, version) =
parse_wit_package_name("wasi:http/incoming-handler@0.2.0")
.expect("should have parsed 'wasi:http/incoming-handler@0.2.0'");
assert_eq!(ns, "wasi".to_string());
assert_eq!(packages, vec!["http".to_string()]);
assert_eq!(interfaces, Some(vec!["incoming-handler".to_string()]));
assert_eq!(func, None);
assert_eq!(version, Version::parse("0.2.0").ok());
let (ns, packages, interfaces, func, version) =
parse_wit_package_name("wasi:keyvalue/atomics.increment@0.2.0-draft")
.expect("should have parsed 'wasi:keyvalue/atomics.increment@0.2.0-draft'");
assert_eq!(ns, "wasi".to_string());
assert_eq!(packages, vec!["keyvalue".to_string()]);
assert_eq!(interfaces, Some(vec!["atomics".to_string()]));
assert_eq!(func, Some("increment".to_string()));
assert_eq!(version, Version::parse("0.2.0-draft").ok());
}
}