Skip to content

Commit 630cfed

Browse files
authored
Merge pull request #10685 from Turbo87/openapi-keywords
Add OpenAPI documentation for keyword API responses
2 parents 3fc2fd2 + 54ba2bb commit 630cfed

File tree

4 files changed

+136
-17
lines changed

4 files changed

+136
-17
lines changed

src/controllers/keyword.rs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ use crate::controllers::helpers::{Paginate, pagination::Paginated};
44
use crate::models::Keyword;
55
use crate::util::errors::AppResult;
66
use crate::views::EncodableKeyword;
7+
use axum::Json;
78
use axum::extract::{FromRequestParts, Path, Query};
8-
use axum_extra::json;
9-
use axum_extra::response::ErasedJson;
109
use diesel::prelude::*;
1110
use http::request::Parts;
1211

@@ -22,19 +21,35 @@ pub struct ListQueryParams {
2221
sort: Option<String>,
2322
}
2423

24+
#[derive(Debug, Serialize, utoipa::ToSchema)]
25+
pub struct ListResponse {
26+
/// The list of keywords.
27+
pub keywords: Vec<EncodableKeyword>,
28+
29+
#[schema(inline)]
30+
pub meta: ListMeta,
31+
}
32+
33+
#[derive(Debug, Serialize, utoipa::ToSchema)]
34+
pub struct ListMeta {
35+
/// The total number of keywords.
36+
#[schema(example = 123)]
37+
pub total: i64,
38+
}
39+
2540
/// List all keywords.
2641
#[utoipa::path(
2742
get,
2843
path = "/api/v1/keywords",
2944
params(ListQueryParams, PaginationQueryParams),
3045
tag = "keywords",
31-
responses((status = 200, description = "Successful Response")),
46+
responses((status = 200, description = "Successful Response", body = inline(ListResponse))),
3247
)]
3348
pub async fn list_keywords(
3449
state: AppState,
3550
params: ListQueryParams,
3651
req: Parts,
37-
) -> AppResult<ErasedJson> {
52+
) -> AppResult<Json<ListResponse>> {
3853
use crate::schema::keywords;
3954

4055
let mut query = keywords::table.into_boxed();
@@ -49,15 +64,15 @@ pub async fn list_keywords(
4964
let mut conn = state.db_read().await?;
5065
let data: Paginated<Keyword> = query.load(&mut conn).await?;
5166
let total = data.total();
52-
let kws = data
53-
.into_iter()
54-
.map(Keyword::into)
55-
.collect::<Vec<EncodableKeyword>>();
67+
let keywords = data.into_iter().map(Keyword::into).collect();
5668

57-
Ok(json!({
58-
"keywords": kws,
59-
"meta": { "total": total },
60-
}))
69+
let meta = ListMeta { total };
70+
Ok(Json(ListResponse { keywords, meta }))
71+
}
72+
73+
#[derive(Debug, Serialize, utoipa::ToSchema)]
74+
pub struct GetResponse {
75+
pub keyword: EncodableKeyword,
6176
}
6277

6378
/// Get keyword metadata.
@@ -68,11 +83,14 @@ pub async fn list_keywords(
6883
("keyword" = String, Path, description = "The keyword to find"),
6984
),
7085
tag = "keywords",
71-
responses((status = 200, description = "Successful Response")),
86+
responses((status = 200, description = "Successful Response", body = inline(GetResponse))),
7287
)]
73-
pub async fn find_keyword(Path(name): Path<String>, state: AppState) -> AppResult<ErasedJson> {
88+
pub async fn find_keyword(
89+
Path(name): Path<String>,
90+
state: AppState,
91+
) -> AppResult<Json<GetResponse>> {
7492
let mut conn = state.db_read().await?;
7593
let kw = Keyword::find_by_keyword(&mut conn, &name).await?;
76-
77-
Ok(json!({ "keyword": EncodableKeyword::from(kw) }))
94+
let keyword = EncodableKeyword::from(kw);
95+
Ok(Json(GetResponse { keyword }))
7896
}

src/openapi.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ other clients).
4040
license(name = "MIT OR Apache-2.0", url = "https://github.com/rust-lang/crates.io/blob/main/README.md#%EF%B8%8F-license"),
4141
version = "0.0.0",
4242
),
43+
components(
44+
schemas(
45+
crate::views::EncodableKeyword,
46+
),
47+
),
4348
modifiers(&SecurityAddon),
4449
servers(
4550
(url = "https://crates.io"),

src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,41 @@ expression: response.json()
44
---
55
{
66
"components": {
7+
"schemas": {
8+
"Keyword": {
9+
"properties": {
10+
"crates_cnt": {
11+
"description": "The total number of crates that have this keyword.",
12+
"example": 42,
13+
"format": "int32",
14+
"type": "integer"
15+
},
16+
"created_at": {
17+
"description": "The date and time this keyword was created.",
18+
"example": "2017-01-06T14:23:11Z",
19+
"format": "date-time",
20+
"type": "string"
21+
},
22+
"id": {
23+
"description": "An opaque identifier for the keyword.",
24+
"example": "http",
25+
"type": "string"
26+
},
27+
"keyword": {
28+
"description": "The keyword itself.",
29+
"example": "http",
30+
"type": "string"
31+
}
32+
},
33+
"required": [
34+
"id",
35+
"keyword",
36+
"created_at",
37+
"crates_cnt"
38+
],
39+
"type": "object"
40+
}
41+
},
742
"securitySchemes": {
843
"api_token": {
944
"description": "The API token is used to authenticate requests from cargo and other clients.",
@@ -1331,6 +1366,40 @@ expression: response.json()
13311366
],
13321367
"responses": {
13331368
"200": {
1369+
"content": {
1370+
"application/json": {
1371+
"schema": {
1372+
"properties": {
1373+
"keywords": {
1374+
"description": "The list of keywords.",
1375+
"items": {
1376+
"$ref": "#/components/schemas/Keyword"
1377+
},
1378+
"type": "array"
1379+
},
1380+
"meta": {
1381+
"properties": {
1382+
"total": {
1383+
"description": "The total number of keywords.",
1384+
"example": 123,
1385+
"format": "int64",
1386+
"type": "integer"
1387+
}
1388+
},
1389+
"required": [
1390+
"total"
1391+
],
1392+
"type": "object"
1393+
}
1394+
},
1395+
"required": [
1396+
"keywords",
1397+
"meta"
1398+
],
1399+
"type": "object"
1400+
}
1401+
}
1402+
},
13341403
"description": "Successful Response"
13351404
}
13361405
},
@@ -1356,6 +1425,21 @@ expression: response.json()
13561425
],
13571426
"responses": {
13581427
"200": {
1428+
"content": {
1429+
"application/json": {
1430+
"schema": {
1431+
"properties": {
1432+
"keyword": {
1433+
"$ref": "#/components/schemas/Keyword"
1434+
}
1435+
},
1436+
"required": [
1437+
"keyword"
1438+
],
1439+
"type": "object"
1440+
}
1441+
}
1442+
},
13591443
"description": "Successful Response"
13601444
}
13611445
},

src/views.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,23 @@ impl From<VersionDownload> for EncodableVersionDownload {
158158
}
159159
}
160160

161-
#[derive(Serialize, Deserialize, Debug)]
161+
#[derive(Serialize, Deserialize, Debug, utoipa::ToSchema)]
162+
#[schema(as = Keyword)]
162163
pub struct EncodableKeyword {
164+
/// An opaque identifier for the keyword.
165+
#[schema(example = "http")]
163166
pub id: String,
167+
168+
/// The keyword itself.
169+
#[schema(example = "http")]
164170
pub keyword: String,
171+
172+
/// The date and time this keyword was created.
173+
#[schema(example = "2017-01-06T14:23:11Z")]
165174
pub created_at: DateTime<Utc>,
175+
176+
/// The total number of crates that have this keyword.
177+
#[schema(example = 42)]
166178
pub crates_cnt: i32,
167179
}
168180

0 commit comments

Comments
 (0)