Skip to main content

constant_string/
lib.rs

1#![warn(missing_docs)]
2
3//! Constant string with support for [Serde](https://crates.io/crates/serde) and [Utoipa](https://crates.io/crates/utoipa).
4
5#[cfg(feature = "serde")]
6pub mod serde;
7
8#[cfg(all(feature = "serde", feature = "utoipa"))]
9/// Implement a constant string.
10#[macro_export]
11macro_rules! constant_string {
12    ($name:ident, $code_name:ident, $code:literal) => {
13        $crate::constant_string_base!($name, $code_name, $code);
14        $crate::constant_string_serde!($name, $code_name, $code);
15        $crate::constant_string_utoipa!($name, $code_name, $code);
16    };
17}
18
19#[cfg(all(feature = "serde", not(feature = "utoipa")))]
20/// Implement a constant string.
21#[macro_export]
22macro_rules! constant_string {
23    ($name:ident, $code_name:ident, $code:literal) => {
24        $crate::constant_string_base!($name, $code_name, $code);
25        $crate::constant_string_serde!($name, $code_name, $code);
26    };
27}
28
29#[cfg(all(not(feature = "serde"), feature = "utoipa"))]
30/// Implement a constant string.
31#[macro_export]
32macro_rules! constant_string {
33    ($name:ident, $code_name:ident, $code:literal) => {
34        $crate::constant_string_base!($name, $code_name, $code);
35        $crate::constant_string_utoipa!($name, $code_name, $code);
36    };
37}
38
39#[cfg(all(not(feature = "serde"), not(feature = "utoipa")))]
40/// Implement a constant string.
41#[macro_export]
42macro_rules! constant_string {
43    ($name:ident, $code_name:ident, $code:literal) => {
44        $crate::constant_string_base!($name, $code_name, $code);
45    };
46}
47
48/// Implement a constant string.
49#[macro_export]
50macro_rules! constant_string_base {
51    ($name:ident, $code_name:ident, $code:literal) => {
52        const $code_name: &str = $code;
53
54        #[derive(Eq, PartialEq)]
55        pub struct $name;
56
57        impl Default for $name {
58            fn default() -> Self {
59                Self
60            }
61        }
62
63        impl ::std::ops::Deref for $name {
64            type Target = str;
65
66            fn deref(&self) -> &Self::Target {
67                $code_name
68            }
69        }
70
71        impl ::std::fmt::Debug for $name {
72            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
73                ::std::fmt::Debug::fmt(&**self, f)
74            }
75        }
76    };
77}
78
79/// Implement [`serde`] traits for a constant string.
80#[cfg(feature = "serde")]
81#[macro_export]
82macro_rules! constant_string_serde {
83    ($name:ident, $code_name:ident, $code:literal) => {
84        impl<'de> ::serde::Deserialize<'de> for $name {
85            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86            where
87                D: ::serde::Deserializer<'de>,
88            {
89                deserializer
90                    .deserialize_any($crate::serde::MustBeStrVisitor($code_name))
91                    .map(|()| Self)
92            }
93        }
94
95        impl ::serde::Serialize for $name {
96            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97            where
98                S: ::serde::Serializer,
99            {
100                serializer.serialize_str($code_name)
101            }
102        }
103    };
104}
105
106/// Implement [`utoipa`] traits for a constant string.
107#[cfg(feature = "utoipa")]
108#[macro_export]
109macro_rules! constant_string_utoipa {
110    ($name:ident, $code_name:ident, $code:literal) => {
111        impl ::utoipa::PartialSchema for $name {
112            fn schema() -> ::utoipa::openapi::RefOr<::utoipa::openapi::schema::Schema> {
113                ::utoipa::openapi::schema::ObjectBuilder::new()
114                    .schema_type(::utoipa::openapi::schema::Type::String)
115                    .enum_values(Some([$code_name]))
116                    .build()
117                    .into()
118            }
119        }
120
121        impl ::utoipa::ToSchema for $name {}
122    };
123}
124
125#[cfg(test)]
126mod tests {
127    use std::ops::Deref;
128
129    constant_string!(Constant, CONSTANT, "constant");
130
131    #[test]
132    fn constant() {
133        assert_eq!(Constant.deref(), "constant");
134        assert_eq!(Constant.to_string(), "constant".to_owned());
135    }
136
137    #[test]
138    #[expect(clippy::default_constructed_unit_structs)]
139    fn default() {
140        assert_eq!(Constant::default().deref(), "constant");
141        assert_eq!(Constant::default().to_string(), "constant".to_owned());
142    }
143
144    #[cfg(feature = "serde")]
145    #[test]
146    fn serde() {
147        assert_eq!(
148            "\"constant\"",
149            serde_json::to_string(&Constant).expect("serializable value")
150        );
151        assert_eq!(
152            Constant,
153            serde_json::from_str("\"constant\"").expect("deserializable value")
154        );
155    }
156
157    #[cfg(feature = "utoipa")]
158    #[test]
159    fn utoipa() {
160        use utoipa::{
161            PartialSchema,
162            openapi::{
163                RefOr, Type,
164                schema::{Object, Schema},
165            },
166        };
167
168        assert_eq!(
169            RefOr::T(Schema::Object(
170                Object::builder()
171                    .schema_type(Type::String)
172                    .enum_values(Some(["constant"]))
173                    .build()
174            )),
175            Constant::schema()
176        )
177    }
178}