argan/host/
mod.rs

1//! Host service types.
2
3// ----------
4
5use crate::{
6	common::{IntoArray, SCOPE_VALIDITY},
7	pattern::{Pattern, Similarity},
8	resource::Resource,
9};
10
11// --------------------------------------------------
12
13mod service;
14pub(crate) use self::service::FinalHost;
15pub use service::{ArcHostService, HostService, LeakedHostService};
16
17// --------------------------------------------------------------------------------
18// --------------------------------------------------------------------------------
19
20/// Representation of the *host* subcomponent of the URI.
21pub struct Host {
22	pattern: Pattern,
23	root_resource: Resource,
24}
25
26impl Host {
27	/// Creates a new `Host` with the given pattern and root resource.
28	///
29	/// ```
30	/// use argan::{Host, Resource};
31	///
32	/// let root = Resource::new("/");
33	/// let host = Host::new("http://{sub_domain}.example.com", root);
34	/// ```
35	///
36	/// The `Host` node checks the request's host and, if matches, passes the request to
37	/// its root resource.
38	pub fn new<P>(host_pattern: P, root: Resource) -> Self
39	where
40		P: AsRef<str>,
41	{
42		let host_pattern = match parse_host_pattern(host_pattern) {
43			Ok(host_pattern) => host_pattern,
44			Err(HostPatternError::Empty) => panic!("empty host pattern"),
45			Err(HostPatternError::Wildcard) => panic!("host pattern cannot be a wildcard"),
46		};
47
48		if !root.is("/") {
49			panic!("host can only have a root resource");
50		}
51
52		Self::with_pattern(host_pattern, root)
53	}
54
55	pub(crate) fn with_pattern(host_pattern: Pattern, mut root: Resource) -> Self {
56		if root.host_pattern_ref().is_none() {
57			root.set_host_pattern(host_pattern.clone());
58		} else {
59			let resource_host_pattern = root.host_pattern_ref().expect(SCOPE_VALIDITY);
60
61			if resource_host_pattern.compare(&host_pattern) != Similarity::Same {
62				panic!(
63					"resource is intended to belong to a host {}",
64					resource_host_pattern,
65				);
66			}
67		}
68
69		Self {
70			pattern: host_pattern,
71			root_resource: root,
72		}
73	}
74
75	// -------------------------
76
77	/// Checks whether the given `pattern` is the `Host`'s pattern.
78	#[inline(always)]
79	pub fn is<P: AsRef<str>>(&self, pattern: P) -> bool {
80		let pattern = Pattern::parse(pattern.as_ref());
81
82		self.pattern.compare(&pattern) == Similarity::Same
83	}
84
85	#[inline(always)]
86	pub(crate) fn pattern_string(&self) -> String {
87		self.pattern.to_string()
88	}
89
90	#[inline(always)]
91	pub(crate) fn pattern_ref(&self) -> &Pattern {
92		&self.pattern
93	}
94
95	#[inline(always)]
96	pub(crate) fn compare_pattern(&self, other_host_pattern: &Pattern) -> Similarity {
97		self.pattern.compare(other_host_pattern)
98	}
99
100	#[inline(always)]
101	pub(crate) fn root_mut(&mut self) -> &mut Resource {
102		&mut self.root_resource
103	}
104
105	#[inline(always)]
106	pub(crate) fn root(&mut self) -> Resource {
107		std::mem::replace(
108			&mut self.root_resource,
109			Resource::with_pattern(Pattern::default()),
110		)
111	}
112
113	#[inline(always)]
114	pub(crate) fn set_root(&mut self, root: Resource) {
115		if self.root_resource.pattern_string() != "" {
116			panic!("host already has a root resource");
117		}
118
119		self.root_resource = root;
120	}
121
122	pub(crate) fn merge_or_replace_root(&mut self, mut new_root: Resource) {
123		if !new_root.has_some_effect() {
124			self.root_resource.keep_subresources(new_root);
125		} else if !self.root_resource.has_some_effect() {
126			new_root.keep_subresources(self.root());
127			self.root_resource = new_root;
128		} else {
129			panic!(
130				"conflicting root resources for a host '{}'",
131				self.pattern_string()
132			)
133		}
134	}
135
136	pub(crate) fn into_pattern_and_root(self) -> (Pattern, Resource) {
137		let Host {
138			pattern,
139			root_resource,
140		} = self;
141
142		(pattern, root_resource)
143	}
144
145	pub(crate) fn finalize(self) -> FinalHost {
146		let Host {
147			pattern,
148			root_resource,
149		} = self;
150
151		FinalHost::new(pattern, root_resource.finalize())
152	}
153
154	/// Converts the `Host` into a service.
155	#[inline(always)]
156	pub fn into_service(self) -> HostService {
157		HostService::new(self.finalize())
158	}
159
160	/// Converts the `Host` into a service that uses `Arc` internally.
161	#[inline(always)]
162	pub fn into_arc_service(self) -> ArcHostService {
163		ArcHostService::from(self.into_service())
164	}
165
166	/// Converts the `Host` into a service with a leaked `&'static`.
167	#[inline(always)]
168	pub fn into_leaked_service(self) -> LeakedHostService {
169		LeakedHostService::from(self.into_service())
170	}
171}
172
173impl IntoArray<Host, 1> for Host {
174	fn into_array(self) -> [Host; 1] {
175		[self]
176	}
177}
178
179// --------------------------------------------------
180
181pub(crate) fn parse_host_pattern<P: AsRef<str>>(
182	host_pattern: P,
183) -> Result<Pattern, HostPatternError> {
184	let host_pattern_str = host_pattern.as_ref();
185
186	if host_pattern_str.is_empty() {
187		return Err(HostPatternError::Empty);
188	}
189
190	let host_pattern_str = host_pattern_str
191		.strip_prefix("https://")
192		.or_else(|| host_pattern_str.strip_prefix("http://"))
193		.unwrap_or(host_pattern_str);
194
195	let host_pattern = host_pattern_str
196		.strip_suffix('/')
197		.map_or(Pattern::parse(host_pattern_str), Pattern::parse);
198
199	if host_pattern.is_wildcard() {
200		return Err(HostPatternError::Wildcard);
201	}
202
203	Ok(host_pattern)
204}
205
206#[derive(Debug, crate::ImplError)]
207pub(crate) enum HostPatternError {
208	#[error("empty host pattern")]
209	Empty,
210	#[error("wildcard host pattern")]
211	Wildcard,
212}
213
214// --------------------------------------------------------------------------------