1use crate::error::Error;
5use ipld_core::ipld::Ipld;
6use ipld_core::serde::to_ipld;
7use serde::{Deserialize, Serialize};
8use serde::{de, ser};
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!(
409 DataModel::try_from(Ipld::Map(BTreeMap::from_iter([(
410 String::from("k"),
411 Ipld::Bool(true)
412 )])))
413 .is_ok()
414 );
415 assert!(
416 DataModel::try_from(Ipld::Map(BTreeMap::from_iter([(
417 String::from("k"),
418 Ipld::Float(1.5)
419 )])))
420 .is_err(),
421 "map with float value should fail"
422 );
423 assert!(
424 DataModel::try_from(Ipld::Link(
425 Cid::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy")
426 .expect("failed to create cid")
427 ))
428 .is_ok()
429 );
430 }
431
432 #[test]
433 fn union() {
434 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
435 #[serde(tag = "$type")]
436 enum FooRefs {
437 #[serde(rename = "example.com#bar")]
438 Bar(Box<Bar>),
439 #[serde(rename = "example.com#baz")]
440 Baz(Box<Baz>),
441 }
442
443 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
444 struct Bar {
445 bar: String,
446 }
447
448 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
449 struct Baz {
450 baz: i32,
451 }
452
453 type Foo = Union<FooRefs>;
454
455 let foo = serde_json::from_str::<Foo>(r#"{"$type":"example.com#bar","bar":"bar"}"#)
456 .expect("failed to deserialize foo");
457 assert_eq!(foo, Union::Refs(FooRefs::Bar(Box::new(Bar { bar: String::from("bar") }))));
458
459 let foo = serde_json::from_str::<Foo>(r#"{"$type":"example.com#baz","baz":42}"#)
460 .expect("failed to deserialize foo");
461 assert_eq!(foo, Union::Refs(FooRefs::Baz(Box::new(Baz { baz: 42 }))));
462
463 let foo = serde_json::from_str::<Foo>(r#"{"$type":"example.com#foo","foo":true}"#)
464 .expect("failed to deserialize foo");
465 assert_eq!(
466 foo,
467 Union::Unknown(UnknownData {
468 r#type: String::from("example.com#foo"),
469 data: Ipld::Map(BTreeMap::from_iter([(String::from("foo"), Ipld::Bool(true))]))
470 })
471 );
472 }
473
474 #[test]
475 fn unknown_serialize() {
476 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
477 struct Foo {
478 foo: Unknown,
479 }
480
481 let foo = Foo {
482 foo: Unknown::Object(BTreeMap::from_iter([(
483 String::from("bar"),
484 DataModel(Ipld::String(String::from("bar"))),
485 )])),
486 };
487 let serialized = to_string(&foo).expect("failed to serialize foo");
488 assert_eq!(serialized, r#"{"foo":{"bar":"bar"}}"#);
489 }
490
491 #[test]
492 fn unknown_deserialize() {
493 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
494 struct Foo {
495 foo: Unknown,
496 }
497
498 {
500 let json = r#"{
501 "foo": {
502 "$type": "example.com#foo",
503 "bar": "bar"
504 }
505 }"#;
506 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
507 assert_eq!(
508 deserialized,
509 Foo {
510 foo: Unknown::Object(BTreeMap::from_iter([
511 (String::from("bar"), DataModel(Ipld::String(String::from("bar")))),
512 (
513 String::from("$type"),
514 DataModel(Ipld::String(String::from("example.com#foo")))
515 )
516 ]))
517 }
518 );
519 }
520 {
522 let json = r#"{
523 "foo": {}
524 }"#;
525 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
526 assert_eq!(deserialized, Foo { foo: Unknown::Object(BTreeMap::default()) });
527 }
528 {
530 let json = r#"{
531 "foo": {
532 "bar": "bar"
533 }
534 }"#;
535 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
536 assert_eq!(
537 deserialized,
538 Foo {
539 foo: Unknown::Object(BTreeMap::from_iter([(
540 String::from("bar"),
541 DataModel(Ipld::String(String::from("bar")))
542 )]))
543 }
544 );
545 }
546 {
548 let json = r#"{
549 "foo": null
550 }"#;
551 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
552 assert_eq!(deserialized, Foo { foo: Unknown::Null });
553 }
554 {
556 let json = r#"{
557 "foo": 42
558 }"#;
559 let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
560 assert_eq!(deserialized, Foo { foo: Unknown::Other(DataModel(Ipld::Integer(42))) });
561 }
562 {
564 let json = r#"{
565 "foo": 42.195
566 }"#;
567 assert!(from_str::<Foo>(json).is_err());
568 }
569 }
570
571 #[test]
572 fn unknown_try_from() {
573 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
574 #[serde(tag = "$type")]
575 enum Foo {
576 #[serde(rename = "example.com#bar")]
577 Bar(Box<Bar>),
578 #[serde(rename = "example.com#baz")]
579 Baz(Box<Baz>),
580 }
581
582 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
583 struct Bar {
584 bar: String,
585 }
586
587 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
588 struct Baz {
589 baz: i32,
590 }
591
592 {
593 let unknown = Unknown::Object(BTreeMap::from_iter([
594 (String::from("$type"), DataModel(Ipld::String(String::from("example.com#bar")))),
595 (String::from("bar"), DataModel(Ipld::String(String::from("barbar")))),
596 ]));
597 let bar = Bar::try_from_unknown(unknown.clone()).expect("failed to convert to Bar");
598 assert_eq!(bar, Bar { bar: String::from("barbar") });
599 let barbaz = Foo::try_from_unknown(unknown).expect("failed to convert to Bar");
600 assert_eq!(barbaz, Foo::Bar(Box::new(Bar { bar: String::from("barbar") })));
601 }
602 {
603 let unknown = Unknown::Object(BTreeMap::from_iter([
604 (String::from("$type"), DataModel(Ipld::String(String::from("example.com#baz")))),
605 (String::from("baz"), DataModel(Ipld::Integer(42))),
606 ]));
607 let baz = Baz::try_from_unknown(unknown.clone()).expect("failed to convert to Baz");
608 assert_eq!(baz, Baz { baz: 42 });
609 let barbaz = Foo::try_from_unknown(unknown).expect("failed to convert to Bar");
610 assert_eq!(barbaz, Foo::Baz(Box::new(Baz { baz: 42 })));
611 }
612 }
613
614 #[test]
615 fn serialize_unknown_from_cid_link() {
616 {
618 let cid_link =
619 CidLink::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy")
620 .expect("failed to create cid-link");
621 let unknown = cid_link.try_into_unknown().expect("failed to convert to unknown");
622 assert_eq!(
623 serde_json::to_string(&unknown).expect("failed to serialize unknown"),
624 r#"{"$link":"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"}"#
625 );
626 }
627 {
629 let cid_link =
630 CidLink::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy")
631 .expect("failed to create cid-link");
632 let blob_ref = BlobRef::Typed(TypedBlobRef::Blob(Blob {
633 r#ref: cid_link,
634 mime_type: "text/plain".into(),
635 size: 0,
636 }));
637 let unknown = blob_ref.try_into_unknown().expect("failed to convert to unknown");
638 let serialized = serde_json::to_string(&unknown).expect("failed to serialize unknown");
639 assert!(
640 serialized.contains("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"),
641 "serialized unknown should contain cid string: {serialized}"
642 );
643 }
644 }
645}