sui_rpc/field/
field_mask_util.rs1use super::FIELD_PATH_SEPARATOR;
2use super::FIELD_PATH_WILDCARD;
3use super::FIELD_SEPARATOR;
4use super::FieldMaskTree;
5use super::MessageField;
6use super::MessageFields;
7
8use prost_types::FieldMask;
9
10pub trait FieldMaskUtil: sealed::Sealed {
11 fn normalize(self) -> FieldMask;
12
13 fn from_str(s: &str) -> FieldMask;
14
15 fn from_paths<I: AsRef<str>, T: IntoIterator<Item = I>>(paths: T) -> FieldMask;
16
17 fn display(&self) -> impl std::fmt::Display + '_;
18
19 fn validate<M: MessageFields>(&self) -> Result<(), &str>;
20}
21
22impl FieldMaskUtil for FieldMask {
23 fn normalize(self) -> FieldMask {
24 FieldMaskTree::from(self).to_field_mask()
25 }
26
27 fn from_str(s: &str) -> FieldMask {
28 Self::from_paths(s.split(FIELD_PATH_SEPARATOR))
29 }
30
31 fn from_paths<I: AsRef<str>, T: IntoIterator<Item = I>>(paths: T) -> FieldMask {
32 FieldMask {
33 paths: paths
34 .into_iter()
35 .filter_map(|path| {
36 let path = path.as_ref();
37 if path.is_empty() {
38 None
39 } else {
40 Some(path.to_owned())
41 }
42 })
43 .collect(),
44 }
45 }
46
47 fn display(&self) -> impl std::fmt::Display + '_ {
48 FieldMaskDisplay(self)
49 }
50
51 fn validate<M: MessageFields>(&self) -> Result<(), &str> {
52 fn is_valid_path(mut fields: &[&MessageField], mut path: &str) -> bool {
56 loop {
57 let (field_name, remainder) = path
58 .split_once(FIELD_SEPARATOR)
59 .map(|(field, remainder)| (field, (!remainder.is_empty()).then_some(remainder)))
60 .unwrap_or((path, None));
61
62 if let Some(field) = fields.iter().find(|field| field.name == field_name) {
63 match (field.message_fields, remainder) {
64 (None, None) | (Some(_), None) => return true,
65 (None, Some(_)) => return false,
66 (Some(sub_message_fields), Some(remainder)) => {
67 fields = sub_message_fields;
68 path = remainder;
69 }
70 }
71 } else {
72 return false;
73 }
74 }
75 }
76
77 for path in &self.paths {
78 if path == FIELD_PATH_WILDCARD {
79 continue;
80 }
81 if !is_valid_path(M::FIELDS, path) {
82 return Err(path);
83 }
84 }
85
86 Ok(())
87 }
88}
89
90struct FieldMaskDisplay<'a>(&'a FieldMask);
91
92impl std::fmt::Display for FieldMaskDisplay<'_> {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 use std::fmt::Write;
95
96 let mut first = true;
97
98 for path in &self.0.paths {
99 if path.is_empty() {
101 continue;
102 }
103
104 if first {
107 first = false;
108 } else {
109 f.write_char(FIELD_PATH_SEPARATOR)?;
110 }
111 f.write_str(path)?;
112 }
113
114 Ok(())
115 }
116}
117
118mod sealed {
119 pub trait Sealed {}
120
121 impl Sealed for prost_types::FieldMask {}
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_to_string() {
130 assert!(
131 FieldMask::display(&FieldMask::default())
132 .to_string()
133 .is_empty()
134 );
135
136 let mask = FieldMask::from_paths(["foo"]);
137 assert_eq!(FieldMask::display(&mask).to_string(), "foo");
138 assert_eq!(mask.display().to_string(), "foo");
139 let mask = FieldMask::from_paths(["foo", "bar"]);
140 assert_eq!(FieldMask::display(&mask).to_string(), "foo,bar");
141
142 let mask = FieldMask::from_paths(["", "foo", "", "bar", ""]);
144 assert_eq!(FieldMask::display(&mask).to_string(), "foo,bar");
145 }
146
147 #[test]
148 fn test_from_str() {
149 let mask = FieldMask::from_str("");
150 assert!(mask.paths.is_empty());
151
152 let mask = FieldMask::from_str("foo");
153 assert_eq!(mask.paths.len(), 1);
154 assert_eq!(mask.paths[0], "foo");
155
156 let mask = FieldMask::from_str("foo,bar.baz");
157 assert_eq!(mask.paths.len(), 2);
158 assert_eq!(mask.paths[0], "foo");
159 assert_eq!(mask.paths[1], "bar.baz");
160
161 let mask = FieldMask::from_str(",foo,,bar,");
163 assert_eq!(mask.paths.len(), 2);
164 assert_eq!(mask.paths[0], "foo");
165 assert_eq!(mask.paths[1], "bar");
166 }
167
168 #[test]
169 fn test_validate() {
170 struct Foo;
171 impl MessageFields for Foo {
172 const FIELDS: &'static [&'static MessageField] = &[
173 &MessageField::new("bar").with_message_fields(Bar::FIELDS),
174 &MessageField::new("baz"),
175 ];
176 }
177 struct Bar;
178
179 impl MessageFields for Bar {
180 const FIELDS: &'static [&'static MessageField] = &[
181 &MessageField {
182 name: "a",
183 json_name: "a",
184 number: 1,
185 message_fields: None,
186 },
187 &MessageField {
188 name: "b",
189 json_name: "b",
190 number: 2,
191 message_fields: None,
192 },
193 ];
194 }
195
196 let mask = FieldMask::from_str("");
197 assert_eq!(mask.validate::<Foo>(), Ok(()));
198 let mask = FieldMask::from_str("bar");
199 assert_eq!(mask.validate::<Foo>(), Ok(()));
200 let mask = FieldMask::from_str("bar.a");
201 assert_eq!(mask.validate::<Foo>(), Ok(()));
202 let mask = FieldMask::from_str("bar.a,bar.b");
203 assert_eq!(mask.validate::<Foo>(), Ok(()));
204 let mask = FieldMask::from_str("bar.a,bar.b,bar.c");
205 assert_eq!(mask.validate::<Foo>(), Err("bar.c"));
206 let mask = FieldMask::from_str("baz");
207 assert_eq!(mask.validate::<Foo>(), Ok(()));
208 let mask = FieldMask::from_str("baz.a");
209 assert_eq!(mask.validate::<Foo>(), Err("baz.a"));
210 let mask = FieldMask::from_str("foobar");
211 assert_eq!(mask.validate::<Foo>(), Err("foobar"));
212 }
213}