summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2024-12-29 22:40:12 +0100
committerJoel Klinghed <the_jk@spawned.biz>2024-12-29 22:40:12 +0100
commit776406684bdc591a4c97b58b8d28f689881c285e (patch)
tree7551fcc2d10eb4d75314b2ad93bce6c328481413 /server
parent7bc8e8b7262a3f3abe3222b3b434838e85cdb2bb (diff)
Add openapi generation using utoipa
Diffstat (limited to 'server')
-rw-r--r--server/Cargo.lock244
-rw-r--r--server/Cargo.toml2
-rw-r--r--server/src/api_model.rs47
-rw-r--r--server/src/auth.rs80
-rw-r--r--server/src/main.rs55
5 files changed, 397 insertions, 31 deletions
diff --git a/server/Cargo.lock b/server/Cargo.lock
index 865b62d..68acb20 100644
--- a/server/Cargo.lock
+++ b/server/Cargo.lock
@@ -81,6 +81,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
+name = "arbitrary"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
name = "async-stream"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -201,6 +210,12 @@ dependencies = [
]
[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
name = "bytemuck"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -291,6 +306,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
+name = "crc32fast"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -346,6 +370,17 @@ dependencies = [
]
[[package]]
+name = "derive_arbitrary"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.90",
+]
+
+[[package]]
name = "devise"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -468,6 +503,8 @@ dependencies = [
"serde",
"sqlx",
"time",
+ "utoipa",
+ "utoipa-swagger-ui",
]
[[package]]
@@ -491,6 +528,16 @@ dependencies = [
]
[[package]]
+name = "flate2"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
name = "flume"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1076,6 +1123,12 @@ dependencies = [
]
[[package]]
+name = "lockfree-object-pool"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
+
+[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1128,6 +1181,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1689,6 +1752,40 @@ dependencies = [
]
[[package]]
+name = "rust-embed"
+version = "8.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn 2.0.90",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
+dependencies = [
+ "sha2",
+ "walkdir",
+]
+
+[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1750,6 +1847,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1869,6 +1975,12 @@ dependencies = [
]
[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
+[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1969,7 +2081,7 @@ dependencies = [
"sha2",
"smallvec",
"sqlformat",
- "thiserror",
+ "thiserror 1.0.69",
"tokio",
"tokio-stream",
"tracing",
@@ -2051,7 +2163,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
- "thiserror",
+ "thiserror 1.0.69",
"tracing",
"whoami",
]
@@ -2089,7 +2201,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
- "thiserror",
+ "thiserror 1.0.69",
"tracing",
"whoami",
]
@@ -2209,7 +2321,16 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
- "thiserror-impl",
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
+dependencies = [
+ "thiserror-impl 2.0.9",
]
[[package]]
@@ -2224,6 +2345,17 @@ dependencies = [
]
[[package]]
+name = "thiserror-impl"
+version = "2.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.90",
+]
+
+[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2475,6 +2607,12 @@ dependencies = [
]
[[package]]
+name = "unicase"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
+
+[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2565,6 +2703,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
+name = "utoipa"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68e76d357bc95c7d0939c92c04c9269871a8470eea39cb1f0231eeadb0c47d0f"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_json",
+ "utoipa-gen",
+]
+
+[[package]]
+name = "utoipa-gen"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "564b03f8044ad6806bdc0d635e88be24967e785eef096df6b2636d2cc1e05d4b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 2.0.90",
+]
+
+[[package]]
+name = "utoipa-swagger-ui"
+version = "8.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db4b5ac679cc6dfc5ea3f2823b0291c777750ffd5e13b21137e0f7ac0e8f9617"
+dependencies = [
+ "base64 0.22.1",
+ "mime_guess",
+ "regex",
+ "rocket",
+ "rust-embed",
+ "serde",
+ "serde_json",
+ "utoipa",
+ "utoipa-swagger-ui-vendored",
+ "zip",
+]
+
+[[package]]
+name = "utoipa-swagger-ui-vendored"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d"
+
+[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2583,6 +2769,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2636,6 +2832,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2921,3 +3126,34 @@ dependencies = [
"quote",
"syn 2.0.90",
]
+
+[[package]]
+name = "zip"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45"
+dependencies = [
+ "arbitrary",
+ "crc32fast",
+ "crossbeam-utils",
+ "displaydoc",
+ "flate2",
+ "indexmap",
+ "memchr",
+ "thiserror 2.0.9",
+ "zopfli",
+]
+
+[[package]]
+name = "zopfli"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
+dependencies = [
+ "bumpalo",
+ "crc32fast",
+ "lockfree-object-pool",
+ "log",
+ "once_cell",
+ "simd-adler32",
+]
diff --git a/server/Cargo.toml b/server/Cargo.toml
index fc8cb6f..9348d86 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -10,3 +10,5 @@ rocket_db_pools = { version = "0.2.0", features = ["sqlx_mysql"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.7.0", default-features = false, features = ["macros", "migrate"] }
time = "0.3.34"
+utoipa = { version = "5", features = ["rocket_extras"] }
+utoipa-swagger-ui = { version = "8", features = ["rocket", "vendored"], default-features = false }
diff --git a/server/src/api_model.rs b/server/src/api_model.rs
index 286e11f..a7c8e88 100644
--- a/server/src/api_model.rs
+++ b/server/src/api_model.rs
@@ -1,6 +1,7 @@
use rocket::serde::Serialize;
+use utoipa::ToSchema;
-#[derive(Serialize, Copy, Clone)]
+#[derive(Serialize, Copy, Clone, ToSchema)]
pub enum ReviewState {
Draft,
Open,
@@ -8,70 +9,96 @@ pub enum ReviewState {
Closed,
}
-#[derive(Serialize)]
+#[derive(Serialize, ToSchema)]
pub struct User {
+ #[schema(example = 1337u64)]
pub id: u64,
+ #[schema(example = "jsmith")]
pub username: String,
+ #[schema(example = "John Smith")]
pub name: String,
+ #[schema(example = true)]
pub active: bool,
}
-#[derive(Serialize)]
+#[derive(Serialize, ToSchema)]
pub struct Review {
+ #[schema(example = 1000u64)]
pub id: u64,
+ #[schema(example = "FAKE-512: Add more features")]
pub title: String,
+ #[schema(example = "We're adding more features because features are what we want.")]
pub description: String,
pub owner: User,
pub reviewers: Vec<User>,
pub watchers: Vec<User>,
+ #[schema(example = ReviewState::Open)]
pub state: ReviewState,
+ #[schema(example = 37.5)]
pub progress: f32,
}
-#[derive(Serialize)]
+#[derive(Serialize, ToSchema)]
pub struct ReviewEntry {
+ #[schema(example = 1000u64)]
pub id: u64,
+ #[schema(example = "FAKE-512: Add more features")]
pub title: String,
pub owner: User,
+ #[schema(example = ReviewState::Open)]
pub state: ReviewState,
+ #[schema(example = 37.5)]
pub progress: f32,
}
-#[derive(Serialize)]
+#[derive(Serialize, ToSchema)]
pub struct Reviews {
+ #[schema(example = 0u32)]
pub offset: u32,
+ #[schema(example = 10u32)]
pub limit: u32,
+ #[schema(example = 42u32)]
pub total_count: u32,
+ #[schema(example = true)]
pub more: bool,
pub reviews: Vec<ReviewEntry>,
}
-#[derive(Serialize)]
+#[derive(Serialize, ToSchema)]
pub struct Project {
+ #[schema(example = 1u64)]
pub id: u64,
+ #[schema(example = "FAKE: Features All Kids Erase")]
pub title: String,
+ #[schema(example = "Example project")]
pub description: String,
pub members: Vec<User>,
}
-#[derive(Serialize)]
+#[derive(Serialize, ToSchema)]
pub struct ProjectEntry {
+ #[schema(example = 1u64)]
pub id: u64,
+ #[schema(example = "FAKE: Features All Kids Erase")]
pub title: String,
}
-#[derive(Serialize)]
+#[derive(Serialize, ToSchema)]
pub struct Projects {
+ #[schema(example = 0u32)]
pub offset: u32,
+ #[schema(example = 10u32)]
pub limit: u32,
+ #[schema(example = 1u32)]
pub total_count: u32,
+ #[schema(example = false)]
pub more: bool,
pub projects: Vec<ProjectEntry>,
}
-#[derive(Serialize)]
+#[derive(Serialize, ToSchema)]
pub struct StatusResponse {
pub ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
- pub error: Option<String>,
+ pub error: Option<&'static str>,
}
diff --git a/server/src/auth.rs b/server/src/auth.rs
index 31e18a0..4e66448 100644
--- a/server/src/auth.rs
+++ b/server/src/auth.rs
@@ -12,10 +12,31 @@ use std::collections::BTreeMap;
use std::sync::Mutex;
use std::time::Instant;
use time::Duration;
+use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
+use utoipa::{Modify, OpenApi, ToSchema};
use crate::api_model;
-#[derive(FromForm)]
+#[derive(OpenApi)]
+#[openapi(
+ paths(login, logout, status,),
+ modifiers(&AuthApiAddon),
+)]
+pub struct AuthApi;
+
+pub struct AuthApiAddon;
+
+impl Modify for AuthApiAddon {
+ fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
+ let components = openapi.components.as_mut().unwrap();
+ components.add_security_scheme(
+ "session",
+ SecurityScheme::ApiKey(ApiKey::Cookie(ApiKeyValue::new(SESSION_COOKIE))),
+ )
+ }
+}
+
+#[derive(FromForm, ToSchema)]
struct Login<'r> {
username: &'r str,
password: &'r str,
@@ -49,6 +70,14 @@ pub enum SessionError {
}
const SESSION_COOKIE: &str = "s";
+const STATUS_OK: api_model::StatusResponse = api_model::StatusResponse {
+ ok: true,
+ error: None,
+};
+const STATUS_UNAUTHORIZED: api_model::StatusResponse = api_model::StatusResponse {
+ ok: false,
+ error: Some("Unauthorized"),
+};
fn validate(sessions: &State<Sessions>, session: &Session, request: &Request<'_>) -> bool {
match request.client_ip() {
@@ -123,6 +152,17 @@ fn new_session(
}
}
+#[utoipa::path(
+ responses(
+ (status = 200, description = "Login successful", body = api_model::StatusResponse,
+ example = json!(STATUS_OK)),
+ (status = 401, description = "Login failed", body = api_model::StatusResponse,
+ example = json!(STATUS_UNAUTHORIZED)),
+ ),
+ security(
+ (),
+ ),
+)]
#[post("/login", data = "<login>")]
fn login(
auth_config: &State<AuthConfig>,
@@ -142,15 +182,20 @@ fn login(
.build();
cookies.add_private(cookie);
- Ok(Json(api_model::StatusResponse {
- ok: true,
- error: None,
- }))
+ Ok(Json(STATUS_OK))
} else {
Err(Unauthorized("Unknown username or password"))
}
}
+#[utoipa::path(
+ responses(
+ (status = 200, description = "Logout successful", body = api_model::StatusResponse, example = json!(STATUS_OK)),
+ ),
+ security(
+ ("session" = []),
+ ),
+)]
#[get("/logout")]
fn logout(
session: Session,
@@ -169,26 +214,27 @@ fn logout(
cookies.remove_private(cookie);
- Json(api_model::StatusResponse {
- ok: true,
- error: None,
- })
+ Json(STATUS_OK)
}
+#[utoipa::path(
+ responses(
+ (status = 200, description = "Current status", body = api_model::StatusResponse, example = json!(STATUS_OK)),
+ (status = 401, description = "Not authorized", body = api_model::StatusResponse, example = json!(STATUS_UNAUTHORIZED)),
+ ),
+ security(
+ (),
+ ("session" = []),
+ ),
+)]
#[get("/status")]
fn status(_session: Session) -> Json<api_model::StatusResponse> {
- Json(api_model::StatusResponse {
- ok: true,
- error: None,
- })
+ Json(STATUS_OK)
}
#[catch(401)]
fn unauthorized() -> Json<api_model::StatusResponse> {
- Json(api_model::StatusResponse {
- ok: false,
- error: Some("Unauthorized".to_string()),
- })
+ Json(STATUS_UNAUTHORIZED)
}
pub fn stage(basepath: String) -> AdHoc {
diff --git a/server/src/main.rs b/server/src/main.rs
index 223d861..124d914 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -8,14 +8,25 @@ use rocket::response::status::NotFound;
use rocket::serde::json::Json;
use rocket::{futures, Build, Rocket};
use rocket_db_pools::{sqlx, Connection, Database};
+use utoipa::OpenApi;
+use utoipa_swagger_ui::SwaggerUi;
mod api_model;
mod auth;
+use auth::AuthApiAddon;
+
#[derive(Database)]
#[database("eyeballs")]
struct Db(sqlx::MySqlPool);
+#[derive(OpenApi)]
+#[openapi(
+ paths(projects, project, reviews, review,),
+ modifiers(&AuthApiAddon),
+)]
+pub struct MainApi;
+
enum Role {
Reviewer,
Watcher,
@@ -52,6 +63,14 @@ impl TryFrom<u8> for api_model::ReviewState {
}
}
+#[utoipa::path(
+ responses(
+ (status = 200, description = "Get all projects", body = api_model::Projects),
+ ),
+ security(
+ ("session" = []),
+ ),
+)]
#[get("/projects?<limit>&<offset>")]
async fn projects<'r>(
mut db: Connection<Db>,
@@ -93,6 +112,15 @@ async fn projects<'r>(
})
}
+#[utoipa::path(
+ responses(
+ (status = 200, description = "Get project", body = api_model::Project),
+ (status = 404, description = "No such project"),
+ ),
+ security(
+ ("session" = []),
+ ),
+)]
#[get("/project/<projectid>")]
async fn project<'r>(
mut db: Connection<Db>,
@@ -130,6 +158,14 @@ async fn project<'r>(
Ok(Json(project))
}
+#[utoipa::path(
+ responses(
+ (status = 200, description = "Get all reviews for project", body = api_model::Reviews),
+ ),
+ security(
+ ("session" = []),
+ ),
+)]
#[get("/project/<projectid>/reviews?<limit>&<offset>")]
async fn reviews<'r>(
mut db: Connection<Db>,
@@ -180,6 +216,15 @@ async fn reviews<'r>(
})
}
+#[utoipa::path(
+ responses(
+ (status = 200, description = "Get review", body = api_model::Review),
+ (status = 404, description = "No such review"),
+ ),
+ security(
+ ("session" = []),
+ ),
+)]
#[get("/review/<reviewid>")]
async fn review<'r>(
mut db: Connection<Db>,
@@ -258,10 +303,20 @@ async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
async fn main() -> Result<(), rocket::Error> {
let basepath = "/api/v1";
+ let mut api = MainApi::openapi();
+ api.merge(auth::AuthApi::openapi());
+ api.servers = Some(vec![utoipa::openapi::ServerBuilder::new()
+ .url(basepath)
+ .build()]);
+
let _rocket = rocket::build()
.attach(Db::init())
.attach(AdHoc::try_on_ignite("Database Migrations", run_migrations))
.mount(basepath, routes![projects, project, reviews, review])
+ .mount(
+ "/",
+ SwaggerUi::new("/openapi/ui/<_..>").url("/openapi/openapi.json", api),
+ )
.attach(auth::stage(basepath.to_string()))
.launch()
.await?;