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 {
14 Self {
15 type_url: type_url.into(),
16 value: msg.encode_to_bytes(),
17 ..Default::default()
18 }
19 }
20
21 pub fn unpack_unchecked<T: buffa::Message>(&self) -> Result<T, buffa::DecodeError> {
32 T::decode(&mut self.value.as_ref())
33 }
34
35 pub fn unpack_if<T: buffa::Message>(
45 &self,
46 expected_type_url: &str,
47 ) -> Result<Option<T>, buffa::DecodeError> {
48 if self.type_url != expected_type_url {
49 return Ok(None);
50 }
51 T::decode(&mut self.value.as_ref()).map(Some)
52 }
53
54 pub fn is_type(&self, type_url: &str) -> bool {
56 self.type_url == type_url
57 }
58
59 pub fn type_url(&self) -> &str {
61 &self.type_url
62 }
63}
64
65pub fn register_wkt_types(reg: &mut buffa::type_registry::TypeRegistry) {
90 use crate::google::protobuf::*;
91 use buffa::type_registry::{any_encode_text, any_merge_text, TextAnyEntry};
92
93 macro_rules! register_type {
94 ($type:ty, $wkt:expr) => {
95 #[cfg(feature = "json")]
96 {
97 use alloc::string::ToString;
98 reg.register_json_any(buffa::type_registry::JsonAnyEntry {
99 type_url: <$type>::TYPE_URL,
100 to_json: |bytes| {
101 let msg = <$type as buffa::Message>::decode(&mut &*bytes)
102 .map_err(|e| e.to_string())?;
103 serde_json::to_value(&msg).map_err(|e| e.to_string())
104 },
105 from_json: |value| {
106 let msg: $type =
107 serde_json::from_value(value).map_err(|e| e.to_string())?;
108 Ok(buffa::Message::encode_to_vec(&msg))
109 },
110 is_wkt: $wkt,
111 });
112 }
113 reg.register_text_any(TextAnyEntry {
118 type_url: <$type>::TYPE_URL,
119 text_encode: any_encode_text::<$type>,
120 text_merge: any_merge_text::<$type>,
121 });
122 };
123 }
124
125 register_type!(Duration, true);
127 register_type!(Timestamp, true);
128 register_type!(FieldMask, true);
129 register_type!(Value, true);
130 register_type!(Struct, true);
131 register_type!(ListValue, true);
132 register_type!(BoolValue, true);
133 register_type!(Int32Value, true);
134 register_type!(UInt32Value, true);
135 register_type!(Int64Value, true);
136 register_type!(UInt64Value, true);
137 register_type!(FloatValue, true);
138 register_type!(DoubleValue, true);
139 register_type!(StringValue, true);
140 register_type!(BytesValue, true);
141 register_type!(Any, true);
142
143 register_type!(Empty, false);
145}
146
147impl buffa::text::TextFormat for Any {
160 fn encode_text(&self, enc: &mut buffa::text::TextEncoder<'_>) -> core::fmt::Result {
161 if !self.type_url.is_empty() && enc.try_write_any_expanded(&self.type_url, &self.value)? {
162 return Ok(());
163 }
164 if !self.type_url.is_empty() {
166 enc.write_field_name("type_url")?;
167 enc.write_string(&self.type_url)?;
168 }
169 if !self.value.is_empty() {
170 enc.write_field_name("value")?;
171 enc.write_bytes(&self.value)?;
172 }
173 Ok(())
174 }
175
176 fn merge_text(
177 &mut self,
178 dec: &mut buffa::text::TextDecoder<'_>,
179 ) -> Result<(), buffa::text::ParseError> {
180 while let Some(name) = dec.read_field_name()? {
181 match name {
182 "type_url" => self.type_url = dec.read_string()?.into_owned(),
183 "value" => self.value = dec.read_bytes()?.into(),
184 _ if name.starts_with('[') => {
185 let (url, bytes) = dec.read_any_expansion(name)?;
186 self.type_url = url.into();
187 self.value = bytes.into();
188 }
189 _ => dec.skip_value()?,
190 }
191 }
192 Ok(())
193 }
194}
195
196#[cfg(test)]
197mod text_tests {
198 use super::Any;
199 use buffa::text::{decode_from_str, encode_to_string};
200
201 #[test]
202 fn vanilla_roundtrip_no_registry() {
203 let orig = Any {
207 type_url: "type.example.com/Foo".into(),
208 value: alloc::vec![0x08, 0x2A].into(), ..Default::default()
210 };
211 let text = encode_to_string(&orig);
212 assert_eq!(text, r#"type_url: "type.example.com/Foo" value: "\010*""#);
213 let back: Any = decode_from_str(&text).unwrap();
214 assert_eq!(back.type_url, orig.type_url);
215 assert_eq!(back.value, orig.value);
216 }
217
218 }
222
223#[cfg(feature = "json")]
231struct Base64Bytes<'a>(&'a [u8]);
232
233#[cfg(feature = "json")]
234impl serde::Serialize for Base64Bytes<'_> {
235 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
236 buffa::json_helpers::bytes::serialize(self.0, s)
237 }
238}
239
240#[cfg(feature = "json")]
241impl serde::Serialize for Any {
242 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
243 use serde::ser::SerializeMap;
244
245 if self.type_url.is_empty() {
246 return s.serialize_map(Some(0))?.end();
247 }
248
249 let lookup = buffa::any_registry::with_any_registry(|reg| {
250 reg.and_then(|r| r.lookup(&self.type_url))
251 .map(|e| (e.to_json, e.is_wkt))
252 });
253
254 match lookup {
255 Some((to_json, is_wkt)) => {
256 let json_val = to_json(&self.value).map_err(serde::ser::Error::custom)?;
257 if is_wkt {
258 let mut map = s.serialize_map(Some(2))?;
259 map.serialize_entry("@type", &self.type_url)?;
260 map.serialize_entry("value", &json_val)?;
261 map.end()
262 } else {
263 let fields = match &json_val {
264 serde_json::Value::Object(m) => m,
265 _ => {
266 return Err(serde::ser::Error::custom(
267 "Any: to_json for non-WKT must return a JSON object",
268 ))
269 }
270 };
271 let mut map = s.serialize_map(Some(1 + fields.len()))?;
272 map.serialize_entry("@type", &self.type_url)?;
273 for (k, v) in fields {
274 map.serialize_entry(k, v)?;
275 }
276 map.end()
277 }
278 }
279 None => {
280 let mut map = s.serialize_map(Some(2))?;
281 map.serialize_entry("@type", &self.type_url)?;
282 map.serialize_entry("value", &Base64Bytes(&self.value))?;
283 map.end()
284 }
285 }
286 }
287}
288
289#[cfg(feature = "json")]
290impl<'de> serde::Deserialize<'de> for Any {
291 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
292 let mut obj: serde_json::Map<String, serde_json::Value> =
294 serde::Deserialize::deserialize(d)?;
295
296 let type_url = match obj.remove("@type") {
297 Some(serde_json::Value::String(s)) => s,
298 Some(_) => {
299 return Err(serde::de::Error::custom("@type must be a string"));
300 }
301 None => return Ok(Self::default()),
302 };
303
304 if type_url.is_empty() || !type_url.contains('/') {
308 return Err(serde::de::Error::custom(
309 "@type must be a valid type URL containing a '/' (e.g. type.googleapis.com/pkg.Type)",
310 ));
311 }
312
313 let lookup = buffa::any_registry::with_any_registry(|reg| {
314 reg.and_then(|r| r.lookup(&type_url))
315 .map(|e| (e.from_json, e.is_wkt))
316 });
317
318 let value = match lookup {
319 Some((from_json, true)) => {
320 let json_val = obj.remove("value").unwrap_or(serde_json::Value::Null);
321 from_json(json_val).map_err(serde::de::Error::custom)?
322 }
323 Some((from_json, false)) => {
324 let json_obj = serde_json::Value::Object(obj);
325 from_json(json_obj).map_err(serde::de::Error::custom)?
326 }
327 None => {
328 match obj.remove("value") {
330 Some(serde_json::Value::String(s)) => buffa::json_helpers::bytes::deserialize(
331 serde::de::value::StringDeserializer::<D::Error>::new(s),
332 )?,
333 _ => alloc::vec::Vec::new(),
334 }
335 }
336 };
337
338 Ok(Self {
339 type_url,
340 value: value.into(),
341 ..Default::default()
342 })
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use crate::google::protobuf::Timestamp;
350 use buffa::Message as _;
351
352 #[test]
353 fn any_view_to_owned_from_source_is_zero_copy() {
354 use crate::google::protobuf::__buffa::view::AnyView;
355 use buffa::view::{MessageView as _, OwnedView};
356
357 let src = Any {
358 type_url: "type.googleapis.com/x".into(),
359 value: bytes::Bytes::from_static(&[1u8; 256]),
360 ..Default::default()
361 };
362 let buf = bytes::Bytes::from(src.encode_to_vec());
363
364 let view = AnyView::decode_view(&buf).unwrap();
366 let owned = view.to_owned_from_source(Some(&buf)).unwrap();
367 assert_eq!(owned.value, src.value);
368 let value_ptr = owned.value.as_ptr() as usize;
369 let buf_range = (buf.as_ptr() as usize)..(buf.as_ptr() as usize + buf.len());
370 assert!(
371 buf_range.contains(&value_ptr),
372 "owned.value should point into buf (slice_ref), got {value_ptr:#x} outside {buf_range:#x?}"
373 );
374
375 let ov = OwnedView::<AnyView<'static>>::decode(buf.clone()).unwrap();
379 let owned2 = ov.to_owned_message().unwrap();
380 assert_eq!(owned2.value, src.value);
381 assert!(buf_range.contains(&(owned2.value.as_ptr() as usize)));
382
383 let copied = view.to_owned_message().unwrap();
385 assert_eq!(copied.value, src.value);
386 assert!(!buf_range.contains(&(copied.value.as_ptr() as usize)));
387 }
388
389 #[cfg(feature = "arbitrary")]
390 #[test]
391 fn any_arbitrary_with_bytes_value() {
392 use arbitrary::{Arbitrary, Unstructured};
393 let raw = [0u8; 64];
397 let mut u = Unstructured::new(&raw);
398 let any = Any::arbitrary(&mut u).unwrap();
399 let _ = any.value.slice(..);
400 }
401
402 #[test]
403 fn pack_and_unpack() {
404 let ts = Timestamp {
405 seconds: 1_000_000_000,
406 nanos: 0,
407 ..Default::default()
408 };
409 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
410 assert_eq!(
411 any.type_url(),
412 "type.googleapis.com/google.protobuf.Timestamp"
413 );
414
415 let decoded: Timestamp = any.unpack_unchecked().unwrap();
416 assert_eq!(decoded, ts);
417 }
418
419 #[test]
420 fn unpack_if_matching() {
421 let ts = Timestamp {
422 seconds: 42,
423 ..Default::default()
424 };
425 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
426
427 let result: Option<Timestamp> = any
428 .unpack_if("type.googleapis.com/google.protobuf.Timestamp")
429 .unwrap();
430 assert_eq!(result, Some(ts));
431 }
432
433 #[test]
434 fn unpack_if_wrong_type_returns_none() {
435 let ts = Timestamp {
436 seconds: 42,
437 ..Default::default()
438 };
439 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
440
441 let result: Option<Timestamp> = any
442 .unpack_if("type.googleapis.com/google.protobuf.Duration")
443 .unwrap();
444 assert!(result.is_none());
445 }
446
447 #[test]
448 fn clone_shares_payload_buffer() {
449 let orig = Any {
450 type_url: "type.googleapis.com/example.Msg".into(),
451 value: alloc::vec![0xAB; 1024].into(),
452 ..Default::default()
453 };
454 let dup = orig.clone();
455 assert_eq!(orig.value.as_ptr(), dup.value.as_ptr());
456 assert_eq!(orig.value.len(), dup.value.len());
457 }
458
459 #[test]
460 fn is_type() {
461 let ts = Timestamp::default();
462 let any = Any::pack(&ts, "type.googleapis.com/google.protobuf.Timestamp");
463 assert!(any.is_type("type.googleapis.com/google.protobuf.Timestamp"));
464 assert!(!any.is_type("type.googleapis.com/google.protobuf.Duration"));
465 }
466
467 #[test]
468 fn round_trip_encoding() {
469 let ts = Timestamp {
470 seconds: 99,
471 nanos: 1,
472 ..Default::default()
473 };
474 let any = Any::pack(&ts, "test");
475
476 let bytes = any.encode_to_vec();
477 let decoded_any = Any::decode(&mut bytes.as_slice()).unwrap();
478 let decoded_ts: Timestamp = decoded_any.unpack_unchecked().unwrap();
479 assert_eq!(decoded_ts, ts);
480 }
481
482 #[cfg(feature = "json")]
483 mod serde_tests {
484 use super::*;
485 use crate::google::protobuf::Duration;
486 use buffa::any_registry::clear_any_registry;
487 use buffa::type_registry::{clear_text_registry, set_type_registry, TypeRegistry};
488
489 static REGISTRY_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
493
494 fn with_registry<R>(f: impl FnOnce() -> R) -> R {
495 let _guard = REGISTRY_LOCK.lock().unwrap();
496 let mut reg = TypeRegistry::new();
497 register_wkt_types(&mut reg);
498 set_type_registry(reg);
499 let result = f();
500 clear_any_registry();
501 clear_text_registry();
502 result
503 }
504
505 fn without_registry<R>(f: impl FnOnce() -> R) -> R {
506 let _guard = REGISTRY_LOCK.lock().unwrap();
507 clear_any_registry();
508 clear_text_registry();
509 f()
510 }
511
512 #[test]
519 fn text_registry_roundtrip_wkt() {
520 use crate::google::protobuf::Empty;
521 use buffa::text::{decode_from_str, encode_to_string};
522 with_registry(|| {
523 let any = Any::pack(&Empty::default(), Empty::TYPE_URL);
525 let text = encode_to_string(&any);
526 assert_eq!(text, "[type.googleapis.com/google.protobuf.Empty] {}");
528
529 let back: Any = decode_from_str(&text).unwrap();
530 assert_eq!(back.type_url, Empty::TYPE_URL);
531 assert_eq!(back.value, alloc::vec::Vec::<u8>::new());
532 });
533 }
534
535 #[test]
536 fn text_unregistered_url_errors_on_decode() {
537 use buffa::text::decode_from_str;
538 with_registry(|| {
541 let result: Result<Any, _> =
542 decode_from_str("[type.googleapis.com/unknown.Type] { x: 1 }");
543 assert!(result.is_err(), "unknown URL should error, not skip");
544 });
545 }
546
547 #[test]
548 fn text_bracket_without_registry_errors() {
549 use buffa::text::decode_from_str;
550 without_registry(|| {
552 let result: Result<Any, _> = decode_from_str("[type.example.com/Unknown] { x: 1 }");
553 assert!(result.is_err());
554 });
555 }
556
557 #[test]
558 fn serialize_wkt_uses_value_wrapping() {
559 with_registry(|| {
560 let ts = Timestamp {
561 seconds: 1_000_000_000,
562 nanos: 0,
563 ..Default::default()
564 };
565 let any = Any::pack(&ts, Timestamp::TYPE_URL);
566 let json = serde_json::to_value(&any).unwrap();
567 assert_eq!(json["@type"], Timestamp::TYPE_URL);
568 assert_eq!(json["value"], "2001-09-09T01:46:40Z");
569 });
570 }
571
572 #[test]
573 fn serialize_duration_wkt() {
574 with_registry(|| {
575 let dur = Duration::from_secs_nanos(1, 500_000_000);
576 let any = Any::pack(&dur, Duration::TYPE_URL);
577 let json = serde_json::to_value(&any).unwrap();
578 assert_eq!(json["@type"], Duration::TYPE_URL);
579 assert_eq!(json["value"], "1.500s");
580 });
581 }
582
583 #[test]
584 fn serialize_empty_any_is_empty_object() {
585 with_registry(|| {
586 let any = Any::default();
587 let json = serde_json::to_string(&any).unwrap();
588 assert_eq!(json, "{}");
589 });
590 }
591
592 #[test]
593 fn deserialize_wkt_from_json() {
594 with_registry(|| {
595 let json = r#"{
596 "@type": "type.googleapis.com/google.protobuf.Duration",
597 "value": "1.5s"
598 }"#;
599 let any: Any = serde_json::from_str(json).unwrap();
600 assert_eq!(any.type_url, Duration::TYPE_URL);
601
602 let dur: Duration = any.unpack_unchecked().unwrap();
603 assert_eq!(dur.seconds, 1);
604 assert_eq!(dur.nanos, 500_000_000);
605 });
606 }
607
608 #[test]
609 fn deserialize_unordered_type_tag() {
610 with_registry(|| {
611 let json = r#"{
613 "value": "1.5s",
614 "@type": "type.googleapis.com/google.protobuf.Duration"
615 }"#;
616 let any: Any = serde_json::from_str(json).unwrap();
617 assert_eq!(any.type_url, Duration::TYPE_URL);
618
619 let dur: Duration = any.unpack_unchecked().unwrap();
620 assert_eq!(dur.seconds, 1);
621 assert_eq!(dur.nanos, 500_000_000);
622 });
623 }
624
625 #[test]
626 fn roundtrip_wkt_json() {
627 with_registry(|| {
628 let ts = Timestamp {
629 seconds: 1_000_000_000,
630 nanos: 0,
631 ..Default::default()
632 };
633 let any = Any::pack(&ts, Timestamp::TYPE_URL);
634 let json = serde_json::to_string(&any).unwrap();
635 let decoded: Any = serde_json::from_str(&json).unwrap();
636 let decoded_ts: Timestamp = decoded.unpack_unchecked().unwrap();
637 assert_eq!(decoded_ts, ts);
638 });
639 }
640
641 #[test]
642 fn nested_any_roundtrip() {
643 with_registry(|| {
644 let dur = Duration::from_secs(42);
645 let inner_any = Any::pack(&dur, Duration::TYPE_URL);
646 let outer_any = Any::pack(&inner_any, Any::TYPE_URL);
647
648 let json = serde_json::to_string(&outer_any).unwrap();
649 let decoded_outer: Any = serde_json::from_str(&json).unwrap();
650 let decoded_inner: Any = decoded_outer.unpack_unchecked().unwrap();
651 let decoded_dur: Duration = decoded_inner.unpack_unchecked().unwrap();
652 assert_eq!(decoded_dur.seconds, 42);
653 });
654 }
655
656 #[test]
657 fn fallback_base64_without_registry() {
658 without_registry(|| {
659 let any = Any {
660 type_url: "type.googleapis.com/unknown.Type".into(),
661 value: vec![0x08, 0x96, 0x01].into(),
662 ..Default::default()
663 };
664 let json = serde_json::to_string(&any).unwrap();
665 assert!(json.contains("@type"));
666 assert!(json.contains("value"));
667
668 let decoded: Any = serde_json::from_str(&json).unwrap();
669 assert_eq!(decoded.type_url, any.type_url);
670 assert_eq!(decoded.value, any.value);
671 });
672 }
673
674 #[test]
675 fn deserialize_missing_type_returns_default() {
676 let json = r#"{}"#;
677 let any: Any = serde_json::from_str(json).unwrap();
678 assert_eq!(any, Any::default());
679 }
680
681 #[test]
682 fn fallback_base64_with_registry_but_unknown_type() {
683 with_registry(|| {
684 let any = Any {
685 type_url: "type.googleapis.com/unknown.Type".into(),
686 value: vec![0x08, 0x96, 0x01].into(),
687 ..Default::default()
688 };
689 let json = serde_json::to_string(&any).unwrap();
690 let decoded: Any = serde_json::from_str(&json).unwrap();
691 assert_eq!(decoded.type_url, any.type_url);
692 assert_eq!(decoded.value, any.value);
693 });
694 }
695
696 #[test]
697 fn deserialize_rejects_empty_type_url() {
698 let json = r#"{"@type": "", "value": ""}"#;
699 let err = serde_json::from_str::<Any>(json).unwrap_err();
700 assert!(err.to_string().contains("valid type URL"), "{err}");
701 }
702
703 #[test]
704 fn deserialize_rejects_type_url_without_slash() {
705 let json = r#"{"@type": "not_a_url", "value": ""}"#;
706 let err = serde_json::from_str::<Any>(json).unwrap_err();
707 assert!(err.to_string().contains("valid type URL"), "{err}");
708 }
709
710 fn user_type_to_json(bytes: &[u8]) -> Result<serde_json::Value, String> {
718 use buffa::encoding::Tag;
719 let mut cur = bytes;
720 let mut id = 0i64;
721 while !cur.is_empty() {
722 let tag = Tag::decode(&mut cur).map_err(|e| e.to_string())?;
723 if tag.field_number() == 1 {
724 id =
725 buffa::encoding::decode_varint(&mut cur).map_err(|e| e.to_string())? as i64;
726 } else {
727 buffa::encoding::skip_field(tag, &mut cur).map_err(|e| e.to_string())?;
728 }
729 }
730 Ok(serde_json::json!({ "id": id }))
731 }
732
733 fn user_type_from_json(value: serde_json::Value) -> Result<alloc::vec::Vec<u8>, String> {
735 use buffa::encoding::{encode_varint, Tag, WireType};
736 let id = value
737 .get("id")
738 .and_then(|v| v.as_i64())
739 .ok_or_else(|| "missing or invalid 'id' field".to_string())?;
740 let mut buf = alloc::vec::Vec::new();
741 Tag::new(1, WireType::Varint).encode(&mut buf);
742 encode_varint(id as u64, &mut buf);
743 Ok(buf)
744 }
745
746 fn with_user_type_registry<R>(f: impl FnOnce() -> R) -> R {
747 use buffa::type_registry::JsonAnyEntry;
748 let _guard = REGISTRY_LOCK.lock().unwrap();
749 let mut reg = TypeRegistry::new();
750 reg.register_json_any(JsonAnyEntry {
752 type_url: "type.example.com/user.Thing",
753 to_json: user_type_to_json,
754 from_json: user_type_from_json,
755 is_wkt: false,
756 });
757 set_type_registry(reg);
758 let result = f();
759 clear_any_registry();
760 clear_text_registry();
761 result
762 }
763
764 #[test]
765 fn serialize_non_wkt_inlines_fields() {
766 with_user_type_registry(|| {
767 let any = Any {
769 type_url: "type.example.com/user.Thing".into(),
770 value: vec![0x08, 0x2A].into(),
772 ..Default::default()
773 };
774
775 let json = serde_json::to_value(&any).unwrap();
776 assert_eq!(json["@type"], "type.example.com/user.Thing");
778 assert_eq!(json["id"], 42);
779 assert!(
781 json.get("value").is_none(),
782 "non-WKT should not use 'value' wrapping: {json}"
783 );
784 });
785 }
786
787 #[test]
788 fn deserialize_non_wkt_from_inlined_fields() {
789 with_user_type_registry(|| {
790 let json = r#"{
791 "@type": "type.example.com/user.Thing",
792 "id": 99
793 }"#;
794 let any: Any = serde_json::from_str(json).unwrap();
795 assert_eq!(any.type_url, "type.example.com/user.Thing");
796 assert_eq!(any.value, vec![0x08, 99]);
798 });
799 }
800
801 #[test]
802 fn non_wkt_round_trip() {
803 with_user_type_registry(|| {
804 let original = Any {
805 type_url: "type.example.com/user.Thing".into(),
806 value: vec![0x08, 0x07].into(), ..Default::default()
808 };
809 let json = serde_json::to_string(&original).unwrap();
810 let decoded: Any = serde_json::from_str(&json).unwrap();
811 assert_eq!(decoded.type_url, original.type_url);
812 assert_eq!(decoded.value, original.value);
813 });
814 }
815
816 #[test]
817 fn serialize_non_wkt_rejects_non_object_json() {
818 use buffa::type_registry::JsonAnyEntry;
822 let _guard = REGISTRY_LOCK.lock().unwrap();
823 let mut reg = TypeRegistry::new();
824 reg.register_json_any(JsonAnyEntry {
825 type_url: "type.example.com/user.BadType",
826 to_json: |_bytes| Ok(serde_json::Value::Number(42.into())),
827 from_json: |_v| Ok(alloc::vec::Vec::new()),
828 is_wkt: false,
829 });
830 set_type_registry(reg);
831
832 let any = Any {
833 type_url: "type.example.com/user.BadType".into(),
834 value: vec![].into(),
835 ..Default::default()
836 };
837 let result = serde_json::to_string(&any);
838 clear_any_registry();
839 clear_text_registry();
840 assert!(result.is_err(), "expected error for non-object to_json");
841 assert!(
842 result
843 .unwrap_err()
844 .to_string()
845 .contains("must return a JSON object"),
846 "wrong error message"
847 );
848 }
849
850 #[test]
851 fn deserialize_rejects_non_string_type() {
852 let json = r#"{"@type": 123}"#;
854 let err = serde_json::from_str::<Any>(json).unwrap_err();
855 assert!(err.to_string().contains("@type must be a string"), "{err}");
856 }
857 }
858}