http_types/other/
source_map.rs1use crate::headers::{HeaderName, HeaderValue, Headers, SOURCE_MAP};
2use crate::{bail_status as bail, Status, Url};
3
4use std::convert::TryInto;
5
6#[derive(Debug)]
34pub struct SourceMap {
35 location: Url,
36}
37
38impl SourceMap {
39 pub fn new(location: Url) -> Self {
41 Self { location }
42 }
43
44 pub fn from_headers<U>(base_url: U, headers: impl AsRef<Headers>) -> crate::Result<Option<Self>>
46 where
47 U: TryInto<Url>,
48 U::Error: std::fmt::Debug,
49 {
50 let headers = match headers.as_ref().get(SOURCE_MAP) {
51 Some(headers) => headers,
52 None => return Ok(None),
53 };
54
55 let header_value = headers.iter().last().unwrap();
58
59 let url = match Url::parse(header_value.as_str()) {
60 Ok(url) => url,
61 Err(_) => match base_url.try_into() {
62 Ok(base_url) => base_url.join(header_value.as_str().trim()).status(500)?,
63 Err(_) => bail!(500, "Invalid base url provided"),
64 },
65 };
66
67 Ok(Some(Self { location: url }))
68 }
69
70 pub fn apply(&self, mut headers: impl AsMut<Headers>) {
72 headers.as_mut().insert(self.name(), self.value());
73 }
74
75 pub fn name(&self) -> HeaderName {
77 SOURCE_MAP
78 }
79
80 pub fn value(&self) -> HeaderValue {
82 let output = self.location.to_string();
83
84 unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
86 }
87
88 pub fn location(&self) -> &Url {
90 &self.location
91 }
92
93 pub fn set_location<U>(&mut self, location: U) -> Result<(), U::Error>
95 where
96 U: TryInto<Url>,
97 U::Error: std::fmt::Debug,
98 {
99 self.location = location.try_into()?;
100 Ok(())
101 }
102}
103
104#[cfg(test)]
105mod test {
106 use super::*;
107 use crate::headers::Headers;
108
109 #[test]
110 fn smoke() -> crate::Result<()> {
111 let source_map = SourceMap::new(Url::parse("https://example.net/test.json")?);
112
113 let mut headers = Headers::new();
114 source_map.apply(&mut headers);
115
116 let base_url = Url::parse("https://example.net/")?;
117 let source_map = SourceMap::from_headers(base_url, headers)?.unwrap();
118 assert_eq!(
119 source_map.location(),
120 &Url::parse("https://example.net/test.json")?
121 );
122 Ok(())
123 }
124
125 #[test]
126 fn bad_request_on_parse_error() {
127 let mut headers = Headers::new();
128 headers.insert(SOURCE_MAP, "htt://<nori ate the tag. yum.>");
129 let err = SourceMap::from_headers(Url::parse("https://example.net").unwrap(), headers)
130 .unwrap_err();
131 assert_eq!(err.status(), 500);
132 }
133
134 #[test]
135 fn fallback_works() -> crate::Result<()> {
136 let mut headers = Headers::new();
137 headers.insert(SOURCE_MAP, "/test.json");
138
139 let base_url = Url::parse("https://fallback.net/")?;
140 let source_map = SourceMap::from_headers(base_url, headers)?.unwrap();
141 assert_eq!(
142 source_map.location(),
143 &Url::parse("https://fallback.net/test.json")?
144 );
145
146 let mut headers = Headers::new();
147 headers.insert(SOURCE_MAP, "https://example.com/test.json");
148
149 let base_url = Url::parse("https://fallback.net/")?;
150 let source_map = SourceMap::from_headers(base_url, headers)?.unwrap();
151 assert_eq!(
152 source_map.location(),
153 &Url::parse("https://example.com/test.json")?
154 );
155 Ok(())
156 }
157}