use crate::namespace::NamespacePathError;
#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
pub enum LocaleRelativeFtlPathError {
#[error("path must not be empty")]
Empty,
#[error("path must be relative")]
Absolute,
#[error("path must use '/' separators")]
Backslash,
#[error("path must end with .ftl")]
MissingFtlSuffix,
#[error("{0}")]
InvalidStem(#[from] NamespacePathError),
}
#[derive(
Clone,
Debug,
derive_more::AsRef,
derive_more::Deref,
derive_more::Display,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[as_ref(str)]
#[deref(forward)]
pub struct LocaleRelativeFtlPath(String);
impl LocaleRelativeFtlPath {
pub fn try_new(path: impl Into<String>) -> Result<Self, LocaleRelativeFtlPathError> {
let path = path.into();
validate_locale_relative_ftl_path(&path)?;
Ok(Self(path))
}
#[allow(
clippy::panic,
reason = "static metadata uses literal paths; use try_new for dynamic input"
)]
pub fn from_static_path(path: &'static str) -> Self {
Self::try_new(path)
.unwrap_or_else(|error| panic!("invalid locale-relative FTL path '{path}': {error}"))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl PartialEq<&str> for LocaleRelativeFtlPath {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<LocaleRelativeFtlPath> for &str {
fn eq(&self, other: &LocaleRelativeFtlPath) -> bool {
*self == other.as_str()
}
}
impl serde::Serialize for LocaleRelativeFtlPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for LocaleRelativeFtlPath {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <String as serde::Deserialize>::deserialize(deserializer)?;
Self::try_new(value).map_err(serde::de::Error::custom)
}
}
fn validate_locale_relative_ftl_path(path: &str) -> Result<(), LocaleRelativeFtlPathError> {
if path.is_empty() {
return Err(LocaleRelativeFtlPathError::Empty);
}
if path.starts_with('/') {
return Err(LocaleRelativeFtlPathError::Absolute);
}
if path.contains('\\') {
return Err(LocaleRelativeFtlPathError::Backslash);
}
let Some(stem) = path.strip_suffix(".ftl") else {
return Err(LocaleRelativeFtlPathError::MissingFtlSuffix);
};
crate::namespace::validate_namespace_path_typed(stem)?;
Ok(())
}