aws_smithy_http_server/schema/
service.rs1use aws_smithy_schema::traits::HttpTrait;
25use aws_smithy_schema::{Schema, ShapeId};
26
27#[derive(Debug)]
29pub struct OperationSchema<'a> {
30 shape_id: ShapeId<'a>,
31 input: &'a Schema<'a>,
32 output: &'a Schema<'a>,
33 errors: &'a [&'a Schema<'a>],
34}
35
36impl<'a> OperationSchema<'a> {
37 pub const fn new(
39 shape_id: ShapeId<'a>,
40 input: &'a Schema<'a>,
41 output: &'a Schema<'a>,
42 errors: &'a [&'a Schema<'a>],
43 ) -> Self {
44 Self {
45 shape_id,
46 input,
47 output,
48 errors,
49 }
50 }
51
52 pub const fn shape_id(&self) -> &ShapeId<'a> {
54 &self.shape_id
55 }
56
57 pub fn http(&self) -> Option<&'a HttpTrait<'a>> {
62 self.input.http()
63 }
64
65 pub fn input(&self) -> &'a Schema<'a> {
67 self.input
68 }
69
70 pub fn output(&self) -> &'a Schema<'a> {
72 self.output
73 }
74
75 pub fn errors(&self) -> &'a [&'a Schema<'a>] {
77 self.errors
78 }
79}
80
81#[derive(Debug)]
83pub struct ServiceSchema<'a> {
84 shape_id: ShapeId<'a>,
85 version: Option<&'a str>,
86 protocols: &'a [ShapeId<'a>],
87 operations: &'a [&'a OperationSchema<'a>],
88 traits: Option<&'a std::sync::LazyLock<aws_smithy_schema::TraitMap>>,
91}
92
93impl<'a> ServiceSchema<'a> {
94 pub const fn new(
100 shape_id: ShapeId<'a>,
101 version: Option<&'a str>,
102 protocols: &'a [ShapeId<'a>],
103 operations: &'a [&'a OperationSchema<'a>],
104 ) -> Self {
105 Self {
106 shape_id,
107 version,
108 protocols,
109 operations,
110 traits: None,
111 }
112 }
113
114 pub const fn with_traits(mut self, traits: &'a std::sync::LazyLock<aws_smithy_schema::TraitMap>) -> Self {
116 self.traits = Some(traits);
117 self
118 }
119
120 pub fn traits(&self) -> Option<&aws_smithy_schema::TraitMap> {
122 self.traits.map(|traits| &**traits)
123 }
124
125 pub const fn shape_id(&self) -> &ShapeId<'a> {
127 &self.shape_id
128 }
129
130 pub const fn version(&self) -> Option<&'a str> {
132 self.version
133 }
134
135 pub fn protocols(&self) -> &'a [ShapeId<'a>] {
137 self.protocols
138 }
139
140 pub fn operations(&self) -> &'a [&'a OperationSchema<'a>] {
142 self.operations
143 }
144
145 pub fn operation(&self, id: &ShapeId<'_>) -> Option<&'a OperationSchema<'a>> {
147 self.operations
148 .iter()
149 .copied()
150 .find(|operation| operation.shape_id().as_str() == id.as_str())
151 }
152}
153
154#[allow(dead_code)]
157fn _assert_operation_schema_covariant<'a, 'b: 'a>(s: OperationSchema<'b>) -> OperationSchema<'a> {
158 s
159}
160
161#[allow(dead_code)]
162fn _assert_service_schema_covariant<'a, 'b: 'a>(s: ServiceSchema<'b>) -> ServiceSchema<'a> {
163 s
164}
165
166#[cfg(test)]
167mod test {
168 use super::*;
169 use aws_smithy_schema::{shape_id, ShapeType};
170
171 static UNIT: Schema<'static> = Schema::new(shape_id!("smithy.api", "Unit"), ShapeType::Structure);
172 static GET_INPUT: Schema<'static> = Schema::new(shape_id!("example", "GetInput"), ShapeType::Structure)
173 .with_http(HttpTrait::new("GET", "/get/{id}", None));
174 static NOT_FOUND: Schema<'static> = Schema::new(shape_id!("example", "NotFound"), ShapeType::Structure);
175 static GET_ERRORS: &[&Schema<'static>] = &[&NOT_FOUND];
176 static GET: OperationSchema<'static> =
177 OperationSchema::new(shape_id!("example", "Get"), &GET_INPUT, &UNIT, GET_ERRORS);
178 static PUT: OperationSchema<'static> = OperationSchema::new(shape_id!("example", "Put"), &UNIT, &UNIT, &[]);
179 static PROTOCOLS: &[ShapeId<'static>] = &[shape_id!("aws.protocols", "restJson1")];
180 static OPERATIONS: &[&OperationSchema<'static>] = &[&GET, &PUT];
181 static SERVICE: ServiceSchema<'static> = ServiceSchema::new(
182 shape_id!("example", "Service"),
183 Some("2024-01-01"),
184 PROTOCOLS,
185 OPERATIONS,
186 );
187
188 #[test]
189 fn operation_descriptor_exposes_shapes_and_http_binding() {
190 assert_eq!(GET.shape_id().as_str(), "example#Get");
191 assert!(std::ptr::eq(GET.input(), &GET_INPUT));
192 assert!(std::ptr::eq(GET.output(), &UNIT));
193 assert_eq!(GET.errors().len(), 1);
194 assert_eq!(GET.errors()[0].shape_id().as_str(), "example#NotFound");
195
196 let http = GET.http().expect("@http on the operation input");
197 assert_eq!(http.method(), "GET");
198 assert_eq!(http.uri(), "/get/{id}");
199 assert!(PUT.http().is_none());
200 }
201
202 #[test]
203 fn service_descriptor_exposes_version_protocols_and_operations() {
204 assert_eq!(SERVICE.shape_id().as_str(), "example#Service");
205 assert_eq!(SERVICE.version(), Some("2024-01-01"));
206 assert_eq!(SERVICE.protocols().len(), 1);
207 assert_eq!(SERVICE.protocols()[0].as_str(), "aws.protocols#restJson1");
208 assert_eq!(SERVICE.operations().len(), 2);
209 }
210
211 #[test]
212 fn service_descriptor_looks_up_operations_by_shape_id() {
213 let put = SERVICE
214 .operation(&shape_id!("example", "Put"))
215 .expect("bound operation");
216 assert!(std::ptr::eq(put, &PUT));
217 assert!(SERVICE.operation(&shape_id!("example", "Missing")).is_none());
218 }
219
220 #[test]
221 fn descriptors_can_be_built_with_a_non_static_lifetime() {
222 let operation = OperationSchema::new(ShapeId::from_parts("ns#Op", "ns", "Op"), &UNIT, &UNIT, &[]);
223 let operations = [&operation];
224 let service = ServiceSchema::new(ShapeId::from_parts("ns#Svc", "ns", "Svc"), None, &[], &operations);
225
226 assert_eq!(service.operations()[0].shape_id().as_str(), "ns#Op");
227 assert!(service.version().is_none());
228 }
229}