This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch 58_maintenance
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/58_maintenance by this push:
new 561c176ce0 [58_maintenance] fix(arrow-schema): validate `UnionFields`
when deserializing (#11760)
561c176ce0 is described below
commit 561c176ce077b50b255eb3c5082fe99bdb7e6310
Author: Jeffrey Vo <[email protected]>
AuthorDate: Thu Oct 22 18:24:36 2026 +0900
[58_maintenance] fix(arrow-schema): validate `UnionFields` when
deserializing (#11760)
- Backport https://github.com/apache/arrow-rs/pull/11638
- For inclusion in https://github.com/apache/arrow-rs/issues/11665
Cherry-pick of
https://github.com/apache/arrow-rs/commit/a2b405fe22b6657fd4f69eb139e6836cafa3b55f
Co-authored-by: ump45nose <[email protected]>
Co-authored-by: ump45nose <[email protected]>
Co-authored-by: Andrew Lamb <[email protected]>
---
arrow-schema/Cargo.toml | 1 +
arrow-schema/src/fields.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/arrow-schema/Cargo.toml b/arrow-schema/Cargo.toml
index 52e1082ecb..4b8938e2cc 100644
--- a/arrow-schema/Cargo.toml
+++ b/arrow-schema/Cargo.toml
@@ -55,6 +55,7 @@ all-features = true
criterion = { workspace = true, default-features = false }
insta = { workspace = true, default-features = true }
postcard = { version = "1.0.10", default-features = false, features =
["use-std"] }
+serde_json = "1.0"
[[bench]]
name = "ffi"
diff --git a/arrow-schema/src/fields.rs b/arrow-schema/src/fields.rs
index 3b33d161b7..d414720e53 100644
--- a/arrow-schema/src/fields.rs
+++ b/arrow-schema/src/fields.rs
@@ -324,10 +324,23 @@ impl<'a> IntoIterator for &'a Fields {
/// - All type ids are unique
/// - All type ids are non-negative
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct UnionFields(Arc<[(i8, FieldRef)]>);
+// Manual implementation to enforce invariants per docstring above
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for UnionFields {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let entries = Vec::<(i8, FieldRef)>::deserialize(deserializer)?;
+ let (type_ids, fields): (Vec<i8>, Vec<FieldRef>) =
entries.into_iter().unzip();
+ Self::try_new(type_ids, fields).map_err(serde::de::Error::custom)
+ }
+}
+
impl std::fmt::Debug for UnionFields {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.as_ref().fmt(f)
@@ -1058,6 +1071,42 @@ mod tests {
assert_eq!(res.unwrap().len(), 3);
}
+ #[cfg(feature = "serde")]
+ #[test]
+ fn test_union_fields_serde_round_trip() {
+ let fields = UnionFields::try_new(
+ vec![1, 3],
+ vec![
+ Field::new("a", DataType::Int32, true),
+ Field::new("b", DataType::Utf8, true),
+ ],
+ )
+ .unwrap();
+ let serialized = postcard::to_stdvec(&fields).unwrap();
+ let deserialized: UnionFields =
postcard::from_bytes(&serialized).unwrap();
+ assert_eq!(fields, deserialized);
+ }
+
+ #[cfg(feature = "serde")]
+ #[test]
+ fn test_union_fields_deserialize_should_validate() {
+ let field = Arc::new(Field::new("a", DataType::Int32, true));
+
+ let duplicate: Vec<(i8, FieldRef)> = vec![(1, Arc::clone(&field)), (1,
Arc::clone(&field))];
+ let serialized = serde_json::to_string(&duplicate).unwrap();
+ let err =
serde_json::from_str::<UnionFields>(&serialized).unwrap_err();
+ assert!(err.to_string().contains("duplicate type id: 1"), "{err}");
+
+ let negative: Vec<(i8, FieldRef)> = vec![(0, Arc::clone(&field)), (-1,
field)];
+ let serialized = serde_json::to_string(&negative).unwrap();
+ let err =
serde_json::from_str::<UnionFields>(&serialized).unwrap_err();
+ assert!(
+ err.to_string()
+ .contains("type ids must be non-negative: -1"),
+ "{err}"
+ );
+ }
+
#[test]
#[should_panic(
expected = "invalid arguments passed: InvalidArgumentError(\"type ids
must be non-negative: -1\")"