#![cfg(feature = "reflect")]
use std::sync::Arc;
use buffa_descriptor::reflect::{DynamicMessage, ReflectMessage, ReflectMessageMut, Value};
use buffa_descriptor::DescriptorPool;
const FDS_BYTES: &[u8] = include_bytes!("protos/reflect_test.fds");
fn pool() -> Arc<DescriptorPool> {
Arc::new(DescriptorPool::decode(FDS_BYTES).expect("pool builds from protoc FDS"))
}
#[test]
fn extension_set_get_has_through_reflect_message() {
let p = pool();
let idx = p.message_index("reflect.ext.Extendable").unwrap();
let ext = p.extension_by_name("reflect.ext.ext_int32").unwrap();
let mut msg = DynamicMessage::new(Arc::clone(&p), idx);
assert!(!msg.has(ext.field()));
msg.set(ext.field(), Value::I32(42));
assert!(msg.has(ext.field()));
assert!(matches!(
msg.get(ext.field()),
buffa_descriptor::reflect::ValueRef::I32(42)
));
let md = p.message_by_name("reflect.ext.Extendable").unwrap();
msg.set(md.field(1).unwrap(), Value::I32(1));
let mut seen: Vec<u32> = Vec::new();
msg.for_each_set(&mut |fd, _| seen.push(fd.number()));
seen.sort_unstable();
assert_eq!(seen, vec![1, 100]);
}
#[test]
fn extension_binary_round_trip() {
let p = pool();
let idx = p.message_index("reflect.ext.Extendable").unwrap();
let payload_idx = p.message_index("reflect.ext.Payload").unwrap();
let md = p.message_by_name("reflect.ext.Extendable").unwrap();
let mut msg = DynamicMessage::new(Arc::clone(&p), idx);
msg.set(md.field(1).unwrap(), Value::I32(7));
let ext_i32 = p.extension_by_name("reflect.ext.ext_int32").unwrap();
let ext_str = p.extension_by_name("reflect.ext.ext_string").unwrap();
let ext_rep = p.extension_by_name("reflect.ext.ext_repeated").unwrap();
let ext_msg = p.extension_by_name("reflect.ext.ext_message").unwrap();
let ext_nested = p.extension_by_name("reflect.ext.Scope.ext_nested").unwrap();
msg.set(ext_i32.field(), Value::I32(100));
msg.set(ext_str.field(), Value::String("hello".into()));
msg.set(
ext_rep.field(),
Value::List(vec![Value::I32(1), Value::I32(2)]),
);
let mut payload = DynamicMessage::new(Arc::clone(&p), payload_idx);
payload.set(
p.message_by_name("reflect.ext.Payload")
.unwrap()
.field(1)
.unwrap(),
Value::String("inner".into()),
);
msg.set(ext_msg.field(), Value::Message(payload));
msg.set(ext_nested.field(), Value::I64(9));
let bytes = msg.encode_to_vec();
let decoded = DynamicMessage::decode(Arc::clone(&p), idx, &bytes).unwrap();
assert_eq!(decoded.field_by_number(100), Some(&Value::I32(100)));
assert_eq!(
decoded.field_by_number(101),
Some(&Value::String("hello".into()))
);
assert_eq!(
decoded.field_by_number(102),
Some(&Value::List(vec![Value::I32(1), Value::I32(2)]))
);
assert!(matches!(
decoded.field_by_number(103),
Some(&Value::Message(_))
));
assert_eq!(decoded.field_by_number(110), Some(&Value::I64(9)));
assert_eq!(decoded.unknown_fields().len(), 0);
assert_eq!(decoded.encode_to_vec(), bytes);
}
#[test]
fn unregistered_extension_range_number_round_trips_as_unknown() {
let p = pool();
let idx = p.message_index("reflect.ext.Extendable").unwrap();
let mut bytes = Vec::new();
buffa::encoding::Tag::new(150, buffa::encoding::WireType::Varint).encode(&mut bytes);
buffa::encoding::encode_varint(5, &mut bytes);
let decoded = DynamicMessage::decode(Arc::clone(&p), idx, &bytes).unwrap();
assert_eq!(decoded.unknown_fields().len(), 1);
assert_eq!(decoded.encode_to_vec(), bytes);
}
#[cfg(feature = "json")]
mod json {
use super::*;
#[test]
fn extension_json_round_trip() {
let p = pool();
let idx = p.message_index("reflect.ext.Extendable").unwrap();
let ext = p.extension_by_name("reflect.ext.ext_int32").unwrap();
let nested = p.extension_by_name("reflect.ext.Scope.ext_nested").unwrap();
let mut msg = DynamicMessage::new(Arc::clone(&p), idx);
msg.set(ext.field(), Value::I32(42));
msg.set(nested.field(), Value::I64(9));
let json = msg.to_json().unwrap();
assert!(
json.contains(r#""[reflect.ext.ext_int32]":42"#),
"extension serialized with bracketed full name: {json}"
);
assert!(
json.contains(r#""[reflect.ext.Scope.ext_nested]":"9""#),
"int64 extension serialized as quoted string: {json}"
);
let parsed = DynamicMessage::from_json(Arc::clone(&p), idx, &json).unwrap();
assert_eq!(parsed.field_by_number(100), Some(&Value::I32(42)));
assert_eq!(parsed.field_by_number(110), Some(&Value::I64(9)));
assert_eq!(parsed, msg);
}
#[test]
fn extension_json_rejects_wrong_extendee_and_unknown() {
let p = pool();
let payload_idx = p.message_index("reflect.ext.Payload").unwrap();
assert!(DynamicMessage::from_json(
Arc::clone(&p),
payload_idx,
r#"{"[reflect.ext.ext_int32]": 1}"#
)
.is_err());
let idx = p.message_index("reflect.ext.Extendable").unwrap();
let input = r#"{"[reflect.ext.no_such_ext]": 1}"#;
assert!(DynamicMessage::from_json(Arc::clone(&p), idx, input).is_err());
let m = DynamicMessage::from_json_ignoring_unknown(Arc::clone(&p), idx, input).unwrap();
assert_eq!(m.encode_to_vec(), Vec::<u8>::new());
}
#[test]
fn extension_json_duplicate_key_rejected() {
let p = pool();
let idx = p.message_index("reflect.ext.Extendable").unwrap();
assert!(DynamicMessage::from_json(
Arc::clone(&p),
idx,
r#"{"[reflect.ext.ext_int32]": 1, "[reflect.ext.ext_int32]": 2}"#
)
.is_err());
}
}
#[test]
fn group_typed_extension_round_trips() {
let p = pool();
let idx = p.message_index("reflect.ext.Extendable").unwrap();
let ext = p
.extension_by_name("reflect.ext.extgroup")
.expect("group extension registered");
assert!(ext.field().is_delimited(), "groups encode delimited");
let group_idx = p.message_index("reflect.ext.ExtGroup").unwrap();
let mut group = DynamicMessage::new(Arc::clone(&p), group_idx);
group.set(p.message(group_idx).field(1).unwrap(), Value::I32(77));
let mut msg = DynamicMessage::new(Arc::clone(&p), idx);
msg.set(ext.field(), Value::Message(group));
let bytes = msg.encode_to_vec();
let decoded = DynamicMessage::decode(Arc::clone(&p), idx, &bytes).unwrap();
assert_eq!(decoded.unknown_fields().len(), 0, "group decoded typed");
let Some(Value::Message(g)) = decoded.field_by_number(120) else {
panic!("group extension not present after round-trip");
};
assert_eq!(g.field_by_number(1), Some(&Value::I32(77)));
assert_eq!(decoded.encode_to_vec(), bytes);
}
mod malformed {
use super::*;
use buffa::Message;
use buffa_descriptor::generated::descriptor::FileDescriptorSet;
fn base_set() -> FileDescriptorSet {
FileDescriptorSet::decode_from_slice(FDS_BYTES).unwrap()
}
#[test]
fn duplicate_extension_number_rejected() {
let mut set = base_set();
let ext_file = set
.file
.iter_mut()
.find(|f| f.package.as_deref() == Some("reflect.ext"))
.unwrap();
let mut dup = ext_file.extension[0].clone();
dup.name = Some("ext_int32_clone".into());
ext_file.extension.push(dup);
let err = DescriptorPool::new(set).unwrap_err();
assert!(
err.to_string().contains("more than one extension claims"),
"unexpected error: {err}"
);
}
#[test]
fn extension_outside_declared_range_rejected() {
let mut set = base_set();
let ext_file = set
.file
.iter_mut()
.find(|f| f.package.as_deref() == Some("reflect.ext"))
.unwrap();
ext_file.extension[0].number = Some(200);
let err = DescriptorPool::new(set).unwrap_err();
assert!(
err.to_string().contains("invalid field number"),
"unexpected error: {err}"
);
}
#[test]
fn extension_oneof_index_is_scrubbed() {
let mut set = base_set();
let ext_file = set
.file
.iter_mut()
.find(|f| f.package.as_deref() == Some("reflect.ext"))
.unwrap();
ext_file.extension[0].oneof_index = Some(0);
let p = DescriptorPool::new(set).unwrap();
let ext = p.extension_by_name("reflect.ext.ext_int32").unwrap();
assert_eq!(ext.field().oneof_index(), None);
}
}