Skip to content

Commit 3bd9ef0

Browse files
committed
Add canonical URL for rustdoc pages
Omit the link for crates that specify their own documentation URL.
1 parent c280b04 commit 3bd9ef0

File tree

4 files changed

+93
-1
lines changed

4 files changed

+93
-1
lines changed

src/test/fakes.rs

+5
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,11 @@ impl<'a> FakeRelease<'a> {
245245
self
246246
}
247247

248+
pub(crate) fn documentation(mut self, documentation: Option<String>) -> Self {
249+
self.package.documentation = documentation;
250+
self
251+
}
252+
248253
/// Returns the release_id
249254
pub(crate) fn create(mut self) -> Result<i32> {
250255
use std::fs;

src/web/crate_details.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub struct CrateDetails {
3838
pub(crate) metadata: MetaData,
3939
is_library: bool,
4040
license: Option<String>,
41-
documentation_url: Option<String>,
41+
pub(crate) documentation_url: Option<String>,
4242
total_items: Option<f32>,
4343
documented_items: Option<f32>,
4444
total_items_needing_examples: Option<f32>,

src/web/rustdoc.rs

+83
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult<Response> {
195195
#[derive(Debug, Clone, PartialEq, Serialize)]
196196
struct RustdocPage {
197197
latest_path: String,
198+
canonical_url: String,
198199
permalink_path: String,
199200
latest_version: String,
200201
target: String,
@@ -483,6 +484,27 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
483484

484485
let latest_path = format!("/crate/{}/latest{}{}", name, target_redirect, query_string);
485486

487+
// Set the canonical URL for search engines to the `/latest/` page on docs.rs.
488+
// For crates with a documentation URL, where that URL doesn't point at docs.rs,
489+
// omit the canonical link to avoid penalizing external documentation.
490+
// Note: The URL this points to may not exist. For instance, if we're rendering
491+
// `struct Foo` in version 0.1.0 of a crate, and version 0.2.0 of that crate removes
492+
// `struct Foo`, this will point at a 404. That's fine: search engines will crawl
493+
// the target and will not canonicalize to a URL that doesn't exist.
494+
let canonical_url = if krate.documentation_url.is_none()
495+
|| krate
496+
.documentation_url
497+
.as_ref()
498+
.unwrap()
499+
.starts_with("https://docs.rs/")
500+
{
501+
// Don't include index.html in the canonical URL.
502+
let canonical_path = inner_path.replace("index.html", "");
503+
format!("https://docs.rs/{}/latest/{}", name, canonical_path)
504+
} else {
505+
"".to_string()
506+
};
507+
486508
metrics
487509
.recently_accessed_releases
488510
.record(krate.crate_id, krate.release_id, target);
@@ -496,6 +518,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
496518
rendering_time.step("rewrite html");
497519
RustdocPage {
498520
latest_path,
521+
canonical_url,
499522
permalink_path,
500523
latest_version,
501524
target,
@@ -1980,4 +2003,64 @@ mod test {
19802003
Ok(())
19812004
})
19822005
}
2006+
2007+
#[test]
2008+
fn canonical_url() {
2009+
wrapper(|env| {
2010+
env.fake_release()
2011+
.name("dummy-dash")
2012+
.version("0.1.0")
2013+
.documentation(Some("http://example.com".to_string()))
2014+
.rustdoc_file("dummy_dash/index.html")
2015+
.create()?;
2016+
2017+
env.fake_release()
2018+
.name("dummy-docs")
2019+
.version("0.1.0")
2020+
.documentation(Some("https://docs.rs/foo".to_string()))
2021+
.rustdoc_file("dummy_docs/index.html")
2022+
.create()?;
2023+
2024+
env.fake_release()
2025+
.name("dummy-nodocs")
2026+
.version("0.1.0")
2027+
.documentation(None)
2028+
.rustdoc_file("dummy_nodocs/index.html")
2029+
.rustdoc_file("dummy_nodocs/struct.Foo.html")
2030+
.create()?;
2031+
2032+
let web = env.frontend();
2033+
2034+
assert!(!web
2035+
.get("/dummy-dash/0.1.0/dummy_dash/")
2036+
.send()?
2037+
.text()?
2038+
.contains("rel=\"canonical\""),);
2039+
2040+
assert!(web
2041+
.get("/dummy-docs/0.1.0/dummy_docs/")
2042+
.send()?
2043+
.text()?
2044+
.contains(
2045+
"<link rel=\"canonical\" href=\"https://docs.rs/dummy-docs/latest/dummy_docs/\" />"
2046+
),);
2047+
2048+
assert!(
2049+
web
2050+
.get("/dummy-nodocs/0.1.0/dummy_nodocs/")
2051+
.send()?
2052+
.text()?
2053+
.contains("<link rel=\"canonical\" href=\"https://docs.rs/dummy-nodocs/latest/dummy_nodocs/\" />"),
2054+
);
2055+
2056+
assert!(
2057+
web
2058+
.get("/dummy-nodocs/0.1.0/dummy_nodocs/struct.Foo.html")
2059+
.send()?
2060+
.text()?
2061+
.contains("<link rel=\"canonical\" href=\"https://docs.rs/dummy-nodocs/latest/dummy_nodocs/struct.Foo.html\" />"),
2062+
);
2063+
Ok(())
2064+
})
2065+
}
19832066
}

templates/rustdoc/head.html

+4
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@
33

44
<link rel="search" href="/-/static/opensearch.xml" type="application/opensearchdescription+xml" title="Docs.rs" />
55

6+
{%- if canonical_url -%}
7+
<link rel="canonical" href="{{canonical_url | safe}}" />
8+
{%- endif -%}
9+
610
<script type="text/javascript">{%- include "theme.js" -%}</script>

0 commit comments

Comments
 (0)