a3s_boot/
serialization.rs1use crate::{BootError, BootResponse, BoxFuture, ExecutionContext, Interceptor, Result};
2use serde_json::{Map, Value};
3use std::collections::BTreeSet;
4
5#[derive(Debug, Clone, PartialEq, Eq, Default)]
7pub struct SerializationOptions {
8 include_fields: Option<BTreeSet<String>>,
9 exclude_fields: BTreeSet<String>,
10 skip_null_fields: bool,
11}
12
13impl SerializationOptions {
14 pub fn new() -> Self {
15 Self::default()
16 }
17
18 pub fn include_field(mut self, field: impl Into<String>) -> Self {
19 self.include_fields
20 .get_or_insert_with(BTreeSet::new)
21 .insert(field.into());
22 self
23 }
24
25 pub fn include_fields<I, S>(mut self, fields: I) -> Self
26 where
27 I: IntoIterator<Item = S>,
28 S: Into<String>,
29 {
30 for field in fields {
31 self = self.include_field(field);
32 }
33 self
34 }
35
36 pub fn exclude_field(mut self, field: impl Into<String>) -> Self {
37 self.exclude_fields.insert(field.into());
38 self
39 }
40
41 pub fn exclude_fields<I, S>(mut self, fields: I) -> Self
42 where
43 I: IntoIterator<Item = S>,
44 S: Into<String>,
45 {
46 for field in fields {
47 self = self.exclude_field(field);
48 }
49 self
50 }
51
52 pub fn skip_null_fields(mut self) -> Self {
53 self.skip_null_fields = true;
54 self
55 }
56
57 pub fn included_fields(&self) -> Option<impl Iterator<Item = &str>> {
58 self.include_fields
59 .as_ref()
60 .map(|fields| fields.iter().map(String::as_str))
61 }
62
63 pub fn excluded_fields(&self) -> impl Iterator<Item = &str> {
64 self.exclude_fields.iter().map(String::as_str)
65 }
66
67 pub fn skips_null_fields(&self) -> bool {
68 self.skip_null_fields
69 }
70
71 pub fn is_empty(&self) -> bool {
72 self.include_fields.is_none() && self.exclude_fields.is_empty() && !self.skip_null_fields
73 }
74
75 fn merged_with(&self, route_options: &Self) -> Self {
76 let mut merged = self.clone();
77 if route_options.include_fields.is_some() {
78 merged.include_fields = route_options.include_fields.clone();
79 }
80 merged
81 .exclude_fields
82 .extend(route_options.exclude_fields.iter().cloned());
83 merged.skip_null_fields |= route_options.skip_null_fields;
84 merged
85 }
86}
87
88#[derive(Debug, Clone, Default)]
90pub struct SerializationInterceptor {
91 options: SerializationOptions,
92}
93
94impl SerializationInterceptor {
95 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn with_options(options: SerializationOptions) -> Self {
100 Self { options }
101 }
102
103 pub fn options(&self) -> &SerializationOptions {
104 &self.options
105 }
106}
107
108impl Interceptor for SerializationInterceptor {
109 fn after(
110 &self,
111 context: ExecutionContext,
112 response: BootResponse,
113 ) -> BoxFuture<'static, Result<BootResponse>> {
114 let options = self.options.clone();
115 Box::pin(async move { serialize_response(response, &options, &context.serialization) })
116 }
117}
118
119fn serialize_response(
120 mut response: BootResponse,
121 interceptor_options: &SerializationOptions,
122 route_options: &SerializationOptions,
123) -> Result<BootResponse> {
124 let options = interceptor_options.merged_with(route_options);
125 if options.is_empty()
126 || response.is_streaming()
127 || !response.has_body()
128 || !response.is_json_content_type()
129 {
130 return Ok(response);
131 }
132
133 let had_content_length = !response.header_values("content-length").is_empty();
134 let mut value = serde_json::from_slice::<Value>(&response.body)
135 .map_err(|error| BootError::Internal(format!("invalid JSON response body: {error}")))?;
136 transform_payload(&mut value, &options);
137 response.body = serde_json::to_vec(&value)
138 .map_err(|error| BootError::Internal(format!("failed to serialize response: {error}")))?;
139
140 if had_content_length {
141 response.headers.remove("content-length");
142 response
143 .appended_headers
144 .retain(|(name, _)| !name.eq_ignore_ascii_case("content-length"));
145 let content_length = response.body.len() as u64;
146 response = response.with_content_length(content_length);
147 }
148
149 Ok(response)
150}
151
152fn transform_payload(value: &mut Value, options: &SerializationOptions) {
153 match value {
154 Value::Object(object) => transform_object(object, options),
155 Value::Array(values) => {
156 for value in values {
157 if let Value::Object(object) = value {
158 transform_object(object, options);
159 }
160 }
161 }
162 _ => {}
163 }
164}
165
166fn transform_object(object: &mut Map<String, Value>, options: &SerializationOptions) {
167 if let Some(include_fields) = &options.include_fields {
168 object.retain(|field, _| include_fields.contains(field));
169 }
170
171 for field in &options.exclude_fields {
172 object.remove(field);
173 }
174
175 if options.skip_null_fields {
176 object.retain(|_, value| !value.is_null());
177 }
178}