Skip to main content

ras_types/domain/
url_pattern.rs

1use 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}