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                    .examples([$code_name])
119                    .build()
120                    .into()
121            }
122        }
123
124        impl ::utoipa::ToSchema for $name {}
125    };
126}
127
128#[cfg(test)]
129mod tests {
130    use std::ops::Deref;
131
132    constant_string!(Constant, CONSTANT, "constant");
133
134    #[test]
135    fn constant() {
136        assert_eq!(Constant.deref(), "constant");
137        assert_eq!(Constant.to_string(), "constant".to_owned());
138    }
139
140    #[test]
141    #[expect(clippy::default_constructed_unit_structs)]
142    fn default() {
143        assert_eq!(Constant::default().deref(), "constant");
144        assert_eq!(Constant::default().to_string(), "constant".to_owned());
145    }
146
147    #[cfg(feature = "serde")]
148    #[test]
149    fn serde() {
150        assert_eq!(
151            "\"constant\"",
152            serde_json::to_string(&Constant).expect("serializable value")
153        );
154        assert_eq!(
155            Constant,
156            serde_json::from_str("\"constant\"").expect("deserializable value")
157        );
158    }
159
160    #[cfg(feature = "utoipa")]
161    #[test]
162    fn utoipa() {
163        use utoipa::{
164            PartialSchema,
165            openapi::{
166                RefOr, Type,
167                schema::{Object, Schema},
168            },
169        };
170
171        assert_eq!(
172            RefOr::T(Schema::Object(
173                Object::builder()
174                    .schema_type(Type::String)
175                    .enum_values(Some(["constant"]))
176                    .examples(["constant"])
177                    .build()
178            )),
179            Constant::schema()
180        )
181    }
182}