use std::collections::hash_map;
use std::collections::HashMap;
use std::fmt;
use aws_smithy_types::type_erasure::TypeErasedBox;
use aws_smithy_types::DiscriminatedDocument;
use crate::document::DocumentShapeDeserializer;
use crate::schema::error_envelope::sanitize_error_code;
use crate::serde::{SerdeError, ShapeDeserializer};
use crate::Schema;
use crate::ShapeId;
pub type DeserializeFn = fn(&mut dyn ShapeDeserializer) -> Result<TypeErasedBox, SerdeError>;
pub type ErrorDeserializeFn =
fn(&mut dyn ShapeDeserializer) -> Result<Box<dyn std::error::Error + Send + Sync>, SerdeError>;
#[derive(Clone, Copy)]
pub struct RegistryEntry {
schema: &'static Schema<'static>,
deserialize: DeserializeFn,
error_deserialize: Option<ErrorDeserializeFn>,
}
impl RegistryEntry {
pub const fn new(schema: &'static Schema<'static>, deserialize: DeserializeFn) -> Self {
Self {
schema,
deserialize,
error_deserialize: None,
}
}
pub const fn new_error(
schema: &'static Schema<'static>,
deserialize: DeserializeFn,
error_deserialize: ErrorDeserializeFn,
) -> Self {
Self {
schema,
deserialize,
error_deserialize: Some(error_deserialize),
}
}
pub fn schema(&self) -> &'static Schema<'static> {
self.schema
}
pub fn deserialize_fn(&self) -> DeserializeFn {
self.deserialize
}
pub fn error_deserialize_fn(&self) -> Option<ErrorDeserializeFn> {
self.error_deserialize
}
}
impl fmt::Debug for RegistryEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RegistryEntry")
.field("shape_id", self.schema.shape_id())
.finish_non_exhaustive()
}
}
#[derive(Default)]
pub struct TypeRegistry {
entries: HashMap<ShapeId<'static>, RegistryEntry>,
}
impl TypeRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> TypeRegistryBuilder {
TypeRegistryBuilder {
entries: HashMap::new(),
}
}
pub fn schema_for(&self, id: &ShapeId<'_>) -> Option<&'static Schema<'static>> {
self.entries.get(id.as_str()).map(|e| e.schema)
}
pub fn schema_for_fqn(&self, fqn: &str) -> Option<&'static Schema<'static>> {
self.entries.get(fqn).map(|e| e.schema)
}
pub fn entry_for(&self, id: &ShapeId<'_>) -> Option<&RegistryEntry> {
self.entries.get(id.as_str())
}
pub fn entry_for_fqn(&self, fqn: &str) -> Option<&RegistryEntry> {
self.entries.get(fqn)
}
pub fn entry_for_error_code(&self, wire_code: &str) -> Option<&RegistryEntry> {
let wire_code = sanitize_error_code(wire_code);
self.iter()
.find_map(|(id, entry)| (id.shape_name() == wire_code).then_some(entry))
}
pub fn deserialize_document(
&self,
document: &DiscriminatedDocument,
) -> Result<TypeErasedBox, SerdeError> {
let id = document.discriminator().ok_or_else(|| {
SerdeError::invalid_input(
"document has no discriminator; cannot resolve shape via TypeRegistry".to_string(),
)
})?;
let entry = self
.entries
.get(id)
.ok_or_else(|| SerdeError::unknown_member(id.to_string()))?;
let mut deser = DocumentShapeDeserializer::new_with_settings(
document.document(),
document.settings().cloned(),
);
(entry.deserialize)(&mut deser)
}
pub fn compose(mut self, other: TypeRegistry) -> TypeRegistry {
for (id, entry) in other.entries {
self.entries.insert(id, entry);
}
self
}
pub fn or<'a>(&'a self, fallback: &'a TypeRegistry) -> ComposedRegistry<'a> {
ComposedRegistry {
primary: self,
fallback,
}
}
pub fn iter(&self) -> Iter<'_> {
Iter {
inner: self.entries.iter(),
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl fmt::Debug for TypeRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypeRegistry")
.field("entries", &self.entries.len())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ComposedRegistry<'a> {
primary: &'a TypeRegistry,
fallback: &'a TypeRegistry,
}
impl ComposedRegistry<'_> {
pub fn entry_for(&self, id: &ShapeId<'_>) -> Option<&RegistryEntry> {
self.primary
.entry_for(id)
.or_else(|| self.fallback.entry_for(id))
}
pub fn entry_for_fqn(&self, fqn: &str) -> Option<&RegistryEntry> {
self.primary
.entry_for_fqn(fqn)
.or_else(|| self.fallback.entry_for_fqn(fqn))
}
pub fn entry_for_error_code(&self, wire_code: &str) -> Option<&RegistryEntry> {
self.primary
.entry_for_error_code(wire_code)
.or_else(|| self.fallback.entry_for_error_code(wire_code))
}
}
pub fn reify_error(
registry: ComposedRegistry<'_>,
error_code: &str,
deser: &mut dyn ShapeDeserializer,
) -> Option<Box<dyn std::error::Error + Send + Sync>> {
let entry = registry.entry_for_error_code(error_code)?;
(entry.error_deserialize_fn()?)(deser).ok()
}
#[derive(Default)]
pub struct TypeRegistryBuilder {
entries: HashMap<ShapeId<'static>, RegistryEntry>,
}
impl fmt::Debug for TypeRegistryBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypeRegistryBuilder")
.field("entries", &self.entries.len())
.finish_non_exhaustive()
}
}
impl TypeRegistryBuilder {
pub fn insert(mut self, id: ShapeId<'static>, entry: RegistryEntry) -> Self {
self.entries.insert(id, entry);
self
}
pub fn insert_shape(
mut self,
schema: &'static Schema<'static>,
deserialize: DeserializeFn,
) -> Self {
let id = schema.shape_id().clone();
self.entries
.insert(id, RegistryEntry::new(schema, deserialize));
self
}
pub fn insert_error_shape(
mut self,
schema: &'static Schema<'static>,
deserialize: DeserializeFn,
error_deserialize: ErrorDeserializeFn,
) -> Self {
let id = schema.shape_id().clone();
self.entries.insert(
id,
RegistryEntry::new_error(schema, deserialize, error_deserialize),
);
self
}
pub fn build(self) -> TypeRegistry {
TypeRegistry {
entries: self.entries,
}
}
}
pub struct Iter<'a> {
inner: hash_map::Iter<'a, ShapeId<'static>, RegistryEntry>,
}
impl fmt::Debug for Iter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Iter").finish_non_exhaustive()
}
}
impl<'a> Iterator for Iter<'a> {
type Item = (&'a ShapeId<'static>, &'a RegistryEntry);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl ExactSizeIterator for Iter<'_> {
fn len(&self) -> usize {
self.inner.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use aws_smithy_types::{Blob, Document, DocumentError, DocumentSettings, Number};
use std::sync::Arc;
use crate::shape_id;
use crate::ShapeType;
static M_FOO_NAME: Schema<'static> = Schema::new_member(
shape_id!("smithy.example", "Foo", "name"),
ShapeType::String,
"name",
0,
);
static FOO_SCHEMA: Schema<'static> = Schema::new_struct(
shape_id!("smithy.example", "Foo"),
ShapeType::Structure,
&[&M_FOO_NAME],
);
static M_BAR_VALUE: Schema<'static> = Schema::new_member(
shape_id!("smithy.example", "Bar", "value"),
ShapeType::Integer,
"value",
0,
);
static BAR_SCHEMA: Schema<'static> = Schema::new_struct(
shape_id!("smithy.example", "Bar"),
ShapeType::Structure,
&[&M_BAR_VALUE],
);
static M_WIDGET_DATA: Schema<'static> = Schema::new_member(
shape_id!("smithy.example", "Widget", "data"),
ShapeType::Blob,
"data",
0,
);
static WIDGET_SCHEMA: Schema<'static> = Schema::new_struct(
shape_id!("smithy.example", "Widget"),
ShapeType::Structure,
&[&M_WIDGET_DATA],
);
fn assert_same_fn(actual: DeserializeFn, expected: DeserializeFn) {
#[cfg(not(miri))]
assert!(
std::ptr::fn_addr_eq(actual, expected),
"expected the registered deserialize fn"
);
#[cfg(miri)]
let _ = (actual, expected);
}
#[derive(Debug, PartialEq)]
struct Foo {
name: Option<String>,
}
fn deserialize_foo(deser: &mut dyn ShapeDeserializer) -> Result<TypeErasedBox, SerdeError> {
let mut out = Foo { name: None };
deser.read_struct(&FOO_SCHEMA, &mut |member, sub| {
if let Some(0) = member.member_index() {
out.name = Some(sub.read_string(member)?);
}
Ok(())
})?;
Ok(TypeErasedBox::new(out))
}
#[derive(Debug, PartialEq)]
struct Bar {
value: Option<i32>,
}
fn deserialize_bar(deser: &mut dyn ShapeDeserializer) -> Result<TypeErasedBox, SerdeError> {
let mut out = Bar { value: None };
deser.read_struct(&BAR_SCHEMA, &mut |member, sub| {
if let Some(0) = member.member_index() {
out.value = Some(sub.read_integer(member)?);
}
Ok(())
})?;
Ok(TypeErasedBox::new(out))
}
#[derive(Debug, PartialEq)]
struct FooReplacement {
replaced: bool,
}
fn deserialize_foo_replacement(
_deser: &mut dyn ShapeDeserializer,
) -> Result<TypeErasedBox, SerdeError> {
Ok(TypeErasedBox::new(FooReplacement { replaced: true }))
}
#[derive(Debug, PartialEq)]
struct Widget {
data: Option<Blob>,
}
fn deserialize_widget(deser: &mut dyn ShapeDeserializer) -> Result<TypeErasedBox, SerdeError> {
let mut out = Widget { data: None };
deser.read_struct(&WIDGET_SCHEMA, &mut |member, sub| {
if let Some(0) = member.member_index() {
out.data = Some(sub.read_blob(member)?);
}
Ok(())
})?;
Ok(TypeErasedBox::new(out))
}
#[derive(Debug)]
struct JsonishSettings;
impl DocumentSettings for JsonishSettings {
fn protocol_id(&self) -> &str {
"smithy.example#Jsonish"
}
fn coerce_string_to_blob(&self, s: &str) -> Result<Vec<u8>, DocumentError> {
aws_smithy_types::base64::decode(s)
.map_err(|e| DocumentError::unsupported(format!("invalid base64 blob: {e}")))
}
}
#[test]
fn schema_for_returns_registered_schema() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.insert_shape(&BAR_SCHEMA, deserialize_bar)
.build();
let foo = registry
.schema_for(&shape_id!("smithy.example", "Foo"))
.unwrap();
assert_eq!(foo.shape_id(), FOO_SCHEMA.shape_id());
let bar = registry
.schema_for(&shape_id!("smithy.example", "Bar"))
.unwrap();
assert_eq!(bar.shape_id(), BAR_SCHEMA.shape_id());
}
#[test]
fn schema_for_returns_none_for_unregistered_shape() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
assert!(registry
.schema_for(&shape_id!("smithy.example", "DoesNotExist"))
.is_none());
}
#[test]
fn entry_for_returns_schema_and_fn() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let entry = registry
.entry_for(&shape_id!("smithy.example", "Foo"))
.unwrap();
assert_eq!(entry.schema().shape_id(), FOO_SCHEMA.shape_id());
assert_same_fn(entry.deserialize_fn(), deserialize_foo as DeserializeFn);
}
#[test]
fn entry_for_error_code_resolves_sanitized_codes() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.insert_shape(&BAR_SCHEMA, deserialize_bar)
.build();
for code in [
"Foo",
"smithy.example#Foo",
"Foo:http://internal.example.com/x",
"smithy.example#Foo:http://internal.example.com/x",
] {
let entry = registry
.entry_for_error_code(code)
.unwrap_or_else(|| panic!("expected a match for {code:?}"));
assert_eq!(entry.schema().shape_id(), FOO_SCHEMA.shape_id());
}
let bar = registry.entry_for_error_code("Bar").unwrap();
assert_eq!(bar.schema().shape_id(), BAR_SCHEMA.shape_id());
}
#[test]
fn entry_for_error_code_returns_none_for_unknown_code() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
assert!(registry.entry_for_error_code("DoesNotExist").is_none());
assert!(registry.entry_for_error_code("Foo#Nope").is_none());
}
#[test]
fn len_and_is_empty() {
let empty = TypeRegistry::new();
assert_eq!(empty.len(), 0);
assert!(empty.is_empty());
let populated = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.insert_shape(&BAR_SCHEMA, deserialize_bar)
.build();
assert_eq!(populated.len(), 2);
assert!(!populated.is_empty());
}
#[test]
fn iter_yields_all_entries() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.insert_shape(&BAR_SCHEMA, deserialize_bar)
.build();
let mut ids: Vec<String> = registry.iter().map(|(id, _)| id.to_string()).collect();
ids.sort();
assert_eq!(
ids,
vec![
"smithy.example#Bar".to_string(),
"smithy.example#Foo".to_string(),
]
);
assert_eq!(registry.iter().len(), 2);
}
#[test]
fn compose_unions_disjoint_registries() {
let a = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let b = TypeRegistry::builder()
.insert_shape(&BAR_SCHEMA, deserialize_bar)
.build();
let merged = a.compose(b);
assert_eq!(merged.len(), 2);
assert!(merged
.schema_for(&shape_id!("smithy.example", "Foo"))
.is_some());
assert!(merged
.schema_for(&shape_id!("smithy.example", "Bar"))
.is_some());
}
#[test]
fn compose_lets_other_override_self() {
let a = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let b = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo_replacement)
.build();
let merged = a.compose(b);
assert_eq!(merged.len(), 1);
let doc = DiscriminatedDocument::new(Document::Object(Default::default()))
.with_discriminator(FOO_SCHEMA.shape_id().as_str());
let boxed = merged.deserialize_document(&doc).unwrap();
let result = *boxed.downcast::<FooReplacement>().expect("override fn ran");
assert_eq!(result, FooReplacement { replaced: true });
}
#[test]
fn composed_registry_prefers_primary_then_widens() {
let primary = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let fallback = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo_replacement)
.insert_shape(&BAR_SCHEMA, deserialize_bar)
.build();
let composed = primary.or(&fallback);
let foo = composed.entry_for_error_code("Foo").unwrap();
assert_same_fn(foo.deserialize_fn(), deserialize_foo as DeserializeFn);
let bar = composed.entry_for_error_code("Bar").unwrap();
assert_eq!(bar.schema().shape_id(), BAR_SCHEMA.shape_id());
assert!(composed.entry_for_error_code("Nope").is_none());
}
#[test]
fn composed_registry_entry_for_and_fqn_compose() {
let primary = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let fallback = TypeRegistry::builder()
.insert_shape(&BAR_SCHEMA, deserialize_bar)
.build();
let composed = primary.or(&fallback);
assert_eq!(
composed
.entry_for(&shape_id!("smithy.example", "Foo"))
.unwrap()
.schema()
.shape_id(),
FOO_SCHEMA.shape_id()
);
assert_eq!(
composed
.entry_for(&shape_id!("smithy.example", "Bar"))
.unwrap()
.schema()
.shape_id(),
BAR_SCHEMA.shape_id()
);
assert!(composed.entry_for_fqn("smithy.example#Foo").is_some());
assert!(composed.entry_for_fqn("smithy.example#Bar").is_some());
assert!(composed.entry_for_fqn("smithy.example#Nope").is_none());
}
fn error_ctor_primary(
_deser: &mut dyn ShapeDeserializer,
) -> Result<Box<dyn std::error::Error + Send + Sync>, SerdeError> {
Ok("primary".into())
}
fn error_ctor_fallback(
_deser: &mut dyn ShapeDeserializer,
) -> Result<Box<dyn std::error::Error + Send + Sync>, SerdeError> {
Ok("fallback".into())
}
fn error_ctor_always_errors(
_deser: &mut dyn ShapeDeserializer,
) -> Result<Box<dyn std::error::Error + Send + Sync>, SerdeError> {
Err(SerdeError::custom("boom"))
}
#[test]
fn reify_error_scopes_to_primary_then_widens() {
let primary = TypeRegistry::builder()
.insert_error_shape(&FOO_SCHEMA, deserialize_foo_replacement, error_ctor_primary)
.build();
let fallback = TypeRegistry::builder()
.insert_error_shape(
&BAR_SCHEMA,
deserialize_foo_replacement,
error_ctor_fallback,
)
.build();
let composed = primary.or(&fallback);
let doc = Document::Object(Default::default());
let boxed = reify_error(composed, "Foo", &mut DocumentShapeDeserializer::new(&doc))
.expect("Foo reifies from the operation registry");
assert_eq!(boxed.to_string(), "primary");
let boxed = reify_error(composed, "Bar", &mut DocumentShapeDeserializer::new(&doc))
.expect("Bar widens to the service registry");
assert_eq!(boxed.to_string(), "fallback");
assert!(reify_error(composed, "Nope", &mut DocumentShapeDeserializer::new(&doc)).is_none());
}
#[test]
fn reify_error_swallows_deserialize_failure() {
let primary = TypeRegistry::builder()
.insert_error_shape(
&FOO_SCHEMA,
deserialize_foo_replacement,
error_ctor_always_errors,
)
.build();
let empty = TypeRegistry::new();
let composed = primary.or(&empty);
let doc = Document::Object(Default::default());
assert!(reify_error(composed, "Foo", &mut DocumentShapeDeserializer::new(&doc)).is_none());
}
#[test]
fn deserialize_document_round_trip() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.insert_shape(&BAR_SCHEMA, deserialize_bar)
.build();
let mut foo_members = aws_smithy_types::document::DocumentObject::new();
foo_members.insert("name".to_string(), Document::String("hello".to_string()));
let foo_doc = DiscriminatedDocument::new(Document::Object(foo_members))
.with_discriminator(FOO_SCHEMA.shape_id().as_str());
let boxed = registry.deserialize_document(&foo_doc).unwrap();
let foo = *boxed.downcast::<Foo>().expect("downcast to Foo");
assert_eq!(
foo,
Foo {
name: Some("hello".to_string())
}
);
let mut bar_members = aws_smithy_types::document::DocumentObject::new();
bar_members.insert("value".to_string(), Document::Number(Number::PosInt(42)));
let bar_doc = DiscriminatedDocument::new(Document::Object(bar_members))
.with_discriminator(BAR_SCHEMA.shape_id().as_str());
let boxed = registry.deserialize_document(&bar_doc).unwrap();
let bar = *boxed.downcast::<Bar>().expect("downcast to Bar");
assert_eq!(bar, Bar { value: Some(42) });
}
#[test]
fn deserialize_document_errors_when_discriminator_missing() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let doc = DiscriminatedDocument::new(Document::Object(Default::default()));
let err = registry.deserialize_document(&doc).unwrap_err();
match err {
SerdeError::InvalidInput { message } => {
assert!(
message.contains("discriminator"),
"expected message to mention discriminator, got: {message}"
);
}
other => panic!("expected InvalidInput, got {other:?}"),
}
}
#[test]
fn deserialize_document_errors_when_shape_id_unregistered() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let doc = DiscriminatedDocument::new(Document::Object(Default::default()))
.with_discriminator("smithy.example#Unregistered");
let err = registry.deserialize_document(&doc).unwrap_err();
match err {
SerdeError::UnknownMember { member_name } => {
assert_eq!(member_name, "smithy.example#Unregistered");
}
other => panic!("expected UnknownMember, got {other:?}"),
}
}
#[test]
fn deserialize_document_propagates_inner_error() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let mut foo_members = aws_smithy_types::document::DocumentObject::new();
foo_members.insert("name".to_string(), Document::Number(Number::PosInt(5)));
let doc = DiscriminatedDocument::new(Document::Object(foo_members))
.with_discriminator(FOO_SCHEMA.shape_id().as_str());
let err = registry.deserialize_document(&doc).unwrap_err();
match err {
SerdeError::TypeMismatch { .. } => {}
other => panic!("expected TypeMismatch, got {other:?}"),
}
}
#[test]
fn deserialize_document_threads_settings_for_blob_coercion() {
let registry = TypeRegistry::builder()
.insert_shape(&WIDGET_SCHEMA, deserialize_widget)
.build();
let mut members = aws_smithy_types::document::DocumentObject::new();
members.insert("data".to_string(), Document::String("YWJjZA==".to_string()));
let doc = DiscriminatedDocument::new(Document::Object(members))
.with_discriminator(WIDGET_SCHEMA.shape_id().as_str())
.with_settings(Arc::new(JsonishSettings));
let boxed = registry
.deserialize_document(&doc)
.expect("blob coercion should succeed when settings are threaded through");
let widget = *boxed.downcast::<Widget>().expect("downcast to Widget");
assert_eq!(
widget,
Widget {
data: Some(Blob::new(b"abcd".to_vec())),
}
);
}
#[test]
fn deserialize_document_without_settings_cannot_coerce_blob() {
let registry = TypeRegistry::builder()
.insert_shape(&WIDGET_SCHEMA, deserialize_widget)
.build();
let mut members = aws_smithy_types::document::DocumentObject::new();
members.insert("data".to_string(), Document::String("YWJjZA==".to_string()));
let doc = DiscriminatedDocument::new(Document::Object(members))
.with_discriminator(WIDGET_SCHEMA.shape_id().as_str());
let err = registry.deserialize_document(&doc).unwrap_err();
assert!(
matches!(err, SerdeError::TypeMismatch { .. }),
"expected TypeMismatch without settings, got {err:?}"
);
}
#[test]
fn builder_insert_keyed_form() {
let id = FOO_SCHEMA.shape_id();
let registry = TypeRegistry::builder()
.insert(id.clone(), RegistryEntry::new(&FOO_SCHEMA, deserialize_foo))
.build();
assert_eq!(registry.len(), 1);
assert!(registry.schema_for(id).is_some());
}
#[test]
fn debug_impls_do_not_panic() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let entry = registry.entry_for(FOO_SCHEMA.shape_id()).unwrap();
let _ = format!("{registry:?}");
let _ = format!("{entry:?}");
}
#[test]
fn schema_for_works_with_runtime_shape_id() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let owned_fqn = String::from("smithy.example#Foo");
let owned_ns = String::from("smithy.example");
let owned_name = String::from("Foo");
let runtime_id: ShapeId<'_> = ShapeId::from_parts(&owned_fqn, &owned_ns, &owned_name);
let schema = registry.schema_for(&runtime_id).expect("found via FQN");
assert_eq!(schema.shape_id(), FOO_SCHEMA.shape_id());
let owned_other = String::from("smithy.example#Nope");
let runtime_miss: ShapeId<'_> = ShapeId::from_parts(&owned_other, &owned_ns, &owned_name);
assert!(registry.schema_for(&runtime_miss).is_none());
}
#[test]
fn schema_for_fqn_returns_registered_schema() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.insert_shape(&BAR_SCHEMA, deserialize_bar)
.build();
let foo = registry
.schema_for_fqn("smithy.example#Foo")
.expect("registered");
assert_eq!(foo.shape_id(), FOO_SCHEMA.shape_id());
assert!(registry.schema_for_fqn("smithy.example#Missing").is_none());
}
#[test]
fn entry_for_fqn_returns_registered_entry() {
let registry = TypeRegistry::builder()
.insert_shape(&FOO_SCHEMA, deserialize_foo)
.build();
let entry = registry
.entry_for_fqn("smithy.example#Foo")
.expect("registered");
assert_same_fn(entry.deserialize_fn(), deserialize_foo as DeserializeFn);
}
}