Skip to main content

topcoat_openapi/
open_api.rs

1use std::borrow::Cow;
2
3use topcoat_router::Router;
4
5use crate::{Document, SecurityScheme, generate::generate};
6
7/// The settings of an OpenAPI document, such as the API's title and version.
8///
9/// Call [`generate`](Self::generate) with a router to create the document
10/// for its documented routes. For example, a route can serve it:
11///
12/// ```
13/// use topcoat::{
14///     Result,
15///     context::Cx,
16///     openapi::{Document, OpenApi},
17///     router::{route, router},
18/// };
19///
20/// #[route(GET "/openapi.json")]
21/// async fn spec(cx: &Cx) -> Result<Document> {
22///     Ok(OpenApi::new("Acme API", "1.0.0")
23///         .description("Everything you need for tea.")
24///         .generate(&router(cx)))
25/// }
26/// ```
27///
28/// See [`Operation`](crate::Operation) for documenting routes.
29#[derive(Debug, Clone)]
30pub struct OpenApi {
31    pub(crate) title: Cow<'static, str>,
32    pub(crate) version: Cow<'static, str>,
33    pub(crate) description: Option<Cow<'static, str>>,
34    pub(crate) servers: Vec<Cow<'static, str>>,
35    pub(crate) tags: Vec<(Cow<'static, str>, Cow<'static, str>)>,
36    pub(crate) security_schemes: Vec<(Cow<'static, str>, SecurityScheme)>,
37}
38
39impl OpenApi {
40    /// Creates the settings for an API with the given title and version.
41    ///
42    /// The version is that of the API itself, such as `1.0.0`.
43    #[must_use]
44    pub fn new(title: impl Into<Cow<'static, str>>, version: impl Into<Cow<'static, str>>) -> Self {
45        Self {
46            title: title.into(),
47            version: version.into(),
48            description: None,
49            servers: Vec::new(),
50            tags: Vec::new(),
51            security_schemes: Vec::new(),
52        }
53    }
54
55    /// Sets a description of the API as a whole.
56    #[must_use]
57    pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
58        self.description = Some(description.into());
59        self
60    }
61
62    /// Adds a base URL the API is reachable at, such as
63    /// `https://api.example.com`.
64    #[must_use]
65    pub fn server(mut self, url: impl Into<Cow<'static, str>>) -> Self {
66        self.servers.push(url.into());
67        self
68    }
69
70    /// Describes a tag that operations use.
71    ///
72    /// Documentation tools show the description next to the tag's routes
73    /// and list tags in the order they are described here.
74    #[must_use]
75    pub fn tag(
76        mut self,
77        name: impl Into<Cow<'static, str>>,
78        description: impl Into<Cow<'static, str>>,
79    ) -> Self {
80        self.tags.push((name.into(), description.into()));
81        self
82    }
83
84    /// Registers a way for clients to authenticate under `name`.
85    ///
86    /// Operations require it by that name with
87    /// [`Operation::security`](crate::Operation::security).
88    #[must_use]
89    pub fn security_scheme(
90        mut self,
91        name: impl Into<Cow<'static, str>>,
92        scheme: SecurityScheme,
93    ) -> Self {
94        self.security_schemes.push((name.into(), scheme));
95        self
96    }
97
98    /// Creates the document for the documented routes of `router`.
99    ///
100    /// A route is documented when an [`Operation`](crate::Operation) is
101    /// attached to it, to a layer or layout that wraps it, or to the router
102    /// builder, unless the operation is
103    /// [hidden](crate::Operation::hidden).
104    ///
105    /// # Panics
106    ///
107    /// Panics when the routes are documented inconsistently:
108    ///
109    /// - An operation describes a path parameter that its route's path does not have.
110    /// - An operation requires a security scheme that is not registered.
111    /// - Two operations share an operation ID.
112    /// - A type passed to [`Operation::query`](crate::Operation::query) is not a struct with named
113    ///   fields.
114    #[must_use]
115    pub fn generate(&self, router: &Router) -> Document {
116        generate(self, router)
117    }
118}