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//! # Example
6//! ```
7//! # extern crate serde;
8//! # extern crate utoipa;
9//! #
10//! use constant_string::constant_string;
11//! use serde::{Deserialize, Serialize};
12//! use utoipa::ToSchema;
13//!
14//! constant_string!(NotFoundErrorCode, NOT_FOUND_ERROR_CODE, "notFound");
15//!
16//! #[derive(Debug, Default, Deserialize, Serialize, ToSchema)]
17//! struct NotFoundError {
18//!     code: NotFoundErrorCode,
19//! }
20//! ```
21//!
22//! # Features
23//! - `serde` - Implement [`Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html) and [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html) traits from [`serde`](https://docs.rs/serde/latest/serde/).
24//! - `utoipa` - Implement [`ToSchema`](https://docs.rs/utoipa/latest/utoipa/trait.ToSchema.html) trait from [`utoipa`](https://docs.rs/utoipa/latest/utoipa/).
25
26#[cfg(feature = "serde")]
27pub mod serde;
28
29#[cfg(all(feature = "serde", feature = "utoipa"))]
30/// Implement a constant string.
31///
32/// # Example
33/// ```
34/// # use constant_string::constant_string;
35/// #
36/// constant_string!(NotFoundErrorCode, NOT_FOUND_ERROR_CODE, "notFound");
37/// ```
38#[macro_export]
39macro_rules! constant_string {
40    ($name:ident, $code_name:ident, $code:literal) => {
41        $crate::constant_string_base!($name, $code_name, $code);
42        $crate::constant_string_serde!($name, $code_name, $code);
43        $crate::constant_string_utoipa!($name, $code_name, $code);
44    };
45}
46
47#[cfg(all(feature = "serde", not(feature = "utoipa")))]
48/// Implement a constant string.
49///
50/// # Example
51/// ```
52/// # use constant_string::constant_string;
53/// #
54/// constant_string!(NotFoundErrorCode, NOT_FOUND_ERROR_CODE, "notFound");
55/// ```
56#[macro_export]
57macro_rules! constant_string {
58    ($name:ident, $code_name:ident, $code:literal) => {
59        $crate::constant_string_base!($name, $code_name, $code);
60        $crate::constant_string_serde!($name, $code_name, $code);
61    };
62}
63
64#[cfg(all(not(feature = "serde"), feature = "utoipa"))]
65/// Implement a constant string.
66///
67/// # Example
68/// ```
69/// # use constant_string::constant_string;
70/// #
71/// constant_string!(NotFoundErrorCode, NOT_FOUND_ERROR_CODE, "notFound");
72/// ```
73#[macro_export]
74macro_rules! constant_string {
75    ($name:ident, $code_name:ident, $code:literal) => {
76        $crate::constant_string_base!($name, $code_name, $code);
77        $crate::constant_string_utoipa!($name, $code_name, $code);
78    };
79}
80
81#[cfg(all(not(feature = "serde"), not(feature = "utoipa")))]
82/// Implement a constant string.
83///
84/// # Example
85/// ```
86/// # use constant_string::constant_string;
87/// #
88/// constant_string!(NotFoundErrorCode, NOT_FOUND_ERROR_CODE, "notFound");
89/// ```
90#[macro_export]
91macro_rules! constant_string {
92    ($name:ident, $code_name:ident, $code:literal) => {
93        $crate::constant_string_base!($name, $code_name, $code);
94    };
95}
96
97/// Implement a constant string.
98#[doc(hidden)]
99#[macro_export]
100macro_rules! constant_string_base {
101    ($name:ident, $code_name:ident, $code:literal) => {
102        #[doc = concat!("Constant for [`", stringify!($name), "`].")]
103        const $code_name: &str = $code;
104
105        #[doc = concat!("Constant string `", stringify!($code), "`.")]
106        #[derive(Eq, PartialEq)]
107        pub struct $name;
108
109        impl Default for $name {
110            fn default() -> Self {
111                Self
112            }
113        }
114
115        impl ::std::ops::Deref for $name {
116            type Target = str;
117
118            fn deref(&self) -> &Self::Target {
119                $code_name
120            }
121        }
122
123        impl ::std::fmt::Debug for $name {
124            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
125                ::std::fmt::Debug::fmt(&**self, f)
126            }
127        }
128    };
129}
130
131/// Implement [`serde`] traits for a constant string.
132#[cfg(feature = "serde")]
133#[doc(hidden)]
134#[macro_export]
135macro_rules! constant_string_serde {
136    ($name:ident, $code_name:ident, $code:literal) => {
137        impl<'de> ::serde::Deserialize<'de> for $name {
138            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
139            where
140                D: ::serde::Deserializer<'de>,
141            {
142                deserializer
143                    .deserialize_any($crate::serde::MustBeStrVisitor($code_name))
144                    .map(|()| Self)
145            }
146        }
147
148        impl ::serde::Serialize for $name {
149            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150            where
151                S: ::serde::Serializer,
152            {
153                serializer.serialize_str($code_name)
154            }
155        }
156    };
157}
158
159/// Implement [`utoipa`] traits for a constant string.
160#[cfg(feature = "utoipa")]
161#[doc(hidden)]
162#[macro_export]
163macro_rules! constant_string_utoipa {
164    ($name:ident, $code_name:ident, $code:literal) => {
165        impl ::utoipa::PartialSchema for $name {
166            fn schema() -> ::utoipa::openapi::RefOr<::utoipa::openapi::schema::Schema> {
167                ::utoipa::openapi::schema::ObjectBuilder::new()
168                    .schema_type(::utoipa::openapi::schema::Type::String)
169                    .enum_values(Some([$code_name]))
170                    .build()
171                    .into()
172            }
173        }
174
175        impl ::utoipa::ToSchema for $name {}
176    };
177}
178
179#[cfg(test)]
180mod tests {
181    use std::ops::Deref;
182
183    constant_string!(Constant, CONSTANT, "constant");
184
185    #[test]
186    fn constant() {
187        assert_eq!(Constant.deref(), "constant");
188        assert_eq!(Constant.to_string(), "constant".to_owned());
189    }
190
191    #[test]
192    #[expect(clippy::default_constructed_unit_structs)]
193    fn default() {
194        assert_eq!(Constant::default().deref(), "constant");
195        assert_eq!(Constant::default().to_string(), "constant".to_owned());
196    }
197
198    #[cfg(feature = "serde")]
199    #[test]
200    fn serde() {
201        assert_eq!(
202            "\"constant\"",
203            serde_json::to_string(&Constant).expect("serializable value")
204        );
205        assert_eq!(
206            Constant,
207            serde_json::from_str("\"constant\"").expect("deserializable value")
208        );
209    }
210
211    #[cfg(feature = "utoipa")]
212    #[test]
213    fn utoipa() {
214        use utoipa::{
215            PartialSchema,
216            openapi::{
217                RefOr, Type,
218                schema::{Object, Schema},
219            },
220        };
221
222        assert_eq!(
223            RefOr::T(Schema::Object(
224                Object::builder()
225                    .schema_type(Type::String)
226                    .enum_values(Some(["constant"]))
227                    .build()
228            )),
229            Constant::schema()
230        )
231    }
232}