Skip to content

Commit cccd8d9

Browse files
jshajyn514
authored andcommitted
Add canonical URL for rustdoc pages
Omit the link for crates that specify their own documentation URL.
1 parent 19b354a commit cccd8d9

File tree

4 files changed

+93
-1
lines changed

4 files changed

+93
-1
lines changed

Diff for: 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_url(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;

Diff for: 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>,

Diff for: src/web/rustdoc.rs

+83
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult<Response> {
200200
#[derive(Debug, Clone, PartialEq, Serialize)]
201201
struct RustdocPage {
202202
latest_path: String,
203+
canonical_url: String,
203204
permalink_path: String,
204205
latest_version: String,
205206
target: String,
@@ -488,6 +489,27 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
488489

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

492+
// Set the canonical URL for search engines to the `/latest/` page on docs.rs.
493+
// For crates with a documentation URL, where that URL doesn't point at docs.rs,
494+
// omit the canonical link to avoid penalizing external documentation.
495+
// Note: The URL this points to may not exist. For instance, if we're rendering
496+
// `struct Foo` in version 0.1.0 of a crate, and version 0.2.0 of that crate removes
497+
// `struct Foo`, this will point at a 404. That's fine: search engines will crawl
498+
// the target and will not canonicalize to a URL that doesn't exist.
499+
let canonical_url = if krate.documentation_url.is_none()
500+
|| krate
501+
.documentation_url
502+
.as_ref()
503+
.unwrap()
504+
.starts_with("https://docs.rs/")
505+
{
506+
// Don't include index.html in the canonical URL.
507+
let canonical_path = inner_path.replace("index.html", "");
508+
format!("https://docs.rs/{}/latest/{}", name, canonical_path)
509+
} else {
510+
"".to_string()
511+
};
512+
491513
metrics
492514
.recently_accessed_releases
493515
.record(krate.crate_id, krate.release_id, target);
@@ -501,6 +523,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
501523
rendering_time.step("rewrite html");
502524
RustdocPage {
503525
latest_path,
526+
canonical_url,
504527
permalink_path,
505528
latest_version,
506529
target,
@@ -2012,4 +2035,64 @@ mod test {
20122035
Ok(())
20132036
})
20142037
}
2038+
2039+
#[test]
2040+
fn canonical_url() {
2041+
wrapper(|env| {
2042+
env.fake_release()
2043+
.name("dummy-dash")
2044+
.version("0.1.0")
2045+
.documentation_url(Some("http://example.com".to_string()))
2046+
.rustdoc_file("dummy_dash/index.html")
2047+
.create()?;
2048+
2049+
env.fake_release()
2050+
.name("dummy-docs")
2051+
.version("0.1.0")
2052+
.documentation_url(Some("https://docs.rs/foo".to_string()))
2053+
.rustdoc_file("dummy_docs/index.html")
2054+
.create()?;
2055+
2056+
env.fake_release()
2057+
.name("dummy-nodocs")
2058+
.version("0.1.0")
2059+
.documentation_url(None)
2060+
.rustdoc_file("dummy_nodocs/index.html")
2061+
.rustdoc_file("dummy_nodocs/struct.Foo.html")
2062+
.create()?;
2063+
2064+
let web = env.frontend();
2065+
2066+
assert!(!web
2067+
.get("/dummy-dash/0.1.0/dummy_dash/")
2068+
.send()?
2069+
.text()?
2070+
.contains("rel=\"canonical\""),);
2071+
2072+
assert!(web
2073+
.get("/dummy-docs/0.1.0/dummy_docs/")
2074+
.send()?
2075+
.text()?
2076+
.contains(
2077+
"<link rel=\"canonical\" href=\"https://docs.rs/dummy-docs/latest/dummy_docs/\" />"
2078+
),);
2079+
2080+
assert!(
2081+
web
2082+
.get("/dummy-nodocs/0.1.0/dummy_nodocs/")
2083+
.send()?
2084+
.text()?
2085+
.contains("<link rel=\"canonical\" href=\"https://docs.rs/dummy-nodocs/latest/dummy_nodocs/\" />"),
2086+
);
2087+
2088+
assert!(
2089+
web
2090+
.get("/dummy-nodocs/0.1.0/dummy_nodocs/struct.Foo.html")
2091+
.send()?
2092+
.text()?
2093+
.contains("<link rel=\"canonical\" href=\"https://docs.rs/dummy-nodocs/latest/dummy_nodocs/struct.Foo.html\" />"),
2094+
);
2095+
Ok(())
2096+
})
2097+
}
20152098
}

Diff for: 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)