use compact_str::CompactString;
use url::Url;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum SiwxOriginError {
#[error("siwx origin is required")]
Missing,
#[error("siwx origin must be an absolute http(s) URL")]
NotAbsolute,
#[error("siwx origin authority is invalid")]
InvalidAuthority,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SiwxOrigin {
origin: CompactString,
domain: CompactString,
}
impl SiwxOrigin {
pub fn parse(raw: &str) -> Result<Self, SiwxOriginError> {
let trimmed = raw.trim();
if trimmed.is_empty() {
return Err(SiwxOriginError::Missing);
}
let parsed = Url::parse(trimmed).map_err(|_| SiwxOriginError::NotAbsolute)?;
if parsed.scheme() != "http" && parsed.scheme() != "https" {
return Err(SiwxOriginError::NotAbsolute);
}
if !parsed.username().is_empty() || parsed.password().is_some() {
return Err(SiwxOriginError::InvalidAuthority);
}
if parsed.host_str().is_none_or(str::is_empty) {
return Err(SiwxOriginError::InvalidAuthority);
}
let serialized = parsed.origin().ascii_serialization();
let domain = serialized
.strip_prefix("https://")
.or_else(|| serialized.strip_prefix("http://"))
.ok_or(SiwxOriginError::NotAbsolute)?;
Ok(Self {
origin: CompactString::from(serialized.as_str()),
domain: CompactString::from(domain),
})
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.origin
}
#[must_use]
pub fn domain(&self) -> &str {
&self.domain
}
#[must_use]
pub fn uri(&self, path: &str) -> CompactString {
join_origin_path(&self.origin, path)
}
#[must_use]
pub fn store_key(&self, path: &str) -> CompactString {
join_origin_path(&self.origin, path)
}
}
fn join_origin_path(origin: &str, path: &str) -> CompactString {
let path = if path.is_empty() { "/" } else { path };
let mut out = CompactString::from(origin);
if !path.starts_with('/') {
out.push('/');
}
out.push_str(path);
out
}