Skip to content

Commit 473de74

Browse files
committed
extract EscapedURI into subcrate
1 parent cb9dbe4 commit 473de74

File tree

13 files changed

+82
-54
lines changed

13 files changed

+82
-54
lines changed

Cargo.lock

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ exclude = [
2222

2323
[workspace.dependencies]
2424
anyhow = { version = "1.0.42", features = ["backtrace"]}
25+
askama = "0.14.0"
2526
bincode = "2.0.1"
2627
chrono = { version = "0.4.11", default-features = false, features = ["clock", "serde"] }
2728
derive_more = { version = "2.0.0", features = ["display", "deref", "from", "into", "from_str"] }
29+
http = "1.0.0"
2830
opentelemetry = "0.31.0"
2931
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "metrics"] }
3032
opentelemetry-resource-detectors = "0.10.0"
@@ -44,6 +46,7 @@ docs_rs_env_vars = { path = "crates/lib/docs_rs_env_vars" }
4446
docs_rs_logging = { path = "crates/lib/docs_rs_logging" }
4547
docs_rs_opentelemetry = { path = "crates/lib/docs_rs_opentelemetry" }
4648
docs_rs_types = { path = "crates/lib/docs_rs_types" }
49+
docs_rs_uri = { path = "crates/lib/docs_rs_uri" }
4750
docs_rs_utils = { path = "crates/lib/docs_rs_utils" }
4851
sentry = { workspace = true }
4952
log = "0.4"
@@ -98,7 +101,7 @@ async-stream = "0.3.5"
98101
aws-config = { version = "1.0.0", default-features = false, features = ["rt-tokio", "default-https-client"] }
99102
aws-sdk-s3 = "1.3.0"
100103
aws-smithy-types-convert = { version = "0.60.0", features = ["convert-chrono"] }
101-
http = "1.0.0"
104+
http = { workspace = true }
102105

103106
# Data serialization and deserialization
104107
serde = { workspace = true }
@@ -112,13 +115,12 @@ axum-extra = { version = "0.12.0", features = ["typed-header", "routing", "middl
112115
tower = "0.5.1"
113116
tower-http = { version = "0.6.0", features = ["fs", "trace", "timeout", "catch-panic"] }
114117
mime = "0.3.16"
115-
percent-encoding = "2.2.0"
116118

117119
tempfile = "3.1.0"
118120
fn-error-context = "0.2.0"
119121

120122
# Templating
121-
askama = "0.14.0"
123+
askama = { workspace = true }
122124
walkdir = "2"
123125
phf = "0.13.1"
124126

crates/lib/docs_rs_uri/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "docs_rs_uri"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
anyhow = { workspace = true }
8+
askama = { workspace = true }
9+
bincode = { workspace = true }
10+
http = { workspace = true }
11+
percent-encoding = "2.2.0"
12+
url = { workspace = true }
13+
14+
[dev-dependencies]
15+
test-case = { workspace = true }
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use anyhow::Result;
2+
use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
3+
use std::borrow::Cow;
4+
5+
// from https://github.com/servo/rust-url/blob/master/url/src/parser.rs
6+
// and https://github.com/tokio-rs/axum/blob/main/axum-extra/src/lib.rs
7+
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
8+
const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
9+
10+
pub fn encode_url_path(path: &str) -> String {
11+
utf8_percent_encode(path, PATH).to_string()
12+
}
13+
14+
pub fn url_decode<'a>(input: &'a str) -> Result<Cow<'a, str>> {
15+
Ok(percent_encoding::percent_decode(input.as_bytes()).decode_utf8()?)
16+
}
17+
18+
#[cfg(test)]
19+
mod test {
20+
use super::*;
21+
use test_case::test_case;
22+
23+
#[test_case("/something/", "/something/")] // already valid path
24+
#[test_case("/something>", "/something%3E")] // something to encode
25+
#[test_case("/something%3E", "/something%3E")] // re-running doesn't change anything
26+
fn test_encode_url_path(input: &str, expected: &str) {
27+
assert_eq!(encode_url_path(input), expected);
28+
}
29+
}
Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::web::{encode_url_path, url_decode};
1+
use crate::encode::{encode_url_path, url_decode};
22
use askama::filters::HtmlSafe;
33
use http::{Uri, uri::PathAndQuery};
44
use std::{borrow::Borrow, fmt::Display, iter, str::FromStr};
@@ -200,7 +200,7 @@ impl EscapedURI {
200200
self.uri
201201
}
202202

