google_apis_common/
field_mask.rs

1use std::fmt::{Display, Formatter};
2use std::str::FromStr;
3
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6fn titlecase(source: &str, dest: &mut String) {
7    let mut underscore = false;
8    for c in source.chars() {
9        if c == '_' {
10            underscore = true;
11        } else if underscore {
12            dest.push(c.to_ascii_uppercase());
13            underscore = false;
14        } else {
15            dest.push(c);
16        }
17    }
18}
19
20fn snakecase(source: &str) -> String {
21    let mut dest = String::with_capacity(source.len() + 5);
22    for c in source.chars() {
23        if c.is_ascii_uppercase() {
24            dest.push('_');
25            dest.push(c.to_ascii_lowercase());
26        } else {
27            dest.push(c);
28        }
29    }
30    dest
31}
32
33/// A `FieldMask` as defined in `https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/field_mask.proto#L180`
34#[derive(Clone, Debug, Default, PartialEq, Eq)]
35pub struct FieldMask(Vec<String>);
36
37impl FieldMask {
38    /// Create a new `FieldMask` from a list of paths. These are converted to snake
39    /// case if they aren't already.
40    pub fn new<S: AsRef<str>>(values: &[S]) -> Self {
41        Self(values.iter().map(|s| snakecase(s.as_ref())).collect())
42    }
43}
44
45impl Serialize for FieldMask {
46    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
47    where
48        S: Serializer,
49    {
50        s.serialize_str(self.to_string().as_str())
51    }
52}
53
54impl<'de> Deserialize<'de> for FieldMask {
55    fn deserialize<D>(deserializer: D) -> Result<FieldMask, D::Error>
56    where
57        D: Deserializer<'de>,
58    {
59        let s: &str = Deserialize::deserialize(deserializer)?;
60        Ok(FieldMask::from_str(s).unwrap())
61    }
62}
63
64impl FromStr for FieldMask {
65    type Err = std::convert::Infallible;
66    fn from_str(s: &str) -> Result<Self, Self::Err> {
67        let mut in_quotes = false;
68        let mut prev_ind = 0;
69        let mut paths = Vec::new();
70        for (i, c) in s.char_indices() {
71            if c == '`' {
72                in_quotes = !in_quotes;
73            } else if in_quotes {
74                continue;
75            } else if c == ',' {
76                paths.push(snakecase(&s[prev_ind..i]));
77                prev_ind = i + 1;
78            }
79        }
80        paths.push(snakecase(&s[prev_ind..]));
81        Ok(FieldMask(paths))
82    }
83}
84
85impl Display for FieldMask {
86    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
87        let mut repr = String::new();
88        for path in &self.0 {
89            titlecase(path, &mut repr);
90            repr.push(',');
91        }
92        repr.pop();
93        f.write_str(&repr)
94    }
95}
96
97#[cfg(test)]
98mod test {
99    use crate::field_mask::FieldMask;
100    use serde::{Deserialize, Serialize};
101
102    #[derive(Serialize, Deserialize, Debug, PartialEq)]
103    struct FieldMaskWrapper {
104        fields: Option<FieldMask>,
105    }
106
107    #[test]
108    fn field_mask_roundtrip() {
109        let wrapper = FieldMaskWrapper {
110            fields: Some(FieldMask(vec![
111                "user.display_name".to_string(),
112                "photo".to_string(),
113            ])),
114        };
115        let json_repr = &serde_json::to_string(&wrapper);
116        assert!(json_repr.is_ok(), "serialization should succeed");
117        assert_eq!(
118            wrapper,
119            serde_json::from_str(r#"{"fields": "user.displayName,photo"}"#).unwrap()
120        );
121        assert_eq!(
122            wrapper,
123            serde_json::from_str(json_repr.as_ref().unwrap()).unwrap(),
124            "round trip should succeed"
125        );
126    }
127
128    #[test]
129    fn test_empty_wrapper() {
130        assert_eq!(
131            FieldMaskWrapper { fields: None },
132            serde_json::from_str("{}").unwrap()
133        );
134    }
135}