serde_view/lib.rs
1//! Serialize a view of a structure
2//!
3//! The idea of this crate is to serialize only a sub-set of fields of a struct, making the
4//! decision which fields during runtime.
5//!
6//! ## Basic example
7//!
8//! Assume you have a struct like:
9//!
10//! ```rust
11//! #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
12//! pub struct MyStruct {
13//! id: String,
14//! #[serde(default)]
15//! name: String,
16//! #[serde(default)]
17//! tags: Vec<String>,
18//! }
19//! ```
20//!
21//! Now, you want to make it possible to only serialize a sub-set of the data, making this
22//! decision during runtime. This can be done by adding the [`View`] derive to the struct, and
23//! wrapping the serializing with the [`View::view`] function, adding extra information to the
24//! view context:
25//!
26//! ```rust
27//! use serde_view::View;
28//! use serde_view::ViewFields;
29//!
30//! #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, View)]
31//! pub struct MyStruct {
32//! id: String,
33//! #[serde(default)]
34//! name: String,
35//! #[serde(default)]
36//! tags: Vec<String>,
37//! }
38//!
39//! fn serialize(my: &MyStruct) -> Result<serde_json::Value, serde_json::Error> {
40//! serde_json::to_value(my.as_view().with_fields([
41//! <MyStruct as View>::Fields::Id,
42//! <MyStruct as View>::Fields::Name,
43//! ]).unwrap())
44//! }
45//!
46//! fn serialize_str_fields(my: &MyStruct) -> Result<serde_json::Value, serde_json::Error> {
47//! // as fields can be converted to strings, it is also possible to pass something like a
48//! // comma separated list
49//! serde_json::to_value(my.as_view().with_fields(
50//! <MyStruct as View>::Fields::from_str_split("id,name").unwrap()
51//! ).unwrap())
52//! }
53//! ```
54
55mod ser;
56
57pub use ser::*;
58pub use serde_view_macros::View;
59
60use std::{collections::HashSet, fmt, hash::Hash};
61
62pub trait ViewFields: Clone + Copy + Hash + PartialEq + Eq {
63 fn as_str(&self) -> &'static str;
64 fn from_str(name: &str) -> Result<Self>;
65
66 fn from_str_iter<'a>(names: impl IntoIterator<Item = &'a str>) -> Result<HashSet<Self>> {
67 names.into_iter().map(Self::from_str).collect()
68 }
69
70 fn from_str_split(names: &str) -> Result<HashSet<Self>> {
71 Self::from_str_iter(names.split(','))
72 }
73}
74
75#[derive(Debug, serde::Serialize)]
76pub enum Error {
77 UnknownField(String),
78}
79impl fmt::Display for Error {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 write!(f, "{self:?}")
82 }
83}
84impl std::error::Error for Error {}
85pub type Result<T> = std::result::Result<T, Error>;
86
87pub trait IntoField<VF: ViewFields> {
88 fn into_field(self) -> Result<VF>;
89}
90
91/// Implement for all fields.
92impl<VF: ViewFields> IntoField<VF> for VF {
93 fn into_field(self) -> Result<VF> {
94 Ok(self)
95 }
96}
97
98/// Implement for `&str`, using [`ViewFields::from_str`].
99impl<VF: ViewFields> IntoField<VF> for &str {
100 fn into_field(self) -> Result<VF> {
101 VF::from_str(self)
102 }
103}
104
105pub struct ViewContext<'v, T>
106where
107 T: View,
108{
109 inner: &'v T,
110 fields: HashSet<T::Fields>,
111}
112
113impl<'v, T> ViewContext<'v, T>
114where
115 T: View,
116{
117 pub fn with_fields<I, IF>(mut self, fields: I) -> Result<Self>
118 where
119 I: IntoIterator<Item = IF>,
120 IF: IntoField<T::Fields>,
121 {
122 self.fields = fields
123 .into_iter()
124 .map(|f| f.into_field())
125 .collect::<Result<_>>()?;
126 Ok(self)
127 }
128
129 pub fn add_fields<I, IF>(mut self, fields: I) -> Result<Self>
130 where
131 I: IntoIterator<Item = IF>,
132 IF: IntoField<T::Fields>,
133 {
134 self.fields.extend(
135 fields
136 .into_iter()
137 .map(|f| f.into_field())
138 .collect::<Result<HashSet<T::Fields>>>()?,
139 );
140 Ok(self)
141 }
142
143 pub fn add_field(mut self, field: impl IntoField<T::Fields>) -> Result<Self> {
144 self.fields.insert(field.into_field()?);
145 Ok(self)
146 }
147}
148
149pub trait View: Sized + serde::Serialize {
150 type Fields: ViewFields;
151
152 fn as_view(&self) -> ViewContext<Self> {
153 ViewContext {
154 inner: self,
155 fields: Default::default(),
156 }
157 }
158}