systemprompt_identifiers/
locale.rs1use crate::error::IdValidationError;
14use crate::{DbValue, ToDbValue};
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use std::fmt;
18
19const MAX_LEN: usize = 35;
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, JsonSchema)]
22#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
23#[cfg_attr(feature = "sqlx", sqlx(transparent))]
24#[serde(transparent)]
25pub struct LocaleCode(String);
26
27impl LocaleCode {
28 pub fn try_new(value: impl Into<String>) -> Result<Self, IdValidationError> {
29 let value = value.into();
30 if value.is_empty() {
31 return Err(IdValidationError::empty("LocaleCode"));
32 }
33 if value.len() > MAX_LEN {
34 return Err(IdValidationError::invalid(
35 "LocaleCode",
36 "exceeds 35 characters",
37 ));
38 }
39 let mut subtags = value.split('-');
40 let primary = subtags.next().unwrap_or("");
41 let plen = primary.len();
42 if !(2..=3).contains(&plen) || !primary.chars().all(|c| c.is_ascii_lowercase()) {
43 return Err(IdValidationError::invalid(
44 "LocaleCode",
45 "primary subtag must be 2-3 lowercase ASCII letters",
46 ));
47 }
48 for sub in subtags {
49 let len = sub.len();
50 if !(2..=8).contains(&len) || !sub.chars().all(|c| c.is_ascii_alphanumeric()) {
51 return Err(IdValidationError::invalid(
52 "LocaleCode",
53 "subtag must be 2-8 alphanumeric ASCII characters",
54 ));
55 }
56 }
57 Ok(Self(value))
58 }
59
60 #[must_use]
61 pub fn english() -> Self {
62 Self("en".to_owned())
63 }
64
65 #[must_use]
66 pub fn as_str(&self) -> &str {
67 &self.0
68 }
69}
70
71impl fmt::Display for LocaleCode {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(f, "{}", self.0)
74 }
75}
76
77impl TryFrom<String> for LocaleCode {
78 type Error = IdValidationError;
79
80 fn try_from(s: String) -> Result<Self, Self::Error> {
81 Self::try_new(s)
82 }
83}
84
85impl TryFrom<&str> for LocaleCode {
86 type Error = IdValidationError;
87
88 fn try_from(s: &str) -> Result<Self, Self::Error> {
89 Self::try_new(s)
90 }
91}
92
93impl std::str::FromStr for LocaleCode {
94 type Err = IdValidationError;
95
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 Self::try_new(s)
98 }
99}
100
101impl AsRef<str> for LocaleCode {
102 fn as_ref(&self) -> &str {
103 &self.0
104 }
105}
106
107impl<'de> Deserialize<'de> for LocaleCode {
108 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
109 where
110 D: serde::Deserializer<'de>,
111 {
112 let s = String::deserialize(deserializer)?;
113 Self::try_new(s).map_err(serde::de::Error::custom)
114 }
115}
116
117impl ToDbValue for LocaleCode {
118 fn to_db_value(&self) -> DbValue {
119 DbValue::String(self.0.clone())
120 }
121}
122
123impl ToDbValue for &LocaleCode {
124 fn to_db_value(&self) -> DbValue {
125 DbValue::String(self.0.clone())
126 }
127}