Skip to content

Commit 1cc3164

Browse files
committed
fix(url): fix host extra trailing slash issue
1 parent 758bc51 commit 1cc3164

File tree

2 files changed

+72
-25
lines changed

2 files changed

+72
-25
lines changed

src/url.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,15 +177,25 @@ impl PyUrl {
177177
};
178178
let mut url = format!("{scheme}://{url_host}");
179179
if let Some(path) = path {
180-
url.push('/');
181-
url.push_str(path);
180+
if !url.ends_with('/') {
181+
url.push('/');
182+
}
183+
if path.starts_with('/') {
184+
url.push_str(path.trim_start_matches('/'));
185+
} else {
186+
url.push_str(path);
187+
}
182188
}
183189
if let Some(query) = query {
184-
url.push('?');
190+
if !query.starts_with('?') {
191+
url.push('?');
192+
}
185193
url.push_str(query);
186194
}
187195
if let Some(fragment) = fragment {
188-
url.push('#');
196+
if !fragment.starts_with('#') {
197+
url.push('#');
198+
}
189199
url.push_str(fragment);
190200
}
191201
cls.call1((url,))
@@ -405,15 +415,25 @@ impl PyMultiHostUrl {
405415
};
406416

407417
if let Some(path) = path {
408-
url.push('/');
409-
url.push_str(path);
418+
if !url.ends_with('/') {
419+
url.push('/');
420+
}
421+
if path.starts_with('/') {
422+
url.push_str(path.trim_start_matches('/'));
423+
} else {
424+
url.push_str(path);
425+
}
410426
}
411427
if let Some(query) = query {
412-
url.push('?');
428+
if !query.starts_with('?') {
429+
url.push('?');
430+
}
413431
url.push_str(query);
414432
}
415433
if let Some(fragment) = fragment {
416-
url.push('#');
434+
if !fragment.starts_with('#') {
435+
url.push('#');
436+
}
417437
url.push_str(fragment);
418438
}
419439
cls.call1((url,))

tests/validators/test_url.py

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
from ..conftest import Err, PyAndJson
1111

1212

13-
def test_url_ok(py_and_json: PyAndJson):
14-
v = py_and_json(core_schema.url_schema())
15-
url = v.validate_test('https://example.com/foo/bar?baz=qux#quux')
13+
def assert_example_url(url: Url):
14+
# example URL in question 'https://example.com/foo/bar?baz=qux#quux'
1615

1716
assert isinstance(url, Url)
1817
assert str(url) == 'https://example.com/foo/bar?baz=qux#quux'
@@ -30,23 +29,51 @@ def test_url_ok(py_and_json: PyAndJson):
3029
assert url.port == 443
3130

3231

32+
def test_url_ok(py_and_json: PyAndJson):
33+
v = py_and_json(core_schema.url_schema())
34+
url = v.validate_test('https://example.com/foo/bar?baz=qux#quux')
35+
36+
assert_example_url(url)
37+
38+
3339
def test_url_from_constructor_ok():
3440
url = Url('https://example.com/foo/bar?baz=qux#quux')
3541

36-
assert isinstance(url, Url)
37-
assert str(url) == 'https://example.com/foo/bar?baz=qux#quux'
38-
assert repr(url) == "Url('https://example.com/foo/bar?baz=qux#quux')"
39-
assert url.unicode_string() == 'https://example.com/foo/bar?baz=qux#quux'
40-
assert url.scheme == 'https'
41-
assert url.host == 'example.com'
42-
assert url.unicode_host() == 'example.com'
43-
assert url.path == '/foo/bar'
44-
assert url.query == 'baz=qux'
45-
assert url.query_params() == [('baz', 'qux')]
46-
assert url.fragment == 'quux'
47-
assert url.username is None
48-
assert url.password is None
49-
assert url.port == 443
42+
assert_example_url(url)
43+
44+
45+
def test_url_from_build_ok():
46+
# 1) no host trailing slash/no path leading slash in the input
47+
url = Url.build(scheme='https', host='example.com', path='foo/bar', query='baz=qux', fragment='quux')
48+
assert_example_url(url)
49+
50+
# 2) no host trailing slash/with path leading slash in the input
51+
url = Url.build(scheme='https', host='example.com', path='/foo/bar', query='baz=qux', fragment='quux')
52+
assert_example_url(url)
53+
54+
# 3) with host trailing slash/no path leading slash in the input
55+
url = Url.build(scheme='https', host='example.com/', path='foo/bar', query='baz=qux', fragment='quux')
56+
assert_example_url(url)
57+
58+
# 4) with host trailing slash/with path leading slash in the input
59+
url = Url.build(scheme='https', host='example.com/', path='/foo/bar', query='baz=qux', fragment='quux')
60+
assert_example_url(url)
61+
62+
# 5) query no leading question mark
63+
url = Url.build(scheme='https', host='example.com', path='foo/bar', query='baz=qux', fragment='quux')
64+
assert_example_url(url)
65+
66+
# 6) query with leading question mark
67+
url = Url.build(scheme='https', host='example.com', path='foo/bar', query='?baz=qux', fragment='quux')
68+
assert_example_url(url)
69+
70+
# 7) fragment no leading hash
71+
url = Url.build(scheme='https', host='example.com', path='foo/bar', query='baz=qux', fragment='quux')
72+
assert_example_url(url)
73+
74+
# 8) fragment with leading hash
75+
url = Url.build(scheme='https', host='example.com', path='foo/bar', query='baz=qux', fragment='#quux')
76+
assert_example_url(url)
5077

5178

5279
@pytest.fixture(scope='module', name='url_validator')

0 commit comments

Comments
 (0)