opentalk_types_common/users/
language.rs

1// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5use icu_locid::{LanguageIdentifier, langid};
6
7use crate::utils::ExampleData;
8
9/// A language identifier
10#[derive(
11    Default,
12    Debug,
13    Clone,
14    PartialEq,
15    Eq,
16    Hash,
17    derive_more::Display,
18    derive_more::FromStr,
19    derive_more::From,
20    derive_more::Into,
21    derive_more::AsRef,
22    derive_more::AsMut,
23)]
24#[cfg_attr(
25    feature = "serde",
26    derive(serde::Serialize, serde_with::DeserializeFromStr)
27)]
28pub struct Language(pub LanguageIdentifier);
29
30#[cfg(feature = "bincode")]
31mod bincode_impls {
32    use bincode::{
33        BorrowDecode, Decode, Encode,
34        de::{BorrowDecoder, Decoder},
35        enc::Encoder,
36        error::{DecodeError, EncodeError},
37    };
38
39    use super::Language;
40
41    impl Encode for Language {
42        fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
43            Encode::encode(&self.0.to_string(), encoder)
44        }
45    }
46
47    impl<Context> Decode<Context> for Language {
48        fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
49            let decoded: String = Decode::<Context>::decode(decoder)?;
50            let language_identifier = decoded.parse().map_err(|e| {
51                DecodeError::OtherString(format!("Invalid language identifier: {e}"))
52            })?;
53            Ok(Self(language_identifier))
54        }
55    }
56
57    impl<'de, Context> BorrowDecode<'de, Context> for Language {
58        fn borrow_decode<D: BorrowDecoder<'de, Context = Context>>(
59            decoder: &mut D,
60        ) -> Result<Self, DecodeError> {
61            let decoded: String = Decode::<Context>::decode(decoder)?;
62            let language_identifier = decoded.parse().map_err(|e| {
63                DecodeError::OtherString(format!("Invalid language identifier: {e}"))
64            })?;
65            Ok(Self(language_identifier))
66        }
67    }
68}
69
70impl Language {
71    /// Create a new empty [`Language`]
72    pub fn new() -> Self {
73        Self::default()
74    }
75}
76
77#[cfg(feature = "utoipa")]
78mod impl_to_schema {
79    //! The `#[derive(utoipa::ToSchema)] implementation does not yet properly support
80    //! exposing schema information of types wrapped by the NewType pattern, therefore
81    //! a manual implementation is required for now.
82    //! Issue: <https://github.com/juhaku/utoipa/issues/663>
83
84    use serde_json::json;
85    use utoipa::{
86        PartialSchema, ToSchema,
87        openapi::{ObjectBuilder, RefOr, Schema, Type},
88    };
89
90    use super::Language;
91    use crate::utils::ExampleData as _;
92
93    impl PartialSchema for Language {
94        fn schema() -> RefOr<Schema> {
95            ObjectBuilder::new()
96                .schema_type(Type::String)
97                .description(Some("A language identifier"))
98                .format(Some(utoipa::openapi::SchemaFormat::Custom(
99                    "bcp-47".to_string(),
100                )))
101                .examples([json!(Language::example_data())])
102                .into()
103        }
104    }
105
106    impl ToSchema for Language {
107        fn schemas(schemas: &mut Vec<(String, RefOr<Schema>)>) {
108            schemas.push((Self::name().into(), Self::schema()));
109        }
110    }
111}
112
113impl ExampleData for Language {
114    fn example_data() -> Self {
115        Self(langid!("de"))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use icu_locid::langid;
122    use pretty_assertions::assert_eq;
123
124    use super::Language;
125
126    #[test]
127    fn parse() {
128        assert_eq!(
129            "de-AT".parse::<Language>().unwrap(),
130            Language(langid!("de-AT"))
131        );
132        assert_eq!("xx".parse::<Language>().unwrap(), Language(langid!("xx")));
133    }
134
135    #[test]
136    fn parse_invalid() {
137        assert!("".parse::<Language>().is_err());
138        assert!("🚀".parse::<Language>().is_err());
139    }
140}