Skip to content

Commit b7a4820

Browse files
authored
Merge pull request #10718 from Turbo87/openapi-version-endpoints
Improve OpenAPI documentation for version endpoints
2 parents 40ec540 + de89f96 commit b7a4820

File tree

5 files changed

+174
-21
lines changed

5 files changed

+174
-21
lines changed

src/controllers/version/downloads.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,22 @@ use crate::schema::*;
99
use crate::util::errors::AppResult;
1010
use crate::util::{RequestUtils, redirect};
1111
use crate::views::EncodableVersionDownload;
12+
use axum::Json;
1213
use axum::extract::{FromRequestParts, Query};
1314
use axum::response::{IntoResponse, Response};
1415
use axum_extra::json;
15-
use axum_extra::response::ErasedJson;
1616
use chrono::{Duration, NaiveDate, Utc};
1717
use diesel::prelude::*;
1818
use diesel_async::RunQueryDsl;
1919
use http::request::Parts;
2020

21+
#[derive(Debug, Serialize, utoipa::ToSchema)]
22+
pub struct UrlResponse {
23+
/// The URL to the crate file.
24+
#[schema(example = "https://static.crates.io/crates/serde/serde-1.0.0.crate")]
25+
pub url: String,
26+
}
27+
2128
/// Download a crate version.
2229
///
2330
/// This returns a URL to the location where the crate is stored.
@@ -26,7 +33,10 @@ use http::request::Parts;
2633
path = "/api/v1/crates/{name}/{version}/download",
2734
params(CrateVersionPath),
2835
tag = "versions",
29-
responses((status = 200, description = "Successful Response")),
36+
responses(
37+
(status = 302, description = "Successful Response (default)", headers(("location" = String, description = "The URL to the crate file."))),
38+
(status = 200, description = "Successful Response (for `content-type: application/json`)", body = inline(UrlResponse)),
39+
),
3040
)]
3141
pub async fn download_version(
3242
app: AppState,
@@ -51,6 +61,11 @@ pub struct DownloadsQueryParams {
5161
before_date: Option<NaiveDate>,
5262
}
5363

64+
#[derive(Debug, Serialize, utoipa::ToSchema)]
65+
pub struct DownloadsResponse {
66+
pub version_downloads: Vec<EncodableVersionDownload>,
67+
}
68+
5469
/// Get the download counts for a crate version.
5570
///
5671
/// This includes the per-day downloads for the last 90 days.
@@ -59,13 +74,13 @@ pub struct DownloadsQueryParams {
5974
path = "/api/v1/crates/{name}/{version}/downloads",
6075
params(CrateVersionPath, DownloadsQueryParams),
6176
tag = "versions",
62-
responses((status = 200, description = "Successful Response")),
77+
responses((status = 200, description = "Successful Response", body = inline(DownloadsResponse))),
6378
)]
6479
pub async fn get_version_downloads(
6580
app: AppState,
6681
path: CrateVersionPath,
6782
params: DownloadsQueryParams,
68-
) -> AppResult<ErasedJson> {
83+
) -> AppResult<Json<DownloadsResponse>> {
6984
let mut conn = app.db_read().await?;
7085
let version = path.load_version(&mut conn).await?;
7186

@@ -75,14 +90,14 @@ pub async fn get_version_downloads(
7590

7691
let cutoff_start_date = cutoff_end_date - Duration::days(89);
7792

78-
let downloads = VersionDownload::belonging_to(&version)
93+
let version_downloads = VersionDownload::belonging_to(&version)
7994
.filter(version_downloads::date.between(cutoff_start_date, cutoff_end_date))
8095
.order(version_downloads::date)
8196
.load(&mut conn)
8297
.await?
8398
.into_iter()
8499
.map(VersionDownload::into)
85-
.collect::<Vec<EncodableVersionDownload>>();
100+
.collect();
86101

87-
Ok(json!({ "version_downloads": downloads }))
102+
Ok(Json(DownloadsResponse { version_downloads }))
88103
}

src/controllers/version/readme.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
use crate::app::AppState;
22
use crate::controllers::version::CrateVersionPath;
33
use crate::util::{RequestUtils, redirect};
4+
use axum::Json;
45
use axum::response::{IntoResponse, Response};
5-
use axum_extra::json;
66
use http::request::Parts;
77

8+
#[derive(Debug, Serialize, utoipa::ToSchema)]
9+
pub struct UrlResponse {
10+
/// The URL to the readme file.
11+
#[schema(example = "https://static.crates.io/readmes/serde/serde-1.0.0.html")]
12+
pub url: String,
13+
}
14+
815
/// Get the readme of a crate version.
916
#[utoipa::path(
1017
get,
1118
path = "/api/v1/crates/{name}/{version}/readme",
1219
params(CrateVersionPath),
1320
tag = "versions",
14-
responses((status = 200, description = "Successful Response")),
21+
responses(
22+
(status = 302, description = "Successful Response (default)", headers(("location" = String, description = "The URL to the readme file."))),
23+
(status = 200, description = "Successful Response (for `content-type: application/json`)", body = inline(UrlResponse)),
24+
),
1525
)]
1626
pub async fn get_version_readme(app: AppState, path: CrateVersionPath, req: Parts) -> Response {
17-
let redirect_url = app.storage.readme_location(&path.name, &path.version);
27+
let url = app.storage.readme_location(&path.name, &path.version);
1828
if req.wants_json() {
19-
json!({ "url": redirect_url }).into_response()
29+
Json(UrlResponse { url }).into_response()
2030
} else {
21-
redirect(redirect_url)
31+
redirect(url)
2232
}
2333
}

