diff --git a/src/controllers/version/downloads.rs b/src/controllers/version/downloads.rs index 0ccd4a66fa9..631a29438e8 100644 --- a/src/controllers/version/downloads.rs +++ b/src/controllers/version/downloads.rs @@ -9,15 +9,22 @@ use crate::schema::*; use crate::util::errors::AppResult; use crate::util::{RequestUtils, redirect}; use crate::views::EncodableVersionDownload; +use axum::Json; use axum::extract::{FromRequestParts, Query}; use axum::response::{IntoResponse, Response}; use axum_extra::json; -use axum_extra::response::ErasedJson; use chrono::{Duration, NaiveDate, Utc}; use diesel::prelude::*; use diesel_async::RunQueryDsl; use http::request::Parts; +#[derive(Debug, Serialize, utoipa::ToSchema)] +pub struct UrlResponse { + /// The URL to the crate file. + #[schema(example = "https://static.crates.io/crates/serde/serde-1.0.0.crate")] + pub url: String, +} + /// Download a crate version. /// /// This returns a URL to the location where the crate is stored. @@ -26,7 +33,10 @@ use http::request::Parts; path = "/api/v1/crates/{name}/{version}/download", params(CrateVersionPath), tag = "versions", - responses((status = 200, description = "Successful Response")), + responses( + (status = 302, description = "Successful Response (default)", headers(("location" = String, description = "The URL to the crate file."))), + (status = 200, description = "Successful Response (for `content-type: application/json`)", body = inline(UrlResponse)), + ), )] pub async fn download_version( app: AppState, @@ -51,6 +61,11 @@ pub struct DownloadsQueryParams { before_date: Option, } +#[derive(Debug, Serialize, utoipa::ToSchema)] +pub struct DownloadsResponse { + pub version_downloads: Vec, +} + /// Get the download counts for a crate version. /// /// This includes the per-day downloads for the last 90 days. @@ -59,13 +74,13 @@ pub struct DownloadsQueryParams { path = "/api/v1/crates/{name}/{version}/downloads", params(CrateVersionPath, DownloadsQueryParams), tag = "versions", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(DownloadsResponse))), )] pub async fn get_version_downloads( app: AppState, path: CrateVersionPath, params: DownloadsQueryParams, -) -> AppResult { +) -> AppResult> { let mut conn = app.db_read().await?; let version = path.load_version(&mut conn).await?; @@ -75,14 +90,14 @@ pub async fn get_version_downloads( let cutoff_start_date = cutoff_end_date - Duration::days(89); - let downloads = VersionDownload::belonging_to(&version) + let version_downloads = VersionDownload::belonging_to(&version) .filter(version_downloads::date.between(cutoff_start_date, cutoff_end_date)) .order(version_downloads::date) .load(&mut conn) .await? .into_iter() .map(VersionDownload::into) - .collect::>(); + .collect(); - Ok(json!({ "version_downloads": downloads })) + Ok(Json(DownloadsResponse { version_downloads })) } diff --git a/src/controllers/version/readme.rs b/src/controllers/version/readme.rs index 27236fd2613..19747ff6aba 100644 --- a/src/controllers/version/readme.rs +++ b/src/controllers/version/readme.rs @@ -1,23 +1,33 @@ use crate::app::AppState; use crate::controllers::version::CrateVersionPath; use crate::util::{RequestUtils, redirect}; +use axum::Json; use axum::response::{IntoResponse, Response}; -use axum_extra::json; use http::request::Parts; +#[derive(Debug, Serialize, utoipa::ToSchema)] +pub struct UrlResponse { + /// The URL to the readme file. + #[schema(example = "https://static.crates.io/readmes/serde/serde-1.0.0.html")] + pub url: String, +} + /// Get the readme of a crate version. #[utoipa::path( get, path = "/api/v1/crates/{name}/{version}/readme", params(CrateVersionPath), tag = "versions", - responses((status = 200, description = "Successful Response")), + responses( + (status = 302, description = "Successful Response (default)", headers(("location" = String, description = "The URL to the readme file."))), + (status = 200, description = "Successful Response (for `content-type: application/json`)", body = inline(UrlResponse)), + ), )] pub async fn get_version_readme(app: AppState, path: CrateVersionPath, req: Parts) -> Response { - let redirect_url = app.storage.readme_location(&path.name, &path.version); + let url = app.storage.readme_location(&path.name, &path.version); if req.wants_json() { - json!({ "url": redirect_url }).into_response() + Json(UrlResponse { url }).into_response() } else { - redirect(redirect_url) + redirect(url) } } diff --git a/src/controllers/version/update.rs b/src/controllers/version/update.rs index f74c2f5969c..272bb0517d2 100644 --- a/src/controllers/version/update.rs +++ b/src/controllers/version/update.rs @@ -10,8 +10,6 @@ use crate::util::errors::{AppResult, bad_request, custom}; use crate::views::EncodableVersion; use crate::worker::jobs::{SyncToGitIndex, SyncToSparseIndex, UpdateDefaultVersion}; use axum::Json; -use axum_extra::json; -use axum_extra::response::ErasedJson; use crates_io_worker::BackgroundJob; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; @@ -29,6 +27,11 @@ pub struct VersionUpdateRequest { version: VersionUpdate, } +#[derive(Debug, Serialize, utoipa::ToSchema)] +pub struct UpdateResponse { + pub version: EncodableVersion, +} + /// Update a crate version. /// /// This endpoint allows updating the `yanked` state of a version, including a yank message. @@ -41,14 +44,14 @@ pub struct VersionUpdateRequest { ("cookie" = []), ), tag = "versions", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(UpdateResponse))), )] pub async fn update_version( state: AppState, path: CrateVersionPath, req: Parts, Json(update_request): Json, -) -> AppResult { +) -> AppResult> { let mut conn = state.db_write().await?; let (mut version, krate) = path.load_version_and_crate(&mut conn).await?; validate_yank_update(&update_request.version, &version)?; @@ -74,8 +77,8 @@ pub async fn update_version( VersionOwnerAction::by_version(&mut conn, &version), version.published_by(&mut conn), )?; - let updated_version = EncodableVersion::from(version, &krate.name, published_by, actions); - Ok(json!({ "version": updated_version })) + let version = EncodableVersion::from(version, &krate.name, published_by, actions); + Ok(Json(UpdateResponse { version })) } fn validate_yank_update(update_data: &VersionUpdate, version: &Version) -> AppResult<()> { diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 52991927aa4..9e73339eb4e 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -645,6 +645,33 @@ expression: response.json() ], "type": "object" }, + "VersionDownload": { + "properties": { + "date": { + "description": "The date this download count is for.", + "example": "2019-12-13", + "type": "string" + }, + "downloads": { + "description": "The number of downloads for this version on the given date.", + "example": 123, + "format": "int32", + "type": "integer" + }, + "version": { + "description": "The ID of the version this download count is for.", + "example": 42, + "format": "int32", + "type": "integer" + } + }, + "required": [ + "version", + "downloads", + "date" + ], + "type": "object" + }, "VersionLinks": { "properties": { "authors": { @@ -1829,6 +1856,21 @@ expression: response.json() ], "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "version": { + "$ref": "#/components/schemas/Version" + } + }, + "required": [ + "version" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -1964,7 +2006,35 @@ expression: response.json() ], "responses": { "200": { - "description": "Successful Response" + "content": { + "application/json": { + "schema": { + "properties": { + "url": { + "description": "The URL to the crate file.", + "example": "https://static.crates.io/crates/serde/serde-1.0.0.crate", + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + } + } + }, + "description": "Successful Response (for `content-type: application/json`)" + }, + "302": { + "description": "Successful Response (default)", + "headers": { + "location": { + "description": "The URL to the crate file.", + "schema": { + "type": "string" + } + } + } } }, "summary": "Download a crate version.", @@ -2011,6 +2081,24 @@ expression: response.json() ], "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "version_downloads": { + "items": { + "$ref": "#/components/schemas/VersionDownload" + }, + "type": "array" + } + }, + "required": [ + "version_downloads" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -2046,7 +2134,35 @@ expression: response.json() ], "responses": { "200": { - "description": "Successful Response" + "content": { + "application/json": { + "schema": { + "properties": { + "url": { + "description": "The URL to the readme file.", + "example": "https://static.crates.io/readmes/serde/serde-1.0.0.html", + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + } + } + }, + "description": "Successful Response (for `content-type: application/json`)" + }, + "302": { + "description": "Successful Response (default)", + "headers": { + "location": { + "description": "The URL to the readme file.", + "schema": { + "type": "string" + } + } + } } }, "summary": "Get the readme of a crate version.", diff --git a/src/views.rs b/src/views.rs index 0a099853bb8..2d4ecef3476 100644 --- a/src/views.rs +++ b/src/views.rs @@ -201,10 +201,19 @@ impl EncodableDependency { } } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, utoipa::ToSchema)] +#[schema(as = VersionDownload)] pub struct EncodableVersionDownload { + /// The ID of the version this download count is for. + #[schema(example = 42)] pub version: i32, + + /// The number of downloads for this version on the given date. + #[schema(example = 123)] pub downloads: i32, + + /// The date this download count is for. + #[schema(example = "2019-12-13")] pub date: String, }