@@ -195,6 +195,7 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult<Response> {
195
195
#[ derive( Debug , Clone , PartialEq , Serialize ) ]
196
196
struct RustdocPage {
197
197
latest_path : String ,
198
+ canonical_url : String ,
198
199
permalink_path : String ,
199
200
latest_version : String ,
200
201
target : String ,
@@ -483,6 +484,27 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
483
484
484
485
let latest_path = format ! ( "/crate/{}/latest{}{}" , name, target_redirect, query_string) ;
485
486
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
+
486
508
metrics
487
509
. recently_accessed_releases
488
510
. record ( krate. crate_id , krate. release_id , target) ;
@@ -496,6 +518,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
496
518
rendering_time. step ( "rewrite html" ) ;
497
519
RustdocPage {
498
520
latest_path,
521
+ canonical_url,
499
522
permalink_path,
500
523
latest_version,
501
524
target,
@@ -1980,4 +2003,64 @@ mod test {
1980
2003
Ok ( ( ) )
1981
2004
} )
1982
2005
}
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
+ }
1983
2066
}
0 commit comments