use std::fmt;
#[macro_export]
macro_rules! shape_id {
($ns:literal, $name:literal) => {
$crate::ShapeId::from_parts(concat!($ns, "#", $name), $ns, $name)
};
($ns:literal, $name:literal, $member:literal) => {
$crate::ShapeId::from_parts_with_member(
concat!($ns, "#", $name, "$", $member),
$ns,
$name,
$member,
)
};
}
#[derive(Debug, Clone)]
pub struct ShapeId<'a> {
fqn: &'a str,
namespace: &'a str,
shape_name: &'a str,
member_name: Option<&'a str>,
}
impl PartialEq for ShapeId<'_> {
fn eq(&self, other: &Self) -> bool {
self.fqn == other.fqn
}
}
impl Eq for ShapeId<'_> {}
impl std::hash::Hash for ShapeId<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.fqn.hash(state);
}
}
impl std::borrow::Borrow<str> for ShapeId<'_> {
fn borrow(&self) -> &str {
self.fqn
}
}
impl<'a> ShapeId<'a> {
#[doc(hidden)]
pub const fn from_parts(fqn: &'a str, namespace: &'a str, shape_name: &'a str) -> Self {
Self {
fqn,
namespace,
shape_name,
member_name: None,
}
}
#[doc(hidden)]
pub const fn from_parts_with_member(
fqn: &'a str,
namespace: &'a str,
shape_name: &'a str,
member_name: &'a str,
) -> Self {
Self {
fqn,
namespace,
shape_name,
member_name: Some(member_name),
}
}
pub fn as_str(&self) -> &'a str {
self.fqn
}
pub fn namespace(&self) -> &'a str {
self.namespace
}
pub fn shape_name(&self) -> &'a str {
self.shape_name
}
pub fn member_name(&self) -> Option<&'a str> {
self.member_name
}
}
impl fmt::Display for ShapeId<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.fqn)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shape_id_macro() {
const ID: ShapeId<'static> = shape_id!("smithy.api", "String");
assert_eq!(ID.as_str(), "smithy.api#String");
assert_eq!(ID.namespace(), "smithy.api");
assert_eq!(ID.shape_name(), "String");
assert_eq!(ID.member_name(), None);
}
#[test]
fn test_shape_id_macro_with_member() {
const ID: ShapeId<'static> = shape_id!("com.example", "MyStruct", "field");
assert_eq!(ID.as_str(), "com.example#MyStruct$field");
assert_eq!(ID.namespace(), "com.example");
assert_eq!(ID.shape_name(), "MyStruct");
assert_eq!(ID.member_name(), Some("field"));
}
#[test]
fn test_display() {
let id = shape_id!("smithy.api", "String");
assert_eq!(format!("{id}"), "smithy.api#String");
}
#[test]
fn test_equality() {
let a = shape_id!("smithy.api", "String");
let b = shape_id!("smithy.api", "String");
assert_eq!(a, b);
let c = shape_id!("smithy.api", "String", "foo");
let d = shape_id!("smithy.api", "String", "foo");
assert_eq!(c, d);
}
#[test]
fn test_runtime_lifetime() {
let fqn = String::from("ns#Foo");
let ns = String::from("ns");
let name = String::from("Foo");
let id: ShapeId<'_> = ShapeId::from_parts(&fqn, &ns, &name);
assert_eq!(id.as_str(), "ns#Foo");
assert_eq!(id.namespace(), "ns");
assert_eq!(id.shape_name(), "Foo");
}
#[test]
fn equality_across_lifetimes() {
let static_id: ShapeId<'static> = shape_id!("ns", "Foo");
let owned_fqn = String::from("ns#Foo");
let owned_ns = String::from("ns");
let owned_name = String::from("Foo");
let runtime_id: ShapeId<'_> = ShapeId::from_parts(&owned_fqn, &owned_ns, &owned_name);
assert_eq!(static_id, runtime_id);
let mut h1 = std::collections::hash_map::DefaultHasher::new();
let mut h2 = std::collections::hash_map::DefaultHasher::new();
std::hash::Hash::hash(&static_id, &mut h1);
std::hash::Hash::hash(&runtime_id, &mut h2);
assert_eq!(
std::hash::Hasher::finish(&h1),
std::hash::Hasher::finish(&h2),
);
}
#[test]
fn hash_map_lookup_by_str_and_runtime_id() {
use std::collections::HashMap;
let static_id: ShapeId<'static> = shape_id!("ns", "Foo");
let mut map: HashMap<ShapeId<'static>, u32> = HashMap::new();
map.insert(static_id, 42);
assert_eq!(map.get("ns#Foo"), Some(&42));
let owned_fqn = String::from("ns#Foo");
let owned_ns = String::from("ns");
let owned_name = String::from("Foo");
let runtime_id: ShapeId<'_> = ShapeId::from_parts(&owned_fqn, &owned_ns, &owned_name);
assert_eq!(map.get(runtime_id.as_str()), Some(&42));
assert_eq!(map.get("ns#Bar"), None);
}
}