203-
pub(crate) fn with_fragment(mut self, fragment: impl AsRef<str>) -> Self {
203+
pub fn with_fragment(mut self, fragment: impl AsRef<str>) -> Self {
204204
self.fragment = Some(encode_url_path(fragment.as_ref()));
205205
self
206206
}
@@ -298,8 +298,6 @@ impl PartialEq<str> for EscapedURI {
298298
#[cfg(test)]
299299
mod tests {
300300
use super::EscapedURI;
301-
use crate::web::{cache::CachePolicy, error::AxumNope};
302-
use axum::response::IntoResponse as _;
303301
use http::Uri;
304302
use test_case::test_case;
305303

@@ -309,18 +307,6 @@ mod tests {
309307
assert_eq!(s.parse::<EscapedURI>().unwrap(), *input);
310308
}
311309

312-
#[test]
313-
fn test_redirect_error_encodes_url_path() {
314-
let response = AxumNope::Redirect(
315-
EscapedURI::from_path("/something>"),
316-
CachePolicy::ForeverInCdnAndBrowser,
317-
)
318-
.into_response();
319-
320-
assert_eq!(response.status(), 302);
321-
assert_eq!(response.headers().get("Location").unwrap(), "/something%3E");
322-
}
323-
324310
#[test_case("/something" => "/something")]
325311
#[test_case("/something>" => "/something%3E")]
326312
fn test_escaped_uri_encodes_from_path(input: &str) -> String {

crates/lib/docs_rs_uri/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod encode;
2+
mod escaped_uri;
3+
4+
pub use encode::{encode_url_path, url_decode};
5+
pub use escaped_uri::EscapedURI;

src/web/error.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use crate::{
22
db::PoolError,
33
storage::PathNotFoundError,
4-
web::{AxumErrorPage, cache::CachePolicy, escaped_uri::EscapedURI, releases::Search},
4+
web::{AxumErrorPage, cache::CachePolicy, releases::Search},
55
};
66
use anyhow::{Result, anyhow};
77
use axum::{
88
Json,
99
http::StatusCode,
1010
response::{IntoResponse, Response as AxumResponse},
1111
};
12+
use docs_rs_uri::EscapedURI;
1213
use std::borrow::Cow;
1314

1415
#[derive(Debug, thiserror::Error)]

src/web/extractors/rustdoc.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
33
use crate::{
44
storage::CompressionAlgorithm,
5-
web::{
6-
MatchedRelease, MetaData, error::AxumNope, escaped_uri::EscapedURI, extractors::Path,
7-
url_decode,
8-
},
5+
web::{MatchedRelease, MetaData, error::AxumNope, extractors::Path},
96
};
107
use anyhow::Result;
118
use axum::{
@@ -14,6 +11,7 @@ use axum::{
1411
http::{Uri, request::Parts},
1512
};
1613
use docs_rs_types::{BuildId, KrateName, ReqVersion};
14+
use docs_rs_uri::{EscapedURI, url_decode};
1715
use itertools::Itertools as _;
1816
use serde::Deserialize;
1917

src/web/headers/canonical_url.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use crate::web::escaped_uri::EscapedURI;
21
use anyhow::Result;
32
use askama::filters::HtmlSafe;
43
use axum::http::uri::Uri;
54
use axum_extra::headers::{Header, HeaderName, HeaderValue};
5+
use docs_rs_uri::EscapedURI;
66
use serde::Serialize;
77
use std::{fmt, ops::Deref};
88

src/web/mod.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ pub(crate) mod cache;
66
pub(crate) mod crate_details;
77
mod csp;
88
pub(crate) mod error;
9-
mod escaped_uri;
109
pub(crate) mod extractors;
1110
mod features;
1211
mod file;
@@ -48,7 +47,6 @@ use docs_rs_types::{BuildStatus, CrateId, KrateName, ReqVersion, Version, Versio
4847
use docs_rs_utils::rustc_version::parse_rustc_date;
4948
use error::AxumNope;
5049
use page::TemplateData;
51-
use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
5250
use sentry::integrations::tower as sentry_tower;
5351
use serde::Serialize;
5452
use serde_json::Value;
@@ -61,19 +59,6 @@ use tower::ServiceBuilder;
6159
use tower_http::{catch_panic::CatchPanicLayer, timeout::TimeoutLayer, trace::TraceLayer};
6260
use tracing::{info, instrument};
6361

64-
// from https://github.com/servo/rust-url/blob/master/url/src/parser.rs
65-
// and https://github.com/tokio-rs/axum/blob/main/axum-extra/src/lib.rs
66-
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
67-
const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
68-
69-
pub(crate) fn encode_url_path(path: &str) -> String {
70-
utf8_percent_encode(path, PATH).to_string()
71-
}
72-
73-
pub(crate) fn url_decode<'a>(input: &'a str) -> Result<Cow<'a, str>> {
74-
Ok(percent_encoding::percent_decode(input.as_bytes()).decode_utf8()?)
75-
}
76-
7762
/// Picks the correct "rustdoc.css" static file depending on which rustdoc version was used to
7863
/// generate this version of this crate.
7964
pub fn get_correct_docsrs_style_file(version: &str) -> Result<String> {
@@ -641,8 +626,6 @@ impl_axum_webpage! {
641626

642627
#[cfg(test)]
643628
mod test {
644-
use std::str::FromStr as _;
645-
646629
use super::*;
647630
use crate::docbuilder::DocCoverage;
648631
use crate::test::{
@@ -653,6 +636,7 @@ mod test {
653636
use kuchikiki::traits::TendrilSink;
654637
use pretty_assertions::assert_eq;
655638
use serde_json::json;
639+
use std::str::FromStr as _;
656640
use test_case::test_case;
657641

658642
async fn release(version: &str, env: &TestEnvironment) -> ReleaseId {
@@ -1236,11 +1220,4 @@ mod test {
12361220
assert!(axum_redirect(path).is_err());
12371221
assert!(axum_cached_redirect(path, cache::CachePolicy::NoCaching).is_err());
12381222
}
1239-
1240-
#[test_case("/something/", "/something/")] // already valid path
1241-
#[test_case("/something>", "/something%3E")] // something to encode
1242-
#[test_case("/something%3E", "/something%3E")] // re-running doesn't change anything
1243-
fn test_encode_url_path(input: &str, expected: &str) {
1244-
assert_eq!(encode_url_path(input), expected);
1245-
}
12461223
}

0 commit comments

Comments
 (0)