1 //! [`ToSql`] and [`FromSql`] implementation for [`url::Url`].
2 use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
3 use crate::Result;
4 use url::Url;
5 
6 /// Serialize `Url` to text.
7 impl ToSql for Url {
8     #[inline]
to_sql(&self) -> Result<ToSqlOutput<'_>>9     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
10         Ok(ToSqlOutput::from(self.as_str()))
11     }
12 }
13 
14 /// Deserialize text to `Url`.
15 impl FromSql for Url {
16     #[inline]
column_result(value: ValueRef<'_>) -> FromSqlResult<Self>17     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
18         match value {
19             ValueRef::Text(s) => {
20                 let s = std::str::from_utf8(s).map_err(|e| FromSqlError::Other(Box::new(e)))?;
21                 Url::parse(s).map_err(|e| FromSqlError::Other(Box::new(e)))
22             }
23             _ => Err(FromSqlError::InvalidType),
24         }
25     }
26 }
27 
28 #[cfg(test)]
29 mod test {
30     use crate::{params, Connection, Error, Result};
31     use url::{ParseError, Url};
32 
checked_memory_handle() -> Result<Connection>33     fn checked_memory_handle() -> Result<Connection> {
34         let db = Connection::open_in_memory()?;
35         db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")?;
36         Ok(db)
37     }
38 
get_url(db: &Connection, id: i64) -> Result<Url>39     fn get_url(db: &Connection, id: i64) -> Result<Url> {
40         db.query_row("SELECT v FROM urls WHERE i = ?", [id], |r| r.get(0))
41     }
42 
43     #[test]
test_sql_url() -> Result<()>44     fn test_sql_url() -> Result<()> {
45         let db = &checked_memory_handle()?;
46 
47         let url0 = Url::parse("http://www.example1.com").unwrap();
48         let url1 = Url::parse("http://www.example1.com/��").unwrap();
49         let url2 = "http://www.example2.com/��";
50 
51         db.execute(
52             "INSERT INTO urls (i, v) VALUES (0, ?1), (1, ?2), (2, ?3), (3, ?4)",
53             // also insert a non-hex encoded url (which might be present if it was
54             // inserted separately)
55             params![url0, url1, url2, "illegal"],
56         )?;
57 
58         assert_eq!(get_url(db, 0)?, url0);
59 
60         assert_eq!(get_url(db, 1)?, url1);
61 
62         // Should successfully read it, even though it wasn't inserted as an
63         // escaped url.
64         let out_url2: Url = get_url(db, 2)?;
65         assert_eq!(out_url2, Url::parse(url2).unwrap());
66 
67         // Make sure the conversion error comes through correctly.
68         let err = get_url(db, 3).unwrap_err();
69         match err {
70             Error::FromSqlConversionFailure(_, _, e) => {
71                 assert_eq!(
72                     *e.downcast::<ParseError>().unwrap(),
73                     ParseError::RelativeUrlWithoutBase,
74                 );
75             }
76             e => {
77                 panic!("Expected conversion failure, got {}", e);
78             }
79         }
80         Ok(())
81     }
82 }
83