1use crate::error::Error;
5use ipld_core::ipld::Ipld;
6use ipld_core::serde::to_ipld;
7use serde::{de, ser};
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeMap;
10use std::fmt;
11use std::ops::{Deref, DerefMut};
12
13mod cid_link;
14pub use cid_link::CidLink;
15
16mod integer;
17pub use integer::*;
18
19pub mod string;
20use string::RecordKey;
21
22pub trait Collection: fmt::Debug {
26 const NSID: &'static str;
28
29 type Record: fmt::Debug + de::DeserializeOwned + Serialize;
31
32 fn nsid() -> string::Nsid {
43 Self::NSID.parse().expect("Self::NSID should be a valid NSID")
44 }
45
46 fn repo_path(rkey: &RecordKey) -> String {
56 format!("{}/{}", Self::NSID, rkey.as_str())
57 }
58}
59
60#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
64#[serde(untagged)]
65pub enum BlobRef {
66 Typed(TypedBlobRef),
67 Untyped(UnTypedBlobRef),
68}
69
70#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
72#[serde(tag = "$type", rename_all = "lowercase")]
73pub enum TypedBlobRef {
74 Blob(Blob),
75}
76
77#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
80#[serde(rename_all = "camelCase")]
81pub struct UnTypedBlobRef {
82 pub cid: String,
83 pub mime_type: String,
84}
85
86#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
87#[serde(rename_all = "camelCase")]
88pub struct Blob {
89 pub r#ref: CidLink,
90 pub mime_type: String,
91 pub size: usize, }
93
94#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
96pub struct Object<T> {
97 #[serde(flatten)]
98 pub data: T,
99 #[serde(flatten)]
100 pub extra_data: Ipld,
101}
102
103impl<T> From<T> for Object<T> {
104 fn from(data: T) -> Self {
105 Self { data, extra_data: Ipld::Map(std::collections::BTreeMap::new()) }
106 }
107}
108
109impl<T> Deref for Object<T> {
110 type Target = T;
111
112 fn deref(&self) -> &Self::Target {
113 &self.data
114 }
115}
116
117impl<T> DerefMut for Object<T> {
118 fn deref_mut(&mut self) -> &mut Self::Target {
119 &mut self.data
120 }
121}
122
123#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
125#[serde(untagged)]
126pub enum Union<T> {
127 Refs(T),
128 Unknown(UnknownData),
129}
130
131#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
135pub struct UnknownData {
136 #[serde(rename = "$type")]
137 pub r#type: String,
138 #[serde(flatten)]
139 pub data: Ipld,
140}
141
142#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
169#[serde(untagged)]
170pub enum Unknown {
171 Object(BTreeMap<String, DataModel>),
172 Null,
173 Other(DataModel),
174}
175
176#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
177#[serde(try_from = "Ipld")]
178pub struct DataModel(#[serde(serialize_with = "serialize_data_model")] Ipld);
179
180fn serialize_data_model<S>(ipld: &Ipld, serializer: S) -> Result<S::Ok, S::Error>
181where
182 S: ser::Serializer,
183{
184 match ipld {
185 Ipld::Float(_) => Err(ser::Error::custom("float values are not allowed in ATProtocol")),
186 Ipld::List(list) => {
187 if list.iter().any(|value| matches!(value, Ipld::Float(_))) {
188 Err(ser::Error::custom("float values are not allowed in ATProtocol"))
189 } else {
190 list.iter().cloned().map(DataModel).collect::<Vec<_>>().serialize(serializer)
191 }
192 }
193 Ipld::Map(map) => {
194 if map.values().any(|value| matches!(value, Ipld::Float(_))) {
195 Err(ser::Error::custom("float values are not allowed in ATProtocol"))
196 } else {
197 map.iter()
198 .map(|(k, v)| (k, DataModel(v.clone())))
199 .collect::<BTreeMap<_, _>>()
200 .serialize(serializer)
201 }
202 }
203 Ipld::Link(link) => CidLink(*link).serialize(serializer),
204 _ => ipld.serialize(serializer),
205 }
206}
207
208impl Deref for DataModel {
209 type Target = Ipld;
210
211 fn deref(&self) -> &Self::Target {
212 &self.0
213 }
214}
215
216impl DerefMut for DataModel {
217 fn deref_mut(&mut self) -> &mut Self::Target {
218 &mut self.0
219 }
220}
221
222impl TryFrom<Ipld> for DataModel {
223 type Error = Error;
224
225 fn try_from(value: Ipld) -> Result<Self, Self::Error> {
226 match value {
229 Ipld::Float(_) => Err(Error::NotAllowed),
230 Ipld::List(list) => {
231 if list.iter().any(|value| matches!(value, Ipld::Float(_))) {
232 Err(Error::NotAllowed)
233 } else {
234 Ok(DataModel(Ipld::List(list)))
235 }
236 }
237 Ipld::Map(map) => {
238 if map.values().any(|value| matches!(value, Ipld::Float(_))) {
239 Err(Error::NotAllowed)
240 } else {
241 Ok(DataModel(Ipld::Map(map)))
242 }
243 }
244 data => Ok(DataModel(data)),
245 }
246 }
247}
248
249pub trait TryFromUnknown: Sized {
251 type Error;
252
253 fn try_from_unknown(value: Unknown) -> Result<Self, Self::Error>;
254}
255
256impl<T> TryFromUnknown for T
257where
258 T: de::DeserializeOwned,
259{
260 type Error = Error;
261
262 fn try_from_unknown(value: Unknown) -> Result<Self, Self::Error> {
263 let json = serde_json::to_vec(&value).unwrap();
279 Ok(serde_json::from_slice(&json).unwrap())
280 }
281}
282
283pub trait TryIntoUnknown {
285 type Error;
286
287 fn try_into_unknown(self) -> Result<Unknown, Self::Error>;
288}
289
290impl<T> TryIntoUnknown for T
291where
292 T: Serialize,
293{
294 type Error = Error;
295
296 fn try_into_unknown(self) -> Result<Unknown, Self::Error> {
297 Ok(Unknown::Other(to_ipld(self)?.try_into()?))
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use ipld_core::cid::Cid;
305 use serde_json::{from_str, to_string};
306
307 const CID_LINK_JSON: &str =
308 r#"{"$link":"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"}"#;
309
310 #[test]
311 fn cid_link_serde_json() {
312 let deserialized =
313 from_str::<CidLink>(CID_LINK_JSON).expect("failed to deserialize cid-link");
314 let serialized = to_string(&deserialized).expect("failed to serialize cid-link");
315 assert_eq!(serialized, CID_LINK_JSON);
316 }
317
318 #[test]
319 fn blob_ref_typed_deserialize_json() {
320 let json = format!(
321 r#"{{"$type":"blob","ref":{},"mimeType":"text/plain","size":0}}"#,
322 CID_LINK_JSON
323 );
324 let deserialized = from_str::<BlobRef>(&json).expect("failed to deserialize blob-ref");
325 assert_eq!(
326 deserialized,
327 BlobRef::Typed(TypedBlobRef::Blob(Blob {
328 r#ref: CidLink::try_from(
329 "bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"
330 )
331 .expect("failed to create cid-link"),
332 mime_type: "text/plain".into(),
333 size: 0
334 }))
335 );
336 }
337
338 #[test]
339 fn blob_ref_untyped_deserialize_json() {
340 let json = r#"{"cid":"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy","mimeType":"text/plain"}"#;
341 let deserialized = from_str::<BlobRef>(json).expect("failed to deserialize blob-ref");
342 assert_eq!(
343 deserialized,
344 BlobRef::Untyped(UnTypedBlobRef {
345 cid: "bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy".into(),
346 mime_type: "text/plain".into(),
347 })
348 );
349 }
350
351 #[test]
352 fn blob_ref_serialize_json() {
353 let blob_ref = BlobRef::Typed(TypedBlobRef::Blob(Blob {
354 r#ref: CidLink::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy")
355 .expect("failed to create cid-link"),
356 mime_type: "text/plain".into(),
357 size: 0,
358 }));
359 let serialized = to_string(&blob_ref).expect("failed to serialize blob-ref");
360 assert_eq!(
361 serialized,
362 format!(
363 r#"{{"$type":"blob","ref":{},"mimeType":"text/plain","size":0}}"#,
364 CID_LINK_JSON
365 )
366 );
367 }
368
369 #[test]
370 fn blob_ref_deserialize_dag_cbor() {
371 let dag_cbor = [
373 0xa4, 0x65, 0x24, 0x74, 0x79, 0x70, 0x65, 0x64, 0x62, 0x6c, 0x6f, 0x62, 0x63, 0x72,
374 0x65, 0x66, 0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x55, 0x12, 0x20, 0x2c, 0x26, 0xb4,
375 0x6b, 0x68, 0xff, 0xc6, 0x8f, 0xf9, 0x9b, 0x45, 0x3c, 0x1d, 0x30, 0x41, 0x34, 0x13,
376 0x42, 0x2d, 0x70, 0x64, 0x83, 0xbf, 0xa0, 0xf9, 0x8a, 0x5e, 0x88, 0x62, 0x66, 0xe7,
377 0xae, 0x68, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x6a, 0x74, 0x65, 0x78,
378 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x00,
379 ];
380 let deserialized = serde_ipld_dagcbor::from_slice::<BlobRef>(dag_cbor.as_slice())
381 .expect("failed to deserialize blob-ref");
382 assert_eq!(
383 deserialized,
384 BlobRef::Typed(TypedBlobRef::Blob(Blob {
385 r#ref: CidLink::try_from(
386 "bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"
387 )
388 .expect("failed to create cid-link"),
389 mime_type: "text/plain".into(),
390 size: 0,
391 }))
392 );
393 }
394
395 #[test]
396 fn data_model() {
397 assert!(DataModel::try_from(Ipld::Null).is_ok());
398 assert!(DataModel::try_from(Ipld::Bool(true)).is_ok());
399 assert!(DataModel::try_from(Ipld::Integer(1)).is_ok());
400 assert!(DataModel::try_from(Ipld::Float(1.5)).is_err(), "float value should fail");
401 assert!(DataModel::try_from(Ipld::String("s".into())).is_ok());
402 assert!(DataModel::try_from(Ipld::Bytes(vec![0x01])).is_ok());
403 assert!(DataModel::try_from(Ipld::List(vec![Ipld::Bool(true)])).is_ok());
404 assert!(
405 DataModel::try_from(Ipld::List(vec![Ipld::Bool(true), Ipld::Float(1.5)])).is_err(),
406 "list with float value should fail"
407 );
408 assert!(DataModel::try_from(Ipld::Map(BTreeMap::from_iter([(
409 String::from("k"),
410 Ipld::Bool(true)
411 )])))
412 .is_ok());
413 assert!(
414 DataModel::try_from(Ipld::Map(BTreeMap::from_iter([(
415 String::from("k"),
416 Ipld::Float(1.5)
417 )])))
418 .is_err(),
419 "map with float value should fail"
420 );
421 assert!(DataModel::try_from(Ipld::Link(
422 Cid::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy")
423 .expect("failed to create cid")
424 ))
425 .is_ok());
426 }
427
428 #[test]
429 fn union() {
430 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
431 #[serde(tag = "$type")]
432 enum FooRefs {
433 #[serde(rename = "example.com#bar")]
434 Bar(Box<Bar>),
435 #[serde(rename = "example.com#baz")]
436 Baz(Box<Baz>),
437 }
438
439 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
440 struct Bar {
441 bar: String,
442 }
443
444 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
445 struct Baz {
446 baz: i32,
447 }
448
449 type Foo = Union<FooRefs>;
450
451 let foo = serde_json::from_str::<Foo>(r#"{"$type":"example.com#bar","bar":"bar"}"#)
452 .expect("failed to deserialize foo");
453 assert_eq!(foo, Union::Refs(FooRefs::Bar(Box::new(Bar { bar: String::from("bar") }))));
454
455 let foo = serde_json::from_str::<Foo>(r#"{"$type":"example.com#baz","baz":42}"#)
456 .expect("failed to deserialize foo");
457 assert_eq!(foo, Union::Refs(FooRefs::Baz(Box::new(Baz { baz: 42 }))));
458
459 let foo = serde_json::from_str::<Foo>(r#"{"$type":"example.com#foo","foo":true}"#)
460 .expect("failed to deserialize foo");
461 assert_eq!(
462 foo,
463 Union::Unknown(UnknownData {
464 r#type: String::from("example.com#foo"),
465 data: Ipld::Map(BTreeMap::from_iter([(String::from("foo"), Ipld::Bool(true))]))
466 })
467 );
468 }
469
470 #[test]
471 fn unknown_serialize() {
472 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
473 struct Foo {
474 foo: Unknown,
475 }
476
477 let foo = Foo {
478 foo: Unknown::Object(BTreeMap::from_iter([(
479 String::from("bar"),
480 DataModel(Ipld::String(String::from("bar"))),
481 )])),
482 };
483 let serialized = to_string(&foo).expect("failed to serialize foo");
484 assert_eq!(serialized, r#"{"foo":{"bar":"bar"}}"#);
485 }
486
487 #[test]
488 fn unknown_deserialize() {
489 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
490 struct Foo {
491 foo: Unknown,
492 }
493
494 {
496 let json = r#"{
497 "foo": {
498 "$type": "example.com#foo",
499 "bar": "bar"
500 }
501 }"#;
502 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
503 assert_eq!(
504 deserialized,
505 Foo {
506 foo: Unknown::Object(BTreeMap::from_iter([
507 (String::from("bar"), DataModel(Ipld::String(String::from("bar")))),
508 (
509 String::from("$type"),
510 DataModel(Ipld::String(String::from("example.com#foo")))
511 )
512 ]))
513 }
514 );
515 }
516 {
518 let json = r#"{
519 "foo": {}
520 }"#;
521 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
522 assert_eq!(deserialized, Foo { foo: Unknown::Object(BTreeMap::default()) });
523 }
524 {
526 let json = r#"{
527 "foo": {
528 "bar": "bar"
529 }
530 }"#;
531 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
532 assert_eq!(
533 deserialized,
534 Foo {
535 foo: Unknown::Object(BTreeMap::from_iter([(
536 String::from("bar"),
537 DataModel(Ipld::String(String::from("bar")))
538 )]))
539 }
540 );
541 }
542 {
544 let json = r#"{
545 "foo": null
546 }"#;
547 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
548 assert_eq!(deserialized, Foo { foo: Unknown::Null });
549 }
550 {
552 let json = r#"{
553 "foo": 42
554 }"#;
555 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
556 assert_eq!(deserialized, Foo { foo: Unknown::Other(DataModel(Ipld::Integer(42))) });
557 }
558 {
560 let json = r#"{
561 "foo": 42.195
562 }"#;
563 assert!(from_str::<Foo>(json).is_err());
564 }
565 }
566
567 #[test]
568 fn unknown_try_from() {
569 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
570 #[serde(tag = "$type")]
571 enum Foo {
572 #[serde(rename = "example.com#bar")]
573 Bar(Box<Bar>),
574 #[serde(rename = "example.com#baz")]
575 Baz(Box<Baz>),
576 }
577
578 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
579 struct Bar {
580 bar: String,
581 }
582
583 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
584 struct Baz {
585 baz: i32,
586 }
587
588 {
589 let unknown = Unknown::Object(BTreeMap::from_iter([
590 (String::from("$type"), DataModel(Ipld::String(String::from("example.com#bar")))),
591 (String::from("bar"), DataModel(Ipld::String(String::from("barbar")))),
592 ]));
593 let bar = Bar::try_from_unknown(unknown.clone()).expect("failed to convert to Bar");
594 assert_eq!(bar, Bar { bar: String::from("barbar") });
595 let barbaz = Foo::try_from_unknown(unknown).expect("failed to convert to Bar");
596 assert_eq!(barbaz, Foo::Bar(Box::new(Bar { bar: String::from("barbar") })));
597 }
598 {
599 let unknown = Unknown::Object(BTreeMap::from_iter([
600 (String::from("$type"), DataModel(Ipld::String(String::from("example.com#baz")))),
601 (String::from("baz"), DataModel(Ipld::Integer(42))),
602 ]));
603 let baz = Baz::try_from_unknown(unknown.clone()).expect("failed to convert to Baz");
604 assert_eq!(baz, Baz { baz: 42 });
605 let barbaz = Foo::try_from_unknown(unknown).expect("failed to convert to Bar");
606 assert_eq!(barbaz, Foo::Baz(Box::new(Baz { baz: 42 })));
607 }
608 }
609
610 #[test]
611 fn serialize_unknown_from_cid_link() {
612 {
614 let cid_link =
615 CidLink::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy")
616 .expect("failed to create cid-link");
617 let unknown = cid_link.try_into_unknown().expect("failed to convert to unknown");
618 assert_eq!(
619 serde_json::to_string(&unknown).expect("failed to serialize unknown"),
620 r#"{"$link":"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"}"#
621 );
622 }
623 {
625 let cid_link =
626 CidLink::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy")
627 .expect("failed to create cid-link");
628 let blob_ref = BlobRef::Typed(TypedBlobRef::Blob(Blob {
629 r#ref: cid_link,
630 mime_type: "text/plain".into(),
631 size: 0,
632 }));
633 let unknown = blob_ref.try_into_unknown().expect("failed to convert to unknown");
634 let serialized = serde_json::to_string(&unknown).expect("failed to serialize unknown");
635 assert!(
636 serialized.contains("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"),
637 "serialized unknown should contain cid string: {serialized}"
638 );
639 }
640 }
641}