#![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 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 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(Debug)]
pub struct Schema<'a> {
id: ShapeId<'a>,
shape_type: ShapeType,
member_name: Option<&'a str>,
member_index: Option<usize>,
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>>,
}
#[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);
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: None,
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,
}
}
pub const fn new(id: ShapeId<'a>, shape_type: ShapeType) -> Self {
Self {
id,
shape_type,
..Self::empty_traits()
}
}
pub const fn new_struct(
id: ShapeId<'a>,
shape_type: ShapeType,
members: &'a [&'a Schema<'a>],
) -> Self {
Self {
id,
shape_type,
members: SchemaMembers::Struct { members },
..Self::empty_traits()
}
}
pub const fn new_list(id: ShapeId<'a>, member: &'a Schema<'a>) -> Self {
Self {
id,
shape_type: ShapeType::List,
members: SchemaMembers::List { member },
..Self::empty_traits()
}
}
pub const fn new_map(id: ShapeId<'a>, key: &'a Schema<'a>, value: &'a Schema<'a>) -> Self {
Self {
id,
shape_type: ShapeType::Map,
members: SchemaMembers::Map { key, value },
..Self::empty_traits()
}
}
pub const fn new_member(
id: ShapeId<'a>,
shape_type: ShapeType,
member_name: &'a str,
member_index: usize,
) -> Self {
Self {
id,
shape_type,
member_name: Some(member_name),
member_index: Some(member_index),
..Self::empty_traits()
}
}
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 has_body_members(&self) -> bool {
self.has_body_members
}
pub fn payload_hint(&self) -> PayloadHint {
self.payload_hint
}
pub 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 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
}
pub fn member_name(&self) -> Option<&'a str> {
self.member_name
}
pub fn member_index(&self) -> Option<usize> {
self.member_index
}
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, 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");
}
}