From ee4ed07af102f7ae5f685b78327737b233a4023e Mon Sep 17 00:00:00 2001 From: Eugene Marcotte Date: Tue, 26 Sep 2023 17:56:57 -0400 Subject: [PATCH] Attempt to add 'standard' base64 bytes support https://github.com/Byron/google-apis-rs/issues/442 flags an issue where some APIs respond with non-valid base64 bytes values for the "URL safe" flavor of configuration. This adds support for a "standard" wrapper adjacent to the URL safe one with the intention of finding a way to flag which structures should use which configuration. --- google-apis-common/src/serde.rs | 77 ++++++++++++++++++++++++++++++--- src/generator/lib/rust_type.py | 2 +- src/generator/lib/util.py | 2 +- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/google-apis-common/src/serde.rs b/google-apis-common/src/serde.rs index 29277f3f1a1..7d4f11b6c45 100644 --- a/google-apis-common/src/serde.rs +++ b/google-apis-common/src/serde.rs @@ -136,6 +136,36 @@ pub mod duration { } } +pub mod standard_base64 { + use serde::{Deserialize, Deserializer, Serializer}; + use serde_with::{DeserializeAs, SerializeAs}; + + pub struct Wrapper; + + pub fn to_string(bytes: &Vec) -> String { + base64::encode_config(bytes, base64::STANDARD) + } + + impl SerializeAs> for Wrapper { + fn serialize_as(value: &Vec, s: S) -> Result + where + S: Serializer, + { + s.serialize_str(&to_string(value)) + } + } + + impl<'de> DeserializeAs<'de, Vec> for Wrapper { + fn deserialize_as(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s: &str = Deserialize::deserialize(deserializer)?; + base64::decode_config(s, base64::STANDARD).map_err(serde::de::Error::custom) + } + } +} + pub mod urlsafe_base64 { use serde::{Deserialize, Deserializer, Serializer}; use serde_with::{DeserializeAs, SerializeAs}; @@ -172,7 +202,7 @@ pub fn datetime_to_string(datetime: &chrono::DateTime) -> S #[cfg(test)] mod test { - use super::{duration, urlsafe_base64}; + use super::{duration, urlsafe_base64, standard_base64}; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; @@ -185,11 +215,18 @@ mod test { #[serde_as] #[derive(Serialize, Deserialize, Debug, PartialEq)] - struct Base64Wrapper { + struct Base64URLSafeWrapper { #[serde_as(as = "Option")] bytes: Option>, } + #[serde_as] + #[derive(Serialize, Deserialize, Debug, PartialEq)] + struct Base64StandardWrapper { + #[serde_as(as = "Option")] + bytes: Option>, + } + #[serde_as] #[derive(Serialize, Deserialize, Debug, PartialEq)] struct I64Wrapper { @@ -257,25 +294,46 @@ mod test { } } + #[test] + fn standard_base64_de_success_cases() { + let wrapper: Base64StandardWrapper = + serde_json::from_str(r#"{"bytes": "cVhabzk6U21uOkN+MylFWFRJMVFLdEh2MShmVHp9"}"#).unwrap(); + assert_eq!(Some(b"qXZo9:Smn:C~3)EXTI1QKtHv1(fTz}".as_slice()), wrapper.bytes.as_deref()); + } + #[test] fn urlsafe_base64_de_success_cases() { - let wrapper: Base64Wrapper = + let wrapper: Base64URLSafeWrapper = serde_json::from_str(r#"{"bytes": "aGVsbG8gd29ybGQ="}"#).unwrap(); assert_eq!(Some(b"hello world".as_slice()), wrapper.bytes.as_deref()); } #[test] fn urlsafe_base64_de_failure_cases() { - assert!(serde_json::from_str::(r#"{"bytes": "aGVsbG8gd29ybG+Q"}"#).is_err()); + assert!(serde_json::from_str::(r#"{"bytes": "aGVsbG8gd29ybG+Q"}"#).is_err()); + } + + #[test] + fn standard_base64_de_failure_cases() { + assert!(serde_json::from_str::(r#"{"bytes": "%"}"#).is_err()); } #[test] fn urlsafe_base64_roundtrip() { - let wrapper = Base64Wrapper { + let wrapper = Base64URLSafeWrapper { + bytes: Some(b"Hello world!".to_vec()), + }; + let s = serde_json::to_string(&wrapper).expect("serialization of bytes infallible"); + assert_eq!(wrapper, serde_json::from_str::(&s).unwrap()); + } + + #[test] + fn standard_base64_roundtrip() { + let wrapper = Base64StandardWrapper { bytes: Some(b"Hello world!".to_vec()), }; let s = serde_json::to_string(&wrapper).expect("serialization of bytes infallible"); - assert_eq!(wrapper, serde_json::from_str::(&s).unwrap()); + assert_eq!(wrapper, serde_json::from_str::(&s).unwrap()); } #[test] @@ -304,9 +362,14 @@ mod test { serde_json::from_str("{}").unwrap() ); assert_eq!( - Base64Wrapper { bytes: None }, + Base64URLSafeWrapper { bytes: None }, + serde_json::from_str("{}").unwrap() + ); + assert_eq!( + Base64StandardWrapper { bytes: None }, serde_json::from_str("{}").unwrap() ); + assert_eq!( I64Wrapper { num: None }, serde_json::from_str("{}").unwrap() diff --git a/src/generator/lib/rust_type.py b/src/generator/lib/rust_type.py index e86a747fcc0..039ac7405d5 100644 --- a/src/generator/lib/rust_type.py +++ b/src/generator/lib/rust_type.py @@ -35,7 +35,7 @@ def serde_replace_inner_ty(self, from_to): def serde_as(self) -> Tuple["RustType", bool]: copied = deepcopy(self) from_to = { - Vec(Base("u8")): Base("::client::serde::urlsafe_base64::Wrapper"), + Vec(Base("u8")): Base("::client::serde::standard_base64::Wrapper"), Base("client::chrono::Duration"): Base("::client::serde::duration::Wrapper"), Base("i64"): Base("::client::serde_with::DisplayFromStr"), Base("u64"): Base("::client::serde_with::DisplayFromStr"), diff --git a/src/generator/lib/util.py b/src/generator/lib/util.py index 1c93035c5b9..b6a98311044 100644 --- a/src/generator/lib/util.py +++ b/src/generator/lib/util.py @@ -1183,7 +1183,7 @@ def string_impl(p): """Returns a function which will convert instances of p to a string""" return { "google-duration": lambda x: f"::client::serde::duration::to_string(&{x})", - "byte": lambda x: f"::client::serde::urlsafe_base64::to_string(&{x})", + "byte": lambda x: f"::client::serde::standard_base64::to_string(&{x})", "google-datetime": lambda x: f"::client::serde::datetime_to_string(&{x})", "date-time": lambda x: f"::client::serde::datetime_to_string(&{x})", "google-fieldmask": lambda x: f"{x}.to_string()",