use alloc::string::{String, ToString};
use core::fmt;
use core::ops::Deref;
use core::str::FromStr;
use oxilangtag::LanguageTag;
use serde::{Deserialize, Deserializer, Serialize, de::Error};
use smol_str::{SmolStr, ToSmolStr};
use crate::CowStr;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)]
#[serde(transparent)]
#[repr(transparent)]
pub struct Language(SmolStr);
impl Language {
pub fn new(lang: &str) -> Result<Self, oxilangtag::LanguageTagParseError> {
let tag = LanguageTag::parse(lang)?;
Ok(Language(SmolStr::new(tag.as_str())))
}
pub fn new_static(lang: &'static str) -> Result<Self, oxilangtag::LanguageTagParseError> {
let _ = LanguageTag::parse(lang)?;
Ok(Language(SmolStr::new_static(lang)))
}
fn new_owned(lang: SmolStr) -> Result<Self, SmolStr> {
let tag = LanguageTag::parse(lang.as_str()).map_err(|e| e.to_smolstr())?;
Ok(Language(SmolStr::new(tag.as_str())))
}
pub fn raw(lang: impl AsRef<str>) -> Self {
let lang = lang.as_ref();
let tag = LanguageTag::parse(lang).expect("valid IETF language tag");
Language(SmolStr::new(tag.as_str()))
}
pub unsafe fn unchecked(lang: impl AsRef<str>) -> Self {
let lang = lang.as_ref();
Self(SmolStr::new(lang))
}
pub fn as_str(&self) -> &str {
{
let this = &self.0;
this
}
}
}
impl FromStr for Language {
type Err = SmolStr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s).map_err(|e| e.to_smolstr())
}
}
impl<'de> Deserialize<'de> for Language {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = Deserialize::deserialize(deserializer)?;
Self::new_owned(value).map_err(D::Error::custom)
}
}
impl fmt::Display for Language {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<Language> for String {
fn from(value: Language) -> Self {
value.0.to_string()
}
}
impl From<Language> for SmolStr {
fn from(value: Language) -> Self {
value.0
}
}
impl From<String> for Language {
fn from(value: String) -> Self {
Self::raw(&value)
}
}
impl<'t> From<CowStr<'t>> for Language {
fn from(value: CowStr<'t>) -> Self {
Self::raw(&value)
}
}
impl AsRef<str> for Language {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Deref for Language {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl crate::IntoStatic for Language {
type Output = Language;
fn into_static(self) -> Self::Output {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_language_tags() {
assert!(Language::new("en").is_ok());
assert!(Language::new("en-US").is_ok());
assert!(Language::new("zh-Hans").is_ok());
assert!(Language::new("es-419").is_ok());
}
#[test]
fn case_insensitive_but_preserves() {
let lang = Language::new("en-US").unwrap();
assert_eq!(lang.as_str(), "en-US");
}
#[test]
fn invalid_tags() {
assert!(Language::new("").is_err());
assert!(Language::new("not_a_tag").is_err());
assert!(Language::new("123").is_err());
}
}