Skip to content

Commit 2ce8461

Browse files
authored
Merge pull request #10749 from Turbo87/openapi-metadata
Improve OpenAPI documentation for summary and metadata API endpoints
2 parents dc18b83 + 910540f commit 2ce8461

File tree

3 files changed

+168
-31
lines changed

3 files changed

+168
-31
lines changed

src/controllers/site_metadata.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
use crate::app::AppState;
2+
use axum::Json;
23
use axum::response::IntoResponse;
3-
use axum_extra::json;
4+
5+
#[derive(Debug, Serialize, utoipa::ToSchema)]
6+
pub struct MetadataResponse<'a> {
7+
/// The SHA1 of the currently deployed commit.
8+
#[schema(example = "0aebe2cdfacae1229b93853b1c58f9352195f081")]
9+
pub deployed_sha: &'a str,
10+
11+
/// The SHA1 of the currently deployed commit.
12+
#[schema(example = "0aebe2cdfacae1229b93853b1c58f9352195f081")]
13+
pub commit: &'a str,
14+
15+
/// Whether the crates.io service is in read-only mode.
16+
pub read_only: bool,
17+
}
418

519
/// Get crates.io metadata.
620
///
@@ -10,17 +24,18 @@ use axum_extra::json;
1024
get,
1125
path = "/api/v1/site_metadata",
1226
tag = "other",
13-
responses((status = 200, description = "Successful Response")),
27+
responses((status = 200, description = "Successful Response", body = inline(MetadataResponse<'_>))),
1428
)]
1529
pub async fn get_site_metadata(state: AppState) -> impl IntoResponse {
1630
let read_only = state.config.db.are_all_read_only();
1731

1832
let deployed_sha =
1933
dotenvy::var("HEROKU_SLUG_COMMIT").unwrap_or_else(|_| String::from("unknown"));
2034

21-
json!({
22-
"deployed_sha": &deployed_sha[..],
23-
"commit": &deployed_sha[..],
24-
"read_only": read_only,
35+
Json(MetadataResponse {
36+
deployed_sha: &deployed_sha,
37+
commit: &deployed_sha,
38+
read_only,
2539
})
40+
.into_response()
2641
}

src/controllers/summary.rs

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,41 @@ use crate::schema::{
55
};
66
use crate::util::errors::AppResult;
77
use crate::views::{EncodableCategory, EncodableCrate, EncodableKeyword};
8-
use axum_extra::json;
9-
use axum_extra::response::ErasedJson;
8+
use axum::Json;
109
use diesel::prelude::*;
1110
use diesel_async::{AsyncPgConnection, RunQueryDsl};
1211
use futures_util::FutureExt;
1312
use std::future::Future;
1413

14+
#[derive(Debug, Serialize, utoipa::ToSchema)]
15+
pub struct SummaryResponse {
16+
/// The total number of downloads across all crates.
17+
#[schema(example = 123_456_789)]
18+
num_downloads: i64,
19+
20+
/// The total number of crates on crates.io.
21+
#[schema(example = 123_456)]
22+
num_crates: i64,
23+
24+
/// The 10 most recently created crates.
25+
new_crates: Vec<EncodableCrate>,
26+
27+
/// The 10 crates with the highest total number of downloads.
28+
most_downloaded: Vec<EncodableCrate>,
29+
30+
/// The 10 crates with the highest number of downloads within the last 90 days.
31+
most_recently_downloaded: Vec<EncodableCrate>,
32+
33+
/// The 10 most recently updated crates.
34+
just_updated: Vec<EncodableCrate>,
35+
36+
/// The 10 most popular keywords.
37+
popular_keywords: Vec<EncodableKeyword>,
38+
39+
/// The 10 most popular categories.
40+
popular_categories: Vec<EncodableCategory>,
41+
}
42+
1543
/// Get front page data.
1644
///
1745
/// This endpoint returns a summary of the most important data for the front
@@ -20,9 +48,9 @@ use std::future::Future;
2048
get,
2149
path = "/api/v1/summary",
2250
tag = "other",
23-
responses((status = 200, description = "Successful Response")),
51+
responses((status = 200, description = "Successful Response", body = inline(SummaryResponse))),
2452
)]
25-
pub async fn get_summary(state: AppState) -> AppResult<ErasedJson> {
53+
pub async fn get_summary(state: AppState) -> AppResult<Json<SummaryResponse>> {
2654
let mut conn = state.db_read().await?;
2755

2856
let config = &state.config;
@@ -37,10 +65,10 @@ pub async fn get_summary(state: AppState) -> AppResult<ErasedJson> {
3765
popular_categories,
3866
popular_keywords,
3967
) = tokio::try_join!(
40-
crates::table.count().get_result::<i64>(&mut conn).boxed(),
68+
crates::table.count().get_result(&mut conn).boxed(),
4169
metadata::table
4270
.select(metadata::total_downloads)
43-
.get_result::<i64>(&mut conn)
71+
.get_result(&mut conn)
4472
.boxed(),
4573
crates::table
4674
.inner_join(crate_downloads::table)
@@ -100,25 +128,18 @@ pub async fn get_summary(state: AppState) -> AppResult<ErasedJson> {
100128
encode_crates(&mut conn, just_updated),
101129
)?;
102130

103-
let popular_categories = popular_categories
104-
.into_iter()
105-
.map(Category::into)
106-
.collect::<Vec<EncodableCategory>>();
107-
108-
let popular_keywords = popular_keywords
109-
.into_iter()
110-
.map(Keyword::into)
111-
.collect::<Vec<EncodableKeyword>>();
112-
113-
Ok(json!({
114-
"num_downloads": num_downloads,
115-
"num_crates": num_crates,
116-
"new_crates": new_crates,
117-
"most_downloaded": most_downloaded,
118-
"most_recently_downloaded": most_recently_downloaded,
119-
"just_updated": just_updated,
120-
"popular_keywords": popular_keywords,
121-
"popular_categories": popular_categories,
131+
let popular_categories = popular_categories.into_iter().map(Category::into).collect();
132+
let popular_keywords = popular_keywords.into_iter().map(Keyword::into).collect();
133+
134+
Ok(Json(SummaryResponse {
135+
num_downloads,
136+
num_crates,
137+
new_crates,
138+
most_downloaded,
139+
most_recently_downloaded,
140+
just_updated,
141+
popular_keywords,
142+
popular_categories,
122143
}))
123144
}
124145

src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3661,6 +3661,34 @@ expression: response.json()
36613661
"operationId": "get_site_metadata",
36623662
"responses": {
36633663
"200": {
3664+
"content": {
3665+
"application/json": {
3666+
"schema": {
3667+
"properties": {
3668+
"commit": {
3669+
"description": "The SHA1 of the currently deployed commit.",
3670+
"example": "0aebe2cdfacae1229b93853b1c58f9352195f081",
3671+
"type": "string"
3672+
},
3673+
"deployed_sha": {
3674+
"description": "The SHA1 of the currently deployed commit.",
3675+
"example": "0aebe2cdfacae1229b93853b1c58f9352195f081",
3676+
"type": "string"
3677+
},
3678+
"read_only": {
3679+
"description": "Whether the crates.io service is in read-only mode.",
3680+
"type": "boolean"
3681+
}
3682+
},
3683+
"required": [
3684+
"deployed_sha",
3685+
"commit",
3686+
"read_only"
3687+
],
3688+
"type": "object"
3689+
}
3690+
}
3691+
},
36643692
"description": "Successful Response"
36653693
}
36663694
},
@@ -3676,6 +3704,79 @@ expression: response.json()
36763704
"operationId": "get_summary",
36773705
"responses": {
36783706
"200": {
3707+
"content": {
3708+
"application/json": {
3709+
"schema": {
3710+
"properties": {
3711+
"just_updated": {
3712+
"description": "The 10 most recently updated crates.",
3713+
"items": {
3714+
"$ref": "#/components/schemas/Crate"
3715+
},
3716+
"type": "array"
3717+
},
3718+
"most_downloaded": {
3719+
"description": "The 10 crates with the highest total number of downloads.",
3720+
"items": {
3721+
"$ref": "#/components/schemas/Crate"
3722+
},
3723+
"type": "array"
3724+
},
3725+
"most_recently_downloaded": {
3726+
"description": "The 10 crates with the highest number of downloads within the last 90 days.",
3727+
"items": {
3728+
"$ref": "#/components/schemas/Crate"
3729+
},
3730+
"type": "array"
3731+
},
3732+
"new_crates": {
3733+
"description": "The 10 most recently created crates.",
3734+
"items": {
3735+
"$ref": "#/components/schemas/Crate"
3736+
},
3737+
"type": "array"
3738+
},
3739+
"num_crates": {
3740+
"description": "The total number of crates on crates.io.",
3741+
"example": 123456,
3742+
"format": "int64",
3743+
"type": "integer"
3744+
},
3745+
"num_downloads": {
3746+
"description": "The total number of downloads across all crates.",
3747+
"example": 123456789,
3748+
"format": "int64",
3749+
"type": "integer"
3750+
},
3751+
"popular_categories": {
3752+
"description": "The 10 most popular categories.",
3753+
"items": {
3754+
"$ref": "#/components/schemas/Category"
3755+
},
3756+
"type": "array"
3757+
},
3758+
"popular_keywords": {
3759+
"description": "The 10 most popular keywords.",
3760+
"items": {
3761+
"$ref": "#/components/schemas/Keyword"
3762+
},
3763+
"type": "array"
3764+
}
3765+
},
3766+
"required": [
3767+
"num_downloads",
3768+
"num_crates",
3769+
"new_crates",
3770+
"most_downloaded",
3771+
"most_recently_downloaded",
3772+
"just_updated",
3773+
"popular_keywords",
3774+
"popular_categories"
3775+
],
3776+
"type": "object"
3777+
}
3778+
}
3779+
},
36793780
"description": "Successful Response"
36803781
}
36813782
},

0 commit comments

Comments
 (0)