Skip to main content

rudof_rdf/rdf_core/term/literal/
lang.rs

1use std::hash::Hash;
2
3use oxilangtag::LanguageTag;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7/// A validated language tag wrapper that ensures normalization and validity.
8///
9/// This type wraps an `oxilangtag::LanguageTag` and provides a type-safe way to work
10/// with IETF BCP 47 language tags (e.g., "en", "en-US", "es-ES"). The language tag is
11/// validated and normalized upon construction, ensuring it conforms to the standard.
12///
13/// # Normalization
14///
15/// Language tags are automatically normalized according to BCP 47 rules:
16/// - The primary language subtag is lowercased (e.g., "EN" → "en")
17/// - Script subtags are title-cased (e.g., "latn" → "Latn")
18/// - Region subtags are uppercased (e.g., "us" → "US")
19///
20/// # Serialization
21///
22/// The `#[serde(transparent)]` attribute ensures that this type serializes as a plain
23/// string rather than a struct with a single field, making it compatible with JSON
24/// schemas that expect language tags as strings.
25/// ```
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[serde(transparent)]
28pub struct Lang {
29    lang: LanguageTag<String>,
30}
31
32impl Lang {
33    /// Creates a new `Lang` from a string-like value.
34    ///
35    /// # Errors
36    ///
37    /// Returns `LangParseError` if the language tag is invalid.
38    pub fn new(lang: impl Into<String>) -> Result<Lang, LangParseError> {
39        let lang = oxilangtag::LanguageTag::parse_and_normalize(&lang.into())?;
40        Ok(Lang { lang })
41    }
42
43    /// Returns the language tag as a string slice.
44    #[inline]
45    pub fn as_str(&self) -> &str {
46        self.lang.as_str()
47    }
48}
49
50impl std::fmt::Display for Lang {
51    /// Formats the language tag for display.
52    ///
53    /// This outputs the normalized form of the language tag, making it suitable
54    /// for user-facing output, logging, and serialization to text formats.
55    /// ```
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(f, "{}", self.lang)
58    }
59}
60
61/// Error type for language tag parsing failures.
62///
63/// This error is returned when attempting to construct a `Lang` from an invalid
64/// language tag string. It wraps the underlying parsing error from `oxilangtag`.
65#[derive(Error, Debug)]
66pub enum LangParseError {
67    #[error("Invalid language tag: {0}")]
68    InvalidLangTag(#[from] oxilangtag::LanguageTagParseError),
69}