1use crate::tag::Tag;
2use crate::value::{Props, Value};
3use std::fmt;
4use std::str::FromStr;
5
6pub const DEFAULT_SCRIPT_HOST: &str = "https://privatusstats.com";
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
12pub enum Spa {
13 #[default]
15 Auto,
16 History,
18 Hash,
20 Off,
22}
23
24impl Spa {
25 pub fn as_str(self) -> &'static str {
27 match self {
28 Spa::Auto => "auto",
29 Spa::History => "history",
30 Spa::Hash => "hash",
31 Spa::Off => "off",
32 }
33 }
34}
35
36impl fmt::Display for Spa {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 f.write_str(self.as_str())
39 }
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct UnknownSpaMode(pub String);
45
46impl fmt::Display for UnknownSpaMode {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(
49 f,
50 "unknown spa mode {:?}: use auto, history, hash or off",
51 self.0
52 )
53 }
54}
55
56impl std::error::Error for UnknownSpaMode {}
57
58impl FromStr for Spa {
59 type Err = UnknownSpaMode;
60
61 fn from_str(s: &str) -> Result<Self, Self::Err> {
62 match s.trim().to_ascii_lowercase().as_str() {
63 "auto" => Ok(Spa::Auto),
64 "history" => Ok(Spa::History),
65 "hash" => Ok(Spa::Hash),
66 "off" => Ok(Spa::Off),
67 _ => Err(UnknownSpaMode(s.to_owned())),
68 }
69 }
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, Hash)]
78pub struct CspNonce(pub String);
79
80#[derive(Clone, Debug, Default, PartialEq)]
105pub struct Config {
106 site_id: Option<String>,
107 script_host: Option<String>,
108 modules: Option<Vec<String>>,
109 excluded_paths: Vec<String>,
110 domains: Option<Vec<String>>,
111 spa: Spa,
112 manual: bool,
113 enabled: Option<bool>,
114 auto_inject: Option<bool>,
115 extra_attributes: Vec<(String, String)>,
116 props: Props,
117 queue_stub: bool,
118}
119
120impl Config {
121 pub fn new() -> Self {
124 Config::default()
125 }
126
127 pub fn site_id(mut self, site_id: impl Into<String>) -> Self {
130 self.site_id = Some(site_id.into());
131 self
132 }
133
134 pub fn script_host(mut self, host: impl Into<String>) -> Self {
138 self.script_host = Some(host.into());
139 self
140 }
141
142 pub fn modules<I, S>(mut self, modules: I) -> Self
146 where
147 I: IntoIterator<Item = S>,
148 S: Into<String>,
149 {
150 self.modules = Some(modules.into_iter().map(Into::into).collect());
151 self
152 }
153
154 pub fn default_modules(mut self) -> Self {
156 self.modules = None;
157 self
158 }
159
160 pub fn excluded_paths<I, S>(mut self, paths: I) -> Self
163 where
164 I: IntoIterator<Item = S>,
165 S: Into<String>,
166 {
167 self.excluded_paths = paths.into_iter().map(Into::into).collect();
168 self
169 }
170
171 pub fn domains<I, S>(mut self, domains: I) -> Self
173 where
174 I: IntoIterator<Item = S>,
175 S: Into<String>,
176 {
177 self.domains = Some(domains.into_iter().map(Into::into).collect());
178 self
179 }
180
181 pub fn spa(mut self, spa: Spa) -> Self {
184 self.spa = spa;
185 self
186 }
187
188 pub fn manual(mut self, manual: bool) -> Self {
190 self.manual = manual;
191 self
192 }
193
194 pub fn enabled(mut self, enabled: bool) -> Self {
197 self.enabled = Some(enabled);
198 self
199 }
200
201 pub fn auto_inject(mut self, auto_inject: bool) -> Self {
205 self.auto_inject = Some(auto_inject);
206 self
207 }
208
209 pub fn extra_attribute(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
213 set_pair(&mut self.extra_attributes, name.into(), value.into());
214 self
215 }
216
217 pub fn props<I, K, V>(mut self, props: I) -> Self
223 where
224 I: IntoIterator<Item = (K, V)>,
225 K: Into<String>,
226 V: Into<Value>,
227 {
228 self.props = props.into_iter().collect();
229 self
230 }
231
232 pub fn queue_stub(mut self, queue_stub: bool) -> Self {
235 self.queue_stub = queue_stub;
236 self
237 }
238
239 pub fn tag(&self) -> Tag<'_> {
242 Tag::new(self)
243 }
244
245 pub fn resolved_site_id(&self) -> Option<String> {
248 let configured = self.site_id.as_deref().unwrap_or("").trim();
249 if !configured.is_empty() {
250 return Some(configured.to_owned());
251 }
252 let env = std::env::var("PRIVATUS_SITE_ID").unwrap_or_default();
253 let env = env.trim();
254 (!env.is_empty()).then(|| env.to_owned())
255 }
256
257 pub fn resolved_script_host(&self) -> String {
259 let mut host = self.script_host.as_deref().unwrap_or("").trim().to_owned();
260 if host.is_empty() {
261 host = std::env::var("PRIVATUS_SCRIPT_HOST")
262 .unwrap_or_default()
263 .trim()
264 .to_owned();
265 }
266 if host.is_empty() {
267 host = DEFAULT_SCRIPT_HOST.to_owned();
268 }
269 if host.ends_with('/') {
270 host.pop();
271 }
272 host
273 }
274
275 pub fn is_enabled(&self) -> bool {
278 self.enabled.unwrap_or(cfg!(not(debug_assertions)))
279 }
280
281 pub fn is_auto_inject(&self) -> bool {
283 self.auto_inject.unwrap_or(true)
284 }
285
286 pub(crate) fn modules_list(&self) -> Option<&[String]> {
287 self.modules.as_deref()
288 }
289
290 pub(crate) fn excluded_paths_list(&self) -> &[String] {
291 &self.excluded_paths
292 }
293
294 pub(crate) fn domains_list(&self) -> Option<&[String]> {
295 self.domains.as_deref()
296 }
297
298 pub(crate) fn spa_mode(&self) -> Spa {
299 self.spa
300 }
301
302 pub(crate) fn is_manual(&self) -> bool {
303 self.manual
304 }
305
306 pub(crate) fn extra_attributes_list(&self) -> &[(String, String)] {
307 &self.extra_attributes
308 }
309
310 pub(crate) fn configured_props(&self) -> &Props {
311 &self.props
312 }
313
314 pub(crate) fn wants_queue_stub(&self) -> bool {
315 self.queue_stub
316 }
317}
318
319pub(crate) fn set_pair(pairs: &mut Vec<(String, String)>, name: String, value: String) {
320 match pairs.iter_mut().find(|(k, _)| *k == name) {
321 Some(slot) => slot.1 = value,
322 None => pairs.push((name, value)),
323 }
324}