src/controllers/version/update.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ use crate::util::errors::{AppResult, bad_request, custom};
1010
use crate::views::EncodableVersion;
1111
use crate::worker::jobs::{SyncToGitIndex, SyncToSparseIndex, UpdateDefaultVersion};
1212
use axum::Json;
13-
use axum_extra::json;
14-
use axum_extra::response::ErasedJson;
1513
use crates_io_worker::BackgroundJob;
1614
use diesel::prelude::*;
1715
use diesel_async::{AsyncPgConnection, RunQueryDsl};
@@ -29,6 +27,11 @@ pub struct VersionUpdateRequest {
2927
version: VersionUpdate,
3028
}
3129

30+
#[derive(Debug, Serialize, utoipa::ToSchema)]
31+
pub struct UpdateResponse {
32+
pub version: EncodableVersion,
33+
}
34+
3235
/// Update a crate version.
3336
///
3437
/// This endpoint allows updating the `yanked` state of a version, including a yank message.
@@ -41,14 +44,14 @@ pub struct VersionUpdateRequest {
4144
("cookie" = []),
4245
),
4346
tag = "versions",
44-
responses((status = 200, description = "Successful Response")),
47+
responses((status = 200, description = "Successful Response", body = inline(UpdateResponse))),
4548
)]
4649
pub async fn update_version(
4750
state: AppState,
4851
path: CrateVersionPath,
4952
req: Parts,
5053
Json(update_request): Json<VersionUpdateRequest>,
51-
) -> AppResult<ErasedJson> {
54+
) -> AppResult<Json<UpdateResponse>> {
5255
let mut conn = state.db_write().await?;
5356
let (mut version, krate) = path.load_version_and_crate(&mut conn).await?;
5457
validate_yank_update(&update_request.version, &version)?;
@@ -74,8 +77,8 @@ pub async fn update_version(
7477
VersionOwnerAction::by_version(&mut conn, &version),
7578
version.published_by(&mut conn),
7679
)?;
77-
let updated_version = EncodableVersion::from(version, &krate.name, published_by, actions);
78-
Ok(json!({ "version": updated_version }))
80+
let version = EncodableVersion::from(version, &krate.name, published_by, actions);
81+
Ok(Json(UpdateResponse { version }))
7982
}
8083

