Add API docs with utoipa
This commit is contained in:
parent
300285e84b
commit
f46f456db0
171
backend/Cargo.lock
generated
171
backend/Cargo.lock
generated
|
|
@ -8,6 +8,12 @@ version = "0.11.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
|
|
@ -609,6 +615,17 @@ dependencies = [
|
|||
"tower_governor",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -813,6 +830,16 @@ dependencies = [
|
|||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.10.14"
|
||||
|
|
@ -1226,6 +1253,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
|||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1402,12 +1430,31 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.6"
|
||||
|
|
@ -2034,6 +2081,41 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "6.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "6.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"shellexpand",
|
||||
"syn 1.0.109",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "7.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.29.1"
|
||||
|
|
@ -2078,6 +2160,15 @@ version = "1.0.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
|
||||
[[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 = "schannel"
|
||||
version = "0.1.21"
|
||||
|
|
@ -2374,6 +2465,15 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
|
|
@ -2923,6 +3023,15 @@ version = "1.16.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
|
|
@ -2973,6 +3082,46 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utoipa"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ae74ef183fae36d650f063ae7bde1cacbe1cd7e72b617cbe1e985551878b98"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa-gen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utoipa-gen"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ea8ac818da7e746a63285594cce8a96f5e00ee31994e655bd827569cb8b137b"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utoipa-swagger-ui"
|
||||
version = "3.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "062bba5a3568e126ac72049a63254f4cb1da2eb713db0c1ab2a4c76be191db8c"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"mime_guess",
|
||||
"regex",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.3.2"
|
||||
|
|
@ -3016,6 +3165,16 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
|
|
@ -3316,3 +3475,15 @@ name = "zeroize"
|
|||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e92305c174683d78035cbf1b70e18db6329cc0f1b9cae0a52ca90bf5bfe7125"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -26,3 +26,5 @@ bcrypt = "0.14.0"
|
|||
tower-http = { version = "0.4.0", features = ["cors", "trace"] }
|
||||
tower_governor = "0.0.4"
|
||||
tower = "0.4.13"
|
||||
utoipa = { version = "3.3.0", features = ["axum_extras", "preserve_order"] }
|
||||
utoipa-swagger-ui = { version = "3.1.3", features = ["axum"] }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ use tokio::sync::Mutex;
|
|||
use tower::ServiceBuilder;
|
||||
use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer};
|
||||
use tower_http::{cors::CorsLayer, trace::TraceLayer};
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
mod errors;
|
||||
mod payloads;
|
||||
|
|
@ -31,6 +33,35 @@ async fn main() {
|
|||
// Load env
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
info(title = "Crab Fit API"),
|
||||
paths(
|
||||
routes::get_stats::get_stats,
|
||||
routes::create_event::create_event,
|
||||
routes::get_event::get_event,
|
||||
routes::get_people::get_people,
|
||||
routes::get_person::get_person,
|
||||
routes::update_person::update_person,
|
||||
),
|
||||
components(
|
||||
schemas(
|
||||
payloads::StatsResponse,
|
||||
payloads::EventResponse,
|
||||
payloads::PersonResponse,
|
||||
payloads::EventInput,
|
||||
payloads::GetPersonInput,
|
||||
payloads::UpdatePersonInput,
|
||||
),
|
||||
),
|
||||
tags(
|
||||
(name = "info"),
|
||||
(name = "event"),
|
||||
(name = "person"),
|
||||
),
|
||||
)]
|
||||
struct ApiDoc;
|
||||
|
||||
let shared_state = Arc::new(Mutex::new(ApiState {
|
||||
adaptor: SqlAdaptor::new().await,
|
||||
}));
|
||||
|
|
@ -62,6 +93,7 @@ async fn main() {
|
|||
});
|
||||
|
||||
let app = Router::new()
|
||||
.merge(SwaggerUi::new("/docs").url("/docs/openapi.json", ApiDoc::openapi()))
|
||||
.route("/", get(get_root))
|
||||
.route("/stats", get(get_stats))
|
||||
.route("/event", post(create_event))
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
use axum::Json;
|
||||
use common::{event::Event, person::Person, stats::Stats};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::errors::ApiError;
|
||||
|
||||
pub type ApiResult<T, A> = Result<Json<T>, ApiError<A>>;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct EventInput {
|
||||
pub name: Option<String>,
|
||||
pub times: Vec<String>,
|
||||
pub timezone: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct EventResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
|
|
@ -34,7 +35,7 @@ impl From<Event> for EventResponse {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct StatsResponse {
|
||||
pub event_count: i32,
|
||||
pub person_count: i32,
|
||||
|
|
@ -51,7 +52,7 @@ impl From<Stats> for StatsResponse {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct PersonResponse {
|
||||
pub name: String,
|
||||
pub availability: Vec<String>,
|
||||
|
|
@ -68,12 +69,12 @@ impl From<Person> for PersonResponse {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct GetPersonInput {
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct UpdatePersonInput {
|
||||
pub password: Option<String>,
|
||||
pub availability: Vec<String>,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,31 @@
|
|||
use axum::{extract, Json};
|
||||
use axum::{extract, http::StatusCode, Json};
|
||||
use common::{adaptor::Adaptor, event::Event};
|
||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
errors::ApiError,
|
||||
payloads::{ApiResult, EventInput, EventResponse},
|
||||
payloads::{EventInput, EventResponse},
|
||||
State,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/event",
|
||||
request_body(content = EventInput, description = "New event details"),
|
||||
responses(
|
||||
(status = 201, description = "Created", body = EventResponse),
|
||||
(status = 415, description = "Unsupported input format"),
|
||||
(status = 422, description = "Invalid input provided"),
|
||||
(status = 429, description = "Too many requests"),
|
||||
),
|
||||
tag = "event",
|
||||
)]
|
||||
/// Create a new event
|
||||
pub async fn create_event<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Json(input): Json<EventInput>,
|
||||
) -> ApiResult<EventResponse, A> {
|
||||
) -> Result<(StatusCode, Json<EventResponse>), ApiError<A>> {
|
||||
let adaptor = &state.lock().await.adaptor;
|
||||
|
||||
// Get the current timestamp
|
||||
|
|
@ -55,7 +68,7 @@ pub async fn create_event<A: Adaptor>(
|
|||
.await
|
||||
.map_err(ApiError::AdaptorError)?;
|
||||
|
||||
Ok(Json(event.into()))
|
||||
Ok((StatusCode::CREATED, Json(event.into())))
|
||||
}
|
||||
|
||||
// Generate a random name based on an adjective and a crab species
|
||||
|
|
|
|||
|
|
@ -10,6 +10,20 @@ use crate::{
|
|||
State,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/event/{event_id}",
|
||||
params(
|
||||
("event_id", description = "The ID of the event"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Ok", body = EventResponse),
|
||||
(status = 404, description = "Not found"),
|
||||
(status = 429, description = "Too many requests"),
|
||||
),
|
||||
tag = "event",
|
||||
)]
|
||||
/// Get details about an event
|
||||
pub async fn get_event<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path(event_id): Path<String>,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,20 @@ use crate::{
|
|||
State,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/event/{event_id}/people",
|
||||
params(
|
||||
("event_id", description = "The ID of the event"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Ok", body = [PersonResponse]),
|
||||
(status = 404, description = "Event not found"),
|
||||
(status = 429, description = "Too many requests"),
|
||||
),
|
||||
tag = "person",
|
||||
)]
|
||||
/// Get availabilities for an event
|
||||
pub async fn get_people<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path(event_id): Path<String>,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,25 @@ use crate::{
|
|||
State,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/event/{event_id}/people/{person_name}",
|
||||
params(
|
||||
("event_id", description = "The ID of the event"),
|
||||
("person_name", description = "The name of the person"),
|
||||
),
|
||||
request_body(content = GetPersonInput, description = "Person details"),
|
||||
responses(
|
||||
(status = 200, description = "Ok", body = PersonResponse),
|
||||
(status = 401, description = "Incorrect password"),
|
||||
(status = 404, description = "Event not found"),
|
||||
(status = 415, description = "Unsupported input format"),
|
||||
(status = 422, description = "Invalid input provided"),
|
||||
(status = 429, description = "Too many requests"),
|
||||
),
|
||||
tag = "person",
|
||||
)]
|
||||
/// Login or create a person for an event
|
||||
pub async fn get_person<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path((event_id, person_name)): Path<(String, String)>,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,16 @@ use crate::{
|
|||
State,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/stats",
|
||||
responses(
|
||||
(status = 200, description = "Ok", body = StatsResponse),
|
||||
(status = 429, description = "Too many requests"),
|
||||
),
|
||||
tag = "info",
|
||||
)]
|
||||
/// Get current stats
|
||||
pub async fn get_stats<A: Adaptor>(extract::State(state): State<A>) -> ApiResult<StatsResponse, A> {
|
||||
let adaptor = &state.lock().await.adaptor;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
mod get_event;
|
||||
pub mod get_event;
|
||||
pub use get_event::get_event;
|
||||
|
||||
mod get_stats;
|
||||
pub mod get_stats;
|
||||
pub use get_stats::get_stats;
|
||||
|
||||
mod create_event;
|
||||
pub mod create_event;
|
||||
pub use create_event::create_event;
|
||||
|
||||
mod get_people;
|
||||
pub mod get_people;
|
||||
pub use get_people::get_people;
|
||||
|
||||
mod get_person;
|
||||
pub mod get_person;
|
||||
pub use get_person::get_person;
|
||||
|
||||
mod update_person;
|
||||
pub mod update_person;
|
||||
pub use update_person::update_person;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,25 @@ use crate::{
|
|||
|
||||
use super::get_person::verify_password;
|
||||
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
path = "/event/{event_id}/people/{person_name}",
|
||||
params(
|
||||
("event_id", description = "The ID of the event"),
|
||||
("person_name", description = "The name of the person"),
|
||||
),
|
||||
request_body(content = UpdatePersonInput, description = "Person details"),
|
||||
responses(
|
||||
(status = 200, description = "Ok", body = PersonResponse),
|
||||
(status = 401, description = "Incorrect password"),
|
||||
(status = 404, description = "Event or person not found"),
|
||||
(status = 415, description = "Unsupported input format"),
|
||||
(status = 422, description = "Invalid input provided"),
|
||||
(status = 429, description = "Too many requests"),
|
||||
),
|
||||
tag = "person",
|
||||
)]
|
||||
/// Update a person's availabilities
|
||||
pub async fn update_person<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path((event_id, person_name)): Path<(String, String)>,
|
||||
|
|
|
|||
Loading…
Reference in a new issue