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