Skip to content

Commit e4f3c32

Browse files
committed
fix: final updates
1 parent d3f3c2a commit e4f3c32

File tree

2 files changed

+67
-25
lines changed

2 files changed

+67
-25
lines changed

src/url.rs

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use std::collections::hash_map::DefaultHasher;
2+
use std::fmt;
3+
use std::fmt::{format, Formatter};
24
use std::hash::{Hash, Hasher};
5+
use std::ops::Deref;
36

47
use idna::punycode::decode_to_string;
58
use pyo3::exceptions::PyValueError;
69
use pyo3::once_cell::GILOnceCell;
710
use pyo3::pyclass::CompareOp;
811
use pyo3::types::{PyDict, PyType};
912
use pyo3::{intern, prelude::*};
13+
use url::quirks::username;
1014
use url::Url;
1115

1216
use crate::tools::SchemaDict;
@@ -373,30 +377,26 @@ impl PyMultiHostUrl {
373377
password: Option<&str>,
374378
port: Option<&str>,
375379
) -> PyResult<&'a PyAny> {
376-
// todo better error message
377380
let mut url = if hosts.is_some() && (host.is_some() || user.is_some() || password.is_some() || port.is_some()) {
378-
return Err(PyValueError::new_err("Only one of `host` or `hosts` may be set"));
379-
} else if hosts.is_some() {
381+
return Err(PyValueError::new_err(
382+
"expected one of `hosts` or singular values to be set.",
383+
));
384+
} else if let Some(hosts) = hosts {
380385
// check all of host / user / password / port empty
381386
// build multi-host url
382-
for single_host in hosts.as_deref().unwrap_or_default() {
383-
let mut multi_url = format!("{scheme}://");
384-
if single_host.username.is_some() && single_host.password.is_none()
385-
{
386-
multi_url.push_str(*single_host.username);
387-
multi_url.push('@');
387+
let mut multi_url = format!("{scheme}://");
388+
for (index, single_host) in hosts.iter().enumerate() {
389+
if single_host.is_empty() {
390+
return Err(PyValueError::new_err(
391+
"expected one of 'host', 'username', 'password' or 'port' to be set",
392+
));
388393
}
389-
else if single_host.username.is_none() && single_host.password.is_some()
390-
{
391-
multi_url.push_str(*single_host.password);
392-
multi_url.push('@');
393-
};
394-
if single_host.host
395-
else {
396-
return Err(PyValueError::new_err("Incomplete object."));
394+
multi_url.push_str(&*single_host.to_string());
395+
if index != hosts.len() - 1 {
396+
multi_url.push(',')
397397
};
398398
}
399-
todo!()
399+
multi_url
400400
} else if let Some(host) = host {
401401
let user_password = match (user, password) {
402402
(Some(user), None) => format!("{user}@"),
@@ -413,6 +413,7 @@ impl PyMultiHostUrl {
413413
} else {
414414
return Err(PyValueError::new_err("expected either `host` or `hosts` to be set"));
415415
};
416+
416417
if let Some(path) = path {
417418
url.push('/');
418419
url.push_str(path);
@@ -436,6 +437,12 @@ pub struct MultiHostUrlHost {
436437
port: Option<String>,
437438
}
438439

440+
impl MultiHostUrlHost {
441+
fn is_empty(&self) -> bool {
442+
self.host.is_none() && self.password.is_none() && self.host.is_none() && self.port.is_none()
443+
}
444+
}
445+
439446
impl FromPyObject<'_> for MultiHostUrlHost {
440447
fn extract(ob: &'_ PyAny) -> PyResult<Self> {
441448
let py = ob.py();
@@ -449,6 +456,26 @@ impl FromPyObject<'_> for MultiHostUrlHost {
449456
}
450457
}
451458

459+
impl fmt::Display for MultiHostUrlHost {
460+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
461+
let mut url = String::new();
462+
if self.username.is_some() && self.password.is_some() {
463+
url.push_str(&*format!(
464+
"{}:{}",
465+
self.username.as_ref().unwrap(),
466+
self.password.as_ref().unwrap()
467+
))
468+
}
469+
if self.host.is_some() {
470+
url.push_str(&*format!("@{}", self.host.as_ref().unwrap()))
471+
}
472+
if self.port.is_some() {
473+
url.push_str(&*format!(":{}", self.port.as_ref().unwrap()))
474+
}
475+
write!(f, "{}", url)
476+
}
477+
}
478+
452479
fn host_to_dict<'a>(py: Python<'a>, lib_url: &Url) -> PyResult<&'a PyDict> {
453480
let dict = PyDict::new(py);
454481
dict.set_item(

tests/validators/test_url.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,22 +1237,37 @@ def test_multi_url_build() -> None:
12371237
def test_multi_url_build_hosts_set_with_single_value(field) -> None:
12381238
"""Hosts can't be provided with any single url values."""
12391239
hosts = [
1240-
{'host': '127.0.0.1:5432', 'password': 'testpassword', 'username': 'testuser', 'port': '5432'},
1241-
{'host': '127.0.0.1:5432', 'password': 'testpassword', 'username': 'testuser', 'port': '5432'},
1240+
{'host': '127.0.0.1', 'password': 'testpassword', 'username': 'testuser', 'port': '5432'},
1241+
{'host': '127.0.0.1', 'password': 'testpassword', 'username': 'testuser', 'port': '5432'},
12421242
]
12431243
kwargs = dict(scheme='postgresql', hosts=hosts, path='database', query='sslmode=require', fragment='test')
12441244
kwargs[field] = 'test'
12451245
with pytest.raises(ValueError):
12461246
MultiHostUrl.build(**kwargs)
12471247

12481248

1249-
@pytest.mark.parametrize('field', ['host', 'password', 'username', 'port'])
1250-
def test_multi_url_build_hosts_invalid_host(field) -> None:
1249+
def test_multi_url_build_hosts_empty_host() -> None:
12511250
"""Hosts can't be provided with any single url values."""
1252-
host = {'host': '127.0.0.1:5432', 'password': 'testpassword', 'username': 'testuser', 'port': '5432'}
1253-
del host[field]
1251+
hosts = [{}]
12541252
with pytest.raises(ValueError):
1255-
MultiHostUrl.build(scheme='postgresql', hosts=[host], path='database', query='sslmode=require', fragment='test')
1253+
MultiHostUrl.build(scheme='postgresql', hosts=hosts, path='database', query='sslmode=require', fragment='test')
1254+
1255+
1256+
def test_multi_url_build_hosts() -> None:
1257+
"""Hosts can't be provided with any single url values."""
1258+
hosts = [
1259+
{'host': '127.0.0.1', 'password': 'testpassword', 'username': 'testuser', 'port': '5431'},
1260+
{'host': '127.0.0.1', 'password': 'testpassword', 'username': 'testuser', 'port': '5433'},
1261+
]
1262+
kwargs = dict(scheme='postgresql', hosts=hosts, path='database', query='sslmode=require', fragment='test')
1263+
url = MultiHostUrl.build(**kwargs)
1264+
assert url == MultiHostUrl(
1265+
'postgresql://testuser:[email protected]:5431,testuser:[email protected]:5433/database?sslmode=require#test'
1266+
)
1267+
assert (
1268+
str(url)
1269+
== 'postgresql://testuser:[email protected]:5431,testuser:[email protected]:5433/database?sslmode=require#test'
1270+
)
12561271

12571272

12581273
def test_multi_url_build_neither_host_and_hosts_set() -> None:

0 commit comments

Comments
 (0)