Line data Source code
1 : use anyhow::bail;
2 :
3 : /// A [url](url::Url) type with additional guarantees.
4 : #[repr(transparent)]
5 8 : #[derive(Debug, Clone, PartialEq, Eq)]
6 : pub struct ApiUrl(url::Url);
7 :
8 : impl ApiUrl {
9 : /// Consume the wrapper and return inner [url](url::Url).
10 10 : pub fn into_inner(self) -> url::Url {
11 10 : self.0
12 10 : }
13 :
14 : /// See [`url::Url::path_segments_mut`].
15 10 : pub fn path_segments_mut(&mut self) -> url::PathSegmentsMut {
16 10 : // We've already verified that it works during construction.
17 10 : self.0.path_segments_mut().expect("bad API url")
18 10 : }
19 : }
20 :
21 : /// This instance imposes additional requirements on the url.
22 : impl std::str::FromStr for ApiUrl {
23 : type Err = anyhow::Error;
24 :
25 31 : fn from_str(s: &str) -> anyhow::Result<Self> {
26 31 : let mut url: url::Url = s.parse()?;
27 :
28 : // Make sure that we can build upon this URL.
29 31 : if url.path_segments_mut().is_err() {
30 2 : bail!("bad API url provided");
31 29 : }
32 29 :
33 29 : Ok(Self(url))
34 31 : }
35 : }
36 :
37 : /// This instance is safe because it doesn't allow us to modify the object.
38 : impl std::ops::Deref for ApiUrl {
39 : type Target = url::Url;
40 :
41 430 : fn deref(&self) -> &Self::Target {
42 430 : &self.0
43 430 : }
44 : }
45 :
46 : impl std::fmt::Display for ApiUrl {
47 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 0 : self.0.fmt(f)
49 0 : }
50 : }
51 :
52 : #[cfg(test)]
53 : mod tests {
54 : use super::*;
55 :
56 2 : #[test]
57 2 : fn bad_url() {
58 2 : let url = "test:foobar";
59 2 : url.parse::<url::Url>().expect("unexpected parsing failure");
60 2 : let _ = url.parse::<ApiUrl>().expect_err("should not parse");
61 2 : }
62 :
63 2 : #[test]
64 2 : fn good_url() {
65 2 : let url = "test://foobar";
66 2 : let mut a = url.parse::<url::Url>().expect("unexpected parsing failure");
67 2 : let mut b = url.parse::<ApiUrl>().expect("unexpected parsing failure");
68 2 :
69 2 : a.path_segments_mut().unwrap().push("method");
70 2 : b.path_segments_mut().push("method");
71 2 :
72 2 : assert_eq!(a, b.into_inner());
73 2 : }
74 : }
|