@@ -200,6 +200,7 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult<Response> {
200
200
#[ derive( Debug , Clone , PartialEq , Serialize ) ]
201
201
struct RustdocPage {
202
202
latest_path : String ,
203
+ canonical_url : String ,
203
204
permalink_path : String ,
204
205
latest_version : String ,
205
206
target : String ,
@@ -488,6 +489,27 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
488
489
489
490
let latest_path = format ! ( "/crate/{}/latest{}{}" , name, target_redirect, query_string) ;
490
491
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
+
491
513
metrics
492
514
. recently_accessed_releases
493
515
. record ( krate. crate_id , krate. release_id , target) ;
@@ -501,6 +523,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
501
523
rendering_time. step ( "rewrite html" ) ;
502
524
RustdocPage {
503
525
latest_path,
526
+ canonical_url,
504
527
permalink_path,
505
528
latest_version,
506
529
target,
@@ -2012,4 +2035,64 @@ mod test {
2012
2035
Ok ( ( ) )
2013
2036
} )
2014
2037
}
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
+ }
2015
2098
}
0 commit comments