use crate::{prelude, Schema, ShapeId};
use std::fmt;
#[derive(Clone)]
pub struct OperationSchema<'a> {
id: ShapeId<'a>,
input: &'a Schema<'a>,
output: &'a Schema<'a>,
errors: &'a [&'a Schema<'a>],
}
impl<'a> OperationSchema<'a> {
pub const fn builder(id: ShapeId<'a>) -> OperationSchemaBuilder<'a> {
OperationSchemaBuilder {
id,
input: &prelude::UNIT,
output: &prelude::UNIT,
errors: &[],
}
}
pub fn id(&self) -> ShapeId<'a> {
self.id.clone()
}
pub fn input(&self) -> &'a Schema<'a> {
self.input
}
pub fn output(&self) -> &'a Schema<'a> {
self.output
}
pub fn errors(&self) -> &'a [&'a Schema<'a>] {
self.errors
}
}
impl fmt::Debug for OperationSchema<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
debug_ids(
f,
"OperationSchema",
&self.id,
self.input,
self.output,
self.errors,
)
}
}
#[derive(Clone)]
#[must_use]
pub struct OperationSchemaBuilder<'a> {
id: ShapeId<'a>,
input: &'a Schema<'a>,
output: &'a Schema<'a>,
errors: &'a [&'a Schema<'a>],
}
impl<'a> OperationSchemaBuilder<'a> {
pub const fn input(mut self, input: &'a Schema<'a>) -> Self {
self.input = input;
self
}
pub const fn output(mut self, output: &'a Schema<'a>) -> Self {
self.output = output;
self
}
pub const fn errors(mut self, errors: &'a [&'a Schema<'a>]) -> Self {
self.errors = errors;
self
}
pub const fn build(self) -> OperationSchema<'a> {
OperationSchema {
id: self.id,
input: self.input,
output: self.output,
errors: self.errors,
}
}
}
impl fmt::Debug for OperationSchemaBuilder<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
debug_ids(
f,
"OperationSchemaBuilder",
&self.id,
self.input,
self.output,
self.errors,
)
}
}
fn debug_ids(
f: &mut fmt::Formatter<'_>,
name: &str,
id: &ShapeId<'_>,
input: &Schema<'_>,
output: &Schema<'_>,
errors: &[&Schema<'_>],
) -> fmt::Result {
struct Ids<'s, 'a>(&'s [&'a Schema<'a>]);
impl fmt::Debug for Ids<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(self.0.iter().map(|schema| schema.shape_id().as_str()))
.finish()
}
}
f.debug_struct(name)
.field("id", &id.as_str())
.field("input", &input.shape_id().as_str())
.field("output", &output.shape_id().as_str())
.field("errors", &Ids(errors))
.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{shape_id, ShapeType};
static INPUT: Schema<'static> =
Schema::new_struct(shape_id!("com.example", "Input"), ShapeType::Structure, &[]);
static OUTPUT: Schema<'static> = Schema::new_struct(
shape_id!("com.example", "Output"),
ShapeType::Structure,
&[],
);
static ERROR_A: Schema<'static> = Schema::new_struct(
shape_id!("com.example", "ErrorA"),
ShapeType::Structure,
&[],
);
static ERROR_B: Schema<'static> = Schema::new_struct(
shape_id!("com.example", "ErrorB"),
ShapeType::Structure,
&[],
);
static OPERATION: OperationSchema<'static> =
OperationSchema::builder(shape_id!("com.example", "Operation"))
.input(&INPUT)
.output(&OUTPUT)
.errors(&[&ERROR_B, &ERROR_A])
.build();
#[test]
fn static_descriptor() {
let id = OPERATION.id();
assert_eq!(id.as_str(), "com.example#Operation");
assert_eq!(id.namespace(), "com.example");
assert_eq!(id.shape_name(), "Operation");
assert_eq!(id.member_name(), None);
assert!(std::ptr::eq(OPERATION.input(), &INPUT));
assert!(std::ptr::eq(OPERATION.output(), &OUTPUT));
let errors: Vec<_> = OPERATION
.errors()
.iter()
.map(|error| error.shape_id().as_str())
.collect();
assert_eq!(errors, ["com.example#ErrorB", "com.example#ErrorA"]);
}
#[test]
fn borrowed_descriptor() {
let fqn = String::from("com.example#Borrowed");
let id = ShapeId::from_parts(&fqn, &fqn[..11], &fqn[12..]);
let input = Schema::new_struct(id.clone(), ShapeType::Structure, &[]);
let errors = [&ERROR_A];
let operation = OperationSchema::builder(id)
.input(&input)
.output(&OUTPUT)
.errors(&errors)
.build();
let id = operation.clone().id();
assert_eq!(id.as_str(), "com.example#Borrowed");
assert_eq!(id.namespace(), "com.example");
assert_eq!(id.shape_name(), "Borrowed");
assert!(std::ptr::eq(id.as_str(), fqn.as_str()));
assert!(std::ptr::eq(operation.input(), &input));
}
#[test]
fn builder_defaults_match_an_omitted_input_and_output() {
static DEFAULTS: OperationSchema<'static> =
OperationSchema::builder(shape_id!("com.example", "Ping")).build();
assert!(std::ptr::eq(DEFAULTS.input(), &prelude::UNIT));
assert!(std::ptr::eq(DEFAULTS.output(), &prelude::UNIT));
assert!(DEFAULTS.errors().is_empty());
let operation = OperationSchema::builder(shape_id!("com.example", "Ping"))
.input(&INPUT)
.input(&OUTPUT)
.build();
assert!(std::ptr::eq(operation.input(), &OUTPUT));
}
#[test]
fn debug_shows_ids_only() {
assert_eq!(
format!("{OPERATION:?}"),
r#"OperationSchema { id: "com.example#Operation", input: "com.example#Input", output: "com.example#Output", errors: ["com.example#ErrorB", "com.example#ErrorA"] }"#
);
assert_eq!(
format!(
"{:?}",
OperationSchema::builder(shape_id!("com.example", "Ping"))
),
r#"OperationSchemaBuilder { id: "com.example#Ping", input: "smithy.api#Unit", output: "smithy.api#Unit", errors: [] }"#
);
}
#[test]
#[cfg(target_pointer_width = "64")]
fn size_stays_within_its_budget() {
assert_eq!(std::mem::size_of::<OperationSchema<'static>>(), 96);
}
}