google_cloud_wkt/
field_mask.rs

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/// `FieldMask` represents a set of symbolic field paths, for example:
16///
17/// ```norust
18///     paths: "f.a"
19///     paths: "f.b.d"
20/// ```
21///
22/// Here `f` represents a field in some root message, `a` and `b`
23/// fields in the message found in `f`, and `d` a field found in the
24/// message in `f.b`.
25///
26/// Field masks are used to specify a subset of fields that should be
27/// returned by a get operation or modified by an update operation.
28/// Field masks also have a custom JSON encoding (see below).
29///
30/// # Field Masks in Projections
31///
32/// When used in the context of a projection, a response message or
33/// sub-message is filtered by the API to only contain those fields as
34/// specified in the mask. For example, if the mask in the previous
35/// example is applied to a response message as follows:
36///
37/// ```norust
38///     f {
39///       a : 22
40///       b {
41///         d : 1
42///         x : 2
43///       }
44///       y : 13
45///     }
46///     z: 8
47/// ```
48///
49/// The result will not contain specific values for fields x,y and z
50/// (their value will be set to the default, and omitted in proto text
51/// output):
52///
53///
54/// ```norust
55///     f {
56///       a : 22
57///       b {
58///         d : 1
59///       }
60///     }
61/// ```
62///
63/// A repeated field is not allowed except at the last position of a
64/// paths string.
65///
66/// If a FieldMask object is not present in a get operation, the
67/// operation applies to all fields (as if a FieldMask of all fields
68/// had been specified).
69///
70/// Note that a field mask does not necessarily apply to the
71/// top-level response message. In case of a REST get operation, the
72/// field mask applies directly to the response, but in case of a REST
73/// list operation, the mask instead applies to each individual message
74/// in the returned resource list. In case of a REST custom method,
75/// other definitions may be used. Where the mask applies will be
76/// clearly documented together with its declaration in the API.  In
77/// any case, the effect on the returned resource/resources is required
78/// behavior for APIs.
79///
80/// # Field Masks in Update Operations
81///
82/// A field mask in update operations specifies which fields of the
83/// targeted resource are going to be updated. The API is required
84/// to only change the values of the fields as specified in the mask
85/// and leave the others untouched. If a resource is passed in to
86/// describe the updated values, the API ignores the values of all
87/// fields not covered by the mask.
88///
89/// If a repeated field is specified for an update operation, new values will
90/// be appended to the existing repeated field in the target resource. Note that
91/// a repeated field is only allowed in the last position of a `paths` string.
92///
93/// If a sub-message is specified in the last position of the field mask for an
94/// update operation, then new value will be merged into the existing sub-message
95/// in the target resource.
96///
97/// For example, given the target message:
98///
99/// ```norust
100///     f {
101///       b {
102///         d: 1
103///         x: 2
104///       }
105///       c: [1]
106///     }
107/// ```
108///
109/// And an update message:
110///
111/// ```norust
112///     f {
113///       b {
114///         d: 10
115///       }
116///       c: [2]
117///     }
118/// ```
119///
120/// then if the field mask is:
121///
122/// ```norust
123///  paths: ["f.b", "f.c"]
124/// ```
125///
126/// then the result will be:
127///
128/// ```norust
129///     f {
130///       b {
131///         d: 10
132///         x: 2
133///       }
134///       c: [1, 2]
135///     }
136/// ```
137///
138/// An implementation may provide options to override this default behavior for
139/// repeated and message fields.
140///
141/// In order to reset a field's value to the default, the field must
142/// be in the mask and set to the default value in the provided resource.
143/// Hence, in order to reset all fields of a resource, provide a default
144/// instance of the resource and set all fields in the mask, or do
145/// not provide a mask as described below.
146///
147/// If a field mask is not present on update, the operation applies to
148/// all fields (as if a field mask of all fields has been specified).
149/// Note that in the presence of schema evolution, this may mean that
150/// fields the client does not know and has therefore not filled into
151/// the request will be reset to their default. If this is unwanted
152/// behavior, a specific service may require a client to always specify
153/// a field mask, producing an error if not.
154///
155/// As with get operations, the location of the resource which
156/// describes the updated values in the request message depends on the
157/// operation kind. In any case, the effect of the field mask is
158/// required to be honored by the API.
159///
160/// ## Considerations for HTTP REST
161///
162/// The HTTP kind of an update operation which uses a field mask must
163/// be set to PATCH instead of PUT in order to satisfy HTTP semantics
164/// (PUT must only be used for full updates).
165///
166/// # JSON Encoding of Field Masks
167///
168/// In JSON, a field mask is encoded as a single string where paths are
169/// separated by a comma. Fields name in each path are converted
170/// to/from lower-camel naming conventions.
171///
172/// As an example, consider the following message declarations:
173///
174/// ```norust
175///     message Profile {
176///       User user = 1;
177///       Photo photo = 2;
178///     }
179///     message User {
180///       string display_name = 1;
181///       string address = 2;
182///     }
183/// ```
184///
185/// In proto a field mask for `Profile` may look as such:
186///
187/// ```norust
188///     mask {
189///       paths: "user.display_name"
190///       paths: "photo"
191///     }
192/// ```
193///
194/// In JSON, the same mask is represented as below:
195///
196/// ```norust
197///     {
198///       mask: "user.displayName,photo"
199///     }
200/// ```
201///
202/// # Field Masks and Oneof Fields
203///
204/// Field masks treat fields in oneofs just as regular fields. Consider the
205/// following message:
206///
207/// ```norust
208///     message SampleMessage {
209///       oneof test_oneof {
210///         string name = 4;
211///         SubMessage sub_message = 9;
212///       }
213///     }
214/// ```
215///
216/// The field mask can be:
217///
218/// ```norust
219///     mask {
220///       paths: "name"
221///     }
222/// ```
223///
224/// Or:
225///
226/// ```norust
227///     mask {
228///       paths: "sub_message"
229///     }
230/// ```
231///
232/// Note that oneof type names ("test_oneof" in this case) cannot be used in
233/// paths.
234///
235/// ## Field Mask Verification
236///
237/// The implementation of any API method which has a FieldMask type field in the
238/// request should verify the included field paths, and return an
239/// `INVALID_ARGUMENT` error if any path is unmappable.
240#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize)]
241#[non_exhaustive]
242pub struct FieldMask {
243    /// The set of field mask paths.
244    #[serde(deserialize_with = "crate::field_mask::deserialize_paths")]
245    pub paths: Vec<String>,
246}
247
248impl FieldMask {
249    /// Set the paths.
250    pub fn set_paths<T, V>(mut self, paths: T) -> Self
251    where
252        T: IntoIterator<Item = V>,
253        V: Into<String>,
254    {
255        self.paths = paths.into_iter().map(|v| v.into()).collect();
256        self
257    }
258}
259
260impl crate::message::Message for FieldMask {
261    fn typename() -> &'static str {
262        "type.googleapis.com/google.protobuf.FieldMask"
263    }
264}
265
266/// Implement [`serde`](::serde) serialization for [FieldMask]
267impl serde::ser::Serialize for FieldMask {
268    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
269    where
270        S: serde::ser::Serializer,
271    {
272        use serde::ser::SerializeStruct;
273        let mut state = serializer.serialize_struct("FieldMask", 1)?;
274        state.serialize_field("paths", &self.paths.join(","))?;
275        state.end()
276    }
277}
278
279struct PathVisitor;
280
281fn deserialize_paths<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
282where
283    D: serde::de::Deserializer<'de>,
284{
285    deserializer.deserialize_str(PathVisitor)
286}
287
288impl serde::de::Visitor<'_> for PathVisitor {
289    type Value = Vec<String>;
290
291    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
292        formatter.write_str("a string with comma-separated field mask paths)")
293    }
294
295    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
296    where
297        E: serde::de::Error,
298    {
299        if value.is_empty() {
300            Ok(Vec::new())
301        } else {
302            Ok(value.split(',').map(str::to_string).collect())
303        }
304    }
305}
306
307#[cfg(test)]
308mod test {
309    use super::*;
310    use serde_json::json;
311    use test_case::test_case;
312
313    type Result = std::result::Result<(), Box<dyn std::error::Error>>;
314
315    #[test_case(vec![], ""; "Serialize empty")]
316    #[test_case(vec!["field1"], "field1"; "Serialize single")]
317    #[test_case(vec!["field1", "field2", "field3"], "field1,field2,field3"; "Serialize multiple")]
318    fn test_serialize(paths: Vec<&str>, want: &str) -> Result {
319        let value = serde_json::to_value(FieldMask::default().set_paths(paths))?;
320        let got = value
321            .get("paths")
322            .ok_or("missing paths")?
323            .as_str()
324            .ok_or("paths is not str")?;
325        assert_eq!(want, got);
326        Ok(())
327    }
328
329    #[test_case("", vec![]; "Deserialize empty")]
330    #[test_case("field1", vec!["field1"]; "Deserialize single")]
331    #[test_case("field1,field2,field3", vec!["field1" ,"field2", "field3"]; "Deserialize multiple")]
332    fn test_deserialize(paths: &str, mut want: Vec<&str>) -> Result {
333        let value = json!({ "paths": paths });
334        let mut got = serde_json::from_value::<FieldMask>(value)?;
335        want.sort();
336        got.paths.sort();
337        assert_eq!(got.paths, want);
338        Ok(())
339    }
340
341    #[test]
342    fn deserialize_unexpected_input_type() -> Result {
343        let got = serde_json::from_value::<FieldMask>(json!({"paths": {"a": "b"}}));
344        assert!(got.is_err());
345        let msg = format!("{got:?}");
346        assert!(msg.contains("field mask paths"), "message={}", msg);
347        Ok(())
348    }
349}