#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rustdoc::missing_crate_level_docs,
missing_debug_implementations,
rust_2018_idioms,
unreachable_pub
)]
mod schema {
pub(crate) mod payload_hint;
pub(crate) mod shape_id;
pub(crate) mod shape_type;
pub(crate) mod trait_map;
pub(crate) mod trait_type;
pub(crate) mod traits;
pub(crate) mod codec;
pub(crate) mod document;
pub(crate) mod error_envelope;
pub(crate) mod extension;
pub(crate) mod header_omit_settings;
pub(crate) mod http_protocol;
pub(crate) mod prelude;
pub(crate) mod protocol;
pub(crate) mod registry;
pub(crate) mod serde;
}
pub use schema::payload_hint::PayloadHint;
pub use schema::shape_id::ShapeId;
pub use schema::shape_type::ShapeType;
pub use schema::trait_map::TraitMap;
pub use schema::trait_type::Trait;
pub use schema::trait_type::{AnnotationTrait, DocumentTrait, StringTrait};
pub fn intern_header_name(name: &str) -> &'static str {
static INTERNED: std::sync::LazyLock<
std::sync::Mutex<std::collections::HashSet<&'static str>>,
> = std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashSet::new()));
let mut table = INTERNED
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(&existing) = table.get(name) {
return existing;
}
let leaked: &'static str = Box::leak(name.to_owned().into_boxed_str());
table.insert(leaked);
leaked
}
pub mod prelude {
pub use crate::schema::prelude::*;
}
pub mod extension {
pub use crate::schema::extension::SchemaExtensionKey;
}
pub mod serde {
pub use crate::schema::serde::*;
}
pub mod traits {
pub use crate::schema::traits::*;
}
pub mod codec {
pub use crate::schema::codec::*;
}
pub mod document {
pub use crate::schema::document::*;
}
pub mod header_omit_settings {
pub use crate::schema::header_omit_settings::*;
}
pub mod protocol {
pub use crate::schema::protocol::*;
}
pub mod error_envelope {
pub use crate::schema::error_envelope::*;
}
pub mod http_protocol {
pub use crate::schema::http_protocol::*;
}
pub mod registry {
pub use crate::schema::registry::*;
}
use schema::traits as trait_types;
#[derive(Clone, Copy, PartialEq, Eq)]
struct MemberIndex(usize);
impl MemberIndex {
const NONE: Self = Self(0);
const fn new(index: usize) -> Self {
assert!(
index != usize::MAX,
"member index must be less than usize::MAX"
);
Self(index + 1)
}
const fn get(self) -> Option<usize> {
match self.0 {
0 => None,
encoded => Some(encoded - 1),
}
}
}
impl std::fmt::Debug for MemberIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.get().fmt(f)
}
}
mod response_routing {
pub(crate) const MASK_BITS: usize = 62;
pub(crate) const MASK: u64 = (1u64 << MASK_BITS) - 1;
pub(crate) const HAS_BODY_MEMBERS: u64 = 1u64 << 62;
pub(crate) const NEEDS_SCAN: u64 = 1u64 << 63;
}
#[derive(Debug)]
pub struct Schema<'a> {
id: ShapeId<'a>,
shape_type: ShapeType,
member_name: Option<&'a str>,
member_index: MemberIndex,
response_routing: u64,
members: SchemaMembers<'a>,
original_name: Option<&'a str>,
sensitive: Option<trait_types::SensitiveTrait>,
json_name: Option<trait_types::JsonNameTrait<'a>>,
timestamp_format: Option<trait_types::TimestampFormatTrait>,
xml_name: Option<trait_types::XmlNameTrait<'a>>,
xml_attribute: Option<trait_types::XmlAttributeTrait>,
xml_flattened: Option<trait_types::XmlFlattenedTrait>,
xml_unwrapped_output: bool,
has_body_members: bool,
payload_hint: PayloadHint,
xml_namespace: Option<trait_types::XmlNamespaceTrait<'a>>,
http_header: Option<trait_types::HttpHeaderTrait<'a>>,
http_label: Option<trait_types::HttpLabelTrait>,
http_payload: Option<trait_types::HttpPayloadTrait>,
http_prefix_headers: Option<trait_types::HttpPrefixHeadersTrait<'a>>,
http_query: Option<trait_types::HttpQueryTrait<'a>>,
http_query_params: Option<trait_types::HttpQueryParamsTrait>,
http_response_code: Option<trait_types::HttpResponseCodeTrait>,
http: Option<trait_types::HttpTrait<'a>>,
streaming: Option<trait_types::StreamingTrait>,
event_header: Option<trait_types::EventHeaderTrait>,
event_payload: Option<trait_types::EventPayloadTrait>,
host_label: Option<trait_types::HostLabelTrait>,
media_type: Option<trait_types::MediaTypeTrait<'a>>,
traits: Option<&'a std::sync::LazyLock<TraitMap>>,
extensions: schema::extension::ExtensionStorage,
}
#[derive(Debug)]
enum SchemaMembers<'a> {
None,
Struct { members: &'a [&'a Schema<'a>] },
List { member: &'a Schema<'a> },
Map {
key: &'a Schema<'a>,
value: &'a Schema<'a>,
},
}
#[allow(dead_code)]
fn _assert_schema_covariant<'a, 'b: 'a>(s: &'b Schema<'b>) -> &'a Schema<'a> {
s
}
#[allow(dead_code)]
fn _assert_shape_id_covariant<'a, 'b: 'a>(id: ShapeId<'b>) -> ShapeId<'a> {
id
}
macro_rules! assert_wrapper_covariant {
($fn_name:ident, $wrapper:ident) => {
#[allow(dead_code)]
fn $fn_name<'a, 'b: 'a>(t: trait_types::$wrapper<'b>) -> trait_types::$wrapper<'a> {
t
}
};
}
assert_wrapper_covariant!(_assert_json_name_covariant, JsonNameTrait);
assert_wrapper_covariant!(_assert_xml_name_covariant, XmlNameTrait);
assert_wrapper_covariant!(_assert_media_type_covariant, MediaTypeTrait);
assert_wrapper_covariant!(_assert_http_query_covariant, HttpQueryTrait);
assert_wrapper_covariant!(
_assert_http_prefix_headers_covariant,
HttpPrefixHeadersTrait
);
assert_wrapper_covariant!(_assert_http_header_covariant, HttpHeaderTrait);
assert_wrapper_covariant!(_assert_xml_namespace_covariant, XmlNamespaceTrait);
assert_wrapper_covariant!(_assert_http_covariant, HttpTrait);
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Schema<'static>>();
};
impl<'a> Schema<'a> {
const fn empty_traits() -> Schema<'a> {
Schema {
id: ShapeId::<'a>::from_parts("", "", ""),
shape_type: ShapeType::Boolean,
member_name: None,
member_index: MemberIndex::NONE,
response_routing: 0,
members: SchemaMembers::None,
original_name: None,
sensitive: None,
json_name: None,
timestamp_format: None,
xml_name: None,
xml_attribute: None,
xml_flattened: None,
xml_unwrapped_output: false,
has_body_members: true,
payload_hint: PayloadHint::Unknown,
xml_namespace: None,
http_header: None,
http_label: None,
http_payload: None,
http_prefix_headers: None,
http_query: None,
http_query_params: None,
http_response_code: None,
http: None,
streaming: None,
event_header: None,
event_payload: None,
host_label: None,
media_type: None,
traits: None,
extensions: schema::extension::ExtensionStorage::new(),
}
}
pub const fn new(id: ShapeId<'a>, shape_type: ShapeType) -> Self {
let mut schema = Self::empty_traits();
schema.id = id;
schema.shape_type = shape_type;
schema
}
pub const fn new_struct(
id: ShapeId<'a>,
shape_type: ShapeType,
members: &'a [&'a Schema<'a>],
) -> Self {
let mut schema = Self::empty_traits();
schema.id = id;
schema.shape_type = shape_type;
schema.response_routing = Self::derive_response_routing(members);
schema.members = SchemaMembers::Struct { members };
schema
}
const fn derive_response_routing(members: &[&Schema<'a>]) -> u64 {
let mut word: u64 = 0;
let mut i = 0;
while i < members.len() {
let member = members[i];
let index = match member.member_index.get() {
Some(index) => index,
None => panic!("a struct member schema must carry a member index"),
};
assert!(
index == i,
"struct member schemas must be listed in member-index order, because the \
response routing mask uses a member's array position as its index"
);
if member.has_http_response_binding() {
if i < response_routing::MASK_BITS {
word |= 1u64 << i;
} else {
word |= response_routing::NEEDS_SCAN;
}
} else {
word |= response_routing::HAS_BODY_MEMBERS;
}
i += 1;
}
word
}
pub const fn new_list(id: ShapeId<'a>, member: &'a Schema<'a>) -> Self {
let mut schema = Self::empty_traits();
schema.id = id;
schema.shape_type = ShapeType::List;
schema.members = SchemaMembers::List { member };
schema
}
pub const fn new_map(id: ShapeId<'a>, key: &'a Schema<'a>, value: &'a Schema<'a>) -> Self {
let mut schema = Self::empty_traits();
schema.id = id;
schema.shape_type = ShapeType::Map;
schema.members = SchemaMembers::Map { key, value };
schema
}
pub const fn new_member(
id: ShapeId<'a>,
shape_type: ShapeType,
member_name: &'a str,
member_index: usize,
) -> Self {
let mut schema = Self::empty_traits();
schema.id = id;
schema.shape_type = shape_type;
schema.member_name = Some(member_name);
schema.member_index = MemberIndex::new(member_index);
schema
}
pub fn shape_id(&self) -> &ShapeId<'a> {
&self.id
}
pub fn shape_type(&self) -> ShapeType {
self.shape_type
}
pub fn traits(&self) -> Option<&TraitMap> {
self.traits.map(|lazy| &**lazy)
}
pub fn sensitive(&self) -> Option<&trait_types::SensitiveTrait> {
self.sensitive.as_ref()
}
pub fn json_name(&self) -> Option<&trait_types::JsonNameTrait<'a>> {
self.json_name.as_ref()
}
pub fn timestamp_format(&self) -> Option<&trait_types::TimestampFormatTrait> {
self.timestamp_format.as_ref()
}
pub fn xml_name(&self) -> Option<&trait_types::XmlNameTrait<'a>> {
self.xml_name.as_ref()
}
pub fn xml_namespace(&self) -> Option<&trait_types::XmlNamespaceTrait<'a>> {
self.xml_namespace.as_ref()
}
pub fn xml_attribute(&self) -> bool {
self.xml_attribute.is_some()
}
pub fn xml_flattened(&self) -> bool {
self.xml_flattened.is_some()
}
pub fn xml_unwrapped_output(&self) -> bool {
self.xml_unwrapped_output
}
pub fn streaming(&self) -> bool {
self.streaming.is_some()
}
pub fn has_body_members(&self) -> bool {
self.has_body_members
}
pub fn payload_hint(&self) -> PayloadHint {
self.payload_hint
}
pub const fn has_http_response_binding(&self) -> bool {
self.http_header.is_some()
|| self.http_response_code.is_some()
|| self.http_prefix_headers.is_some()
|| self.http_payload.is_some()
}
pub(crate) const fn response_binding_mask(&self) -> u64 {
self.response_routing & response_routing::MASK
}
pub(crate) const fn has_response_body_members(&self) -> bool {
self.response_routing & response_routing::HAS_BODY_MEMBERS != 0
}
pub(crate) const fn response_bindings_need_scan(&self) -> bool {
self.response_routing & response_routing::NEEDS_SCAN != 0
}
pub fn http_header(&self) -> Option<&trait_types::HttpHeaderTrait<'a>> {
self.http_header.as_ref()
}
pub fn http_query(&self) -> Option<&trait_types::HttpQueryTrait<'a>> {
self.http_query.as_ref()
}
pub fn http_label(&self) -> Option<&trait_types::HttpLabelTrait> {
self.http_label.as_ref()
}
pub fn http_payload(&self) -> Option<&trait_types::HttpPayloadTrait> {
self.http_payload.as_ref()
}
pub fn http_prefix_headers(&self) -> Option<&trait_types::HttpPrefixHeadersTrait<'a>> {
self.http_prefix_headers.as_ref()
}
pub fn media_type(&self) -> Option<&trait_types::MediaTypeTrait<'a>> {
self.media_type.as_ref()
}
pub fn http_query_params(&self) -> Option<&trait_types::HttpQueryParamsTrait> {
self.http_query_params.as_ref()
}
pub fn http_response_code(&self) -> Option<&trait_types::HttpResponseCodeTrait> {
self.http_response_code.as_ref()
}
pub fn http(&self) -> Option<&trait_types::HttpTrait<'a>> {
self.http.as_ref()
}
pub const fn with_original_name(mut self, name: &'a str) -> Self {
self.original_name = Some(name);
self
}
pub const fn with_map_members(mut self, key: &'a Schema<'a>, value: &'a Schema<'a>) -> Self {
self.members = SchemaMembers::Map { key, value };
self
}
pub const fn with_list_member(mut self, member: &'a Schema<'a>) -> Self {
self.members = SchemaMembers::List { member };
self
}
pub const fn with_sensitive(mut self) -> Self {
self.sensitive = Some(trait_types::SensitiveTrait);
self
}
pub const fn with_json_name(mut self, value: &'a str) -> Self {
self.json_name = Some(trait_types::JsonNameTrait::new(value));
self
}
pub const fn with_timestamp_format(mut self, format: trait_types::TimestampFormat) -> Self {
self.timestamp_format = Some(trait_types::TimestampFormatTrait::new(format));
self
}
pub const fn with_xml_name(mut self, value: &'a str) -> Self {
self.xml_name = Some(trait_types::XmlNameTrait::new(value));
self
}
pub const fn with_xml_attribute(mut self) -> Self {
self.xml_attribute = Some(trait_types::XmlAttributeTrait);
self
}
pub const fn with_xml_flattened(mut self) -> Self {
self.xml_flattened = Some(trait_types::XmlFlattenedTrait);
self
}
pub const fn with_xml_unwrapped_output(mut self) -> Self {
self.xml_unwrapped_output = true;
self
}
pub const fn with_no_body_members(mut self) -> Self {
self.has_body_members = false;
self
}
pub const fn with_payload_hint(mut self, hint: PayloadHint) -> Self {
self.payload_hint = hint;
self
}
pub const fn with_http_header(mut self, value: &'static str) -> Self {
self.http_header = Some(trait_types::HttpHeaderTrait::new(value));
self
}
pub const fn with_http_label(mut self) -> Self {
self.http_label = Some(trait_types::HttpLabelTrait);
self
}
pub const fn with_http_payload(mut self) -> Self {
self.http_payload = Some(trait_types::HttpPayloadTrait);
self
}
pub const fn with_http_prefix_headers(mut self, value: &'a str) -> Self {
self.http_prefix_headers = Some(trait_types::HttpPrefixHeadersTrait::new(value));
self
}
pub const fn with_http_query(mut self, value: &'a str) -> Self {
self.http_query = Some(trait_types::HttpQueryTrait::new(value));
self
}
pub const fn with_http_query_params(mut self) -> Self {
self.http_query_params = Some(trait_types::HttpQueryParamsTrait);
self
}
pub const fn with_http_response_code(mut self) -> Self {
self.http_response_code = Some(trait_types::HttpResponseCodeTrait);
self
}
pub const fn with_http(mut self, http: trait_types::HttpTrait<'a>) -> Self {
self.http = Some(http);
self
}
pub const fn with_streaming(mut self) -> Self {
self.streaming = Some(trait_types::StreamingTrait);
self
}
pub const fn with_event_header(mut self) -> Self {
self.event_header = Some(trait_types::EventHeaderTrait);
self
}
pub const fn with_event_payload(mut self) -> Self {
self.event_payload = Some(trait_types::EventPayloadTrait);
self
}
pub const fn with_host_label(mut self) -> Self {
self.host_label = Some(trait_types::HostLabelTrait);
self
}
pub const fn with_media_type(mut self, value: &'a str) -> Self {
self.media_type = Some(trait_types::MediaTypeTrait::new(value));
self
}
pub const fn with_xml_namespace(mut self, uri: &'a str, prefix: Option<&'a str>) -> Self {
self.xml_namespace = Some(trait_types::XmlNamespaceTrait::new(uri, prefix));
self
}
pub const fn with_traits(mut self, traits: &'a std::sync::LazyLock<TraitMap>) -> Self {
self.traits = Some(traits);
self
}
#[inline]
pub fn extension<T: Send + Sync + 'static>(
&self,
key: &'static extension::SchemaExtensionKey<T>,
) -> &T {
self.extensions.get_or_compute(self, key)
}
pub fn member_name(&self) -> Option<&'a str> {
self.member_name
}
pub fn member_index(&self) -> Option<usize> {
self.member_index.get()
}
pub fn original_name(&self) -> Option<&str> {
self.original_name
}
pub fn member_schema(&self, name: &str) -> Option<&Schema<'_>> {
match &self.members {
SchemaMembers::Struct { members } => members
.iter()
.find(|m| m.member_name == Some(name))
.copied(),
_ => None,
}
}
pub fn member_schema_by_index(&self, index: usize) -> Option<&Schema<'_>> {
match &self.members {
SchemaMembers::Struct { members } => members.get(index).copied(),
_ => None,
}
}
pub fn members(&self) -> &[&Schema<'_>] {
match &self.members {
SchemaMembers::Struct { members } => members,
_ => &[],
}
}
pub fn member(&self) -> Option<&Schema<'_>> {
match &self.members {
SchemaMembers::List { member } => Some(member),
SchemaMembers::Map { value, .. } => Some(value),
_ => None,
}
}
pub fn member_borrowed(&self) -> Option<&'a Schema<'a>> {
match &self.members {
SchemaMembers::List { member } => Some(*member),
SchemaMembers::Map { value, .. } => Some(*value),
_ => None,
}
}
pub fn key(&self) -> Option<&Schema<'_>> {
match &self.members {
SchemaMembers::Map { key, .. } => Some(key),
_ => None,
}
}
pub fn key_borrowed(&self) -> Option<&'a Schema<'a>> {
match &self.members {
SchemaMembers::Map { key, .. } => Some(*key),
_ => None,
}
}
pub fn is_member(&self) -> bool {
self.shape_type.is_member()
}
pub fn is_structure(&self) -> bool {
self.shape_type == ShapeType::Structure
}
pub fn is_union(&self) -> bool {
self.shape_type == ShapeType::Union
}
pub fn is_list(&self) -> bool {
self.shape_type == ShapeType::List
}
pub fn is_map(&self) -> bool {
self.shape_type == ShapeType::Map
}
pub fn is_blob(&self) -> bool {
self.shape_type == ShapeType::Blob
}
pub fn is_string(&self) -> bool {
self.shape_type == ShapeType::String
}
}
#[cfg(test)]
mod test {
use crate::{shape_id, MemberIndex, Schema, ShapeId, ShapeType, Trait, TraitMap};
#[derive(Debug)]
struct TestTrait {
id: crate::ShapeId<'static>,
#[allow(dead_code)]
value: String,
}
impl Trait for TestTrait {
fn trait_id(&self) -> &crate::ShapeId<'static> {
&self.id
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[test]
fn test_shape_type_simple() {
assert!(ShapeType::String.is_simple());
assert!(ShapeType::Integer.is_simple());
assert!(ShapeType::Boolean.is_simple());
assert!(!ShapeType::Structure.is_simple());
assert!(!ShapeType::List.is_simple());
}
#[test]
fn test_shape_type_aggregate() {
assert!(ShapeType::Structure.is_aggregate());
assert!(ShapeType::Union.is_aggregate());
assert!(ShapeType::List.is_aggregate());
assert!(ShapeType::Map.is_aggregate());
assert!(!ShapeType::String.is_aggregate());
}
#[test]
fn test_shape_type_member() {
assert!(ShapeType::Member.is_member());
assert!(!ShapeType::String.is_member());
assert!(!ShapeType::Structure.is_member());
}
#[test]
fn test_shape_id_parsing() {
let id = shape_id!("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_with_member() {
let id = shape_id!("com.example", "MyStruct", "member");
assert_eq!(id.namespace(), "com.example");
assert_eq!(id.shape_name(), "MyStruct");
assert_eq!(id.member_name(), Some("member"));
}
#[test]
fn test_trait_map() {
let mut map = TraitMap::new();
assert!(map.is_empty());
assert_eq!(map.len(), 0);
let trait_id = shape_id!("smithy.api", "required");
let test_trait = Box::new(TestTrait {
id: trait_id.clone(),
value: "test".to_string(),
});
map.insert(test_trait);
assert!(!map.is_empty());
assert_eq!(map.len(), 1);
assert!(map.contains(&trait_id));
let retrieved = map.get(&trait_id);
assert!(retrieved.is_some());
}
#[test]
fn test_trait_map_cross_lifetime_lookup() {
let mut map = TraitMap::new();
let trait_id = shape_id!("smithy.api", "required");
map.insert(Box::new(TestTrait {
id: trait_id,
value: "test".to_string(),
}));
assert!(map.contains_fqn("smithy.api#required"));
assert!(map.get_fqn("smithy.api#required").is_some());
assert!(!map.contains_fqn("smithy.api#missing"));
let owned_fqn = String::from("smithy.api#required");
let owned_ns = String::from("smithy.api");
let owned_name = String::from("required");
let runtime_id: ShapeId<'_> = ShapeId::from_parts(&owned_fqn, &owned_ns, &owned_name);
assert!(map.contains(&runtime_id));
assert!(map.get(&runtime_id).is_some());
}
#[test]
fn test_schema_predicates() {
let schema = Schema::new(shape_id!("com.example", "MyStruct"), ShapeType::Structure);
assert!(schema.is_structure());
assert!(!schema.is_union());
assert!(!schema.is_list());
assert!(!schema.is_member());
}
#[test]
fn test_schema_basic() {
let schema = Schema::new(shape_id!("smithy.api", "String"), ShapeType::String);
assert_eq!(schema.shape_id().as_str(), "smithy.api#String");
assert_eq!(schema.shape_type(), ShapeType::String);
assert!(schema.traits().is_none());
assert!(schema.member_name().is_none());
assert!(schema.member_schema("test").is_none());
assert!(schema.member_schema_by_index(0).is_none());
}
#[test]
fn runtime_trait_values_from_an_arena() {
let arena: Vec<String> = vec![
String::from("ns#Foo"), String::from("ns"), String::from("Foo"), String::from("fieldName"), String::from("WireName"), String::from("wire-elem"), String::from("application/json"), String::from("qparam"), String::from("http://ns.example/x"), String::from("px"), ];
let member: Schema<'_> = Schema::new_member(
ShapeId::from_parts(&arena[0], &arena[1], &arena[2]),
ShapeType::String,
&arena[3],
0,
)
.with_json_name(&arena[4])
.with_xml_name(&arena[5])
.with_media_type(&arena[6])
.with_http_query(&arena[7])
.with_xml_namespace(&arena[8], Some(&arena[9]));
assert_eq!(member.json_name().unwrap().value(), "WireName");
assert_eq!(member.xml_name().unwrap().value(), "wire-elem");
assert_eq!(member.media_type().unwrap().value(), "application/json");
assert_eq!(member.http_query().unwrap().value(), "qparam");
let ns = member.xml_namespace().unwrap();
assert_eq!(ns.uri(), "http://ns.example/x");
assert_eq!(ns.prefix(), Some("px"));
assert_eq!(member.member_name(), Some("fieldName"));
let members = [&member];
let schema = Schema::new_struct(
ShapeId::from_parts(&arena[0], &arena[1], &arena[2]),
ShapeType::Structure,
&members,
);
assert_eq!(
schema
.member_schema("fieldName")
.unwrap()
.json_name()
.unwrap()
.value(),
"WireName"
);
}
#[test]
fn runtime_http_trait_from_an_arena() {
let method = String::from("PATCH");
let uri = String::from("/things/{id}");
let schema = Schema::new(shape_id!("ns", "Op"), ShapeType::Structure)
.with_http(crate::traits::HttpTrait::new(&method, &uri, Some(204)));
let http = schema.http().unwrap();
assert_eq!(http.method(), "PATCH");
assert_eq!(http.uri(), "/things/{id}");
assert_eq!(http.code(), 204);
}
#[test]
fn static_schema_still_yields_static_trait_values() {
static S: Schema<'static> =
Schema::new(shape_id!("ns", "Foo"), ShapeType::String).with_json_name("WireName");
let value: &'static str = S.json_name().unwrap().value();
assert_eq!(value, "WireName");
}
#[test]
fn intern_header_name_dedups_by_pointer() {
let from_runtime = String::from("x-dedup-probe");
let a = crate::intern_header_name(&from_runtime);
let b = crate::intern_header_name("x-dedup-probe");
let c = crate::intern_header_name(&String::from("x-dedup-probe"));
assert_eq!(a, "x-dedup-probe");
assert!(std::ptr::eq(a, b), "second intern must reuse the first");
assert!(std::ptr::eq(a, c), "third intern must reuse the first");
let other = crate::intern_header_name("x-dedup-other");
assert!(!std::ptr::eq(a, other));
assert_eq!(other, "x-dedup-other");
}
#[test]
fn interned_header_name_preserves_the_binder_fast_path() {
let arena: Vec<String> = vec![String::from("memberName"), String::from("x-runtime-hdr")];
let member: Schema<'_> =
Schema::new_member(shape_id!("ns", "Foo"), ShapeType::String, &arena[0], 0)
.with_http_header(crate::intern_header_name(&arena[1]));
assert_eq!(
member.http_header().unwrap().value_static(),
Some("x-runtime-hdr")
);
assert_eq!(member.http_header().unwrap().value(), "x-runtime-hdr");
}
#[test]
fn streaming_accessor_reflects_the_propagated_trait() {
static BUFFERED: Schema<'static> = Schema::new_member(
shape_id!("com.example", "Output", "body"),
ShapeType::Blob,
"body",
0,
);
assert!(
!BUFFERED.streaming(),
"a member whose target is not streaming must report false"
);
static STREAMING_BLOB: Schema<'static> = Schema::new_member(
shape_id!("com.example", "Output", "body"),
ShapeType::Blob,
"body",
0,
)
.with_streaming();
assert!(STREAMING_BLOB.streaming());
static EVENT_STREAM: Schema<'static> = Schema::new_member(
shape_id!("com.example", "Output", "events"),
ShapeType::Union,
"events",
0,
)
.with_streaming();
assert!(EVENT_STREAM.streaming());
static STREAMING_PAYLOAD: Schema<'static> = Schema::new_member(
shape_id!("com.example", "Output", "body"),
ShapeType::Blob,
"body",
0,
)
.with_http_payload()
.with_streaming();
assert!(STREAMING_PAYLOAD.streaming());
assert!(STREAMING_PAYLOAD.http_payload().is_some());
static BUFFERED_PAYLOAD: Schema<'static> = Schema::new_member(
shape_id!("com.example", "Output", "body"),
ShapeType::Blob,
"body",
0,
)
.with_http_payload();
assert!(!BUFFERED_PAYLOAD.streaming());
assert!(BUFFERED_PAYLOAD.http_payload().is_some());
}
#[test]
#[cfg(target_pointer_width = "64")]
fn schema_size_stays_within_its_budget() {
assert_eq!(
std::mem::size_of::<Schema<'static>>(),
352,
"Schema changed size; see this test's documentation before updating the number"
);
}
#[test]
fn member_index_encoding_round_trips() {
assert_eq!(MemberIndex::NONE.get(), None);
for index in [0usize, 1, 2, 41, 61, 62, 63, 1000, usize::MAX - 1] {
assert_eq!(
MemberIndex::new(index).get(),
Some(index),
"index {index} did not round-trip"
);
}
assert_eq!(
std::mem::size_of::<MemberIndex>(),
std::mem::size_of::<usize>()
);
assert!(std::mem::size_of::<MemberIndex>() < std::mem::size_of::<Option<usize>>());
}
#[test]
fn member_index_zero_is_distinct_from_absent() {
assert_eq!(MemberIndex::new(0).get(), Some(0));
assert_ne!(MemberIndex::new(0), MemberIndex::NONE);
let member = Schema::new_member(
shape_id!("com.example", "S", "first"),
ShapeType::String,
"first",
0,
);
assert_eq!(
member.member_index(),
Some(0),
"the first member must report index 0, not absent"
);
let non_member = Schema::new(shape_id!("com.example", "S"), ShapeType::Structure);
assert_eq!(non_member.member_index(), None);
}
#[test]
#[should_panic(expected = "member index must be less than usize::MAX")]
fn member_index_rejects_the_unrepresentable_index() {
let _ = MemberIndex::new(usize::MAX);
}
#[test]
fn debug_output_shows_the_decoded_member_index() {
let member = Schema::new_member(
shape_id!("com.example", "S", "third"),
ShapeType::String,
"third",
2,
);
let rendered = format!("{member:?}");
assert!(
rendered.contains("member_index: Some(2)"),
"expected the decoded index in Debug output, got: {rendered}"
);
assert!(
!rendered.contains("member_index: Some(3)") && !rendered.contains("member_index: 3"),
"the raw encoded value leaked into Debug output: {rendered}"
);
let non_member = Schema::new(shape_id!("com.example", "S"), ShapeType::Structure);
assert!(
format!("{non_member:?}").contains("member_index: None"),
"absent index should render as None"
);
}
fn mask_indices(schema: &Schema<'_>) -> Vec<usize> {
let mut mask = schema.response_binding_mask();
let mut out = Vec::new();
while mask != 0 {
let index = mask.trailing_zeros() as usize;
mask &= mask - 1;
out.push(index);
}
out
}
#[test]
fn routing_word_marks_only_response_bound_members() {
static HEADER: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "h"), ShapeType::String, "h", 0)
.with_http_header("x-h");
static BODY: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "b"), ShapeType::String, "b", 1);
static STATUS: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "s"), ShapeType::Integer, "s", 2)
.with_http_response_code();
static PREFIX: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "p"), ShapeType::Map, "p", 3)
.with_http_prefix_headers("x-meta-");
static PAYLOAD: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "pay"), ShapeType::Blob, "pay", 4)
.with_http_payload();
static OUTPUT: Schema<'static> = Schema::new_struct(
shape_id!("t", "O"),
ShapeType::Structure,
&[&HEADER, &BODY, &STATUS, &PREFIX, &PAYLOAD],
);
assert_eq!(mask_indices(&OUTPUT), vec![0, 2, 3, 4]);
assert!(OUTPUT.has_response_body_members());
assert!(!OUTPUT.response_bindings_need_scan());
for (i, member) in OUTPUT.members().iter().enumerate() {
assert_eq!(
OUTPUT.response_binding_mask() & (1u64 << i) != 0,
member.has_http_response_binding(),
"mask bit {i} disagrees with has_http_response_binding()"
);
}
}
#[test]
fn request_only_bindings_are_body_members_on_a_response() {
static QUERY: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "q"), ShapeType::String, "q", 0)
.with_http_query("q");
static LABEL: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "l"), ShapeType::String, "l", 1)
.with_http_label();
static OUTPUT: Schema<'static> =
Schema::new_struct(shape_id!("t", "O"), ShapeType::Structure, &[&QUERY, &LABEL]);
assert_eq!(
mask_indices(&OUTPUT),
Vec::<usize>::new(),
"request-only bindings must not be treated as response-bound"
);
assert!(
OUTPUT.has_response_body_members(),
"request-only bound members are body members on a response"
);
}
#[test]
fn a_fully_bound_structure_has_no_body_members() {
static H1: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "a"), ShapeType::String, "a", 0)
.with_http_header("x-a");
static H2: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "b"), ShapeType::String, "b", 1)
.with_http_header("x-b");
static OUTPUT: Schema<'static> =
Schema::new_struct(shape_id!("t", "O"), ShapeType::Structure, &[&H1, &H2]);
assert_eq!(mask_indices(&OUTPUT), vec![0, 1]);
assert!(!OUTPUT.has_response_body_members());
assert!(!OUTPUT.response_bindings_need_scan());
}
#[test]
fn an_empty_structure_routes_through_the_composite() {
static EMPTY: Schema<'static> =
Schema::new_struct(shape_id!("t", "E"), ShapeType::Structure, &[]);
assert_eq!(EMPTY.response_binding_mask(), 0);
assert!(!EMPTY.has_response_body_members());
assert!(!EMPTY.response_bindings_need_scan());
}
#[test]
fn a_body_only_structure_reports_exactly_that() {
static M0: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "a"), ShapeType::String, "a", 0);
static M1: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "b"), ShapeType::Integer, "b", 1);
static OUTPUT: Schema<'static> =
Schema::new_struct(shape_id!("t", "O"), ShapeType::Structure, &[&M0, &M1]);
assert_eq!(OUTPUT.response_binding_mask(), 0);
assert!(OUTPUT.has_response_body_members());
assert!(!OUTPUT.response_bindings_need_scan());
}
#[test]
fn non_struct_schemas_default_to_the_conservative_word() {
let scalar = Schema::new(shape_id!("t", "S"), ShapeType::String);
assert_eq!(scalar.response_binding_mask(), 0);
assert!(!scalar.has_response_body_members());
assert!(!scalar.response_bindings_need_scan());
let member = Schema::new_member(shape_id!("t", "O", "m"), ShapeType::String, "m", 0);
assert_eq!(member.response_binding_mask(), 0);
assert!(!member.has_response_body_members());
}
#[test]
fn unions_get_a_routing_word_too() {
static V0: Schema<'static> =
Schema::new_member(shape_id!("t", "U", "a"), ShapeType::String, "a", 0);
static V1: Schema<'static> =
Schema::new_member(shape_id!("t", "U", "b"), ShapeType::String, "b", 1);
static UNION: Schema<'static> =
Schema::new_struct(shape_id!("t", "U"), ShapeType::Union, &[&V0, &V1]);
assert_eq!(UNION.response_binding_mask(), 0);
assert!(UNION.has_response_body_members());
}
#[test]
fn mask_and_scan_paths_agree_on_the_same_members() {
static H: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "h"), ShapeType::String, "h", 0)
.with_http_header("x-h");
static B: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "b"), ShapeType::String, "b", 1);
static S: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "s"), ShapeType::Integer, "s", 2)
.with_http_response_code();
static OUTPUT: Schema<'static> =
Schema::new_struct(shape_id!("t", "O"), ShapeType::Structure, &[&H, &B, &S]);
let via_mask = mask_indices(&OUTPUT);
let via_scan: Vec<usize> = OUTPUT
.members()
.iter()
.enumerate()
.filter(|(_, m)| m.has_http_response_binding())
.map(|(i, _)| i)
.collect();
assert_eq!(via_mask, via_scan);
}
#[test]
fn mask_bits_resolve_to_member_schemas_in_o1() {
static H: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "h"), ShapeType::String, "h", 0)
.with_http_header("x-wanted");
static B: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "b"), ShapeType::String, "b", 1);
static OUTPUT: Schema<'static> =
Schema::new_struct(shape_id!("t", "O"), ShapeType::Structure, &[&H, &B]);
let indices = mask_indices(&OUTPUT);
assert_eq!(indices, vec![0]);
let member = OUTPUT
.member_schema_by_index(indices[0])
.expect("a mask bit must resolve to a member");
assert_eq!(member.member_name(), Some("h"));
assert_eq!(member.http_header().map(|h| h.value()), Some("x-wanted"));
}
fn struct_with(count: usize, bound: &[usize]) -> (Vec<Schema<'static>>, Vec<&'static str>) {
let names: Vec<&'static str> = (0..count)
.map(|i| Box::leak(format!("m{i}").into_boxed_str()) as &'static str)
.collect();
let members: Vec<Schema<'static>> = (0..count)
.map(|i| {
let m = Schema::new_member(shape_id!("t", "Big"), ShapeType::String, names[i], i);
if bound.contains(&i) {
m.with_http_header(names[i])
} else {
m
}
})
.collect();
(members, names)
}
#[test]
fn a_bound_member_beyond_the_mask_forces_the_scan_fallback() {
let count = 64;
let all: Vec<usize> = (0..count).collect();
let (members, _names) = struct_with(count, &all);
let refs: Vec<&Schema<'static>> = members.iter().collect();
let schema = Schema::new_struct(shape_id!("t", "Big"), ShapeType::Structure, &refs);
assert!(
schema.response_bindings_need_scan(),
"a bound member at position >= 62 must set the scan flag"
);
assert!(
!schema.has_response_body_members(),
"every member is bound, so there is nothing for the body codec"
);
let via_scan: Vec<usize> = schema
.members()
.iter()
.enumerate()
.filter(|(_, m)| m.has_http_response_binding())
.map(|(i, _)| i)
.collect();
assert_eq!(via_scan, all, "the scan path must see all 64 bound members");
}
#[test]
fn a_structure_at_the_mask_boundary_stays_exact() {
let count = crate::response_routing::MASK_BITS;
let all: Vec<usize> = (0..count).collect();
let (members, _names) = struct_with(count, &all);
let refs: Vec<&Schema<'static>> = members.iter().collect();
let schema = Schema::new_struct(shape_id!("t", "Big"), ShapeType::Structure, &refs);
assert!(
!schema.response_bindings_need_scan(),
"62 members fit the mask exactly and must not trigger a scan"
);
assert_eq!(
schema.response_binding_mask().count_ones() as usize,
count,
"every one of the 62 positions should be marked"
);
assert_eq!(mask_indices(&schema), all);
}
#[test]
fn many_members_stay_exact_when_the_bindings_are_within_range() {
let (members, _names) = struct_with(70, &[0, 5, 61]);
let refs: Vec<&Schema<'static>> = members.iter().collect();
let schema = Schema::new_struct(shape_id!("t", "Big"), ShapeType::Structure, &refs);
assert!(!schema.response_bindings_need_scan());
assert_eq!(mask_indices(&schema), vec![0, 5, 61]);
assert!(schema.has_response_body_members());
}
#[test]
fn the_mask_handles_sparse_bindings() {
let (members, _names) = struct_with(50, &[3, 17, 42, 49]);
let refs: Vec<&Schema<'static>> = members.iter().collect();
let schema = Schema::new_struct(shape_id!("t", "Big"), ShapeType::Structure, &refs);
assert_eq!(mask_indices(&schema), vec![3, 17, 42, 49]);
assert!(schema.has_response_body_members());
assert_eq!(schema.response_binding_mask().count_ones(), 4);
}
#[test]
#[should_panic(expected = "member-index order")]
fn out_of_order_members_are_rejected() {
static A: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "a"), ShapeType::String, "a", 0);
static B: Schema<'static> =
Schema::new_member(shape_id!("t", "O", "b"), ShapeType::String, "b", 1);
let _ = Schema::new_struct(shape_id!("t", "O"), ShapeType::Structure, &[&B, &A]);
}
#[test]
#[should_panic(expected = "must carry a member index")]
fn a_non_member_in_the_member_array_is_rejected() {
static NOT_A_MEMBER: Schema<'static> = Schema::new(shape_id!("t", "X"), ShapeType::String);
let _ = Schema::new_struct(shape_id!("t", "O"), ShapeType::Structure, &[&NOT_A_MEMBER]);
}
}