#[macro_export]
macro_rules! literal {
($(#[$attr:meta])* $name:ident($type:tt) = $value:literal) => {
$crate::literal!($(#[$attr])* $name($type => $type) = $value);
};
($(#[$attr:meta])* $name:ident($serde_from:tt => $type:tt) = $value:literal) => {
$crate::literal!(@ $(#[$attr])* $name($serde_from => $type) = $value);
impl $name {
pub const VALUE: $type = $value;
}
impl<T: PartialEq<$type>> PartialEq<T> for $name {
fn eq(&self, other: &T) -> bool {
other.eq(&Self::VALUE)
}
}
};
($(#[$attr:meta])* $name:ident(&$type:tt) = $value:literal) => {
$crate::literal!($(#[$attr])* $name($type => $type) = $value);
};
($(#[$attr:meta])* $name:ident($serde_from:tt => &$type:tt) = $value:literal) => {
$crate::literal!(@ $(#[$attr])* $name($serde_from => $type) = $value);
impl $name {
pub const VALUE: &$type = $value;
}
impl<T: for<'a> PartialEq<&'a $type>> PartialEq<T> for $name {
fn eq(&self, other: &T) -> bool {
other.eq(&Self::VALUE)
}
}
};
(@ $(#[$attr:meta])* $name:ident($serde_from:tt => $type:tt) = $value:literal) => {
#[derive(Clone, Copy)]
$(#[$attr])*
pub struct $name;
impl AsRef<$type> for $name {
fn as_ref(&self) -> &$type {
&Self::VALUE
}
}
impl ::std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple($name::NAME).field(&$name::VALUE).finish()
}
}
impl $name {
const NAME: &'static str = stringify!($name);
}
impl Default for $name {
fn default() -> Self {
Self
}
}
impl ::serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
serializer.serialize_newtype_struct($name::NAME, &$name::VALUE)
}
}
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
<$serde_from as ::serde::Deserialize<'de>>::deserialize(deserializer).and_then(
|value| {
if value == $name::VALUE {
Ok($name)
} else {
Err(::serde::de::Error::custom(concat!(
"Value must be exactly ",
stringify!($value)
)))
}
},
)
}
}
impl ::schemars::JsonSchema for $name {
fn schema_name() -> ::std::borrow::Cow<'static, str> {
$name::NAME.into()
}
fn json_schema(_: &mut ::schemars::SchemaGenerator) -> ::schemars::Schema {
::schemars::json_schema!({
"const": $name::VALUE
})
}
}
impl Eq for $name {}
};
}
#[macro_export]
macro_rules! literal_str {
($(#[$attr:meta])* $name:ident = $value:literal) => {
$crate::literal!($(#[$attr])* $name(String => &str) = $value);
};
}
macro_rules! literal_scalars {
($d:tt $($type:ident)*) => {
$(
::paste::paste! {
#[macro_export]
macro_rules! [<literal_ $type>] {
($d (#[$d attr:meta])* $d name:ident = $d value:literal) => {
$crate::literal!($d (#[$d attr])* $d name ($type) = $d value);
}
}
}
)*
}
}
literal_scalars!($ u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize f32 f64 bool);
#[cfg(test)]
mod tests {
#[test]
fn literal_str_should_work() {
literal_str!(
#[doc = "metametameta"]
MyStr = "my-value"
);
assert_eq!(
serde_json::from_str::<MyStr>("\"my-value\"").unwrap(),
"my-value"
);
assert_eq!(MyStr, MyStr);
}
#[test]
fn literal_u8_should_work() {
literal_u8!(Two = 2);
literal_u8!(
#[doc = "lololol"]
AlsoTwo = 2
);
assert_eq!(Two, 2);
assert_eq!(Two, Two);
assert_eq!(Two, AlsoTwo);
assert_eq!(AlsoTwo, Two);
assert_ne!(Two, 3);
}
use schemars::{json_schema, schema_for};
use serde_json::json;
crate::literal_str!(Xbox = "xbox");
#[test]
fn literal_string_should_deserialize_ok_from_correct_value() {
let ok = serde_json::from_value::<Xbox>(json!("xbox")).unwrap();
assert_eq!(ok, "xbox");
}
#[test]
fn literal_string_should_deserialize_err_from_correct_type_but_wrong_value() {
let err = serde_json::from_value::<Xbox>(json!("ps4")).unwrap_err();
assert_eq!(err.to_string(), "Value must be exactly \"xbox\"");
}
#[test]
fn literal_string_should_produce_correct_json_schema() {
let schema = schema_for!(Xbox);
assert_eq!(
schema,
json_schema!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Xbox",
"const": "xbox"
})
);
}
literal_u32!(OneTwoThree = 123);
#[test]
fn literal_u32_should_deserialize_ok_from_correct_value() {
let ok = serde_json::from_value::<OneTwoThree>(json!(123)).unwrap();
assert_eq!(ok, 123);
}
#[test]
fn literal_u32_should_deserialize_err_from_correct_type_but_wrong_value() {
let err = serde_json::from_value::<OneTwoThree>(json!(321)).unwrap_err();
assert_eq!(err.to_string(), "Value must be exactly 123");
}
#[test]
fn literal_u32_should_produce_correct_json_schema() {
let schema = schema_for!(OneTwoThree);
assert_eq!(
schema,
json_schema!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "OneTwoThree",
"const": 123
})
);
}
}