#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub struct SortingConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<SortingLocale>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multilingual: Option<SortingMultilingualMode>,
#[serde(
flatten,
default,
skip_serializing_if = "std::collections::BTreeMap::is_empty"
)]
#[cfg_attr(feature = "schema", schemars(skip))]
pub unknown_fields: std::collections::BTreeMap<String, serde_yaml::Value>,
}
impl SortingConfig {
pub fn merge(&mut self, other: &SortingConfig) {
if let Some(locale) = &other.locale {
self.locale = Some(locale.clone());
}
if let Some(multilingual) = other.multilingual {
self.multilingual = Some(multilingual);
}
for (key, value) in &other.unknown_fields {
self.unknown_fields.insert(key.clone(), value.clone());
}
}
#[must_use]
pub fn effective_locale(&self) -> SortingLocale {
self.locale.clone().unwrap_or_default()
}
#[must_use]
pub fn effective_multilingual(&self) -> SortingMultilingualMode {
self.multilingual.unwrap_or_default()
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub enum SortingLocale {
#[default]
Auto,
Bcp47(String),
}
impl SortingLocale {
#[must_use]
pub fn as_explicit_tag(&self) -> Option<&str> {
match self {
Self::Auto => None,
Self::Bcp47(tag) => Some(tag.as_str()),
}
}
}
impl Serialize for SortingLocale {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Auto => serializer.serialize_str("auto"),
Self::Bcp47(tag) => serializer.serialize_str(tag),
}
}
}
impl<'de> Deserialize<'de> for SortingLocale {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct SortingLocaleVisitor;
impl de::Visitor<'_> for SortingLocaleVisitor {
type Value = SortingLocale;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("\"auto\" or a BCP 47 locale tag")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(E::custom("sorting.locale must not be empty"));
}
if trimmed == "auto" {
Ok(SortingLocale::Auto)
} else {
Ok(SortingLocale::Bcp47(trimmed.to_string()))
}
}
}
deserializer.deserialize_str(SortingLocaleVisitor)
}
}
#[cfg(feature = "schema")]
impl JsonSchema for SortingLocale {
fn schema_name() -> std::borrow::Cow<'static, str> {
"SortingLocale".into()
}
fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"description": "\"auto\" or an explicit BCP 47 locale tag used for collation."
})
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum SortingMultilingualMode {
#[default]
Uniform,
Romanized,
PerScript,
}