Line data Source code
1 : use anyhow::bail;
2 :
3 : /// A [url](url::Url) type with additional guarantees.
4 : #[repr(transparent)]
5 : #[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 18 : pub(crate) fn into_inner(self) -> url::Url {
11 18 : self.0
12 18 : }
13 :
14 : /// See [`url::Url::path_segments_mut`].
15 18 : pub(crate) fn path_segments_mut(&mut self) -> url::PathSegmentsMut<'_> {
16 18 : // We've already verified that it works during construction.
17 18 : self.0.path_segments_mut().expect("bad API url")
18 18 : }
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 24 : fn from_str(s: &str) -> anyhow::Result<Self> {
26 24 : let mut url: url::Url = s.parse()?;
27 :
28 : // Make sure that we can build upon this URL.
29 24 : if url.path_segments_mut().is_err() {
30 6 : bail!("bad API url provided");
31 18 : }
32 18 :
33 18 : Ok(Self(url))
34 24 : }
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 0 : fn deref(&self) -> &Self::Target {
42 0 : &self.0
43 0 : }
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 : #[test]
57 6 : fn bad_url() {
58 6 : let url = "test:foobar";
59 6 : url.parse::<url::Url>().expect("unexpected parsing failure");
60 6 : let _ = url.parse::<ApiUrl>().expect_err("should not parse");
61 6 : }
62 :
63 : #[test]
64 6 : fn good_url() {
65 6 : let url = "test://foobar";
66 6 : let mut a = url.parse::<url::Url>().expect("unexpected parsing failure");
67 6 : let mut b = url.parse::<ApiUrl>().expect("unexpected parsing failure");
68 6 :
69 6 : a.path_segments_mut().unwrap().push("method");
70 6 : b.path_segments_mut().push("method");
71 6 :
72 6 : assert_eq!(a, b.into_inner());
73 6 : }
74 : }
|