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        #[doc = concat!("Constant for [`", stringify!($name), "`].")]
53        const $code_name: &str = $code;
54
55        #[doc = concat!("Constant string `", stringify!($code), "`.")]
56        #[derive(Eq, PartialEq)]
57        pub struct $name;
58
59        impl Default for $name {
60            fn default() -> Self {
61                Self
62            }
63        }
64
65        impl ::std::ops::Deref for $name {
66            type Target = str;
67
68            fn deref(&self) -> &Self::Target {
69                $code_name
70            }
71        }
72
73        impl ::std::fmt::Debug for $name {
74            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
75                ::std::fmt::Debug::fmt(&**self, f)
76            }
77        }
78    };
79}
80
81/// Implement [`serde`] traits for a constant string.
82#[cfg(feature = "serde")]
83#[macro_export]
84macro_rules! constant_string_serde {
85    ($name:ident, $code_name:ident, $code:literal) => {
86        impl<'de> ::serde::Deserialize<'de> for $name {
87            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88            where
89                D: ::serde::Deserializer<'de>,
90            {
91                deserializer
92                    .deserialize_any($crate::serde::MustBeStrVisitor($code_name))
93                    .map(|()| Self)
94            }
95        }
96
97        impl ::serde::Serialize for $name {
98            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
99            where
100                S: ::serde::Serializer,
101            {
102                serializer.serialize_str($code_name)
103            }
104        }
105    };
106}
107
108/// Implement [`utoipa`] traits for a constant string.
109#[cfg(feature = "utoipa")]
110#[macro_export]
111macro_rules! constant_string_utoipa {
112    ($name:ident, $code_name:ident, $code:literal) => {
113        impl ::utoipa::PartialSchema for $name {
114            fn schema() -> ::utoipa::openapi::RefOr<::utoipa::openapi::schema::Schema> {
115                ::utoipa::openapi::schema::ObjectBuilder::new()
116                    .schema_type(::utoipa::openapi::schema::Type::String)
117                    .enum_values(Some([$code_name]))
118                    .build()
119                    .into()
120            }
121        }
122
123        impl ::utoipa::ToSchema for $name {}
124    };
125}
126
127#[cfg(test)]
128mod tests {
129    use std::ops::Deref;
130
131    constant_string!(Constant, CONSTANT, "constant");
132
133    #[test]
134    fn constant() {
135        assert_eq!(Constant.deref(), "constant");
136        assert_eq!(Constant.to_string(), "constant".to_owned());
137    }
138
139    #[test]
140    #[expect(clippy::default_constructed_unit_structs)]
141    fn default() {
142        assert_eq!(Constant::default().deref(), "constant");
143        assert_eq!(Constant::default().to_string(), "constant".to_owned());
144    }
145
146    #[cfg(feature = "serde")]
147    #[test]
148    fn serde() {
149        assert_eq!(
150            "\"constant\"",
151            serde_json::to_string(&Constant).expect("serializable value")
152        );
153        assert_eq!(
154            Constant,
155            serde_json::from_str("\"constant\"").expect("deserializable value")
156        );
157    }
158
159    #[cfg(feature = "utoipa")]
160    #[test]
161    fn utoipa() {
162        use utoipa::{
163            PartialSchema,
164            openapi::{
165                RefOr, Type,
166                schema::{Object, Schema},
167            },
168        };
169
170        assert_eq!(
171            RefOr::T(Schema::Object(
172                Object::builder()
173                    .schema_type(Type::String)
174                    .enum_values(Some(["constant"]))
175                    .build()
176            )),
177            Constant::schema()
178        )
179    }
180}