diff --git a/.sqlx/query-f93135119437ea6896864d413e0c6321ba93e043002509290acf736e7210caf2.json b/.sqlx/query-05bc6ad8a62d65aeedfff6c4a10a8d09ff86b671b31668bb2571b151206ef7f5.json similarity index 56% rename from .sqlx/query-f93135119437ea6896864d413e0c6321ba93e043002509290acf736e7210caf2.json rename to .sqlx/query-05bc6ad8a62d65aeedfff6c4a10a8d09ff86b671b31668bb2571b151206ef7f5.json index 0a37c5564..66bade59a 100644 --- a/.sqlx/query-f93135119437ea6896864d413e0c6321ba93e043002509290acf736e7210caf2.json +++ b/.sqlx/query-05bc6ad8a62d65aeedfff6c4a10a8d09ff86b671b31668bb2571b151206ef7f5.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n crates.name,\n releases.version,\n releases.description,\n releases.target_name,\n releases.rustdoc_status,\n releases.default_target,\n releases.doc_targets,\n releases.yanked,\n builds.rustc_version as \"rustc_version?\"\n FROM releases\n INNER JOIN crates ON crates.id = releases.crate_id\n LEFT JOIN LATERAL (\n SELECT * FROM builds\n WHERE builds.rid = releases.id\n ORDER BY builds.build_finished\n DESC LIMIT 1\n ) AS builds ON true\n WHERE crates.name = $1 AND releases.version = $2", + "query": "SELECT\n crates.name as \"name: KrateName\",\n releases.version,\n releases.description,\n releases.target_name,\n releases.rustdoc_status,\n releases.default_target,\n releases.doc_targets,\n releases.yanked,\n builds.rustc_version as \"rustc_version?\"\n FROM releases\n INNER JOIN crates ON crates.id = releases.crate_id\n LEFT JOIN LATERAL (\n SELECT * FROM builds\n WHERE builds.rid = releases.id\n ORDER BY builds.build_finished\n DESC LIMIT 1\n ) AS builds ON true\n WHERE crates.name = $1 AND releases.version = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "name", + "name": "name: KrateName", "type_info": "Text" }, { @@ -67,5 +67,5 @@ true ] }, - "hash": "f93135119437ea6896864d413e0c6321ba93e043002509290acf736e7210caf2" + "hash": "05bc6ad8a62d65aeedfff6c4a10a8d09ff86b671b31668bb2571b151206ef7f5" } diff --git a/.sqlx/query-c30ed3510f60d2ae638d941aa8e1dee3ceecb713f8bd10869462726443a36ed5.json b/.sqlx/query-cf5426dc0b94b07b8aea1fde144bbbb3caa1e6ebfb5d9348f5d27a134b82a55c.json similarity index 62% rename from .sqlx/query-c30ed3510f60d2ae638d941aa8e1dee3ceecb713f8bd10869462726443a36ed5.json rename to .sqlx/query-cf5426dc0b94b07b8aea1fde144bbbb3caa1e6ebfb5d9348f5d27a134b82a55c.json index 041427168..0cfc44e90 100644 --- a/.sqlx/query-c30ed3510f60d2ae638d941aa8e1dee3ceecb713f8bd10869462726443a36ed5.json +++ b/.sqlx/query-cf5426dc0b94b07b8aea1fde144bbbb3caa1e6ebfb5d9348f5d27a134b82a55c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n crates.id AS \"crate_id: CrateId\",\n releases.id AS \"release_id: ReleaseId\",\n crates.name,\n releases.version,\n releases.description,\n releases.dependencies,\n releases.readme,\n releases.description_long,\n releases.release_time,\n release_build_status.build_status as \"build_status!: BuildStatus\",\n -- this is the latest build ID that generated content\n -- it's used to invalidate some blob storage related caches.\n builds.id as \"latest_build_id?: BuildId\",\n releases.rustdoc_status,\n releases.archive_storage,\n releases.repository_url,\n releases.homepage_url,\n releases.keywords,\n releases.have_examples,\n releases.target_name,\n repositories.host as \"repo_host?\",\n repositories.stars as \"repo_stars?\",\n repositories.forks as \"repo_forks?\",\n repositories.issues as \"repo_issues?\",\n repositories.name as \"repo_name?\",\n releases.is_library,\n releases.yanked,\n releases.doc_targets,\n releases.license,\n releases.documentation_url,\n releases.default_target,\n releases.source_size as \"source_size?\",\n builds.documentation_size as \"documentation_size?\",\n -- we're using the rustc version here to set the correct CSS file\n -- in the metadata.\n -- So we're only interested in successful builds here.\n builds.rustc_version as \"rustc_version?\",\n doc_coverage.total_items,\n doc_coverage.documented_items,\n doc_coverage.total_items_needing_examples,\n doc_coverage.items_with_examples\n FROM releases\n INNER JOIN release_build_status ON releases.id = release_build_status.rid\n INNER JOIN crates ON releases.crate_id = crates.id\n LEFT JOIN doc_coverage ON doc_coverage.release_id = releases.id\n LEFT JOIN repositories ON releases.repository_id = repositories.id\n LEFT JOIN LATERAL (\n SELECT rustc_version, documentation_size, id\n FROM builds\n WHERE\n builds.rid = releases.id AND\n builds.build_status = 'success'\n ORDER BY builds.build_finished\n DESC LIMIT 1\n ) AS builds ON true\n WHERE crates.name = $1 AND releases.version = $2;", + "query": "SELECT\n crates.id AS \"crate_id: CrateId\",\n releases.id AS \"release_id: ReleaseId\",\n crates.name as \"name: KrateName\",\n releases.version,\n releases.description,\n releases.dependencies,\n releases.readme,\n releases.description_long,\n releases.release_time,\n release_build_status.build_status as \"build_status!: BuildStatus\",\n -- this is the latest build ID that generated content\n -- it's used to invalidate some blob storage related caches.\n builds.id as \"latest_build_id?: BuildId\",\n releases.rustdoc_status,\n releases.archive_storage,\n releases.repository_url,\n releases.homepage_url,\n releases.keywords,\n releases.have_examples,\n releases.target_name,\n repositories.host as \"repo_host?\",\n repositories.stars as \"repo_stars?\",\n repositories.forks as \"repo_forks?\",\n repositories.issues as \"repo_issues?\",\n repositories.name as \"repo_name?\",\n releases.is_library,\n releases.yanked,\n releases.doc_targets,\n releases.license,\n releases.documentation_url,\n releases.default_target,\n releases.source_size as \"source_size?\",\n builds.documentation_size as \"documentation_size?\",\n -- we're using the rustc version here to set the correct CSS file\n -- in the metadata.\n -- So we're only interested in successful builds here.\n builds.rustc_version as \"rustc_version?\",\n doc_coverage.total_items,\n doc_coverage.documented_items,\n doc_coverage.total_items_needing_examples,\n doc_coverage.items_with_examples\n FROM releases\n INNER JOIN release_build_status ON releases.id = release_build_status.rid\n INNER JOIN crates ON releases.crate_id = crates.id\n LEFT JOIN doc_coverage ON doc_coverage.release_id = releases.id\n LEFT JOIN repositories ON releases.repository_id = repositories.id\n LEFT JOIN LATERAL (\n SELECT rustc_version, documentation_size, id\n FROM builds\n WHERE\n builds.rid = releases.id AND\n builds.build_status = 'success'\n ORDER BY builds.build_finished\n DESC LIMIT 1\n ) AS builds ON true\n WHERE crates.name = $1 AND releases.version = $2;", "describe": { "columns": [ { @@ -15,7 +15,7 @@ }, { "ordinal": 2, - "name": "name", + "name": "name: KrateName", "type_info": "Text" }, { @@ -240,5 +240,5 @@ true ] }, - "hash": "c30ed3510f60d2ae638d941aa8e1dee3ceecb713f8bd10869462726443a36ed5" + "hash": "cf5426dc0b94b07b8aea1fde144bbbb3caa1e6ebfb5d9348f5d27a134b82a55c" } diff --git a/.sqlx/query-13002f36b4c77ce0055b591051f1f807bdbe47991abab7cd781f5dda5d8b8b56.json b/.sqlx/query-d68c28ed8e41b9e3812d15e8447ad2ea418ce40868afa2fc51da3c77a3ca8d9c.json similarity index 61% rename from .sqlx/query-13002f36b4c77ce0055b591051f1f807bdbe47991abab7cd781f5dda5d8b8b56.json rename to .sqlx/query-d68c28ed8e41b9e3812d15e8447ad2ea418ce40868afa2fc51da3c77a3ca8d9c.json index 667249a45..b5f790a93 100644 --- a/.sqlx/query-13002f36b4c77ce0055b591051f1f807bdbe47991abab7cd781f5dda5d8b8b56.json +++ b/.sqlx/query-d68c28ed8e41b9e3812d15e8447ad2ea418ce40868afa2fc51da3c77a3ca8d9c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n id as \"id: CrateId\",\n name\n FROM crates\n WHERE normalize_crate_name(name) = normalize_crate_name($1)", + "query": "\n SELECT\n id as \"id: CrateId\",\n name as \"name: KrateName\"\n FROM crates\n WHERE normalize_crate_name(name) = normalize_crate_name($1)", "describe": { "columns": [ { @@ -10,7 +10,7 @@ }, { "ordinal": 1, - "name": "name", + "name": "name: KrateName", "type_info": "Text" } ], @@ -24,5 +24,5 @@ false ] }, - "hash": "13002f36b4c77ce0055b591051f1f807bdbe47991abab7cd781f5dda5d8b8b56" + "hash": "d68c28ed8e41b9e3812d15e8447ad2ea418ce40868afa2fc51da3c77a3ca8d9c" } diff --git a/src/db/types/krate_name.rs b/src/db/types/krate_name.rs index 44e963b67..0b963426b 100644 --- a/src/db/types/krate_name.rs +++ b/src/db/types/krate_name.rs @@ -18,7 +18,17 @@ use std::{io::Write, str::FromStr}; /// FIXME: this should actually come from some shared crate between the rust projects, /// so the amount of duplication is less. #[derive( - Debug, Clone, PartialEq, Eq, Hash, Deref, Into, Display, DeserializeFromStr, SerializeDisplay, + Debug, + Clone, + PartialEq, + Eq, + Hash, + Deref, + Into, + Display, + DeserializeFromStr, + SerializeDisplay, + bincode::Encode, )] pub struct KrateName(String); diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index af4bffd04..6822c51d9 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -2,7 +2,10 @@ use crate::{ AsyncStorage, db::{ BuildId, CrateId, ReleaseId, - types::{BuildStatus, dependencies::ReleaseDependencyList, version::Version}, + types::{ + BuildStatus, dependencies::ReleaseDependencyList, krate_name::KrateName, + version::Version, + }, }, impl_axum_webpage, registry_api::OwnerKind, @@ -36,7 +39,7 @@ use std::sync::Arc; // TODO: Add target name and versions #[derive(Debug, Clone, PartialEq)] pub(crate) struct CrateDetails { - pub(crate) name: String, + pub(crate) name: KrateName, pub(crate) version: Version, pub(crate) description: Option, pub(crate) owners: Vec<(String, String, OwnerKind)>, @@ -136,7 +139,7 @@ impl CrateDetails { r#"SELECT crates.id AS "crate_id: CrateId", releases.id AS "release_id: ReleaseId", - crates.name, + crates.name as "name: KrateName", releases.version, releases.description, releases.dependencies, @@ -437,7 +440,7 @@ pub(crate) async fn releases_for_crate( #[template(path = "crate/details.html")] struct CrateDetailsPage { version: Version, - name: String, + name: KrateName, owners: Vec<(String, String, OwnerKind)>, metadata: MetaData, documented_items: Option, diff --git a/src/web/extractors/rustdoc.rs b/src/web/extractors/rustdoc.rs index 4d7d29b99..5b84959c3 100644 --- a/src/web/extractors/rustdoc.rs +++ b/src/web/extractors/rustdoc.rs @@ -1,7 +1,7 @@ //! special rustdoc extractors use crate::{ - db::BuildId, + db::{BuildId, types::krate_name::KrateName}, storage::CompressionAlgorithm, web::{ MatchedRelease, MetaData, ReqVersion, error::AxumNope, escaped_uri::EscapedURI, @@ -50,6 +50,7 @@ pub(crate) struct RustdocParams { original_uri: Option, name: String, + confirmed_name: Option, req_version: ReqVersion, doc_target: Option, inner_path: Option, @@ -68,6 +69,7 @@ impl std::fmt::Debug for RustdocParams { .field("page_kind", &self.page_kind) .field("original_uri", &self.original_uri) .field("name", &self.name) + .field("confirmed_name", &self.confirmed_name) .field("req_version", &self.req_version) .field("doc_target", &self.doc_target) .field("inner_path", &self.inner_path) @@ -166,6 +168,7 @@ impl RustdocParams { pub(crate) fn new(name: impl Into) -> Self { Self { name: name.into().trim().into(), + confirmed_name: None, req_version: ReqVersion::default(), original_uri: None, doc_target: None, @@ -200,11 +203,12 @@ impl RustdocParams { } pub(crate) fn from_metadata(metadata: &MetaData) -> Self { - RustdocParams::new(&metadata.name).apply_metadata(metadata) + RustdocParams::new(metadata.name.to_string()).apply_metadata(metadata) } pub(crate) fn apply_metadata(self, metadata: &MetaData) -> RustdocParams { - self.with_name(&metadata.name) + self.with_name(metadata.name.to_string()) + .with_confirmed_name(Some(metadata.name.clone())) .with_req_version(&metadata.req_version) // first set the doc-target list .with_maybe_doc_targets(metadata.doc_targets.clone()) @@ -214,12 +218,13 @@ impl RustdocParams { } pub(crate) fn from_matched_release(matched_release: &MatchedRelease) -> Self { - RustdocParams::new(&matched_release.name).apply_matched_release(matched_release) + RustdocParams::new(matched_release.name.to_string()).apply_matched_release(matched_release) } pub(crate) fn apply_matched_release(self, matched_release: &MatchedRelease) -> RustdocParams { let release = &matched_release.release; - self.with_name(&matched_release.name) + self.with_name(matched_release.name.to_string()) + .with_confirmed_name(Some(matched_release.name.clone())) .with_req_version(&matched_release.req_version) .with_maybe_doc_targets(release.doc_targets.as_deref()) .with_maybe_default_target(release.default_target.as_deref()) @@ -236,6 +241,16 @@ impl RustdocParams { }) } + pub(crate) fn confirmed_name(&self) -> Option<&KrateName> { + self.confirmed_name.as_ref() + } + pub(crate) fn with_confirmed_name(self, confirmed_name: Option>) -> Self { + self.update(|mut params| { + params.confirmed_name = confirmed_name.map(Into::into); + params + }) + } + pub(crate) fn req_version(&self) -> &ReqVersion { &self.req_version } diff --git a/src/web/mod.rs b/src/web/mod.rs index 451c40cf9..9f27ae8e5 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -5,7 +5,7 @@ pub mod page; use crate::{ db::{ CrateId, - types::{BuildStatus, version::Version}, + types::{BuildStatus, krate_name::KrateName, version::Version}, }, utils::{get_correct_docsrs_style_file, report_error}, web::{ @@ -201,12 +201,12 @@ impl TryFrom<&str> for ReqVersion { #[derive(Debug)] pub(crate) struct MatchedRelease { /// crate name - pub name: String, + pub name: KrateName, /// The crate name that was found when attempting to load a crate release. /// `match_version` will attempt to match a provided crate name against similar crate names with /// dashes (`-`) replaced with underscores (`_`) and vice versa. - pub corrected_name: Option, + pub corrected_name: Option, /// what kind of version did we get in the request? ("latest", semver, exact) pub req_version: ReqVersion, @@ -344,12 +344,12 @@ async fn match_version( name: &str, input_version: &ReqVersion, ) -> Result { - let (crate_id, corrected_name) = { + let (crate_id, name, corrected_name) = { let row = sqlx::query!( r#" SELECT id as "id: CrateId", - name + name as "name: KrateName" FROM crates WHERE normalize_crate_name(name) = normalize_crate_name($1)"#, name, @@ -359,10 +359,14 @@ async fn match_version( .context("error fetching crate")? .ok_or(AxumNope::CrateNotFound)?; + let name: KrateName = name + .parse() + .expect("here we know it's valid, because we found it after normalizing"); + if row.name != name { - (row.id, Some(row.name)) + (row.id, name, Some(row.name)) } else { - (row.id, None) + (row.id, name, None) } }; @@ -383,7 +387,7 @@ async fn match_version( .find(|release| &release.version == parsed_req_version) { return Ok(MatchedRelease { - name: name.to_owned(), + name, corrected_name, req_version: input_version.clone(), release: release.clone(), @@ -689,7 +693,7 @@ where /// MetaData used in header #[derive(Debug, Clone, PartialEq, Eq, Serialize, bincode::Encode)] pub(crate) struct MetaData { - pub(crate) name: String, + pub(crate) name: KrateName, /// The exact version of the release being shown. pub(crate) version: Version, /// The version identifier in the request that was used to request this page. @@ -718,7 +722,7 @@ impl MetaData { ) -> Result { let row = sqlx::query!( r#"SELECT - crates.name, + crates.name as "name: KrateName", releases.version, releases.description, releases.target_name, @@ -1208,7 +1212,7 @@ mod test { #[test] fn serialize_metadata() { let mut metadata = MetaData { - name: "serde".to_string(), + name: "serde".parse().unwrap(), version: "1.0.0".parse().unwrap(), req_version: ReqVersion::Latest, description: Some("serde does stuff".to_string()), @@ -1295,7 +1299,7 @@ mod test { assert_eq!( metadata.unwrap(), MetaData { - name: "foo".to_string(), + name: "foo".parse().unwrap(), version: "0.1.0".parse().unwrap(), req_version: ReqVersion::Latest, description: Some("Fake package".to_string()),