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) {
117 use crate::google::protobuf::*;
118 use buffa::type_registry::{any_encode_text, any_merge_text, TextAnyEntry};
119
120 macro_rules! register_type {
121 ($type:ty, $wkt:expr) => {
122 #[cfg(feature = "json")]
123 {
124 use alloc::string::ToString;
125 reg.register_json_any(buffa::type_registry::JsonAnyEntry {
126 type_url: <$type>::TYPE_URL,
127 to_json: |bytes| {
128 let msg = <$type as buffa::Message>::decode(&mut &*bytes)
129 .map_err(|e| e.to_string())?;
130 serde_json::to_value(&msg).map_err(|e| e.to_string())
131 },
132 from_json: |value| {
133 let msg: $type =
134 serde_json::from_value(value).map_err(|e| e.to_string())?;
135 buffa::Message::try_encode_to_vec(&msg).map_err(|e| e.to_string())
136 },
137 is_wkt: $wkt,
138 });
139 }
140 reg.register_text_any(TextAnyEntry {
145 type_url: <$type>::TYPE_URL,
146 text_encode: any_encode_text::<$type>,
147 text_merge: any_merge_text::<$type>,
148 });
149 };
150 }
151 macro_rules! register_text_only {
152 ($type:ty) => {
153 reg.register_text_any(TextAnyEntry {
154 type_url: <$type>::TYPE_URL,
155 text_encode: any_encode_text::<$type>,
156 text_merge: any_merge_text::<$type>,
157 });
158 };
159 }
160
161 register_type!(Duration, true);
163 register_type!(Timestamp, true);
164 register_type!(FieldMask, true);
165 register_type!(Value, true);
166 register_type!(Struct, true);
167 register_type!(ListValue, true);
168 register_type!(BoolValue, true);
169 register_type!(Int32Value, true);
170 register_type!(UInt32Value, true);
171 register_type!(Int64Value, true);
172 register_type!(UInt64Value, true);
173 register_type!(FloatValue, true);
174 register_type!(DoubleValue, true);
175 register_type!(StringValue, true);
176 register_type!(BytesValue, true);
177 register_type!(Any, true);
178
179 register_type!(Empty, false);
184
185 register_text_only!(Api);
186 register_text_only!(Method);
187 register_text_only!(Mixin);
188 register_text_only!(Type);
189 register_text_only!(Field);
190 register_text_only!(Enum);
191 register_text_only!(EnumValue);
192 register_text_only!(crate::google::protobuf::Option);
193 register_text_only!(SourceContext);
194}
195
196impl buffa::text::TextFormat for Any {
209 fn encode_text(&self, enc: &mut buffa::text::TextEncoder<'_>) -> core::fmt::Result {
210 if !self.type_url.is_empty() && enc.try_write_any_expanded(&self.type_url, &self.value)? {
211 return Ok(());
212 }
213 if !self.type_url.is_empty() {
215 enc.write_field_name("type_url")?;
216 enc.write_string(&self.type_url)?;
217 }
218 if !self.value.is_empty() {
219 enc.write_field_name("value")?;
220 enc.write_bytes(&self.value)?;
221 }
222 Ok(())
223 }
224
225 fn merge_text(
226 &mut self,
227 dec: &mut buffa::text::TextDecoder<'_>,
228 ) -> Result<(), buffa::text::ParseError> {
229 while let Some(name) = dec.read_field_name()? {
230 match name {
231 "type_url" => self.type_url = dec.read_string()?.into_owned(),
232 "value" => self.value = dec.read_bytes()?.into(),
233 _ if name.starts_with('[') => {
234 let (url, bytes) = dec.read_any_expansion(name)?;
235 self.type_url = url.into();
236 self.value = bytes.into();
237 }
238 _ => dec.skip_value()?,
239 }
240 }
241 Ok(())
242 }
243}
244
245#[cfg(test)]
246mod text_tests {
247 use super::Any;
248 use buffa::text::{decode_from_str, encode_to_string};
249
250 #[test]
251 fn vanilla_roundtrip_no_registry() {
252 let orig = Any {
256 type_url: "type.example.com/Foo".into(),
257 value: alloc::vec![0x08, 0x2A].into(), ..Default::default()
259 };
260 let text = encode_to_string(&orig);
261 assert_eq!(text, r#"type_url: "type.example.com/Foo" value: "\010*""#);
262 let back: Any = decode_from_str(&text).unwrap();
263 assert_eq!(back.type_url, orig.type_url);
264 assert_eq!(back.value, orig.value);
265 }
266
267 }
271
272#[cfg(feature = "json")]
280struct Base64Bytes<'a>(&'a [u8]);
281
282#[cfg(feature = "json")]
283impl serde::Serialize for Base64Bytes<'_> {
284 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
285 buffa::json_helpers::bytes::serialize(self.0, s)
286 }
287}
288
289#[cfg(feature = "json")]
290impl serde::Serialize for Any {
291 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
292 use serde::ser::SerializeMap;
293
294 if self.type_url.is_empty() {
295 return s.serialize_map(Some(0))?.end();
296 }
297
298 let lookup = buffa::any_registry::with_any_registry(|reg| {
299 reg.and_then(|r| r.lookup(&self.type_url))
300 .map(|e| (e.to_json, e.is_wkt))
301 });
302
303 match lookup {
304 Some((to_json, is_wkt)) => {
305 let Some(_depth_guard) = buffa::type_registry::enter_any_expansion() else {
310 return Err(serde::ser::Error::custom(alloc::format!(
311 "Any expansion nested deeper than {} levels",
312 buffa::type_registry::MAX_ANY_EXPANSION_DEPTH
313 )));
314 };
315 let json_val = to_json(&self.value).map_err(serde::ser::Error::custom)?;
316 if is_wkt {
317 let mut map = s.serialize_map(Some(2))?;
318 map.serialize_entry("@type", &self.type_url)?;
319 map.serialize_entry("value", &json_val)?;
320 map.end()
321 } else {
322 let fields = match &json_val {
323 serde_json::Value::Object(m) => m,
324 _ => {
325 return Err(serde::ser::Error::custom(
326 "Any: to_json for non-WKT must return a JSON object",
327 ))
328 }
329 };
330 let mut map = s.serialize_map(Some(1 + fields.len()))?;
331 map.serialize_entry("@type", &self.type_url)?;
332 for (k, v) in fields {
333 map.serialize_entry(k, v)?;
334 }
335 map.end()
336 }
337 }
338 None => {
339 let mut map = s.serialize_map(Some(2))?;
340 map.serialize_entry("@type", &self.type_url)?;
341 map.serialize_entry("value", &Base64Bytes(&self.value))?;
342 map.end()
343 }
344 }
345 }
346}
347
348#[cfg(feature = "json")]
349impl<'de> serde::Deserialize<'de> for Any {
350 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
351 let mut obj: serde_json::Map<String, serde_json::Value> =
353 serde::Deserialize::deserialize(d)?;
354
355 let type_url = match obj.remove("@type") {
356 Some(serde_json::Value::String(s)) => s,
357 Some(_) => {
358 return Err(serde::de::Error::custom("@type must be a string"));
359 }
360 None if obj.is_empty() => return Ok(Self::default()),
361 None => {
362 return Err(serde::de::Error::custom(
363 "Any object missing string \"@type\"",
364 ));
365 }
366 };
367
368 let type_name = type_url.rsplit('/').next().unwrap_or("");
372 if type_url.is_empty() || !type_url.contains('/') || type_name.is_empty() {
373 return Err(serde::de::Error::custom(
374 "@type must be a valid type URL containing a '/' and a non-empty type name (e.g. type.googleapis.com/pkg.Type)",
375 ));
376 }
377
378 let lookup = buffa::any_registry::with_any_registry(|reg| {
379 reg.and_then(|r| r.lookup(&type_url))
380 .map(|e| (e.from_json, e.is_wkt))
381 });
382
383 let value = match lookup {
384 Some((from_json, true)) => {
385 let json_val = obj.remove("value").unwrap_or(serde_json::Value::Null);
386 from_json(json_val).map_err(serde::de::Error::custom)?
387 }
388 Some((from_json, false)) => {
389 let json_obj = serde_json::Value::Object(obj);
390 from_json(json_obj).map_err(serde::de::Error::custom)?
391 }
392 None => {
393 match obj.remove("value") {
395 Some(serde_json::Value::String(s)) => buffa::json_helpers::bytes::deserialize(
396 serde::de::value::StringDeserializer::<D::Error>::new(s),
397 )?,
398 _ => alloc::vec::Vec::new(),
399 }
400 }
401 };
402
403 Ok(Self {
404 type_url,
405 value: value.into(),
406 ..Default::default()
407 })
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use crate::google::protobuf::Timestamp;
415 use buffa::Message as _;
416
417 #[test]
418 fn any_view_to_owned_from_source_is_zero_copy() {
419 use crate::google::protobuf::__buffa::view::AnyView;
420 use buffa::view::{MessageView as _, OwnedView};
421
422 let src = Any {
423 type_url: "type.googleapis.com/x".into(),
424 value: bytes::Bytes::from_static(&[1u8; 256]),
425 ..Default::default()
426 };
427 let buf = bytes::Bytes::from(src.encode_to_vec());
428
429 let view = AnyView::decode_view(&buf).unwrap();
431 let owned = view.to_owned_from_source(Some(&buf)).unwrap();
432 assert_eq!(owned.value, src.value);
433 let value_ptr = owned.value.as_ptr() as usize;
434 let buf_range = (buf.as_ptr() as usize)..(buf.as_ptr() as usize + buf.len());
435 assert!(
436 buf_range.contains(&value_ptr),
437 "owned.value should point into buf (slice_ref), got {value_ptr:#x} outside {buf_range:#x?}"
438 );
439
440 let ov = OwnedView::<AnyView<'static>>::decode(buf.clone()).unwrap();
444 let owned2 = ov.to_owned_message();
445 assert_eq!(owned2.value, src.value);
446 assert!(buf_range.contains(&(owned2.value.as_ptr() as usize)));
447
448 let copied = view.to_owned_message().unwrap();
450 assert_eq!(copied.value, src.value);
451 assert!(!buf_range.contains(&(copied.value.as_ptr() as usize)));
452 }
453
454 #[cfg(feature = "arbitrary")]
455 #[test]
456 fn any_arbitrary_with_bytes_value() {
457 use arbitrary::{Arbitrary, Unstructured};
458 let raw = [0u8; 64];
462 let mut u = Unstructured::new(&raw);
463 let any = Any::arbitrary(&mut u).unwrap();
464 let _ = any.value.slice(..);
465 }
466
467 #[derive(Clone, Default, PartialEq, Debug)]
473 struct HugeMsg;
474
475 impl buffa::DefaultInstance for HugeMsg {
476 fn default_instance() -> &'static Self {
477 static INST: buffa::__private::OnceBox<HugeMsg> = buffa::__private::OnceBox::new();
478 INST.get_or_init(|| alloc::boxed::Box::new(HugeMsg))
479 }
480 }
481
482 impl buffa::Message for HugeMsg {
483 fn compute_size(&self, _cache: &mut buffa::SizeCache) -> u32 {
484 buffa::MAX_MESSAGE_BYTES + 1
485 }
486 fn write_to(&self, _cache: &mut buffa::SizeCache, _buf: &mut impl buffa::EncodeSink) {}
487 fn merge_field(
488 &mut self,
489 tag: buffa::encoding::Tag,
490 buf: &mut impl bytes::Buf,
491 _ctx: buffa::DecodeContext<'_>,
492 ) -> Result<(), buffa::DecodeError> {
493 buffa::encoding::skip_field(tag, buf)?;
494 Ok(())
495 }
496 fn clear(&mut self) {}
497 }
498
499 #[test]
500 fn try_pack_over_limit_errs() {
501 assert_eq!(
502 Any::try_pack(&HugeMsg, "type.googleapis.com/x"),
503 Err(buffa::EncodeError::MessageTooLarge)
504 );
505 }
506
507 #[test]
508 #[should_panic(expected = "2 GiB protobuf limit")]
509 fn pack_over_limit_panics() {
510 let _ = Any::pack(&HugeMsg, "type.googleapis.com/x");
511 }
512
513 #[test]
514 fn try_pack_matches_pack_for_normal_messages() {
515 let ts = Timestamp {
516 seconds: 42,
517 ..Default::default()
518 };
519 let url = "type.googleapis.com/google.protobuf.Timestamp";
520 assert_eq!(Any::try_pack(&ts, url).unwrap(), Any::pack(&ts, url));
521 }
522
523 #[test]
524 fn pack_and_unpack() {
525 let ts = Timestamp {
526 seconds: 1_000_000_000,
527 nanos: 0,
528 ..Default::default()
529 };
530 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
531 assert_eq!(
532 any.type_url(),
533 "type.googleapis.com/google.protobuf.Timestamp"
534 );
535
536 let decoded: Timestamp = any.unpack_unchecked().unwrap();
537 assert_eq!(decoded, ts);
538 }
539
540 #[test]
541 fn unpack_if_matching() {
542 let ts = Timestamp {
543 seconds: 42,
544 ..Default::default()
545 };
546 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
547
548 let result: Option<Timestamp> = any
549 .unpack_if("type.googleapis.com/google.protobuf.Timestamp")
550 .unwrap();
551 assert_eq!(result, Some(ts));
552 }
553
554 #[test]
555 fn unpack_if_wrong_type_returns_none() {
556 let ts = Timestamp {
557 seconds: 42,
558 ..Default::default()
559 };
560 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
561
562 let result: Option<Timestamp> = any
563 .unpack_if("type.googleapis.com/google.protobuf.Duration")
564 .unwrap();
565 assert!(result.is_none());
566 }
567
568 #[test]
569 fn clone_shares_payload_buffer() {
570 let orig = Any {
571 type_url: "type.googleapis.com/example.Msg".into(),
572 value: alloc::vec![0xAB; 1024].into(),
573 ..Default::default()
574 };
575 let dup = orig.clone();
576 assert_eq!(orig.value.as_ptr(), dup.value.as_ptr());
577 assert_eq!(orig.value.len(), dup.value.len());
578 }
579
580 #[test]
581 fn is_type() {
582 let ts = Timestamp::default();
583 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
584 assert!(any.is_type("type.googleapis.com/google.protobuf.Timestamp"));
585 assert!(!any.is_type("type.googleapis.com/google.protobuf.Duration"));
586 }
587
588 #[test]
589 fn round_trip_encoding() {
590 let ts = Timestamp {
591 seconds: 99,
592 nanos: 1,
593 ..Default::default()
594 };
595 let any = Any::pack(&ts, "test");
596
597 let bytes = any.encode_to_vec();
598 let decoded_any = Any::decode(&mut bytes.as_slice()).unwrap();
599 let decoded_ts: Timestamp = decoded_any.unpack_unchecked().unwrap();
600 assert_eq!(decoded_ts, ts);
601 }
602
603 #[cfg(feature = "json")]
604 mod serde_tests {
605 use super::*;
606 use crate::google::protobuf::Duration;
607 use buffa::any_registry::clear_any_registry;
608 use buffa::type_registry::{
609 clear_text_registry, set_type_registry, TypeRegistry, MAX_ANY_EXPANSION_DEPTH,
610 };
611
612 static REGISTRY_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
616
617 fn with_registry<R>(f: impl FnOnce() -> R) -> R {
618 let _guard = REGISTRY_LOCK.lock().unwrap();
619 let mut reg = TypeRegistry::new();
620 register_wkt_types(&mut reg);
621 set_type_registry(reg);
622 let result = f();
623 clear_any_registry();
624 clear_text_registry();
625 result
626 }
627
628 fn without_registry<R>(f: impl FnOnce() -> R) -> R {
629 let _guard = REGISTRY_LOCK.lock().unwrap();
630 clear_any_registry();
631 clear_text_registry();
632 f()
633 }
634
635 #[test]
642 fn text_registry_roundtrip_wkt() {
643 use crate::google::protobuf::Empty;
644 use buffa::text::{decode_from_str, encode_to_string};
645 with_registry(|| {
646 let any = Any::pack(&Empty::default(), Empty::TYPE_URL);
648 let text = encode_to_string(&any);
649 assert_eq!(text, "[type.googleapis.com/google.protobuf.Empty] {}");
651
652 let back: Any = decode_from_str(&text).unwrap();
653 assert_eq!(back.type_url, Empty::TYPE_URL);
654 assert_eq!(back.value, alloc::vec::Vec::<u8>::new());
655 });
656 }
657
658 #[test]
659 fn text_registry_roundtrip_source_context() {
660 use crate::google::protobuf::SourceContext;
661 use buffa::text::{decode_from_str, encode_to_string};
662 with_registry(|| {
663 let sc = SourceContext {
664 file_name: "google/protobuf/api.proto".into(),
665 ..Default::default()
666 };
667 let any = Any::pack(&sc, SourceContext::TYPE_URL);
668 let text = encode_to_string(&any);
669 assert_eq!(
670 text,
671 r#"[type.googleapis.com/google.protobuf.SourceContext] {file_name: "google/protobuf/api.proto"}"#
672 );
673
674 let back: Any = decode_from_str(&text).unwrap();
675 assert_eq!(back.type_url, SourceContext::TYPE_URL);
676 let unpacked: SourceContext = back.unpack_unchecked().unwrap();
677 assert_eq!(unpacked.file_name, sc.file_name);
678 });
679 }
680
681 #[test]
682 fn text_registry_roundtrip_type_and_option() {
683 use crate::google::protobuf::{Field, SourceContext, Syntax, Type};
686 use buffa::text::{decode_from_str, encode_to_string};
687 with_registry(|| {
688 let ty = Type {
689 name: "google.example.v1.Msg".into(),
690 fields: alloc::vec![Field {
691 name: "id".into(),
692 number: 1,
693 json_name: "id".into(),
694 ..Default::default()
695 }],
696 syntax: Syntax::SYNTAX_PROTO3.into(),
697 ..Default::default()
698 };
699 let any = Any::pack(&ty, Type::TYPE_URL);
700 let text = encode_to_string(&any);
701 assert!(
702 text.starts_with("[type.googleapis.com/google.protobuf.Type] {"),
703 "{text}"
704 );
705 let back: Any = decode_from_str(&text).unwrap();
706 let unpacked: Type = back.unpack_unchecked().unwrap();
707 assert_eq!(unpacked, ty);
708
709 let sc = SourceContext {
710 file_name: "a/b.proto".into(),
711 ..Default::default()
712 };
713 let opt = crate::google::protobuf::Option {
714 name: "source".into(),
715 value: buffa::MessageField::some(Any::pack(&sc, SourceContext::TYPE_URL)),
716 ..Default::default()
717 };
718 let any = Any::pack(&opt, crate::google::protobuf::Option::TYPE_URL);
719 let text = encode_to_string(&any);
720 assert!(
721 text.contains("[type.googleapis.com/google.protobuf.SourceContext]"),
722 "nested Any must expand through the registry: {text}"
723 );
724 let back: Any = decode_from_str(&text).unwrap();
725 let unpacked: crate::google::protobuf::Option = back.unpack_unchecked().unwrap();
726 assert_eq!(unpacked, opt);
727 });
728 }
729
730 #[test]
731 fn text_unregistered_url_errors_on_decode() {
732 use buffa::text::decode_from_str;
733 with_registry(|| {
736 let result: Result<Any, _> =
737 decode_from_str("[type.googleapis.com/unknown.Type] { x: 1 }");
738 assert!(result.is_err(), "unknown URL should error, not skip");
739 });
740 }
741
742 #[test]
743 fn text_bracket_without_registry_errors() {
744 use buffa::text::decode_from_str;
745 without_registry(|| {
747 let result: Result<Any, _> = decode_from_str("[type.example.com/Unknown] { x: 1 }");
748 assert!(result.is_err());
749 });
750 }
751
752 #[test]
753 fn serialize_wkt_uses_value_wrapping() {
754 with_registry(|| {
755 let ts = Timestamp {
756 seconds: 1_000_000_000,
757 nanos: 0,
758 ..Default::default()
759 };
760 let any = Any::pack(&ts, Timestamp::TYPE_URL);
761 let json = serde_json::to_value(&any).unwrap();
762 assert_eq!(json["@type"], Timestamp::TYPE_URL);
763 assert_eq!(json["value"], "2001-09-09T01:46:40Z");
764 });
765 }
766
767 #[test]
768 fn serialize_duration_wkt() {
769 with_registry(|| {
770 let dur = Duration::from_secs_nanos(1, 500_000_000);
771 let any = Any::pack(&dur, Duration::TYPE_URL);
772 let json = serde_json::to_value(&any).unwrap();
773 assert_eq!(json["@type"], Duration::TYPE_URL);
774 assert_eq!(json["value"], "1.500s");
775 });
776 }
777
778 #[test]
779 fn serialize_empty_any_is_empty_object() {
780 with_registry(|| {
781 let any = Any::default();
782 let json = serde_json::to_string(&any).unwrap();
783 assert_eq!(json, "{}");
784 });
785 }
786
787 #[test]
788 fn deserialize_wkt_from_json() {
789 with_registry(|| {
790 let json = r#"{
791 "@type": "type.googleapis.com/google.protobuf.Duration",
792 "value": "1.5s"
793 }"#;
794 let any: Any = serde_json::from_str(json).unwrap();
795 assert_eq!(any.type_url, Duration::TYPE_URL);
796
797 let dur: Duration = any.unpack_unchecked().unwrap();
798 assert_eq!(dur.seconds, 1);
799 assert_eq!(dur.nanos, 500_000_000);
800 });
801 }
802
803 #[test]
804 fn deserialize_unordered_type_tag() {
805 with_registry(|| {
806 let json = r#"{
808 "value": "1.5s",
809 "@type": "type.googleapis.com/google.protobuf.Duration"
810 }"#;
811 let any: Any = serde_json::from_str(json).unwrap();
812 assert_eq!(any.type_url, Duration::TYPE_URL);
813
814 let dur: Duration = any.unpack_unchecked().unwrap();
815 assert_eq!(dur.seconds, 1);
816 assert_eq!(dur.nanos, 500_000_000);
817 });
818 }
819
820 #[test]
821 fn roundtrip_wkt_json() {
822 with_registry(|| {
823 let ts = Timestamp {
824 seconds: 1_000_000_000,
825 nanos: 0,
826 ..Default::default()
827 };
828 let any = Any::pack(&ts, Timestamp::TYPE_URL);
829 let json = serde_json::to_string(&any).unwrap();
830 let decoded: Any = serde_json::from_str(&json).unwrap();
831 let decoded_ts: Timestamp = decoded.unpack_unchecked().unwrap();
832 assert_eq!(decoded_ts, ts);
833 });
834 }
835
836 #[test]
837 fn nested_any_roundtrip() {
838 with_registry(|| {
839 let dur = Duration::from_secs(42);
840 let inner_any = Any::pack(&dur, Duration::TYPE_URL);
841 let outer_any = Any::pack(&inner_any, Any::TYPE_URL);
842
843 let json = serde_json::to_string(&outer_any).unwrap();
844 let decoded_outer: Any = serde_json::from_str(&json).unwrap();
845 let decoded_inner: Any = decoded_outer.unpack_unchecked().unwrap();
846 let decoded_dur: Duration = decoded_inner.unpack_unchecked().unwrap();
847 assert_eq!(decoded_dur.seconds, 42);
848 });
849 }
850
851 #[test]
852 fn fallback_base64_without_registry() {
853 without_registry(|| {
854 let any = Any {
855 type_url: "type.googleapis.com/unknown.Type".into(),
856 value: vec![0x08, 0x96, 0x01].into(),
857 ..Default::default()
858 };
859 let json = serde_json::to_string(&any).unwrap();
860 assert!(json.contains("@type"));
861 assert!(json.contains("value"));
862
863 let decoded: Any = serde_json::from_str(&json).unwrap();
864 assert_eq!(decoded.type_url, any.type_url);
865 assert_eq!(decoded.value, any.value);
866 });
867 }
868
869 #[test]
870 fn deserialize_missing_type_returns_default() {
871 let json = r#"{}"#;
872 let any: Any = serde_json::from_str(json).unwrap();
873 assert_eq!(any, Any::default());
874 }
875
876 #[test]
877 fn deserialize_rejects_nonempty_object_without_type() {
878 for json in [r#"{"value":""}"#, r#"{"unknown":1}"#] {
879 let err = serde_json::from_str::<Any>(json).unwrap_err();
880 assert!(
881 err.to_string()
882 .contains("Any object missing string \"@type\""),
883 "{json}: {err}"
884 );
885 }
886 }
887
888 #[test]
889 fn fallback_base64_with_registry_but_unknown_type() {
890 with_registry(|| {
891 let any = Any {
892 type_url: "type.googleapis.com/unknown.Type".into(),
893 value: vec![0x08, 0x96, 0x01].into(),
894 ..Default::default()
895 };
896 let json = serde_json::to_string(&any).unwrap();
897 let decoded: Any = serde_json::from_str(&json).unwrap();
898 assert_eq!(decoded.type_url, any.type_url);
899 assert_eq!(decoded.value, any.value);
900 });
901 }
902
903 #[test]
904 fn deserialize_rejects_empty_type_url() {
905 let json = r#"{"@type": "", "value": ""}"#;
906 let err = serde_json::from_str::<Any>(json).unwrap_err();
907 assert!(err.to_string().contains("valid type URL"), "{err}");
908 }
909
910 #[test]
911 fn deserialize_rejects_type_url_without_slash() {
912 let json = r#"{"@type": "not_a_url", "value": ""}"#;
913 let err = serde_json::from_str::<Any>(json).unwrap_err();
914 assert!(err.to_string().contains("valid type URL"), "{err}");
915 }
916
917 #[test]
918 fn deserialize_accepts_arbitrary_type_url_prefix() {
919 without_registry(|| {
920 let json = r#"{"@type": "example.com/custom.Type", "value": "CAI="}"#;
921 let any: Any = serde_json::from_str(json).unwrap();
922 assert_eq!(any.type_url, "example.com/custom.Type");
923 assert_eq!(any.value, vec![0x08, 0x02]);
924 });
925 }
926
927 #[test]
928 fn deserialize_rejects_type_url_with_empty_type_name() {
929 without_registry(|| {
930 let json = r#"{"@type": "type.googleapis.com/", "value": ""}"#;
931 let err = serde_json::from_str::<Any>(json).unwrap_err();
932 assert!(err.to_string().contains("valid type URL"), "{err}");
933 });
934 }
935
936 fn user_type_to_json(bytes: &[u8]) -> Result<serde_json::Value, String> {
944 use buffa::encoding::Tag;
945 let mut cur = bytes;
946 let mut id = 0i64;
947 while !cur.is_empty() {
948 let tag = Tag::decode(&mut cur).map_err(|e| e.to_string())?;
949 if tag.field_number() == 1 {
950 id =
951 buffa::encoding::decode_varint(&mut cur).map_err(|e| e.to_string())? as i64;
952 } else {
953 buffa::encoding::skip_field(tag, &mut cur).map_err(|e| e.to_string())?;
954 }
955 }
956 Ok(serde_json::json!({ "id": id }))
957 }
958
959 fn user_type_from_json(value: serde_json::Value) -> Result<alloc::vec::Vec<u8>, String> {
961 use buffa::encoding::{encode_varint, Tag, WireType};
962 let id = value
963 .get("id")
964 .and_then(|v| v.as_i64())
965 .ok_or_else(|| "missing or invalid 'id' field".to_string())?;
966 let mut buf = alloc::vec::Vec::new();
967 Tag::new(1, WireType::Varint).encode(&mut buf);
968 encode_varint(id as u64, &mut buf);
969 Ok(buf)
970 }
971
972 fn with_user_type_registry<R>(f: impl FnOnce() -> R) -> R {
973 use buffa::type_registry::JsonAnyEntry;
974 let _guard = REGISTRY_LOCK.lock().unwrap();
975 let mut reg = TypeRegistry::new();
976 reg.register_json_any(JsonAnyEntry {
978 type_url: "type.example.com/user.Thing",
979 to_json: user_type_to_json,
980 from_json: user_type_from_json,
981 is_wkt: false,
982 });
983 set_type_registry(reg);
984 let result = f();
985 clear_any_registry();
986 clear_text_registry();
987 result
988 }
989
990 #[test]
991 fn serialize_non_wkt_inlines_fields() {
992 with_user_type_registry(|| {
993 let any = Any {
995 type_url: "type.example.com/user.Thing".into(),
996 value: vec![0x08, 0x2A].into(),
998 ..Default::default()
999 };
1000
1001 let json = serde_json::to_value(&any).unwrap();
1002 assert_eq!(json["@type"], "type.example.com/user.Thing");
1004 assert_eq!(json["id"], 42);
1005 assert!(
1007 json.get("value").is_none(),
1008 "non-WKT should not use 'value' wrapping: {json}"
1009 );
1010 });
1011 }
1012
1013 #[test]
1014 fn deserialize_non_wkt_from_inlined_fields() {
1015 with_user_type_registry(|| {
1016 let json = r#"{
1017 "@type": "type.example.com/user.Thing",
1018 "id": 99
1019 }"#;
1020 let any: Any = serde_json::from_str(json).unwrap();
1021 assert_eq!(any.type_url, "type.example.com/user.Thing");
1022 assert_eq!(any.value, vec![0x08, 99]);
1024 });
1025 }
1026
1027 #[test]
1028 fn non_wkt_round_trip() {
1029 with_user_type_registry(|| {
1030 let original = Any {
1031 type_url: "type.example.com/user.Thing".into(),
1032 value: vec![0x08, 0x07].into(), ..Default::default()
1034 };
1035 let json = serde_json::to_string(&original).unwrap();
1036 let decoded: Any = serde_json::from_str(&json).unwrap();
1037 assert_eq!(decoded.type_url, original.type_url);
1038 assert_eq!(decoded.value, original.value);
1039 });
1040 }
1041
1042 #[test]
1043 fn serialize_non_wkt_rejects_non_object_json() {
1044 use buffa::type_registry::JsonAnyEntry;
1048 let _guard = REGISTRY_LOCK.lock().unwrap();
1049 let mut reg = TypeRegistry::new();
1050 reg.register_json_any(JsonAnyEntry {
1051 type_url: "type.example.com/user.BadType",
1052 to_json: |_bytes| Ok(serde_json::Value::Number(42.into())),
1053 from_json: |_v| Ok(alloc::vec::Vec::new()),
1054 is_wkt: false,
1055 });
1056 set_type_registry(reg);
1057
1058 let any = Any {
1059 type_url: "type.example.com/user.BadType".into(),
1060 value: vec![].into(),
1061 ..Default::default()
1062 };
1063 let result = serde_json::to_string(&any);
1064 clear_any_registry();
1065 clear_text_registry();
1066 assert!(result.is_err(), "expected error for non-object to_json");
1067 assert!(
1068 result
1069 .unwrap_err()
1070 .to_string()
1071 .contains("must return a JSON object"),
1072 "wrong error message"
1073 );
1074 }
1075
1076 #[test]
1085 fn corrupt_any_value_bytes_encode_to_text_without_panicking() {
1086 with_registry(|| {
1087 let a = Any {
1088 type_url: Duration::TYPE_URL.to_string(),
1089 value: bytes::Bytes::from_static(&[0xFF]),
1092 ..Default::default()
1093 };
1094 assert_eq!(
1095 buffa::text::encode_to_string(&a),
1096 "[type.googleapis.com/google.protobuf.Duration] {}",
1097 "the expansion is still emitted, with an empty body"
1098 );
1099 });
1100 }
1101
1102 #[test]
1103 fn deserialize_rejects_non_string_type() {
1104 let json = r#"{"@type": 123}"#;
1106 let err = serde_json::from_str::<Any>(json).unwrap_err();
1107 assert!(err.to_string().contains("@type must be a string"), "{err}");
1108 }
1109
1110 fn any_chain(depth: usize) -> Any {
1112 let mut cur = Any::default();
1113 for _ in 0..depth {
1114 cur = Any {
1115 type_url: Any::TYPE_URL.to_string(),
1116 value: buffa::Message::encode_to_vec(&cur).into(),
1117 ..Default::default()
1118 };
1119 }
1120 cur
1121 }
1122
1123 #[test]
1124 fn a_shallow_any_chain_still_expands() {
1125 with_registry(|| {
1126 let json = serde_json::to_string(&any_chain(8)).expect("well within the cap");
1127 assert_eq!(json.matches("\"@type\"").count(), 8, "{json}");
1129 });
1130 }
1131
1132 #[test]
1133 fn a_deep_any_chain_is_refused_rather_than_recursed() {
1134 with_registry(|| {
1135 let deep = any_chain(usize::try_from(MAX_ANY_EXPANSION_DEPTH).unwrap() + 5);
1138 let wire = buffa::Message::encode_to_vec(&deep);
1139 let decoded = <Any as buffa::Message>::decode_from_slice(&wire)
1140 .expect("decoding the chain is one level deep and always succeeds");
1141
1142 let err = serde_json::to_string(&decoded)
1143 .expect_err("expansion past the cap must be an error, not a deeper stack");
1144 assert!(
1145 err.to_string()
1146 .contains(&alloc::format!("{MAX_ANY_EXPANSION_DEPTH} levels")),
1147 "the error should state the limit it hit, as a number the \
1148 reader can act on rather than a constant to go look up: {err}"
1149 );
1150 });
1151 }
1152
1153 #[test]
1154 fn the_expansion_depth_is_restored_after_a_refusal() {
1155 with_registry(|| {
1156 let deep = any_chain(usize::try_from(MAX_ANY_EXPANSION_DEPTH).unwrap() + 5);
1157 assert!(serde_json::to_string(&deep).is_err());
1158
1159 let json = serde_json::to_string(&any_chain(4))
1163 .expect("a rejected serialization must not poison the thread");
1164 assert_eq!(json.matches("\"@type\"").count(), 4, "{json}");
1165 });
1166 }
1167
1168 #[test]
1169 fn a_deep_any_chain_falls_back_to_the_vanilla_text_form() {
1170 with_registry(|| {
1171 let deep = any_chain(usize::try_from(MAX_ANY_EXPANSION_DEPTH).unwrap() + 5);
1172 let text = buffa::text::encode_to_string(&deep);
1176 assert!(
1177 text.contains("type_url:"),
1178 "the innermost levels must fall back to the vanilla form: {text}"
1179 );
1180 assert_eq!(
1185 text.matches("[type.googleapis.com/google.protobuf.Any]")
1186 .count(),
1187 usize::try_from(MAX_ANY_EXPANSION_DEPTH).unwrap(),
1188 "expansion should stop at exactly the cap"
1189 );
1190 });
1191 }
1192 }
1193}