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