mirror of
https://github.com/OMGeeky/google-apis-rs.git
synced 2025-12-26 17:02:24 +01:00
Fix cargo check w.r.t. FieldMask
The serde traits are now directly implemented for FieldMask - this helps address potential serde issues with wrapper types, and simplifies the serde process somewhat.
This commit is contained in:
121
google-apis-common/src/field_mask.rs
Normal file
121
google-apis-common/src/field_mask.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
fn titlecase(source: &str, dest: &mut String) {
|
||||
let mut underscore = false;
|
||||
for c in source.chars() {
|
||||
if c == '_' {
|
||||
underscore = true;
|
||||
} else if underscore {
|
||||
dest.push(c.to_ascii_uppercase());
|
||||
underscore = false;
|
||||
} else {
|
||||
dest.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn snakecase(source: &str) -> String {
|
||||
let mut dest = String::with_capacity(source.len() + 5);
|
||||
for c in source.chars() {
|
||||
if c.is_ascii_uppercase() {
|
||||
dest.push('_');
|
||||
dest.push(c.to_ascii_lowercase());
|
||||
} else {
|
||||
dest.push(c);
|
||||
}
|
||||
}
|
||||
dest
|
||||
}
|
||||
|
||||
/// A `FieldMask` as defined in `https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/field_mask.proto#L180`
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct FieldMask(Vec<String>);
|
||||
|
||||
impl Serialize for FieldMask {
|
||||
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
s.serialize_str(self.to_string().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for FieldMask {
|
||||
fn deserialize<D>(deserializer: D) -> Result<FieldMask, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: &str = Deserialize::deserialize(deserializer)?;
|
||||
Ok(FieldMask::from_str(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldMask {
|
||||
fn from_str(s: &str) -> FieldMask {
|
||||
let mut in_quotes = false;
|
||||
let mut prev_ind = 0;
|
||||
let mut paths = Vec::new();
|
||||
for (i, c) in s.chars().enumerate() {
|
||||
if c == '`' {
|
||||
in_quotes = !in_quotes;
|
||||
} else if in_quotes {
|
||||
continue;
|
||||
} else if c == ',' {
|
||||
paths.push(snakecase(&s[prev_ind..i]));
|
||||
prev_ind = i + 1;
|
||||
}
|
||||
}
|
||||
paths.push(snakecase(&s[prev_ind..]));
|
||||
FieldMask(paths)
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
let mut repr = String::new();
|
||||
for path in &self.0 {
|
||||
titlecase(path, &mut repr);
|
||||
repr.push(',');
|
||||
}
|
||||
repr.pop();
|
||||
repr
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::field_mask::FieldMask;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct FieldMaskWrapper {
|
||||
fields: Option<FieldMask>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_mask_roundtrip() {
|
||||
let wrapper = FieldMaskWrapper {
|
||||
fields: Some(FieldMask(vec![
|
||||
"user.display_name".to_string(),
|
||||
"photo".to_string(),
|
||||
])),
|
||||
};
|
||||
let json_repr = &serde_json::to_string(&wrapper);
|
||||
assert!(json_repr.is_ok(), "serialization should succeed");
|
||||
assert_eq!(
|
||||
wrapper,
|
||||
serde_json::from_str(r#"{"fields": "user.displayName,photo"}"#).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper,
|
||||
serde_json::from_str(json_repr.as_ref().unwrap()).unwrap(),
|
||||
"round trip should succeed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_wrapper() {
|
||||
assert_eq!(
|
||||
FieldMaskWrapper { fields: None },
|
||||
serde_json::from_str("{}").unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod serde;
|
||||
pub mod field_mask;
|
||||
|
||||
use std::error;
|
||||
use std::error::Error as StdError;
|
||||
@@ -25,8 +26,9 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::time::sleep;
|
||||
use tower_service;
|
||||
|
||||
pub use yup_oauth2 as oauth2;
|
||||
pub use chrono;
|
||||
pub use field_mask::FieldMask;
|
||||
pub use yup_oauth2 as oauth2;
|
||||
|
||||
const LINE_ENDING: &str = "\r\n";
|
||||
|
||||
@@ -846,37 +848,6 @@ mod yup_oauth2_impl {
|
||||
}
|
||||
}
|
||||
|
||||
fn titlecase(source: &str, dest: &mut String) {
|
||||
let mut underscore = false;
|
||||
for c in source.chars() {
|
||||
if c == '_' {
|
||||
underscore = true;
|
||||
} else if underscore {
|
||||
dest.push(c.to_ascii_uppercase());
|
||||
underscore = false;
|
||||
} else {
|
||||
dest.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A `FieldMask` as defined in `https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/field_mask.proto#L180`
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct FieldMask(Vec<String>);
|
||||
|
||||
impl FieldMask {
|
||||
pub fn to_string(&self) -> String {
|
||||
let mut repr = String::new();
|
||||
for path in &self.0 {
|
||||
titlecase(path, &mut repr);
|
||||
repr.push(',');
|
||||
}
|
||||
repr.pop();
|
||||
repr
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_api {
|
||||
use super::*;
|
||||
|
||||
@@ -157,61 +157,6 @@ pub mod urlsafe_base64 {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod field_mask {
|
||||
use crate::FieldMask;
|
||||
/// Implementation based on `https://chromium.googlesource.com/infra/luci/luci-go/+/23ea7a05c6a5/common/proto/fieldmasks.go#184`
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
fn snakecase(source: &str) -> String {
|
||||
let mut dest = String::with_capacity(source.len() + 5);
|
||||
for c in source.chars() {
|
||||
if c.is_ascii_uppercase() {
|
||||
dest.push('_');
|
||||
dest.push(c.to_ascii_lowercase());
|
||||
} else {
|
||||
dest.push(c);
|
||||
}
|
||||
}
|
||||
dest
|
||||
}
|
||||
|
||||
fn parse_field_mask(s: &str) -> FieldMask {
|
||||
let mut in_quotes = false;
|
||||
let mut prev_ind = 0;
|
||||
let mut paths = Vec::new();
|
||||
for (i, c) in s.chars().enumerate() {
|
||||
if c == '`' {
|
||||
in_quotes = !in_quotes;
|
||||
} else if in_quotes {
|
||||
continue;
|
||||
} else if c == ',' {
|
||||
paths.push(snakecase(&s[prev_ind..i]));
|
||||
prev_ind = i + 1;
|
||||
}
|
||||
}
|
||||
paths.push(snakecase(&s[prev_ind..]));
|
||||
FieldMask(paths)
|
||||
}
|
||||
|
||||
pub fn serialize<S>(x: &Option<FieldMask>, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match x {
|
||||
None => s.serialize_none(),
|
||||
Some(fieldmask) => s.serialize_some(fieldmask.to_string().as_str()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<FieldMask>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: Option<&str> = Deserialize::deserialize(deserializer)?;
|
||||
Ok(s.map(parse_field_mask))
|
||||
}
|
||||
}
|
||||
|
||||
pub mod str_like {
|
||||
/// Implementation based on `https://chromium.googlesource.com/infra/luci/luci-go/+/23ea7a05c6a5/common/proto/fieldmasks.go#184`
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
@@ -243,8 +188,7 @@ pub mod str_like {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{duration, field_mask, str_like, urlsafe_base64};
|
||||
use crate::FieldMask;
|
||||
use super::{duration, str_like, urlsafe_base64};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
@@ -259,12 +203,6 @@ mod test {
|
||||
bytes: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct FieldMaskWrapper {
|
||||
#[serde(default, with = "field_mask")]
|
||||
fields: Option<FieldMask>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct I64Wrapper {
|
||||
#[serde(default, with = "str_like")]
|
||||
@@ -355,27 +293,6 @@ mod test {
|
||||
assert_eq!(wrapper, serde_json::from_str::<Base64Wrapper>(&s).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_mask_roundtrip() {
|
||||
let wrapper = FieldMaskWrapper {
|
||||
fields: Some(FieldMask(vec![
|
||||
"user.display_name".to_string(),
|
||||
"photo".to_string(),
|
||||
])),
|
||||
};
|
||||
let json_repr = &serde_json::to_string(&wrapper);
|
||||
assert!(json_repr.is_ok(), "serialization should succeed");
|
||||
assert_eq!(
|
||||
wrapper,
|
||||
serde_json::from_str(r#"{"fields": "user.displayName,photo"}"#).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper,
|
||||
serde_json::from_str(json_repr.as_ref().unwrap()).unwrap(),
|
||||
"round trip should succeed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_roundtrip() {
|
||||
let wrapper = I64Wrapper {
|
||||
@@ -397,9 +314,17 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_empty_wrapper() {
|
||||
assert_eq!(DurationWrapper { duration: None }, serde_json::from_str("{}").unwrap());
|
||||
assert_eq!(Base64Wrapper { bytes: None }, serde_json::from_str("{}").unwrap());
|
||||
assert_eq!(FieldMaskWrapper { fields: None }, serde_json::from_str("{}").unwrap());
|
||||
assert_eq!(I64Wrapper { num: None }, serde_json::from_str("{}").unwrap());
|
||||
assert_eq!(
|
||||
DurationWrapper { duration: None },
|
||||
serde_json::from_str("{}").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Base64Wrapper { bytes: None },
|
||||
serde_json::from_str("{}").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
I64Wrapper { num: None },
|
||||
serde_json::from_str("{}").unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ ${struct} {
|
||||
#[serde(default, with = "client::serde::urlsafe_base64")]
|
||||
% elif p.get("format") == "google-duration":
|
||||
#[serde(default, with = "client::serde::duration")]
|
||||
% elif p.get("format") == "google-fieldmask":
|
||||
#[serde(default, with = "client::serde::field_mask")]
|
||||
% elif p.get("format") in {"uint64", "int64"}:
|
||||
#[serde(default, with = "client::serde::str_like")]
|
||||
% endif
|
||||
|
||||
Reference in New Issue
Block a user