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}