seamless/api/info.rs
1use std::collections::HashMap;
2use serde::Serialize;
3
4/// A representation of some type, including its description and shape.
5/// This is given back for anything which implements the [`trait@crate::ApiBody`] trait,
6/// and is automatically generated if one uses the [`macro@crate::ApiBody`] macro on some type.
7#[derive(Debug,Clone,PartialEq,Eq,Serialize)]
8pub struct ApiBodyInfo {
9 /// A human friendly description of the type. When using the
10 /// [`ApiBody`](seamless_macros::ApiBody) macro, this will be automatically
11 /// populated based on the doc comments on the type.
12 pub description: String,
13 /// The shape of the type. This should correspond to the JSON returned when
14 /// serializing the type. If you use the [`ApiBody`](seamless_macros::ApiBody)
15 /// macro, this is guaranteed to be the case.
16 #[serde(rename = "shape")]
17 pub ty: ApiBodyType
18}
19
20// Primarily for internal use; structs can
21// be converted directly to this, so we know at the
22// type level that they can be represented in this way,
23// and can skip a little enum matching
24#[doc(hidden)]
25pub struct ApiBodyStructInfo {
26 pub description: String,
27 pub struc: HashMap<String, ApiBodyInfo>
28}
29
30/// An enum representing the shape of the JSON that is provided or output from the API.
31/// There is a straightforward mapping from this to TypeScript types.
32#[derive(Debug,Clone,PartialEq,Eq,Serialize)]
33#[serde(tag = "type")]
34pub enum ApiBodyType {
35 /// Corresponds to the TypeScript type `string`.
36 String,
37 /// Corresponds to the TypeScript type `number`.
38 Number,
39 /// Corresponds to the TypeScript type `boolean`.
40 Boolean,
41 /// Corresponds to the TypeScript type `null`.
42 Null,
43 /// Corresponds to the TypeScript type `any`.
44 ///
45 /// This is used when the shape cannot be statically determined, and we want to
46 /// indicate that any type can be provided. In many cases [`ApiBodyType::Unknown`]
47 /// may be preferred.
48 Any,
49 /// Corresponds to the TypeScript type `unknown`.
50 ///
51 /// This is used when the shape cannot be statically determined, and we want to
52 /// indicate that we're not sure what the type is, unlike [`ApiBodyType::Any`]
53 /// which hints that *any* type can be provided.
54 Unknown,
55 /// Indicate that we expect binary data to be provided. This may correspond to
56 /// `Blob` in a UI.
57 Binary,
58 /// An array of values of one type, where each value has the type `value`, eg
59 /// `string[]` or `number[]`.
60 ArrayOf {
61 /// The type of all of the values in the array.
62 value: Box<ApiBodyInfo>
63 },
64 /// A fixed length array of values that can be of mixed types, eg
65 /// `[string, number, Foo]`.
66 TupleOf {
67 /// A list of each of the types in this fixed length array.
68 values: Vec<ApiBodyInfo>
69 },
70 /// An object where the keys are strings and the values are all of the same type, eg
71 /// `{ [key: string]: Foo }`.
72 ObjectOf {
73 /// The type of all of the values in the object/map.
74 value: Box<ApiBodyInfo>
75 },
76 /// An object whose keys and value types are known at compile time, eg
77 /// `{ foo: string, bar: boolean, wibble: Foo }`.
78 Object {
79 /// The property name and type of each entry in the object.
80 keys: HashMap<String, ApiBodyInfo>
81 },
82 /// The type is one of several variants, eg
83 /// `string | number | Foo`.
84 OneOf {
85 /// Each of the possible types that this can be.
86 values: Vec<ApiBodyInfo>
87 },
88 /// The type is a string literal with a specific value, eg
89 /// `"stringvalue"`.
90 StringLiteral {
91 /// The exact string literal that we expect.
92 literal: String
93 },
94 /// The type is optional, and need not be provided. It corresponds to either
95 /// `{ key?: Foo }` in objects, or `Foo | undefined` in other contexts.
96 Optional {
97 /// The type that is optional.
98 value: Box<ApiBodyInfo>
99 }
100}
101
102/// Any type that implements this trait can be described in terms of [`ApiBodyInfo`], and
103/// can potentially also be serialized or deserizlied from JSON.
104///
105/// This type should not be manually implemented in most cases; instead the [`ApiBody`](seamless_macros::ApiBody)
106/// macro should be relied on to ensure that the description and shape of the type are consistent with how
107/// it will be serialized.
108///
109/// In some cases however, it is necessary to manually implement this for a type (for example, an external type).
110pub trait ApiBody {
111 /// This returns information about the shape of the type and description of parts of it.
112 fn api_body_info() -> ApiBodyInfo;
113
114 /// Serialize the type to JSON.
115 fn to_json_vec(&self) -> Vec<u8>
116 where Self: ::serde::Serialize {
117 serde_json::to_vec(self)
118 .expect("Failed to serialize to JSON due to an invalid manual implementation (1)")
119 }
120
121 /// Serialize the type to a [`serde_json::Value`].
122 fn to_json_value(&self) -> serde_json::Value
123 where Self: ::serde::Serialize {
124 serde_json::to_value(self)
125 .expect("Failed to serialize to JSON due to an invalid manual implementation (2)")
126 }
127
128 /// Deserialize from bytes containing a JSON value.
129 fn from_json_slice(bytes: &[u8]) -> serde_json::Result<Self>
130 where Self: ::serde::de::DeserializeOwned {
131 serde_json::from_slice(bytes)
132 }
133
134 /// Deserialize from a [`serde_json::Value`].
135 fn from_json_value(value: serde_json::Value) -> serde_json::Result<Self>
136 where Self: ::serde::de::DeserializeOwned {
137 serde_json::from_value(value)
138 }
139}
140
141/// This trait is implemented for all struct types, so that we know, at compile time,
142/// whether we're working with a struct type that can be flattened or not.
143#[doc(hidden)]
144pub trait ApiBodyStruct {
145 fn api_body_struct_info() -> ApiBodyStructInfo;
146}
147
148impl <T: ApiBodyStruct> ApiBodyStruct for Box<T> {
149 fn api_body_struct_info() -> ApiBodyStructInfo {
150 T::api_body_struct_info()
151 }
152}
153
154// *** Below are the various built-in implementations of ApiBodyInfo ***
155
156// Boxing:
157impl <T: ApiBody> ApiBody for Box<T> {
158 fn api_body_info() -> ApiBodyInfo {
159 T::api_body_info()
160 }
161}
162
163// Basic collections:
164impl <T: ApiBody> ApiBody for Vec<T> {
165 fn api_body_info() -> ApiBodyInfo {
166 ApiBodyInfo {
167 description: String::new(),
168 ty: ApiBodyType::ArrayOf { value: Box::new(T::api_body_info()) }
169 }
170 }
171}
172impl <T: ApiBody> ApiBody for HashMap<String,T> {
173 fn api_body_info() -> ApiBodyInfo {
174 ApiBodyInfo {
175 description: String::new(),
176 ty: ApiBodyType::ObjectOf { value: Box::new(T::api_body_info()) }
177 }
178 }
179}
180impl <T: ApiBody> ApiBody for Option<T> {
181 fn api_body_info() -> ApiBodyInfo {
182 ApiBodyInfo {
183 description: String::new(),
184 ty: ApiBodyType::Optional { value: Box::new(T::api_body_info()) }
185 }
186 }
187}
188
189// Primitives:
190macro_rules! impl_api_body {
191 ( $( $($name:path),+ => $ty:expr ),+ ) => (
192 $($(
193 impl ApiBody for $name {
194 fn api_body_info() -> ApiBodyInfo {
195 ApiBodyInfo {
196 description: String::new(),
197 ty: $ty
198 }
199 }
200 }
201 )+)+
202 )
203}
204impl_api_body! {
205 i8, i16, i32, i64, isize,
206 u8, u16, u32, u64, usize,
207 f32, f64,
208 std::sync::atomic::AtomicI8,
209 std::sync::atomic::AtomicI16,
210 std::sync::atomic::AtomicI32,
211 std::sync::atomic::AtomicI64,
212 std::sync::atomic::AtomicIsize,
213 std::sync::atomic::AtomicU8,
214 std::sync::atomic::AtomicU16,
215 std::sync::atomic::AtomicU32,
216 std::sync::atomic::AtomicU64,
217 serde_json::Number,
218 std::sync::atomic::AtomicUsize => ApiBodyType::Number,
219 bool,
220 std::sync::atomic::AtomicBool => ApiBodyType::Boolean,
221 String => ApiBodyType::String
222}
223impl <'a> ApiBody for &'a str {
224 fn api_body_info() -> ApiBodyInfo {
225 ApiBodyInfo {
226 description: String::new(),
227 ty: ApiBodyType::String
228 }
229 }
230}
231
232// Tuples:
233impl ApiBody for () {
234 fn api_body_info() -> ApiBodyInfo {
235 ApiBodyInfo {
236 description: String::new(),
237 ty: ApiBodyType::Null
238 }
239 }
240}
241macro_rules! impl_api_body_tuples {
242 ( $( $( $name:ident )+ ),+ ) => (
243 $(
244 impl <$($name: ApiBody),+> ApiBody for ( $($name,)+ ) {
245 fn api_body_info() -> ApiBodyInfo {
246 ApiBodyInfo {
247 description: String::new(),
248 ty: ApiBodyType::TupleOf {
249 values: vec![$($name::api_body_info(),)+]
250 }
251 }
252 }
253 }
254 )+
255 )
256}
257impl_api_body_tuples! {
258 A,
259 A B,
260 A B C,
261 A B C D,
262 A B C D E,
263 A B C D E F,
264 A B C D E F G,
265 A B C D E F G H,
266 A B C D E F G H I,
267 A B C D E F G H I J
268}
269
270impl ApiBody for serde_json::Value {
271 fn api_body_info() -> ApiBodyInfo {
272 ApiBodyInfo {
273 description: String::new(),
274 ty: ApiBodyType::Any
275 }
276 }
277}
278
279impl <T: ApiBody> ApiBody for serde_json::Map<String, T> {
280 fn api_body_info() -> ApiBodyInfo {
281 ApiBodyInfo {
282 description: String::new(),
283 ty: ApiBodyType::ObjectOf { value: Box::new(T::api_body_info()) }
284 }
285 }
286}
287
288#[cfg(feature = "uuid")]
289impl ApiBody for uuid::Uuid {
290 fn api_body_info() -> ApiBodyInfo {
291 ApiBodyInfo {
292 description: "A 128 bit UUID".to_owned(),
293 ty: ApiBodyType::String
294 }
295 }
296}