8184
fn validate_yank_update(update_data: &VersionUpdate, version: &Version) -> AppResult<()> {

src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,33 @@ expression: response.json()
645645
],
646646
"type": "object"
647647
},
648+
"VersionDownload": {
649+
"properties": {
650+
"date": {
651+
"description": "The date this download count is for.",
652+
"example": "2019-12-13",
653+
"type": "string"
654+
},
655+
"downloads": {
656+
"description": "The number of downloads for this version on the given date.",
657+
"example": 123,
658+
"format": "int32",
659+
"type": "integer"
660+
},
661+
"version": {
662+
"description": "The ID of the version this download count is for.",
663+
"example": 42,
664+
"format": "int32",
665+
"type": "integer"
666+
}
667+
},
668+
"required": [
669+
"version",
670+
"downloads",
671+
"date"
672+
],
673+
"type": "object"
674+
},
648675
"VersionLinks": {
649676
"properties": {
650677
"authors": {
@@ -1829,6 +1856,21 @@ expression: response.json()
18291856
],
18301857
"responses": {
18311858
"200": {
1859+
"content": {
1860+
"application/json": {
1861+
"schema": {
1862+
"properties": {
1863+
"version": {
1864+
"$ref": "#/components/schemas/Version"
1865+
}
1866+
},
1867+
"required": [
1868+
"version"
1869+
],
1870+
"type": "object"
1871+
}
1872+
}
1873+
},
18321874
"description": "Successful Response"
18331875
}
18341876
},
@@ -1964,7 +2006,35 @@ expression: response.json()
19642006
],
19652007
"responses": {
19662008
"200": {
1967-
"description": "Successful Response"
2009+
"content": {
2010+
"application/json": {
2011+
"schema": {
2012+
"properties": {
2013+
"url": {
2014+
"description": "The URL to the crate file.",
2015+
"example": "https://static.crates.io/crates/serde/serde-1.0.0.crate",
2016+
"type": "string"
2017+
}
2018+
},
2019+
"required": [
2020+
"url"
2021+
],
2022+
"type": "object"
2023+
}
2024+
}
2025+
},
2026+
"description": "Successful Response (for `content-type: application/json`)"
2027+
},
2028+
"302": {
2029+
"description": "Successful Response (default)",
2030+
"headers": {
2031+
"location": {
2032+
"description": "The URL to the crate file.",
2033+
"schema": {
2034+
"type": "string"
2035+
}
2036+
}
2037+
}
19682038
}
19692039
},
19702040
"summary": "Download a crate version.",
@@ -2011,6 +2081,24 @@ expression: response.json()
20112081
],
20122082
"responses": {
20132083
"200": {
2084+
"content": {
2085+
"application/json": {
2086+
"schema": {
2087+
"properties": {
2088+
"version_downloads": {
2089+
"items": {
2090+
"$ref": "#/components/schemas/VersionDownload"
2091+
},
2092+
"type": "array"
2093+
}
2094+
},
2095+
"required": [
2096+
"version_downloads"
2097+
],
2098+
"type": "object"
2099+
}
2100+
}
2101+
},
20142102
"description": "Successful Response"
20152103
}
20162104
},
@@ -2046,7 +2134,35 @@ expression: response.json()
20462134
],
20472135
"responses": {
20482136
"200": {
2049-
"description": "Successful Response"
2137+
"content": {
2138+
"application/json": {
2139+
"schema": {
2140+
"properties": {
2141+
"url": {
2142+
"description": "The URL to the readme file.",
2143+
"example": "https://static.crates.io/readmes/serde/serde-1.0.0.html",
2144+
"type": "string"
2145+
}
2146+
},
2147+
"required": [
2148+
"url"
2149+
],
2150+
"type": "object"
2151+
}
2152+
}
2153+
},
2154+
"description": "Successful Response (for `content-type: application/json`)"
2155+
},
2156+
"302": {
2157+
"description": "Successful Response (default)",
2158+
"headers": {
2159+
"location": {
2160+
"description": "The URL to the readme file.",
2161+
"schema": {
2162+
"type": "string"
2163+
}
2164+
}
2165+
}
20502166
}
20512167
},
20522168
"summary": "Get the readme of a crate version.",

src/views.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,19 @@ impl EncodableDependency {
201201
}
202202
}
203203

204-
#[derive(Serialize, Deserialize, Debug)]
204+
#[derive(Serialize, Deserialize, Debug, utoipa::ToSchema)]
205+
#[schema(as = VersionDownload)]
205206
pub struct EncodableVersionDownload {
207+
/// The ID of the version this download count is for.
208+
#[schema(example = 42)]
206209
pub version: i32,
210+
211+
/// The number of downloads for this version on the given date.
212+
#[schema(example = 123)]
207213
pub downloads: i32,
214+
215+
/// The date this download count is for.
216+
#[schema(example = "2019-12-13")]
208217
pub date: String,
209218
}
210219

0 commit comments

Comments
 (0)