Skip to main content

es_fluent_shared/resource/
keys.rs

1use crate::fluent::FluentDomain;
2use crate::namespace::{NamespacePathError, ResolvedNamespace};
3use crate::registry::StaticFluentDomain;
4
5/// Error produced while validating a resource key.
6#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
7#[error("{0}")]
8pub struct ResourceKeyError(#[from] pub(super) NamespacePathError);
9
10/// Stable key for a localized resource.
11///
12/// Keys use the canonical shape:
13/// - `{domain}` for base files
14/// - `{domain}/{namespace}` for namespaced files
15#[derive(
16    Clone, Debug, derive_more::AsRef, derive_more::Display, Eq, Hash, Ord, PartialEq, PartialOrd,
17)]
18#[as_ref(str)]
19pub struct ResourceKey(String);
20
21impl ResourceKey {
22    /// Validates and creates a resource key.
23    pub fn try_new(key: impl Into<String>) -> Result<Self, ResourceKeyError> {
24        let key = key.into();
25        crate::namespace::validate_namespace_path_typed(&key)?;
26        Ok(Self(key))
27    }
28
29    /// Validates and creates a resource key from static metadata.
30    #[allow(
31        clippy::panic,
32        reason = "static metadata uses literal keys; use try_new for dynamic input"
33    )]
34    pub fn from_static_path(key: &'static str) -> Self {
35        Self::try_new(key)
36            .unwrap_or_else(|error| panic!("invalid static resource key '{key}': {error}"))
37    }
38
39    /// Creates a base resource key from an already-validated static Fluent domain.
40    pub fn from_static_domain(domain: StaticFluentDomain) -> Self {
41        Self(domain.as_str().to_string())
42    }
43
44    /// Creates a namespaced resource key from already-validated domain and namespace values.
45    pub fn from_static_domain_and_namespace(
46        domain: StaticFluentDomain,
47        namespace: &ResolvedNamespace,
48    ) -> Self {
49        Self(format!("{}/{namespace}", domain.as_str()))
50    }
51
52    pub(super) fn from_domain(domain: &FluentDomain) -> Self {
53        Self(domain.as_str().to_string())
54    }
55
56    pub(super) fn from_domain_and_namespace(
57        domain: &FluentDomain,
58        namespace: &ResolvedNamespace,
59    ) -> Self {
60        Self(format!("{}/{namespace}", domain.as_str()))
61    }
62
63    /// Returns the key as a string slice.
64    pub fn as_str(&self) -> &str {
65        &self.0
66    }
67
68    /// Returns the domain segment of the key.
69    pub fn domain(&self) -> &str {
70        self.0.split('/').next().unwrap_or(self.as_str())
71    }
72
73    /// Returns the domain segment as a validated Fluent domain.
74    pub fn domain_name(&self) -> FluentDomain {
75        FluentDomain::try_new(self.domain())
76            .expect("resource key validation should guarantee a valid domain segment")
77    }
78}
79
80impl serde::Serialize for ResourceKey {
81    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
82    where
83        S: serde::Serializer,
84    {
85        serializer.serialize_str(self.as_str())
86    }
87}
88
89impl<'de> serde::Deserialize<'de> for ResourceKey {
90    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91    where
92        D: serde::Deserializer<'de>,
93    {
94        let value = <String as serde::Deserialize>::deserialize(deserializer)?;
95        Self::try_new(value).map_err(serde::de::Error::custom)
96    }
97}