diff --git a/Cargo.toml b/Cargo.toml index 69f6fd65..fb11da3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ stellar-strkey = { version = "0.0.8", optional = true } base64 = { version = "0.13.0", optional = true } serde = { version = "1.0.139", features = ["derive"], optional = true } serde_with = { version = "3.0.0", optional = true } -escape-bytes = { version = "0.1.0", default-features = false, optional = true } +escape-bytes = { version = "0.1.0", default-features = false } hex = { version = "0.4.3", optional = true } arbitrary = {version = "1.1.3", features = ["derive"], optional = true} clap = { version = "4.2.4", default-features = false, features = ["std", "derive", "usage", "help"], optional = true } @@ -36,7 +36,7 @@ serde_json = "1.0.89" [features] default = ["std", "curr"] std = ["alloc"] -alloc = ["dep:hex", "dep:stellar-strkey", "dep:escape-bytes"] +alloc = ["dep:hex", "dep:stellar-strkey", "escape-bytes/alloc"] curr = [] next = [] diff --git a/Makefile b/Makefile index 767c9f7d..21ebcb93 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ CARGO_HACK_ARGS=--feature-powerset --exclude-features default --group-features b CARGO_DOC_ARGS?=--open -XDRGEN_VERSION=cbff4b31 +XDRGEN_VERSION=e90b9ee62a89f346a86ef66f889bcfd8e1a8fbcb XDRGEN_TYPES_CUSTOM_STR_IMPL=PublicKey,AccountId,MuxedAccount,MuxedAccountMed25519,SignerKey,SignerKeyEd25519SignedPayload,NodeId,ScAddress,AssetCode,AssetCode4,AssetCode12 all: build test diff --git a/src/curr/generated.rs b/src/curr/generated.rs index 79e18d06..63f93055 100644 --- a/src/curr/generated.rs +++ b/src/curr/generated.rs @@ -1703,6 +1703,17 @@ impl WriteXdr for BytesM { // StringM ------------------------------------------------------------------------ +/// A string type that contains arbitrary bytes. +/// +/// Convertible, fallibly, to/from a Rust UTF-8 String using +/// [`TryFrom`]/[`TryInto`]/[`StringM::to_utf8_string`]. +/// +/// Convertible, lossyly, to a Rust UTF-8 String using +/// [`StringM::to_utf8_string_lossy`]. +/// +/// Convertible to/from escaped printable-ASCII using +/// [`Display`]/[`ToString`]/[`FromStr`]. + #[cfg(feature = "alloc")] #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr( @@ -1717,38 +1728,15 @@ pub struct StringM(Vec); #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct StringM(Vec); -/// `write_utf8_lossy` is a modified copy of the Rust stdlib docs examples here: -/// -fn write_utf8_lossy(f: &mut impl core::fmt::Write, mut input: &[u8]) -> core::fmt::Result { - loop { - match core::str::from_utf8(input) { - Ok(valid) => { - write!(f, "{valid}")?; - break; - } - Err(error) => { - let (valid, after_valid) = input.split_at(error.valid_up_to()); - write!(f, "{}", core::str::from_utf8(valid).unwrap())?; - write!(f, "\u{FFFD}")?; - - if let Some(invalid_sequence_length) = error.error_len() { - input = &after_valid[invalid_sequence_length..]; - } else { - break; - } - } - } - } - Ok(()) -} - impl core::fmt::Display for StringM { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { #[cfg(feature = "alloc")] let v = &self.0; #[cfg(not(feature = "alloc"))] let v = self.0; - write_utf8_lossy(f, v)?; + for b in escape_bytes::Escape::new(v) { + write!(f, "{}", b as char)?; + } Ok(()) } } @@ -1760,7 +1748,9 @@ impl core::fmt::Debug for StringM { #[cfg(not(feature = "alloc"))] let v = self.0; write!(f, "StringM(")?; - write_utf8_lossy(f, v)?; + for b in escape_bytes::Escape::new(v) { + write!(f, "{}", b as char)?; + } write!(f, ")")?; Ok(()) } @@ -1770,7 +1760,8 @@ impl core::fmt::Debug for StringM { impl core::str::FromStr for StringM { type Err = Error; fn from_str(s: &str) -> core::result::Result { - s.try_into() + let b = escape_bytes::unescape(s.as_bytes()).map_err(|_| Error::Invalid)?; + Ok(Self(b)) } } @@ -1818,24 +1809,24 @@ impl StringM { impl StringM { #[cfg(feature = "alloc")] - pub fn to_string(&self) -> Result { + pub fn to_utf8_string(&self) -> Result { self.try_into() } #[cfg(feature = "alloc")] - pub fn into_string(self) -> Result { + pub fn into_utf8_string(self) -> Result { self.try_into() } #[cfg(feature = "alloc")] #[must_use] - pub fn to_string_lossy(&self) -> String { + pub fn to_utf8_string_lossy(&self) -> String { String::from_utf8_lossy(&self.0).into_owned() } #[cfg(feature = "alloc")] #[must_use] - pub fn into_string_lossy(self) -> String { + pub fn into_utf8_string_lossy(self) -> String { String::from_utf8_lossy(&self.0).into_owned() } } @@ -52060,7 +52051,7 @@ impl Type { } #[cfg(feature = "base64")] - pub fn from_xdr_base64(v: TypeVariant, b64: String, limits: Limits) -> Result { + pub fn from_xdr_base64(v: TypeVariant, b64: impl AsRef<[u8]>, limits: Limits) -> Result { let mut b64_reader = Cursor::new(b64); let mut dec = Limited::new( base64::read::DecoderReader::new(&mut b64_reader, base64::STANDARD), diff --git a/src/curr/scval_conversions.rs b/src/curr/scval_conversions.rs index aae4d98f..3eb830ee 100644 --- a/src/curr/scval_conversions.rs +++ b/src/curr/scval_conversions.rs @@ -371,7 +371,7 @@ impl TryFrom for String { if let ScVal::Symbol(s) = v { // TODO: It might be worth distinguishing the error case where this // is an invalid symbol with invalid characters. - Ok(s.0.into_string().map_err(|_| ())?) + Ok(s.0.into_utf8_string().map_err(|_| ())?) } else { Err(()) } diff --git a/src/next/generated.rs b/src/next/generated.rs index 0beb4e5a..5fb9706c 100644 --- a/src/next/generated.rs +++ b/src/next/generated.rs @@ -1703,6 +1703,17 @@ impl WriteXdr for BytesM { // StringM ------------------------------------------------------------------------ +/// A string type that contains arbitrary bytes. +/// +/// Convertible, fallibly, to/from a Rust UTF-8 String using +/// [`TryFrom`]/[`TryInto`]/[`StringM::to_utf8_string`]. +/// +/// Convertible, lossyly, to a Rust UTF-8 String using +/// [`StringM::to_utf8_string_lossy`]. +/// +/// Convertible to/from escaped printable-ASCII using +/// [`Display`]/[`ToString`]/[`FromStr`]. + #[cfg(feature = "alloc")] #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr( @@ -1717,38 +1728,15 @@ pub struct StringM(Vec); #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct StringM(Vec); -/// `write_utf8_lossy` is a modified copy of the Rust stdlib docs examples here: -/// -fn write_utf8_lossy(f: &mut impl core::fmt::Write, mut input: &[u8]) -> core::fmt::Result { - loop { - match core::str::from_utf8(input) { - Ok(valid) => { - write!(f, "{valid}")?; - break; - } - Err(error) => { - let (valid, after_valid) = input.split_at(error.valid_up_to()); - write!(f, "{}", core::str::from_utf8(valid).unwrap())?; - write!(f, "\u{FFFD}")?; - - if let Some(invalid_sequence_length) = error.error_len() { - input = &after_valid[invalid_sequence_length..]; - } else { - break; - } - } - } - } - Ok(()) -} - impl core::fmt::Display for StringM { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { #[cfg(feature = "alloc")] let v = &self.0; #[cfg(not(feature = "alloc"))] let v = self.0; - write_utf8_lossy(f, v)?; + for b in escape_bytes::Escape::new(v) { + write!(f, "{}", b as char)?; + } Ok(()) } } @@ -1760,7 +1748,9 @@ impl core::fmt::Debug for StringM { #[cfg(not(feature = "alloc"))] let v = self.0; write!(f, "StringM(")?; - write_utf8_lossy(f, v)?; + for b in escape_bytes::Escape::new(v) { + write!(f, "{}", b as char)?; + } write!(f, ")")?; Ok(()) } @@ -1770,7 +1760,8 @@ impl core::fmt::Debug for StringM { impl core::str::FromStr for StringM { type Err = Error; fn from_str(s: &str) -> core::result::Result { - s.try_into() + let b = escape_bytes::unescape(s.as_bytes()).map_err(|_| Error::Invalid)?; + Ok(Self(b)) } } @@ -1818,24 +1809,24 @@ impl StringM { impl StringM { #[cfg(feature = "alloc")] - pub fn to_string(&self) -> Result { + pub fn to_utf8_string(&self) -> Result { self.try_into() } #[cfg(feature = "alloc")] - pub fn into_string(self) -> Result { + pub fn into_utf8_string(self) -> Result { self.try_into() } #[cfg(feature = "alloc")] #[must_use] - pub fn to_string_lossy(&self) -> String { + pub fn to_utf8_string_lossy(&self) -> String { String::from_utf8_lossy(&self.0).into_owned() } #[cfg(feature = "alloc")] #[must_use] - pub fn into_string_lossy(self) -> String { + pub fn into_utf8_string_lossy(self) -> String { String::from_utf8_lossy(&self.0).into_owned() } } @@ -52103,7 +52094,7 @@ impl Type { } #[cfg(feature = "base64")] - pub fn from_xdr_base64(v: TypeVariant, b64: String, limits: Limits) -> Result { + pub fn from_xdr_base64(v: TypeVariant, b64: impl AsRef<[u8]>, limits: Limits) -> Result { let mut b64_reader = Cursor::new(b64); let mut dec = Limited::new( base64::read::DecoderReader::new(&mut b64_reader, base64::STANDARD), diff --git a/src/next/scval_conversions.rs b/src/next/scval_conversions.rs index 7f2fbca8..01267cb1 100644 --- a/src/next/scval_conversions.rs +++ b/src/next/scval_conversions.rs @@ -371,7 +371,7 @@ impl TryFrom for String { if let ScVal::Symbol(s) = v { // TODO: It might be worth distinguishing the error case where this // is an invalid symbol with invalid characters. - Ok(s.0.into_string().map_err(|_| ())?) + Ok(s.0.into_utf8_string().map_err(|_| ())?) } else { Err(()) } diff --git a/tests/tx_debug_display.rs b/tests/tx_debug_display.rs index 8c44dc5c..9354747a 100644 --- a/tests/tx_debug_display.rs +++ b/tests/tx_debug_display.rs @@ -64,13 +64,13 @@ fn test_debug_invalid_utf8() -> Result<(), Error> { ), "BytesM(68656c6c6fc328776f726c64)" ); - // StringM replaces the invalid sequence with the Unicode replacement character. + // StringM escapes strings. assert_eq!( format!( "{:?}", <_ as TryInto>::try_into(b"hello\xc3\x28world")? ), - "StringM(hello�(world)" + r"StringM(hello\xc3(world)" ); Ok(()) } @@ -108,13 +108,13 @@ fn test_display_invalid_utf8() -> Result<(), Error> { ), "68656c6c6fc328776f726c64" ); - // StringM replaces the invalid sequence with the Unicode replacement character. + // StringM escapes strings. assert_eq!( format!( "{}", <_ as TryInto>::try_into(b"hello\xc3\x28world")? ), - "hello�(world" + r"hello\xc3(world" ); Ok(()) }