Skip to content

Commit 75f4a21

Browse files
committed
models/krate: Inline validate() method
This function does not need to be on the `NewCrate` struct. Making it independent from the struct allows us to perform this validation much earlier in the process and combine it with other validations.
1 parent 6f311e0 commit 75f4a21

File tree

2 files changed

+32
-45
lines changed

2 files changed

+32
-45
lines changed

src/controllers/krate/publish.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use hex::ToHex;
99
use hyper::body::Buf;
1010
use sha2::{Digest, Sha256};
1111
use tokio::runtime::Handle;
12+
use url::Url;
1213

1314
use crate::controllers::cargo_prelude::*;
1415
use crate::models::{
@@ -145,7 +146,10 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
145146

146147
let license_file = metadata.license_file.as_deref();
147148

148-
persist.validate()?;
149+
validate_url(persist.homepage, "homepage")?;
150+
validate_url(persist.documentation, "documentation")?;
151+
validate_url(persist.repository, "repository")?;
152+
149153
if is_reserved_name(persist.name, conn)? {
150154
return Err(cargo_err("cannot upload a crate with a reserved name"));
151155
}
@@ -347,6 +351,26 @@ fn is_reserved_name(name: &str, conn: &mut PgConnection) -> QueryResult<bool> {
347351
.get_result(conn)
348352
}
349353

354+
fn validate_url(url: Option<&str>, field: &str) -> AppResult<()> {
355+
let url = match url {
356+
Some(s) => s,
357+
None => return Ok(()),
358+
};
359+
360+
// Manually check the string, as `Url::parse` may normalize relative URLs
361+
// making it difficult to ensure that both slashes are present.
362+
if !url.starts_with("http://") && !url.starts_with("https://") {
363+
return Err(cargo_err(&format_args!(
364+
"URL for field `{field}` must begin with http:// or https:// (url: {url})"
365+
)));
366+
}
367+
368+
// Ensure the entire URL parses as well
369+
Url::parse(url)
370+
.map_err(|_| cargo_err(&format_args!("`{field}` is not a valid url: `{url}`")))?;
371+
Ok(())
372+
}
373+
350374
fn missing_metadata_error_message(missing: &[&str]) -> String {
351375
format!(
352376
"missing or empty metadata fields: {}. Please \
@@ -453,7 +477,12 @@ impl From<TarballError> for BoxedAppError {
453477

454478
#[cfg(test)]
455479
mod tests {
456-
use super::missing_metadata_error_message;
480+
use super::{missing_metadata_error_message, validate_url};
481+
482+
#[test]
483+
fn deny_relative_urls() {
484+
assert_err!(validate_url(Some("https:/example.com/home"), "homepage"));
485+
}
457486

458487
#[test]
459488
fn missing_metadata_error_message_test() {

src/models/krate.rs

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use diesel::associations::Identifiable;
55
use diesel::pg::Pg;
66
use diesel::prelude::*;
77
use diesel::sql_types::{Bool, Text};
8-
use url::Url;
98

109
use crate::app::App;
1110
use crate::controllers::helpers::pagination::*;
@@ -121,33 +120,6 @@ impl<'a> NewCrate<'a> {
121120
})
122121
}
123122

124-
pub fn validate(&self) -> AppResult<()> {
125-
fn validate_url(url: Option<&str>, field: &str) -> AppResult<()> {
126-
let url = match url {
127-
Some(s) => s,
128-
None => return Ok(()),
129-
};
130-
131-
// Manually check the string, as `Url::parse` may normalize relative URLs
132-
// making it difficult to ensure that both slashes are present.
133-
if !url.starts_with("http://") && !url.starts_with("https://") {
134-
return Err(cargo_err(&format_args!(
135-
"URL for field `{field}` must begin with http:// or https:// (url: {url})"
136-
)));
137-
}
138-
139-
// Ensure the entire URL parses as well
140-
Url::parse(url)
141-
.map_err(|_| cargo_err(&format_args!("`{field}` is not a valid url: `{url}`")))?;
142-
Ok(())
143-
}
144-
145-
validate_url(self.homepage, "homepage")?;
146-
validate_url(self.documentation, "documentation")?;
147-
validate_url(self.repository, "repository")?;
148-
Ok(())
149-
}
150-
151123
fn save_new_crate(&self, conn: &mut PgConnection, user_id: i32) -> QueryResult<Option<Crate>> {
152124
use crate::schema::crates::dsl::*;
153125

@@ -502,21 +474,7 @@ impl Crate {
502474

503475
#[cfg(test)]
504476
mod tests {
505-
use crate::models::{Crate, NewCrate};
506-
507-
#[test]
508-
fn deny_relative_urls() {
509-
let krate = NewCrate {
510-
name: "name",
511-
description: None,
512-
homepage: Some("https:/example.com/home"),
513-
documentation: None,
514-
readme: None,
515-
repository: None,
516-
max_upload_size: None,
517-
};
518-
assert_err!(krate.validate());
519-
}
477+
use crate::models::Crate;
520478

521479
#[test]
522480
fn valid_name() {

0 commit comments

Comments
 (0)