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}