1use utoipa::{
5 Modify, PartialSchema, ToSchema,
6 openapi::{
7 OpenApi, Ref, RefOr, ResponseBuilder, SecurityRequirement,
8 content::Content,
9 path::Operation,
10 security::{Http, HttpAuthScheme, SecurityScheme},
11 },
12};
13
14#[derive(ToSchema)]
16pub struct ErrorBody {
17 pub error: String,
19}
20
21pub const TOKEN: &str = "token";
23
24pub struct Conventions;
30
31impl Modify for Conventions {
32 fn modify(&self, openapi: &mut OpenApi) {
33 let components = openapi.components.get_or_insert_with(Default::default);
34 components.add_security_scheme(
35 TOKEN,
36 SecurityScheme::Http(
37 Http::builder()
38 .scheme(HttpAuthScheme::Bearer)
39 .bearer_format("PASETO v4.public")
40 .description(Some(
41 "From the auth service's `POST /login`; valid for 15 minutes.",
42 ))
43 .build(),
44 ),
45 );
46 components
47 .schemas
48 .insert("ErrorBody".into(), ErrorBody::schema());
49 for item in openapi.paths.paths.values_mut() {
50 for operation in [
51 &mut item.get,
52 &mut item.put,
53 &mut item.post,
54 &mut item.delete,
55 ]
56 .into_iter()
57 .flatten()
58 {
59 conventions(operation);
60 }
61 }
62 }
63}
64
65fn conventions(operation: &mut Operation) {
66 if let (Some(id), Some([tag, ..])) = (&operation.operation_id, operation.tags.as_deref())
67 && id != tag
68 {
69 operation.operation_id = Some(format!("{tag}.{id}"));
70 }
71 if operation.security.is_none() {
72 operation.security = Some(vec![SecurityRequirement::new::<_, [&str; 0], &str>(
73 TOKEN,
74 [],
75 )]);
76 add_error(operation, "401", "No token, or not a valid one.");
77 }
78 add_error(
79 operation,
80 "429",
81 "Too many requests from this address; `Retry-After` says when to try again.",
82 );
83}
84
85pub fn add_error(operation: &mut Operation, status: &str, description: &str) {
87 operation
88 .responses
89 .responses
90 .entry(status.into())
91 .or_insert_with(|| error(description));
92}
93
94pub fn error(description: &str) -> RefOr<utoipa::openapi::Response> {
96 ResponseBuilder::new()
97 .description(description)
98 .content(
99 "application/json",
100 Content::new(Some(Ref::from_schema_name(ErrorBody::name()))),
101 )
102 .into()
103}