Skip to main content

topcoat_openapi/
content.rs

1use schemars::{JsonSchema, Schema, SchemaGenerator};
2use topcoat_router::content::{Form, Json};
3
4/// A request or response body that an API document can describe.
5///
6/// A type names its media type and the schema of its payload. Pass such a
7/// type to [`Operation::request`](crate::Operation::request) or
8/// [`Operation::response_with`](crate::Operation::response_with).
9///
10/// Implement it for your own extractors and response types:
11///
12/// ```
13/// use topcoat::openapi::{
14///     Content,
15///     schemars::{Schema, SchemaGenerator, json_schema},
16/// };
17///
18/// struct Csv(String);
19///
20/// impl Content for Csv {
21///     const MEDIA_TYPE: &'static str = "text/csv";
22///
23///     fn schema(_generator: &mut SchemaGenerator) -> Schema {
24///         json_schema!({ "type": "string" })
25///     }
26/// }
27/// ```
28pub trait Content {
29    /// The media type of the body, such as `application/json`.
30    const MEDIA_TYPE: &'static str;
31
32    /// Whether a request has to carry this body.
33    const REQUIRED: bool = true;
34
35    /// Returns the schema of the payload.
36    ///
37    /// Call [`SchemaGenerator::subschema_for`] for a payload type that
38    /// implements [`JsonSchema`], so the document lists the type once and
39    /// refers to it from every body that uses it.
40    fn schema(generator: &mut SchemaGenerator) -> Schema;
41}
42
43impl<T: JsonSchema> Content for Json<T> {
44    const MEDIA_TYPE: &'static str = "application/json";
45
46    fn schema(generator: &mut SchemaGenerator) -> Schema {
47        generator.subschema_for::<T>()
48    }
49}
50
51impl<T: JsonSchema> Content for Form<T> {
52    const MEDIA_TYPE: &'static str = "application/x-www-form-urlencoded";
53
54    fn schema(generator: &mut SchemaGenerator) -> Schema {
55        generator.subschema_for::<T>()
56    }
57}
58
59impl Content for String {
60    const MEDIA_TYPE: &'static str = "text/plain";
61
62    fn schema(generator: &mut SchemaGenerator) -> Schema {
63        generator.subschema_for::<String>()
64    }
65}
66
67impl Content for &str {
68    const MEDIA_TYPE: &'static str = "text/plain";
69
70    fn schema(generator: &mut SchemaGenerator) -> Schema {
71        generator.subschema_for::<String>()
72    }
73}
74
75/// A body that a request may leave out.
76impl<T: Content> Content for Option<T> {
77    const MEDIA_TYPE: &'static str = T::MEDIA_TYPE;
78    const REQUIRED: bool = false;
79
80    fn schema(generator: &mut SchemaGenerator) -> Schema {
81        T::schema(generator)
82    }
83}
84
85/// Generates a schema, adding the types it refers to to the generator.
86pub(crate) type SchemaFn = fn(&mut SchemaGenerator) -> Schema;
87
88/// A [`Content`] type, kept without its type parameter.
89#[derive(Debug, Clone, Copy)]
90pub(crate) struct ContentType {
91    pub(crate) media_type: &'static str,
92    pub(crate) required: bool,
93    pub(crate) schema: SchemaFn,
94}
95
96impl ContentType {
97    /// Describes the body type `T`.
98    pub(crate) fn of<T: Content>() -> Self {
99        Self {
100            media_type: T::MEDIA_TYPE,
101            required: T::REQUIRED,
102            schema: T::schema,
103        }
104    }
105}