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}