ras_types/domain/
url_pattern.rs1use std::str::FromStr;
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5use url::Url;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[serde(transparent)]
9pub struct DomainPattern(String);
10
11#[derive(Debug, Error)]
12pub enum DomainPatternError {
13 #[error("empty pattern")]
14 Empty,
15 #[error("invalid pattern: {0}")]
16 Invalid(String),
17}
18
19impl DomainPattern {
20 pub fn new(pattern: impl Into<String>) -> Result<Self, DomainPatternError> {
21 let s = pattern.into();
22 if s.trim().is_empty() {
23 return Err(DomainPatternError::Empty);
24 }
25 Ok(Self(s.to_lowercase()))
26 }
27
28 #[must_use]
29 pub fn as_str(&self) -> &str {
30 &self.0
31 }
32
33 #[must_use]
34 pub fn matches_url(&self, url: &Url) -> bool {
35 let Some(host) = url.host_str() else {
36 return false;
37 };
38 let host = host.to_lowercase();
39 let pattern = &self.0;
40 if pattern.starts_with("http://") || pattern.starts_with("https://") {
41 return url.as_str().starts_with(pattern.as_str());
42 }
43 if let Some(rest) = pattern.strip_prefix("*.") {
44 return host == rest || host.ends_with(&format!(".{rest}"));
45 }
46 host == *pattern || host == format!("www.{pattern}")
47 }
48}
49
50impl FromStr for DomainPattern {
51 type Err = DomainPatternError;
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 Self::new(s)
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
58pub enum MatchLevel {
59 Hash,
60 Xpath,
61 AxName,
62 Attributes,
63}