Skip to content

Commit 13dd25d

Browse files
authored
Merge pull request #123 from Adhalianna/master
add commit hashes to git sources
2 parents 311f993 + f36838c commit 13dd25d

File tree

8 files changed

+501
-111
lines changed

8 files changed

+501
-111
lines changed

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
//! A new enum representation for serde built on generics. Using it requires some extra attributes and impls.
2+
//!
3+
//! This new representation of enum variants makes it much simpler to
4+
//! deserialize variant from a string and fill in the missing fields using e.g.
5+
//! [`Default::default`] or to deserialize from a struct which is tagged and
6+
//! allows overrifing the default values.
7+
//!
8+
//! It is built on the `VariantRepr` type. It is not recommended to use this
9+
//! type directly. Instead for a selected type that appears within a "newtype"
10+
//! variant of an enum (a variant which wraps a single type) certain traits
11+
//! should be implemented.
12+
//!
13+
//! The traits one should implement before using this module are
14+
//! - `IsEnumVariant<&str, ENUM>` for `VARIANT`,
15+
//! - `Into<VariantRepr<&'static str, ENUM, VARIANT>>` for `VARIANT`,
16+
//! - `TryFrom<VariantRepr<&'static str, ENUM, VARIANT>>` for `VARIANT`,
17+
//! where __`ENUM`__ is the __enum type__ containing the variant which
18+
//! serialization we would like to change and __`VARIANT`__ is the type
19+
//! __wrapped by the variant__.
20+
//!
21+
//! Once those are implemented and the module in which this struct resides is
22+
//! used in serde's attribute as follows:
23+
//! ```rust,ignore
24+
//! #[derive(Serialize, Deserialize, JsonSchema)]
25+
//! #[serde(untagged)]
26+
//! pub enum Source {
27+
//! /// `Source` is the __ENUM__ and `GitSource` is the __VARIANT__ type
28+
//! #[serde(with = "compact_enum_variant")]
29+
//! #[schemars(schema_with = "compact_enum_variant::schema::<Source, GitSource>")]
30+
//! Git(GitSource),
31+
//! }
32+
//! ```
33+
//!
34+
//! Changing a unit variant of an enum to wrap a type and use this module for
35+
//! the serialization can be made to be a backwards compatible change.
36+
37+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
38+
use std::{convert::TryFrom, fmt::Display, marker::PhantomData};
39+
40+
/// Marks a string or other type that can be converted to a string as a label
41+
/// for an variant of type `ENUM`.
42+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
43+
#[serde(transparent)]
44+
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
45+
#[cfg_attr(feature = "schema", schemars(bound = "S: schemars::JsonSchema"))]
46+
pub struct EnumVariant<S: Into<String>, ENUM>(S, PhantomData<fn() -> ENUM>);
47+
48+
/// Establishes a relation with the implementing type and an enum `ENUM`.
49+
pub trait IsEnumVariant<S: Into<String>, ENUM> {
50+
/// Returns a label identifying the type as belonging to one of possible
51+
/// types stored in the enum `ENUM`.
52+
fn variant() -> EnumVariant<S, ENUM>;
53+
}
54+
55+
impl<S: Into<String>, E> EnumVariant<S, E> {
56+
pub fn new(variant_tag: S) -> Self {
57+
Self(variant_tag, PhantomData)
58+
}
59+
}
60+
61+
impl<S: Into<String>, E> From<EnumVariant<S, E>> for String {
62+
fn from(value: EnumVariant<S, E>) -> Self {
63+
value.0.into()
64+
}
65+
}
66+
67+
impl<E> From<&'static str> for EnumVariant<&'static str, E> {
68+
fn from(value: &'static str) -> Self {
69+
EnumVariant::new(value)
70+
}
71+
}
72+
impl<E> From<&str> for EnumVariant<String, E> {
73+
fn from(value: &str) -> Self {
74+
EnumVariant::new(value.to_owned())
75+
}
76+
}
77+
78+
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
79+
#[serde(
80+
untagged,
81+
bound(
82+
serialize = "INNER: Serialize, S: 'static + Serialize",
83+
deserialize = "INNER: Deserialize<'de>, S: Deserialize<'de>"
84+
)
85+
)]
86+
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
87+
pub enum VariantRepr<S: Into<String>, ENUM, INNER> {
88+
// Short stringly-typed representation of the enum variant - a label -
89+
// which assumes all fields are set to their defualts.
90+
Kind(EnumVariant<S, ENUM>),
91+
// Longer representation that describes changes to default contents.
92+
Struct {
93+
kind: EnumVariant<S, ENUM>,
94+
#[serde(flatten)]
95+
strct: INNER,
96+
},
97+
}
98+
99+
pub fn serialize<S, ENUM, VARIANT>(inner: &VARIANT, serializer: S) -> Result<S::Ok, S::Error>
100+
where
101+
S: Serializer,
102+
VARIANT: ToOwned + IsEnumVariant<&'static str, ENUM> + Serialize,
103+
<VARIANT as ToOwned>::Owned: Into<VariantRepr<&'static str, ENUM, VARIANT>>,
104+
{
105+
let compact: VariantRepr<&'static str, ENUM, VARIANT> = inner.to_owned().into();
106+
107+
Serialize::serialize(&compact, serializer)
108+
}
109+
110+
pub fn deserialize<'de, 's, D, ENUM, VARIANT>(deserializer: D) -> Result<VARIANT, D::Error>
111+
where
112+
D: Deserializer<'de>,
113+
VARIANT: IsEnumVariant<&'s str, ENUM>
114+
+ TryFrom<VariantRepr<&'s str, ENUM, VARIANT>>
115+
+ Deserialize<'de>,
116+
<VARIANT as TryFrom<VariantRepr<&'s str, ENUM, VARIANT>>>::Error: Display,
117+
'de: 's,
118+
{
119+
let compact: VariantRepr<&'s str, ENUM, VARIANT> = Deserialize::deserialize(deserializer)?;
120+
let variant = VARIANT::try_from(compact).map_err(serde::de::Error::custom)?;
121+
122+
Ok(variant)
123+
}
124+
125+
/// Enriches the schema generated for `VariantRepr` with const values adequate
126+
/// to the selected variant of an enum.
127+
#[cfg(feature = "schema")]
128+
pub fn schema<
129+
'a,
130+
ENUM: schemars::JsonSchema,
131+
VARIANT: Into<VariantRepr<&'static str, ENUM, VARIANT>>
132+
+ IsEnumVariant<&'a str, ENUM>
133+
+ schemars::JsonSchema,
134+
>(
135+
gen: &mut schemars::gen::SchemaGenerator,
136+
) -> schemars::schema::Schema {
137+
use schemars::JsonSchema;
138+
139+
let mut schema =
140+
<VariantRepr<&'static str, ENUM, VARIANT> as JsonSchema>::json_schema(gen).into_object();
141+
142+
schema
143+
.subschemas
144+
.as_mut()
145+
.and_then(|subschemas| subschemas.any_of.as_mut())
146+
.map(|subschemas| {
147+
let new_subschemas = subschemas.iter_mut().map(|schema| {
148+
let mut schema = schema.clone().into_object();
149+
let typ = &schema
150+
.instance_type
151+
.as_ref()
152+
.and_then(|instance_type| match instance_type {
153+
schemars::schema::SingleOrVec::Single(typ) => Some(**typ),
154+
schemars::schema::SingleOrVec::Vec(_) => None,
155+
})
156+
.unwrap();
157+
match typ {
158+
schemars::schema::InstanceType::Object => {
159+
let object_schema = schema.object();
160+
let kind_property = object_schema.properties.get_mut("kind").unwrap();
161+
let mut kind_property_object = kind_property.clone().into_object();
162+
kind_property_object.const_value = Some(serde_json::Value::String(VARIANT::variant().into()));
163+
*kind_property = schemars::schema::Schema::Object(kind_property_object);
164+
165+
schemars::schema::Schema::Object(schema)
166+
},
167+
schemars::schema::InstanceType::String => {
168+
schema.const_value = Some(serde_json::Value::String(VARIANT::variant().into()));
169+
schema.string = None;
170+
171+
schemars::schema::Schema::Object(schema)
172+
},
173+
_ => panic!("the schema using compact enum variant representation should allow only string or object instances"),
174+
}
175+
}).collect();
176+
*subschemas = new_subschemas;
177+
subschemas
178+
});
179+
180+
schemars::schema::Schema::Object(schema)
181+
}

0 commit comments

Comments
 (0)