1use alloc::string::String;
4
5use crate::google::protobuf::Any;
6
7impl Any {
8 pub fn pack(msg: &impl buffa::Message, type_url: impl Into<String>) -> Self {
20 Self {
21 type_url: type_url.into(),
22 value: msg.encode_to_bytes(),
23 ..Default::default()
24 }
25 }
26
27 pub fn try_pack(
36 msg: &impl buffa::Message,
37 type_url: impl Into<String>,
38 ) -> Result<Self, buffa::EncodeError> {
39 Ok(Self {
40 type_url: type_url.into(),
41 value: msg.try_encode_to_bytes()?,
42 ..Default::default()
43 })
44 }
45
46 pub fn unpack_unchecked<T: buffa::Message>(&self) -> Result<T, buffa::DecodeError> {
57 T::decode(&mut self.value.as_ref())
58 }
59
60 pub fn unpack_if<T: buffa::Message>(
70 &self,
71 expected_type_url: &str,
72 ) -> Result<Option<T>, buffa::DecodeError> {
73 if self.type_url != expected_type_url {
74 return Ok(None);
75 }
76 T::decode(&mut self.value.as_ref()).map(Some)
77 }
78
79 pub fn is_type(&self, type_url: &str) -> bool {
81 self.type_url == type_url
82 }
83
84 pub fn type_url(&self) -> &str {
86 &self.type_url
87 }
88}
89
90pub fn register_wkt_types(reg: &mut buffa::type_registry::TypeRegistry) {
115 use crate::google::protobuf::*;
116 use buffa::type_registry::{any_encode_text, any_merge_text, TextAnyEntry};
117
118 macro_rules! register_type {
119 ($type:ty, $wkt:expr) => {
120 #[cfg(feature = "json")]
121 {
122 use alloc::string::ToString;
123 reg.register_json_any(buffa::type_registry::JsonAnyEntry {
124 type_url: <$type>::TYPE_URL,
125 to_json: |bytes| {
126 let msg = <$type as buffa::Message>::decode(&mut &*bytes)
127 .map_err(|e| e.to_string())?;
128 serde_json::to_value(&msg).map_err(|e| e.to_string())
129 },
130 from_json: |value| {
131 let msg: $type =
132 serde_json::from_value(value).map_err(|e| e.to_string())?;
133 buffa::Message::try_encode_to_vec(&msg).map_err(|e| e.to_string())
134 },
135 is_wkt: $wkt,
136 });
137 }
138 reg.register_text_any(TextAnyEntry {
143 type_url: <$type>::TYPE_URL,
144 text_encode: any_encode_text::<$type>,
145 text_merge: any_merge_text::<$type>,
146 });
147 };
148 }
149
150 register_type!(Duration, true);
152 register_type!(Timestamp, true);
153 register_type!(FieldMask, true);
154 register_type!(Value, true);
155 register_type!(Struct, true);
156 register_type!(ListValue, true);
157 register_type!(BoolValue, true);
158 register_type!(Int32Value, true);
159 register_type!(UInt32Value, true);
160 register_type!(Int64Value, true);
161 register_type!(UInt64Value, true);
162 register_type!(FloatValue, true);
163 register_type!(DoubleValue, true);
164 register_type!(StringValue, true);
165 register_type!(BytesValue, true);
166 register_type!(Any, true);
167
168 register_type!(Empty, false);
170}
171
172impl buffa::text::TextFormat for Any {
185 fn encode_text(&self, enc: &mut buffa::text::TextEncoder<'_>) -> core::fmt::Result {
186 if !self.type_url.is_empty() && enc.try_write_any_expanded(&self.type_url, &self.value)? {
187 return Ok(());
188 }
189 if !self.type_url.is_empty() {
191 enc.write_field_name("type_url")?;
192 enc.write_string(&self.type_url)?;
193 }
194 if !self.value.is_empty() {
195 enc.write_field_name("value")?;
196 enc.write_bytes(&self.value)?;
197 }
198 Ok(())
199 }
200
201 fn merge_text(
202 &mut self,
203 dec: &mut buffa::text::TextDecoder<'_>,
204 ) -> Result<(), buffa::text::ParseError> {
205 while let Some(name) = dec.read_field_name()? {
206 match name {
207 "type_url" => self.type_url = dec.read_string()?.into_owned(),
208 "value" => self.value = dec.read_bytes()?.into(),
209 _ if name.starts_with('[') => {
210 let (url, bytes) = dec.read_any_expansion(name)?;
211 self.type_url = url.into();
212 self.value = bytes.into();
213 }
214 _ => dec.skip_value()?,
215 }
216 }
217 Ok(())
218 }
219}
220
221#[cfg(test)]
222mod text_tests {
223 use super::Any;
224 use buffa::text::{decode_from_str, encode_to_string};
225
226 #[test]
227 fn vanilla_roundtrip_no_registry() {
228 let orig = Any {
232 type_url: "type.example.com/Foo".into(),
233 value: alloc::vec![0x08, 0x2A].into(), ..Default::default()
235 };
236 let text = encode_to_string(&orig);
237 assert_eq!(text, r#"type_url: "type.example.com/Foo" value: "\010*""#);
238 let back: Any = decode_from_str(&text).unwrap();
239 assert_eq!(back.type_url, orig.type_url);
240 assert_eq!(back.value, orig.value);
241 }
242
243 }
247
248#[cfg(feature = "json")]
256struct Base64Bytes<'a>(&'a [u8]);
257
258#[cfg(feature = "json")]
259impl serde::Serialize for Base64Bytes<'_> {
260 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
261 buffa::json_helpers::bytes::serialize(self.0, s)
262 }
263}
264
265#[cfg(feature = "json")]
266impl serde::Serialize for Any {
267 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
268 use serde::ser::SerializeMap;
269
270 if self.type_url.is_empty() {
271 return s.serialize_map(Some(0))?.end();
272 }
273
274 let lookup = buffa::any_registry::with_any_registry(|reg| {
275 reg.and_then(|r| r.lookup(&self.type_url))
276 .map(|e| (e.to_json, e.is_wkt))
277 });
278
279 match lookup {
280 Some((to_json, is_wkt)) => {
281 let json_val = to_json(&self.value).map_err(serde::ser::Error::custom)?;
282 if is_wkt {
283 let mut map = s.serialize_map(Some(2))?;
284 map.serialize_entry("@type", &self.type_url)?;
285 map.serialize_entry("value", &json_val)?;
286 map.end()
287 } else {
288 let fields = match &json_val {
289 serde_json::Value::Object(m) => m,
290 _ => {
291 return Err(serde::ser::Error::custom(
292 "Any: to_json for non-WKT must return a JSON object",
293 ))
294 }
295 };
296 let mut map = s.serialize_map(Some(1 + fields.len()))?;
297 map.serialize_entry("@type", &self.type_url)?;
298 for (k, v) in fields {
299 map.serialize_entry(k, v)?;
300 }
301 map.end()
302 }
303 }
304 None => {
305 let mut map = s.serialize_map(Some(2))?;
306 map.serialize_entry("@type", &self.type_url)?;
307 map.serialize_entry("value", &Base64Bytes(&self.value))?;
308 map.end()
309 }
310 }
311 }
312}
313
314#[cfg(feature = "json")]
315impl<'de> serde::Deserialize<'de> for Any {
316 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
317 let mut obj: serde_json::Map<String, serde_json::Value> =
319 serde::Deserialize::deserialize(d)?;
320
321 let type_url = match obj.remove("@type") {
322 Some(serde_json::Value::String(s)) => s,
323 Some(_) => {
324 return Err(serde::de::Error::custom("@type must be a string"));
325 }
326 None => return Ok(Self::default()),
327 };
328
329 if type_url.is_empty() || !type_url.contains('/') {
333 return Err(serde::de::Error::custom(
334 "@type must be a valid type URL containing a '/' (e.g. type.googleapis.com/pkg.Type)",
335 ));
336 }
337
338 let lookup = buffa::any_registry::with_any_registry(|reg| {
339 reg.and_then(|r| r.lookup(&type_url))
340 .map(|e| (e.from_json, e.is_wkt))
341 });
342
343 let value = match lookup {
344 Some((from_json, true)) => {
345 let json_val = obj.remove("value").unwrap_or(serde_json::Value::Null);
346 from_json(json_val).map_err(serde::de::Error::custom)?
347 }
348 Some((from_json, false)) => {
349 let json_obj = serde_json::Value::Object(obj);
350 from_json(json_obj).map_err(serde::de::Error::custom)?
351 }
352 None => {
353 match obj.remove("value") {
355 Some(serde_json::Value::String(s)) => buffa::json_helpers::bytes::deserialize(
356 serde::de::value::StringDeserializer::<D::Error>::new(s),
357 )?,
358 _ => alloc::vec::Vec::new(),
359 }
360 }
361 };
362
363 Ok(Self {
364 type_url,
365 value: value.into(),
366 ..Default::default()
367 })
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use crate::google::protobuf::Timestamp;
375 use buffa::Message as _;
376
377 #[test]
378 fn any_view_to_owned_from_source_is_zero_copy() {
379 use crate::google::protobuf::__buffa::view::AnyView;
380 use buffa::view::{MessageView as _, OwnedView};
381
382 let src = Any {
383 type_url: "type.googleapis.com/x".into(),
384 value: bytes::Bytes::from_static(&[1u8; 256]),
385 ..Default::default()
386 };
387 let buf = bytes::Bytes::from(src.encode_to_vec());
388
389 let view = AnyView::decode_view(&buf).unwrap();
391 let owned = view.to_owned_from_source(Some(&buf)).unwrap();
392 assert_eq!(owned.value, src.value);
393 let value_ptr = owned.value.as_ptr() as usize;
394 let buf_range = (buf.as_ptr() as usize)..(buf.as_ptr() as usize + buf.len());
395 assert!(
396 buf_range.contains(&value_ptr),
397 "owned.value should point into buf (slice_ref), got {value_ptr:#x} outside {buf_range:#x?}"
398 );
399
400 let ov = OwnedView::<AnyView<'static>>::decode(buf.clone()).unwrap();
404 let owned2 = ov.to_owned_message();
405 assert_eq!(owned2.value, src.value);
406 assert!(buf_range.contains(&(owned2.value.as_ptr() as usize)));
407
408 let copied = view.to_owned_message().unwrap();
410 assert_eq!(copied.value, src.value);
411 assert!(!buf_range.contains(&(copied.value.as_ptr() as usize)));
412 }
413
414 #[cfg(feature = "arbitrary")]
415 #[test]
416 fn any_arbitrary_with_bytes_value() {
417 use arbitrary::{Arbitrary, Unstructured};
418 let raw = [0u8; 64];
422 let mut u = Unstructured::new(&raw);
423 let any = Any::arbitrary(&mut u).unwrap();
424 let _ = any.value.slice(..);
425 }
426
427 #[derive(Clone, Default, PartialEq, Debug)]
433 struct HugeMsg;
434
435 impl buffa::DefaultInstance for HugeMsg {
436 fn default_instance() -> &'static Self {
437 static INST: buffa::__private::OnceBox<HugeMsg> = buffa::__private::OnceBox::new();
438 INST.get_or_init(|| alloc::boxed::Box::new(HugeMsg))
439 }
440 }
441
442 impl buffa::Message for HugeMsg {
443 fn compute_size(&self, _cache: &mut buffa::SizeCache) -> u32 {
444 buffa::MAX_MESSAGE_BYTES + 1
445 }
446 fn write_to(&self, _cache: &mut buffa::SizeCache, _buf: &mut impl buffa::EncodeSink) {}
447 fn merge_field(
448 &mut self,
449 tag: buffa::encoding::Tag,
450 buf: &mut impl bytes::Buf,
451 _ctx: buffa::DecodeContext<'_>,
452 ) -> Result<(), buffa::DecodeError> {
453 buffa::encoding::skip_field(tag, buf)?;
454 Ok(())
455 }
456 fn clear(&mut self) {}
457 }
458
459 #[test]
460 fn try_pack_over_limit_errs() {
461 assert_eq!(
462 Any::try_pack(&HugeMsg, "type.googleapis.com/x"),
463 Err(buffa::EncodeError::MessageTooLarge)
464 );
465 }
466
467 #[test]
468 #[should_panic(expected = "2 GiB protobuf limit")]
469 fn pack_over_limit_panics() {
470 let _ = Any::pack(&HugeMsg, "type.googleapis.com/x");
471 }
472
473 #[test]
474 fn try_pack_matches_pack_for_normal_messages() {
475 let ts = Timestamp {
476 seconds: 42,
477 ..Default::default()
478 };
479 let url = "type.googleapis.com/google.protobuf.Timestamp";
480 assert_eq!(Any::try_pack(&ts, url).unwrap(), Any::pack(&ts, url));
481 }
482
483 #[test]
484 fn pack_and_unpack() {
485 let ts = Timestamp {
486 seconds: 1_000_000_000,
487 nanos: 0,
488 ..Default::default()
489 };
490 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
491 assert_eq!(
492 any.type_url(),
493 "type.googleapis.com/google.protobuf.Timestamp"
494 );
495
496 let decoded: Timestamp = any.unpack_unchecked().unwrap();
497 assert_eq!(decoded, ts);
498 }
499
500 #[test]
501 fn unpack_if_matching() {
502 let ts = Timestamp {
503 seconds: 42,
504 ..Default::default()
505 };
506 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
507
508 let result: Option<Timestamp> = any
509 .unpack_if("type.googleapis.com/google.protobuf.Timestamp")
510 .unwrap();
511 assert_eq!(result, Some(ts));
512 }
513
514 #[test]
515 fn unpack_if_wrong_type_returns_none() {
516 let ts = Timestamp {
517 seconds: 42,
518 ..Default::default()
519 };
520 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
521
522 let result: Option<Timestamp> = any
523 .unpack_if("type.googleapis.com/google.protobuf.Duration")
524 .unwrap();
525 assert!(result.is_none());
526 }
527
528 #[test]
529 fn clone_shares_payload_buffer() {
530 let orig = Any {
531 type_url: "type.googleapis.com/example.Msg".into(),
532 value: alloc::vec![0xAB; 1024].into(),
533 ..Default::default()
534 };
535 let dup = orig.clone();
536 assert_eq!(orig.value.as_ptr(), dup.value.as_ptr());
537 assert_eq!(orig.value.len(), dup.value.len());
538 }
539
540 #[test]
541 fn is_type() {
542 let ts = Timestamp::default();
543 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
544 assert!(any.is_type("type.googleapis.com/google.protobuf.Timestamp"));
545 assert!(!any.is_type("type.googleapis.com/google.protobuf.Duration"));
546 }
547
548 #[test]
549 fn round_trip_encoding() {
550 let ts = Timestamp {
551 seconds: 99,
552 nanos: 1,
553 ..Default::default()
554 };
555 let any = Any::pack(&ts, "test");
556
557 let bytes = any.encode_to_vec();
558 let decoded_any = Any::decode(&mut bytes.as_slice()).unwrap();
559 let decoded_ts: Timestamp = decoded_any.unpack_unchecked().unwrap();
560 assert_eq!(decoded_ts, ts);
561 }
562
563 #[cfg(feature = "json")]
564 mod serde_tests {
565 use super::*;
566 use crate::google::protobuf::Duration;
567 use buffa::any_registry::clear_any_registry;
568 use buffa::type_registry::{clear_text_registry, set_type_registry, TypeRegistry};
569
570 static REGISTRY_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
574
575 fn with_registry<R>(f: impl FnOnce() -> R) -> R {
576 let _guard = REGISTRY_LOCK.lock().unwrap();
577 let mut reg = TypeRegistry::new();
578 register_wkt_types(&mut reg);
579 set_type_registry(reg);
580 let result = f();
581 clear_any_registry();
582 clear_text_registry();
583 result
584 }
585
586 fn without_registry<R>(f: impl FnOnce() -> R) -> R {
587 let _guard = REGISTRY_LOCK.lock().unwrap();
588 clear_any_registry();
589 clear_text_registry();
590 f()
591 }
592
593 #[test]
600 fn text_registry_roundtrip_wkt() {
601 use crate::google::protobuf::Empty;
602 use buffa::text::{decode_from_str, encode_to_string};
603 with_registry(|| {
604 let any = Any::pack(&Empty::default(), Empty::TYPE_URL);
606 let text = encode_to_string(&any);
607 assert_eq!(text, "[type.googleapis.com/google.protobuf.Empty] {}");
609
610 let back: Any = decode_from_str(&text).unwrap();
611 assert_eq!(back.type_url, Empty::TYPE_URL);
612 assert_eq!(back.value, alloc::vec::Vec::<u8>::new());
613 });
614 }
615
616 #[test]
617 fn text_unregistered_url_errors_on_decode() {
618 use buffa::text::decode_from_str;
619 with_registry(|| {
622 let result: Result<Any, _> =
623 decode_from_str("[type.googleapis.com/unknown.Type] { x: 1 }");
624 assert!(result.is_err(), "unknown URL should error, not skip");
625 });
626 }
627
628 #[test]
629 fn text_bracket_without_registry_errors() {
630 use buffa::text::decode_from_str;
631 without_registry(|| {
633 let result: Result<Any, _> = decode_from_str("[type.example.com/Unknown] { x: 1 }");
634 assert!(result.is_err());
635 });
636 }
637
638 #[test]
639 fn serialize_wkt_uses_value_wrapping() {
640 with_registry(|| {
641 let ts = Timestamp {
642 seconds: 1_000_000_000,
643 nanos: 0,
644 ..Default::default()
645 };
646 let any = Any::pack(&ts, Timestamp::TYPE_URL);
647 let json = serde_json::to_value(&any).unwrap();
648 assert_eq!(json["@type"], Timestamp::TYPE_URL);
649 assert_eq!(json["value"], "2001-09-09T01:46:40Z");
650 });
651 }
652
653 #[test]
654 fn serialize_duration_wkt() {
655 with_registry(|| {
656 let dur = Duration::from_secs_nanos(1, 500_000_000);
657 let any = Any::pack(&dur, Duration::TYPE_URL);
658 let json = serde_json::to_value(&any).unwrap();
659 assert_eq!(json["@type"], Duration::TYPE_URL);
660 assert_eq!(json["value"], "1.500s");
661 });
662 }
663
664 #[test]
665 fn serialize_empty_any_is_empty_object() {
666 with_registry(|| {
667 let any = Any::default();
668 let json = serde_json::to_string(&any).unwrap();
669 assert_eq!(json, "{}");
670 });
671 }
672
673 #[test]
674 fn deserialize_wkt_from_json() {
675 with_registry(|| {
676 let json = r#"{
677 "@type": "type.googleapis.com/google.protobuf.Duration",
678 "value": "1.5s"
679 }"#;
680 let any: Any = serde_json::from_str(json).unwrap();
681 assert_eq!(any.type_url, Duration::TYPE_URL);
682
683 let dur: Duration = any.unpack_unchecked().unwrap();
684 assert_eq!(dur.seconds, 1);
685 assert_eq!(dur.nanos, 500_000_000);
686 });
687 }
688
689 #[test]
690 fn deserialize_unordered_type_tag() {
691 with_registry(|| {
692 let json = r#"{
694 "value": "1.5s",
695 "@type": "type.googleapis.com/google.protobuf.Duration"
696 }"#;
697 let any: Any = serde_json::from_str(json).unwrap();
698 assert_eq!(any.type_url, Duration::TYPE_URL);
699
700 let dur: Duration = any.unpack_unchecked().unwrap();
701 assert_eq!(dur.seconds, 1);
702 assert_eq!(dur.nanos, 500_000_000);
703 });
704 }
705
706 #[test]
707 fn roundtrip_wkt_json() {
708 with_registry(|| {
709 let ts = Timestamp {
710 seconds: 1_000_000_000,
711 nanos: 0,
712 ..Default::default()
713 };
714 let any = Any::pack(&ts, Timestamp::TYPE_URL);
715 let json = serde_json::to_string(&any).unwrap();
716 let decoded: Any = serde_json::from_str(&json).unwrap();
717 let decoded_ts: Timestamp = decoded.unpack_unchecked().unwrap();
718 assert_eq!(decoded_ts, ts);
719 });
720 }
721
722 #[test]
723 fn nested_any_roundtrip() {
724 with_registry(|| {
725 let dur = Duration::from_secs(42);
726 let inner_any = Any::pack(&dur, Duration::TYPE_URL);
727 let outer_any = Any::pack(&inner_any, Any::TYPE_URL);
728
729 let json = serde_json::to_string(&outer_any).unwrap();
730 let decoded_outer: Any = serde_json::from_str(&json).unwrap();
731 let decoded_inner: Any = decoded_outer.unpack_unchecked().unwrap();
732 let decoded_dur: Duration = decoded_inner.unpack_unchecked().unwrap();
733 assert_eq!(decoded_dur.seconds, 42);
734 });
735 }
736
737 #[test]
738 fn fallback_base64_without_registry() {
739 without_registry(|| {
740 let any = Any {
741 type_url: "type.googleapis.com/unknown.Type".into(),
742 value: vec![0x08, 0x96, 0x01].into(),
743 ..Default::default()
744 };
745 let json = serde_json::to_string(&any).unwrap();
746 assert!(json.contains("@type"));
747 assert!(json.contains("value"));
748
749 let decoded: Any = serde_json::from_str(&json).unwrap();
750 assert_eq!(decoded.type_url, any.type_url);
751 assert_eq!(decoded.value, any.value);
752 });
753 }
754
755 #[test]
756 fn deserialize_missing_type_returns_default() {
757 let json = r#"{}"#;
758 let any: Any = serde_json::from_str(json).unwrap();
759 assert_eq!(any, Any::default());
760 }
761
762 #[test]
763 fn fallback_base64_with_registry_but_unknown_type() {
764 with_registry(|| {
765 let any = Any {
766 type_url: "type.googleapis.com/unknown.Type".into(),
767 value: vec![0x08, 0x96, 0x01].into(),
768 ..Default::default()
769 };
770 let json = serde_json::to_string(&any).unwrap();
771 let decoded: Any = serde_json::from_str(&json).unwrap();
772 assert_eq!(decoded.type_url, any.type_url);
773 assert_eq!(decoded.value, any.value);
774 });
775 }
776
777 #[test]
778 fn deserialize_rejects_empty_type_url() {
779 let json = r#"{"@type": "", "value": ""}"#;
780 let err = serde_json::from_str::<Any>(json).unwrap_err();
781 assert!(err.to_string().contains("valid type URL"), "{err}");
782 }
783
784 #[test]
785 fn deserialize_rejects_type_url_without_slash() {
786 let json = r#"{"@type": "not_a_url", "value": ""}"#;
787 let err = serde_json::from_str::<Any>(json).unwrap_err();
788 assert!(err.to_string().contains("valid type URL"), "{err}");
789 }
790
791 fn user_type_to_json(bytes: &[u8]) -> Result<serde_json::Value, String> {
799 use buffa::encoding::Tag;
800 let mut cur = bytes;
801 let mut id = 0i64;
802 while !cur.is_empty() {
803 let tag = Tag::decode(&mut cur).map_err(|e| e.to_string())?;
804 if tag.field_number() == 1 {
805 id =
806 buffa::encoding::decode_varint(&mut cur).map_err(|e| e.to_string())? as i64;
807 } else {
808 buffa::encoding::skip_field(tag, &mut cur).map_err(|e| e.to_string())?;
809 }
810 }
811 Ok(serde_json::json!({ "id": id }))
812 }
813
814 fn user_type_from_json(value: serde_json::Value) -> Result<alloc::vec::Vec<u8>, String> {
816 use buffa::encoding::{encode_varint, Tag, WireType};
817 let id = value
818 .get("id")
819 .and_then(|v| v.as_i64())
820 .ok_or_else(|| "missing or invalid 'id' field".to_string())?;
821 let mut buf = alloc::vec::Vec::new();
822 Tag::new(1, WireType::Varint).encode(&mut buf);
823 encode_varint(id as u64, &mut buf);
824 Ok(buf)
825 }
826
827 fn with_user_type_registry<R>(f: impl FnOnce() -> R) -> R {
828 use buffa::type_registry::JsonAnyEntry;
829 let _guard = REGISTRY_LOCK.lock().unwrap();
830 let mut reg = TypeRegistry::new();
831 reg.register_json_any(JsonAnyEntry {
833 type_url: "type.example.com/user.Thing",
834 to_json: user_type_to_json,
835 from_json: user_type_from_json,
836 is_wkt: false,
837 });
838 set_type_registry(reg);
839 let result = f();
840 clear_any_registry();
841 clear_text_registry();
842 result
843 }
844
845 #[test]
846 fn serialize_non_wkt_inlines_fields() {
847 with_user_type_registry(|| {
848 let any = Any {
850 type_url: "type.example.com/user.Thing".into(),
851 value: vec![0x08, 0x2A].into(),
853 ..Default::default()
854 };
855
856 let json = serde_json::to_value(&any).unwrap();
857 assert_eq!(json["@type"], "type.example.com/user.Thing");
859 assert_eq!(json["id"], 42);
860 assert!(
862 json.get("value").is_none(),
863 "non-WKT should not use 'value' wrapping: {json}"
864 );
865 });
866 }
867
868 #[test]
869 fn deserialize_non_wkt_from_inlined_fields() {
870 with_user_type_registry(|| {
871 let json = r#"{
872 "@type": "type.example.com/user.Thing",
873 "id": 99
874 }"#;
875 let any: Any = serde_json::from_str(json).unwrap();
876 assert_eq!(any.type_url, "type.example.com/user.Thing");
877 assert_eq!(any.value, vec![0x08, 99]);
879 });
880 }
881
882 #[test]
883 fn non_wkt_round_trip() {
884 with_user_type_registry(|| {
885 let original = Any {
886 type_url: "type.example.com/user.Thing".into(),
887 value: vec![0x08, 0x07].into(), ..Default::default()
889 };
890 let json = serde_json::to_string(&original).unwrap();
891 let decoded: Any = serde_json::from_str(&json).unwrap();
892 assert_eq!(decoded.type_url, original.type_url);
893 assert_eq!(decoded.value, original.value);
894 });
895 }
896
897 #[test]
898 fn serialize_non_wkt_rejects_non_object_json() {
899 use buffa::type_registry::JsonAnyEntry;
903 let _guard = REGISTRY_LOCK.lock().unwrap();
904 let mut reg = TypeRegistry::new();
905 reg.register_json_any(JsonAnyEntry {
906 type_url: "type.example.com/user.BadType",
907 to_json: |_bytes| Ok(serde_json::Value::Number(42.into())),
908 from_json: |_v| Ok(alloc::vec::Vec::new()),
909 is_wkt: false,
910 });
911 set_type_registry(reg);
912
913 let any = Any {
914 type_url: "type.example.com/user.BadType".into(),
915 value: vec![].into(),
916 ..Default::default()
917 };
918 let result = serde_json::to_string(&any);
919 clear_any_registry();
920 clear_text_registry();
921 assert!(result.is_err(), "expected error for non-object to_json");
922 assert!(
923 result
924 .unwrap_err()
925 .to_string()
926 .contains("must return a JSON object"),
927 "wrong error message"
928 );
929 }
930
931 #[test]
932 fn deserialize_rejects_non_string_type() {
933 let json = r#"{"@type": 123}"#;
935 let err = serde_json::from_str::<Any>(json).unwrap_err();
936 assert!(err.to_string().contains("@type must be a string"), "{err}");
937 }
938 }
939}