feat: better api docs (#6586)

* feat: docs

* fix tombi

* chore: fix some of the routes rendering with missing /

* response schemas

* fix: restore labrinth docs routes

* Fix path parameter docs in routes

* remove utoipa-actix-web

* consistency

* improve version intros

* improve formatting, examples

* better hash examples, move openapi stuff to openapi.rs

* more utoipa param fixes

* request body docs

* chore: remove moderation route from v2, remove ingest & webhooks from v3 spec

* fixes

* chore: tweak sources titles

* fix

* fix test

* improve examples

* increase compiler spawned thread stack size

* remove unused tests & script

* test

* bro what

* fix

---------

Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
This commit is contained in:
François-Xavier Talbot
2026-07-04 14:19:21 +00:00
committed by GitHub
co-authored by aecsocket
parent b26d048a63
commit 4a6fa9fc3d
103 changed files with 4546 additions and 1387 deletions
+110 -133
View File
@@ -19,13 +19,13 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use validator::Validate;
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(project_search);
cfg.service(projects_get);
cfg.service(projects_edit);
cfg.service(random_projects_get);
cfg.service(
utoipa_actix_web::scope("/project")
web::scope("/project")
.service(project_get)
.service(project_get_check)
.service(project_delete)
@@ -39,7 +39,7 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
.service(project_unfollow)
.service(super::teams::team_members_get_project)
.service(
utoipa_actix_web::scope("/{project_id}")
web::scope("/{project_id}")
.service(super::versions::version_list)
.service(super::versions::version_project_get)
.service(dependency_list),
@@ -47,39 +47,25 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
);
}
/// Search projects.
/// Search projects.
#[utoipa::path(
tag = "search",
get,
operation_id = "searchProjects",
params(
(
"query" = Option<String>,
Query,
description = "The query to search for"
),
(
"facets" = Option<String>,
Query,
description = "Search facets JSON"
),
(
"index" = Option<String>,
Query,
description = "Search index to use"
),
(
"offset" = Option<String>,
Query,
description = "Search result offset"
),
(
"limit" = Option<String>,
Query,
description = "Maximum number of search results"
)
("query" = Option<String>, Query, description = "The query to search for"),
("facets" = Option<String>, Query, description = "Search facets JSON"),
("index" = Option<String>, Query, description = "Search index to use"),
("offset" = Option<String>, Query, description = "Search result offset"),
("limit" = Option<String>, Query, description = "Maximum number of search results"),
("show_metadata" = Option<bool>, Query, description = "Whether to include search metadata"),
("typesense_config" = Option<String>, Query, description = "Typesense request configuration"),
("new_filters" = Option<String>, Query, description = "Search filters"),
("filters" = Option<String>, Query, description = "Legacy search filters"),
("version" = Option<String>, Query, description = "Legacy search version")
),
responses(
(status = 200, description = "Expected response to a valid request"),
(status = 200, description = "Expected response to a valid request", body = LegacySearchResults),
(status = 400, description = "Request was invalid, see given error")
)
)]
@@ -180,19 +166,16 @@ pub struct RandomProjects {
pub count: u32,
}
/// Get random projects.
/// Get random projects.
#[utoipa::path(
tag = "projects",
get,
operation_id = "randomProjects",
params(
(
"count" = u32,
Query,
description = "Number of projects to return"
)
("count" = u32, Query, description = "Number of projects to return")
),
responses(
(status = 200, description = "Expected response to a valid request"),
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyProject>),
(status = 400, description = "Request was invalid, see given error")
)
)]
@@ -223,18 +206,15 @@ pub async fn random_projects_get(
}
}
/// Get multiple projects by ID or slug.
/// Get multiple projects by ID or slug.
#[utoipa::path(
tag = "projects",
get,
operation_id = "getProjects",
params(
(
"ids" = String,
Query,
description = "The JSON array of project IDs or slugs"
)
("ids" = String, Query, description = "The JSON array of project IDs or slugs")
),
responses((status = 200, description = "Expected response to a valid request"))
responses((status = 200, description = "Expected response to a valid request", body = Vec<LegacyProject>))
)]
#[get("/projects")]
pub async fn projects_get(
@@ -267,13 +247,17 @@ pub async fn projects_get(
}
}
/// Get a project by ID or slug.
/// Get a project by ID or slug.
#[utoipa::path(
context_path = "/project",
tag = "projects",
get,
operation_id = "getProject",
params(("id" = String, Path, description = "The ID or slug of the project")),
params(
("id" = String, Path, description = "The ID or slug of the project")
),
responses(
(status = 200, description = "Expected response to a valid request"),
(status = 200, description = "Expected response to a valid request", body = LegacyProject),
(
status = 404,
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
@@ -316,13 +300,17 @@ pub async fn project_get(
}
//checks the validity of a project id or slug
/// Check that a project ID or slug exists.
/// Check that a project ID or slug exists.
#[utoipa::path(
context_path = "/project",
tag = "projects",
get,
operation_id = "checkProjectValidity",
params(("id" = String, Path, description = "The ID or slug of the project")),
params(
("id" = String, Path, description = "The ID or slug of the project")
),
responses(
(status = 200, description = "Expected response to a valid request"),
(status = 200, description = "Expected response to a valid request", body = v3::projects::ProjectCheckResponse),
(
status = 404,
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
@@ -347,13 +335,17 @@ struct DependencyInfo {
pub versions: Vec<LegacyVersion>,
}
/// Get dependency projects and versions for a project.
/// Get dependency projects and versions for a project.
#[utoipa::path(
context_path = "/project/{project_id}",
tag = "projects",
get,
operation_id = "getDependencies",
params(("id" = String, Path, description = "The ID or slug of the project")),
params(
("id" = String, Path, description = "The ID or slug of the project")
),
responses(
(status = 200, description = "Expected response to a valid request"),
(status = 200, description = "Expected response to a valid request", body = DependencyInfo),
(
status = 404,
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
@@ -507,14 +499,18 @@ pub struct EditProject {
pub monetization_status: Option<MonetizationStatus>,
}
/// Modify a project.
/// Update a project.
#[utoipa::path(
context_path = "/project",
tag = "projects",
patch,
operation_id = "modifyProject",
params(("id" = String, Path, description = "The ID or slug of the project")),
params(
("id" = String, Path, description = "The ID or slug of the project")
),
request_body = EditProject,
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(
status = 401,
description = "Incorrect token scopes or no authorization to access the requested item(s)"
@@ -763,20 +759,17 @@ pub struct BulkEditProject {
pub discord_url: Option<Option<String>>,
}
/// Bulk-edit multiple projects.
/// Bulk-edit multiple projects.
#[utoipa::path(
tag = "projects",
patch,
operation_id = "patchProjects",
params(
(
"ids" = String,
Query,
description = "The JSON array of project IDs or slugs"
)
("ids" = String, Query, description = "The JSON array of project IDs or slugs")
),
request_body = BulkEditProject,
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(status = 400, description = "Request was invalid, see given error"),
(
status = 401,
@@ -889,17 +882,15 @@ pub struct Extension {
pub ext: String,
}
/// Change a project's icon.
/// Change a project's icon.
#[utoipa::path(
context_path = "/project",
tag = "projects",
patch,
operation_id = "changeProjectIcon",
params(
("id" = String, Path, description = "The ID or slug of the project"),
(
"ext" = String,
Query,
description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"
)
("ext" = String, Query, description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)")
),
request_body(
content(
@@ -912,7 +903,7 @@ pub struct Extension {
)
),
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(status = 400, description = "Request was invalid, see given error")
),
security(("bearer_auth" = ["PROJECT_WRITE"]))
@@ -946,13 +937,17 @@ pub async fn project_icon_edit(
.or_else(v2_reroute::flatten_404_error)
}
/// Delete a project's icon.
/// Delete a project's icon.
#[utoipa::path(
context_path = "/project",
tag = "projects",
delete,
operation_id = "deleteProjectIcon",
params(("id" = String, Path, description = "The ID or slug of the project")),
params(
("id" = String, Path, description = "The ID or slug of the project")
),
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(status = 400, description = "Request was invalid, see given error"),
(
status = 401,
@@ -995,37 +990,19 @@ pub struct GalleryCreateQuery {
pub ordering: Option<i64>,
}
/// Add a gallery image to a project.
/// Add a gallery image to a project.
#[utoipa::path(
context_path = "/project",
tag = "projects",
post,
operation_id = "addGalleryImage",
params(
("id" = String, Path, description = "The ID or slug of the project"),
(
"ext" = String,
Query,
description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"
),
(
"featured" = bool,
Query,
description = "Whether this image is featured"
),
(
"title" = Option<String>,
Query,
description = "Image title"
),
(
"description" = Option<String>,
Query,
description = "Image description"
),
(
"ordering" = Option<i64>,
Query,
description = "Image ordering"
)
("ext" = String, Query, description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"),
("featured" = bool, Query, description = "Whether this image is featured"),
("title" = Option<String>, Query, description = "Image title"),
("description" = Option<String>, Query, description = "Image description"),
("ordering" = Option<i64>, Query, description = "Image ordering")
),
request_body(
content(
@@ -1038,7 +1015,7 @@ pub struct GalleryCreateQuery {
)
),
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(status = 400, description = "Request was invalid, see given error"),
(
status = 401,
@@ -1109,36 +1086,22 @@ pub struct GalleryEditQuery {
pub ordering: Option<i64>,
}
/// Modify a gallery image.
/// Update a gallery image.
#[utoipa::path(
context_path = "/project",
tag = "projects",
patch,
operation_id = "modifyGalleryImage",
params(
("id" = String, Path, description = "The ID or slug of the project"),
("url" = String, Query, description = "URL of the image to edit"),
(
"featured" = Option<bool>,
Query,
description = "Whether this image is featured"
),
(
"title" = Option<Option<String>>,
Query,
description = "Image title"
),
(
"description" = Option<Option<String>>,
Query,
description = "Image description"
),
(
"ordering" = Option<i64>,
Query,
description = "Image ordering"
)
("featured" = Option<bool>, Query, description = "Whether this image is featured"),
("title" = Option<Option<String>>, Query, description = "Image title"),
("description" = Option<Option<String>>, Query, description = "Image description"),
("ordering" = Option<i64>, Query, description = "Image ordering")
),
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(
status = 401,
description = "Incorrect token scopes or no authorization to access the requested item(s)"
@@ -1183,8 +1146,10 @@ pub struct GalleryDeleteQuery {
pub url: String,
}
/// Delete a gallery image.
/// Delete a gallery image.
#[utoipa::path(
context_path = "/project",
tag = "projects",
delete,
operation_id = "deleteGalleryImage",
params(
@@ -1192,7 +1157,7 @@ pub struct GalleryDeleteQuery {
("url" = String, Query, description = "URL of the image to delete")
),
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(status = 400, description = "Request was invalid, see given error"),
(
status = 401,
@@ -1225,13 +1190,17 @@ pub async fn delete_gallery_item(
.or_else(v2_reroute::flatten_404_error)
}
/// Delete a project by ID or slug.
/// Delete a project by ID or slug.
#[utoipa::path(
context_path = "/project",
tag = "projects",
delete,
operation_id = "deleteProject",
params(("id" = String, Path, description = "The ID or slug of the project")),
params(
("id" = String, Path, description = "The ID or slug of the project")
),
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(status = 400, description = "Request was invalid, see given error"),
(
status = 401,
@@ -1263,13 +1232,17 @@ pub async fn project_delete(
.or_else(v2_reroute::flatten_404_error)
}
/// Follow a project.
/// Follow a project.
#[utoipa::path(
context_path = "/project",
tag = "projects",
post,
operation_id = "followProject",
params(("id" = String, Path, description = "The ID or slug of the project")),
params(
("id" = String, Path, description = "The ID or slug of the project")
),
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(status = 400, description = "Request was invalid, see given error"),
(
status = 401,
@@ -1292,13 +1265,17 @@ pub async fn project_follow(
.or_else(v2_reroute::flatten_404_error)
}
/// Unfollow a project.
/// Unfollow a project.
#[utoipa::path(
context_path = "/project",
tag = "projects",
delete,
operation_id = "unfollowProject",
params(("id" = String, Path, description = "The ID or slug of the project")),
params(
("id" = String, Path, description = "The ID or slug of the project")
),
responses(
(status = 204, description = "Expected response to a valid request"),
(status = NO_CONTENT, description = "Expected response to a valid request"),
(status = 400, description = "Request was invalid, see given error"),
(
status = 401,