-
Notifications
You must be signed in to change notification settings - Fork 533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support partially clearing messages with FieldMask? #105
Comments
Interesting, this is one corner of the protobuf feature set I've never encountered. Could you elaborate a bit on when this comes in handy? If it's a useful feature then I'd be positive on adding it to |
The use-case in my mind was something a bit analogous to GraphQL queries: it makes evolving an API easier (because the client has to request explicitly all the fields it needs, and the server knows which ones are no longer used), and it can diminish response payload sizes. I went with GraphQL incidentally with the current project, so I won't be able to make time to implement this, but I think as a derive it shouldn't be too complicated (the only non-trivial part is the nesting). The masked merge has a different use-case (and I don't think much can be shared in terms of implementation), I'm not a heavy user of protocol buffers at the moment but it sounds like a cool feature to have. |
I may be missing something but is there a way to set fields based on string paths? Something like the extended merge provided in java that applies field masks would be a good addition: |
@tomhoule nothing changed about this, right? I can't understand why no one has thought of it yet and how they do without it! |
There's probably a million better ways of doing this... But I'm posting this here because I solved it like this and it might help someone else with it. AFAIK it works, might be a good starting point to implement something more stable use serde::{de::DeserializeOwned, Serialize};
use serde_json::{json, Value};
fn get_nested_value(value: &Value, keys: &[&str]) -> Option<Value> {
let mut current = value;
for key in keys {
match current {
Value::Object(map) => current = map.get(*key)?,
_ => return None,
}
}
Some(current.clone())
}
fn set_nested_value(value: &mut Value, keys: &[&str], new_value: Value) {
let mut current = value;
for key in keys.iter().take(keys.len() - 1) {
current = current
.as_object_mut()
.unwrap() // Safe to unwrap when used in conjunction with get_nested_value
.entry(key.to_string())
.or_insert_with(|| json!({}));
}
current
.as_object_mut()
.unwrap()
.insert(keys.last().unwrap().to_string(), new_value);
}
pub fn apply_mask<T>(obj: T, paths: Vec<String>) -> Result<T, serde_json::Error>
where
T: DeserializeOwned + Serialize,
{
let json_value = serde_json::to_value(obj)?;
// Create a new JSON object with only the specified paths
let mut new_json = json!({});
for path in paths {
let keys: Vec<&str> = path.split('.').collect();
if let Some(value) = get_nested_value(&json_value, &keys) {
set_nested_value(&mut new_json, &keys, value);
}
}
serde_json::from_value::<T>(new_json)
}
#[cfg(test)]
mod tests {
use serde::Deserialize;
use super::*;
#[derive(Deserialize, Serialize, Default, Debug, PartialEq, Clone)]
#[serde(default)]
struct Bar {
pub abc: usize,
}
#[derive(Deserialize, Serialize, Default, Debug, Clone)]
#[serde(default)]
struct Foo {
pub foo: String,
pub bar: Bar,
}
#[test]
fn test_apply_mask() {
let foobar = Foo {
foo: "foo".into(),
bar: Bar { abc: 10 },
};
let paths = vec!["foo".to_string()];
let masked_foobar = apply_mask(foobar.clone(), paths).expect("Failed to serde");
assert_eq!(masked_foobar.foo, "foo");
assert_eq!(masked_foobar.bar, Bar::default());
let paths = vec!["bar".to_string()];
let masked_foobar = apply_mask(foobar, paths).expect("Failed to serde");
assert_eq!(masked_foobar.foo, String::default());
assert_eq!(masked_foobar.bar.abc, 10);
}
} |
The docs for FieldMask (from the well-known types) describe how it can be used for selectively clearing fields on a message. This could be implemented using a custom derive.
Since I would like to use this feature I may be interested in making a PR for implementing it. It could either be part of the generated impls for the messages, taking an
Iterator<Item=&str>
to avoid depending onprost-types
, or implemented there only as an optional trait that can be added in build scripts (EDIT: having it be outside prost is also an option).Is this a desirable feature, and does this sound reasonable?
The text was updated successfully, but these errors were encountered: