Skip to content

Commit 1e6f1c0

Browse files
committed
controllers/version: Use custom deserializer to validate version field
1 parent bb90b39 commit 1e6f1c0

File tree

5 files changed

+14
-25
lines changed

5 files changed

+14
-25
lines changed

src/controllers/version.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ pub mod yank;
44

55
use axum::extract::{FromRequestParts, Path};
66
use diesel_async::AsyncPgConnection;
7+
use serde::de::Error;
8+
use serde::{Deserialize, Deserializer};
79
use utoipa::IntoParams;
810

911
use crate::models::{Crate, Version};
@@ -17,6 +19,7 @@ pub struct CrateVersionPath {
1719
pub name: String,
1820
/// Version number
1921
#[param(example = "1.0.0")]
22+
#[serde(deserialize_with = "deserialize_version")]
2023
pub version: String,
2124
}
2225

@@ -51,3 +54,9 @@ async fn version_and_crate(
5154

5255
Ok((version, krate))
5356
}
57+
58+
fn deserialize_version<'de, D: Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {
59+
let s = String::deserialize(deserializer)?;
60+
let _ = semver::Version::parse(&s).map_err(Error::custom)?;
61+
Ok(s)
62+
}

src/controllers/version/downloads.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use super::CrateVersionPath;
66
use crate::app::AppState;
77
use crate::models::VersionDownload;
88
use crate::schema::*;
9-
use crate::util::errors::{version_not_found, AppResult};
9+
use crate::util::errors::AppResult;
1010
use crate::util::{redirect, RequestUtils};
1111
use crate::views::EncodableVersionDownload;
1212
use axum::response::{IntoResponse, Response};
@@ -56,10 +56,6 @@ pub async fn get_version_downloads(
5656
path: CrateVersionPath,
5757
req: Parts,
5858
) -> AppResult<ErasedJson> {
59-
if semver::Version::parse(&path.version).is_err() {
60-
return Err(version_not_found(&path.name, &path.version));
61-
}
62-
6359
let mut conn = app.db_read().await?;
6460
let version = path.load_version(&mut conn).await?;
6561

src/controllers/version/metadata.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::models::{
2323
};
2424
use crate::rate_limiter::LimitedAction;
2525
use crate::schema::versions;
26-
use crate::util::errors::{bad_request, custom, version_not_found, AppResult};
26+
use crate::util::errors::{bad_request, custom, AppResult};
2727
use crate::views::{EncodableDependency, EncodableVersion};
2828
use crate::worker::jobs::{SyncToGitIndex, SyncToSparseIndex, UpdateDefaultVersion};
2929

@@ -57,10 +57,6 @@ pub async fn get_version_dependencies(
5757
state: AppState,
5858
path: CrateVersionPath,
5959
) -> AppResult<ErasedJson> {
60-
if semver::Version::parse(&path.version).is_err() {
61-
return Err(version_not_found(&path.name, &path.version));
62-
}
63-
6460
let mut conn = state.db_read().await?;
6561
let version = path.load_version(&mut conn).await?;
6662

@@ -105,10 +101,6 @@ pub async fn get_version_authors() -> ErasedJson {
105101
responses((status = 200, description = "Successful Response")),
106102
)]
107103
pub async fn find_version(state: AppState, path: CrateVersionPath) -> AppResult<ErasedJson> {
108-
if semver::Version::parse(&path.version).is_err() {
109-
return Err(version_not_found(&path.name, &path.version));
110-
}
111-
112104
let mut conn = state.db_read().await?;
113105
let (version, krate) = path.load_version_and_crate(&mut conn).await?;
114106
let published_by = version.published_by(&mut conn).await?;
@@ -134,10 +126,6 @@ pub async fn update_version(
134126
req: Parts,
135127
Json(update_request): Json<VersionUpdateRequest>,
136128
) -> AppResult<ErasedJson> {
137-
if semver::Version::parse(&path.version).is_err() {
138-
return Err(version_not_found(&path.name, &path.version));
139-
}
140-
141129
let mut conn = state.db_write().await?;
142130
let (mut version, krate) = path.load_version_and_crate(&mut conn).await?;
143131
validate_yank_update(&update_request.version, &version)?;

src/controllers/version/yank.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use super::CrateVersionPath;
55
use crate::app::AppState;
66
use crate::controllers::helpers::ok_true;
77
use crate::rate_limiter::LimitedAction;
8-
use crate::util::errors::{version_not_found, AppResult};
8+
use crate::util::errors::AppResult;
99
use axum::response::Response;
1010
use http::request::Parts;
1111

@@ -61,10 +61,6 @@ async fn modify_yank(
6161
// FIXME: Should reject bad requests before authentication, but can't due to
6262
// lifetime issues with `req`.
6363

64-
if semver::Version::parse(&path.version).is_err() {
65-
return Err(version_not_found(&path.name, &path.version));
66-
}
67-
6864
let mut conn = state.db_write().await?;
6965
let (mut version, krate) = path.load_version_and_crate(&mut conn).await?;
7066
let auth = authenticate(&req, &mut conn, &krate.name).await?;

src/tests/routes/crates/downloads.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,9 @@ async fn test_version_downloads() {
209209
let response = anon
210210
.get::<()>("/api/v1/crates/foo/invalid-version/downloads")
211211
.await;
212-
assert_eq!(response.status(), StatusCode::NOT_FOUND);
212+
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
213213
assert_snapshot!(
214214
response.text(),
215-
@r#"{"errors":[{"detail":"crate `foo` does not have a version `invalid-version`"}]}"#
215+
@r#"{"errors":[{"detail":"Invalid URL: unexpected character 'i' while parsing major version number"}]}"#
216216
);
217217
}

0 commit comments

Comments
 (0)