lib-humus 0.6.0

Helps creating configurable frontends for humans and computers using axum, Tera and toml.
Documentation
// SPDX-FileCopyrightText: 2026 Slatian <baschdel@disroot.org>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

use std::{
	fmt::Display,
	ops::{Deref, DerefMut},
	str::FromStr,
};

use serde::Deserialize;
use unic_langid::{LanguageIdentifier, LanguageIdentifierError};

/// A wrapper for [[unic_langid::LanguageIdentifier]] that als implements serde traits.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnicodeLanguageIdentifier(LanguageIdentifier);

impl From<LanguageIdentifier> for UnicodeLanguageIdentifier {
	fn from(value: LanguageIdentifier) -> Self {
		Self(value)
	}
}

impl From<UnicodeLanguageIdentifier> for LanguageIdentifier {
	fn from(value: UnicodeLanguageIdentifier) -> Self {
		value.0
	}
}

impl Deref for UnicodeLanguageIdentifier {
	type Target = LanguageIdentifier;

	fn deref(&self) -> &Self::Target {
		&self.0
	}
}

impl DerefMut for UnicodeLanguageIdentifier {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.0
	}
}

impl Display for UnicodeLanguageIdentifier {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		self.0.fmt(f)
	}
}

impl FromStr for UnicodeLanguageIdentifier {
	type Err = LanguageIdentifierError;

	fn from_str(s: &str) -> Result<Self, Self::Err> {
		Ok(Self(s.parse()?))
	}
}

impl serde::Serialize for UnicodeLanguageIdentifier {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: serde::Serializer,
	{
		serializer.serialize_str(self.0.to_string().as_str())
	}
}

impl<'de> Deserialize<'de> for UnicodeLanguageIdentifier {
	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
	where
		D: serde::Deserializer<'de>,
	{
		deserializer.deserialize_str(UnicodeLanguageIdentifierVisitor)
	}
}

struct UnicodeLanguageIdentifierVisitor;

impl<'de> serde::de::Visitor<'de> for UnicodeLanguageIdentifierVisitor {
	type Value = UnicodeLanguageIdentifier;

	fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
		formatter.write_str(
			"A language Tag as defined in Unicode TR35 (similar to BCP 47 i.e. en-US, zh-TW, de-Latn or fr)",
		)
	}

	fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
	where
		E: serde::de::Error,
	{
		let identifier: LanguageIdentifier = v.parse().map_err(|e| {
			E::custom(format!(
				"Unable to parse unicode TR35 language identifier from {v:?}: {e}"
			))
		})?;
		Ok(UnicodeLanguageIdentifier(identifier))
	}
}