use crate::error::Error;
use ipld_core::ipld::Ipld;
use ipld_core::serde::to_ipld;
use std::collections::BTreeMap;
use std::fmt;
use std::ops::{Deref, DerefMut};
mod cid_link;
pub use cid_link::CidLink;
mod integer;
pub use integer::*;
pub mod string;
use string::RecordKey;
pub trait Collection: fmt::Debug {
const NSID: &'static str;
type Record: fmt::Debug + serde::de::DeserializeOwned + serde::Serialize;
fn nsid() -> string::Nsid {
Self::NSID
.parse()
.expect("Self::NSID should be a valid NSID")
}
fn repo_path(rkey: &RecordKey) -> String {
format!("{}/{}", Self::NSID, rkey.as_str())
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum BlobRef {
Typed(TypedBlobRef),
Untyped(UnTypedBlobRef),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "$type", rename_all = "lowercase")]
pub enum TypedBlobRef {
Blob(Blob),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UnTypedBlobRef {
pub cid: String,
pub mime_type: String,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Blob {
pub r#ref: CidLink,
pub mime_type: String,
pub size: usize, }
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Object<T> {
#[serde(flatten)]
pub data: T,
#[serde(flatten)]
pub extra_data: Ipld,
}
impl<T> From<T> for Object<T> {
fn from(data: T) -> Self {
Self {
data,
extra_data: Ipld::Map(std::collections::BTreeMap::new()),
}
}
}
impl<T> Deref for Object<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T> DerefMut for Object<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum Union<T> {
Refs(T),
Unknown(UnknownData),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct UnknownData {
#[serde(rename = "$type")]
pub r#type: String,
#[serde(flatten)]
pub data: Ipld,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum Unknown {
Object(BTreeMap<String, DataModel>),
Null,
Other(DataModel),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(try_from = "Ipld")]
pub struct DataModel(Ipld);
impl Deref for DataModel {
type Target = Ipld;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for DataModel {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl TryFrom<Ipld> for DataModel {
type Error = Error;
fn try_from(value: Ipld) -> Result<Self, Self::Error> {
match value {
Ipld::Float(_) => Err(Error::NotAllowed),
Ipld::List(list) => {
if list.iter().any(|value| matches!(value, Ipld::Float(_))) {
Err(Error::NotAllowed)
} else {
Ok(DataModel(Ipld::List(list)))
}
}
Ipld::Map(map) => {
if map.values().any(|value| matches!(value, Ipld::Float(_))) {
Err(Error::NotAllowed)
} else {
Ok(DataModel(Ipld::Map(map)))
}
}
data => Ok(DataModel(data)),
}
}
}
pub trait TryFromUnknown: Sized {
type Error;
fn try_from_unknown(value: Unknown) -> Result<Self, Self::Error>;
}
impl<T> TryFromUnknown for T
where
T: serde::de::DeserializeOwned,
{
type Error = Error;
fn try_from_unknown(value: Unknown) -> Result<Self, Self::Error> {
let json = serde_json::to_vec(&value).unwrap();
Ok(serde_json::from_slice(&json).unwrap())
}
}
pub trait TryIntoUnknown {
type Error;
fn try_into_unknown(self) -> Result<Unknown, Self::Error>;
}
impl<T> TryIntoUnknown for T
where
T: serde::Serialize,
{
type Error = Error;
fn try_into_unknown(self) -> Result<Unknown, Self::Error> {
Ok(Unknown::Other(to_ipld(self)?.try_into()?))
}
}
#[cfg(test)]
mod tests {
use super::*;
use ipld_core::cid::Cid;
use serde_json::{from_str, to_string};
const CID_LINK_JSON: &str =
r#"{"$link":"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"}"#;
#[test]
fn cid_link_serde_json() {
let deserialized =
from_str::<CidLink>(CID_LINK_JSON).expect("failed to deserialize cid-link");
let serialized = to_string(&deserialized).expect("failed to serialize cid-link");
assert_eq!(serialized, CID_LINK_JSON);
}
#[test]
fn blob_ref_typed_deserialize_json() {
let json = format!(
r#"{{"$type":"blob","ref":{},"mimeType":"text/plain","size":0}}"#,
CID_LINK_JSON
);
let deserialized = from_str::<BlobRef>(&json).expect("failed to deserialize blob-ref");
assert_eq!(
deserialized,
BlobRef::Typed(TypedBlobRef::Blob(Blob {
r#ref: CidLink::try_from(
"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"
)
.expect("failed to create cid-link"),
mime_type: "text/plain".into(),
size: 0
}))
);
}
#[test]
fn blob_ref_untyped_deserialize_json() {
let json = r#"{"cid":"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy","mimeType":"text/plain"}"#;
let deserialized = from_str::<BlobRef>(json).expect("failed to deserialize blob-ref");
assert_eq!(
deserialized,
BlobRef::Untyped(UnTypedBlobRef {
cid: "bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy".into(),
mime_type: "text/plain".into(),
})
);
}
#[test]
fn blob_ref_serialize_json() {
let blob_ref = BlobRef::Typed(TypedBlobRef::Blob(Blob {
r#ref: CidLink::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy")
.expect("failed to create cid-link"),
mime_type: "text/plain".into(),
size: 0,
}));
let serialized = to_string(&blob_ref).expect("failed to serialize blob-ref");
assert_eq!(
serialized,
format!(
r#"{{"$type":"blob","ref":{},"mimeType":"text/plain","size":0}}"#,
CID_LINK_JSON
)
);
}
#[test]
fn blob_ref_deserialize_dag_cbor() {
let dag_cbor = [
0xa4, 0x65, 0x24, 0x74, 0x79, 0x70, 0x65, 0x64, 0x62, 0x6c, 0x6f, 0x62, 0x63, 0x72,
0x65, 0x66, 0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x55, 0x12, 0x20, 0x2c, 0x26, 0xb4,
0x6b, 0x68, 0xff, 0xc6, 0x8f, 0xf9, 0x9b, 0x45, 0x3c, 0x1d, 0x30, 0x41, 0x34, 0x13,
0x42, 0x2d, 0x70, 0x64, 0x83, 0xbf, 0xa0, 0xf9, 0x8a, 0x5e, 0x88, 0x62, 0x66, 0xe7,
0xae, 0x68, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x6a, 0x74, 0x65, 0x78,
0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x00,
];
let deserialized = serde_ipld_dagcbor::from_slice::<BlobRef>(dag_cbor.as_slice())
.expect("failed to deserialize blob-ref");
assert_eq!(
deserialized,
BlobRef::Typed(TypedBlobRef::Blob(Blob {
r#ref: CidLink::try_from(
"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"
)
.expect("failed to create cid-link"),
mime_type: "text/plain".into(),
size: 0,
}))
);
}
#[test]
fn data_model() {
assert!(DataModel::try_from(Ipld::Null).is_ok());
assert!(DataModel::try_from(Ipld::Bool(true)).is_ok());
assert!(DataModel::try_from(Ipld::Integer(1)).is_ok());
assert!(
DataModel::try_from(Ipld::Float(1.5)).is_err(),
"float value should fail"
);
assert!(DataModel::try_from(Ipld::String("s".into())).is_ok());
assert!(DataModel::try_from(Ipld::Bytes(vec![0x01])).is_ok());
assert!(DataModel::try_from(Ipld::List(vec![Ipld::Bool(true)])).is_ok());
assert!(
DataModel::try_from(Ipld::List(vec![Ipld::Bool(true), Ipld::Float(1.5)])).is_err(),
"list with float value should fail"
);
assert!(DataModel::try_from(Ipld::Map(BTreeMap::from_iter([(
String::from("k"),
Ipld::Bool(true)
)])))
.is_ok());
assert!(
DataModel::try_from(Ipld::Map(BTreeMap::from_iter([(
String::from("k"),
Ipld::Float(1.5)
)])))
.is_err(),
"map with float value should fail"
);
assert!(DataModel::try_from(Ipld::Link(
Cid::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy")
.expect("failed to create cid")
))
.is_ok());
}
#[test]
fn union() {
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "$type")]
enum FooRefs {
#[serde(rename = "example.com#bar")]
Bar(Box<Bar>),
#[serde(rename = "example.com#baz")]
Baz(Box<Baz>),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
struct Bar {
bar: String,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
struct Baz {
baz: i32,
}
type Foo = Union<FooRefs>;
let foo = serde_json::from_str::<Foo>(r#"{"$type":"example.com#bar","bar":"bar"}"#)
.expect("failed to deserialize foo");
assert_eq!(
foo,
Union::Refs(FooRefs::Bar(Box::new(Bar {
bar: String::from("bar")
})))
);
let foo = serde_json::from_str::<Foo>(r#"{"$type":"example.com#baz","baz":42}"#)
.expect("failed to deserialize foo");
assert_eq!(foo, Union::Refs(FooRefs::Baz(Box::new(Baz { baz: 42 }))));
let foo = serde_json::from_str::<Foo>(r#"{"$type":"example.com#foo","foo":true}"#)
.expect("failed to deserialize foo");
assert_eq!(
foo,
Union::Unknown(UnknownData {
r#type: String::from("example.com#foo"),
data: Ipld::Map(BTreeMap::from_iter([(
String::from("foo"),
Ipld::Bool(true)
)]))
})
);
}
#[test]
fn unknown_serialize() {
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
struct Foo {
foo: Unknown,
}
let foo = Foo {
foo: Unknown::Object(BTreeMap::from_iter([(
String::from("bar"),
DataModel(Ipld::String(String::from("bar"))),
)])),
};
let serialized = to_string(&foo).expect("failed to serialize foo");
assert_eq!(serialized, r#"{"foo":{"bar":"bar"}}"#);
}
#[test]
fn unknown_deserialize() {
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
struct Foo {
foo: Unknown,
}
{
let json = r#"{
"foo": {
"$type": "example.com#foo",
"bar": "bar"
}
}"#;
let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
assert_eq!(
deserialized,
Foo {
foo: Unknown::Object(BTreeMap::from_iter([
(
String::from("bar"),
DataModel(Ipld::String(String::from("bar")))
),
(
String::from("$type"),
DataModel(Ipld::String(String::from("example.com#foo")))
)
]))
}
);
}
{
let json = r#"{
"foo": {}
}"#;
let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
assert_eq!(
deserialized,
Foo {
foo: Unknown::Object(BTreeMap::default())
}
);
}
{
let json = r#"{
"foo": {
"bar": "bar"
}
}"#;
let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
assert_eq!(
deserialized,
Foo {
foo: Unknown::Object(BTreeMap::from_iter([(
String::from("bar"),
DataModel(Ipld::String(String::from("bar")))
)]))
}
);
}
{
let json = r#"{
"foo": null
}"#;
let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
assert_eq!(deserialized, Foo { foo: Unknown::Null });
}
{
let json = r#"{
"foo": 42
}"#;
let deserialized = from_str::<Foo>(json).expect("failed to deserialize foo");
assert_eq!(
deserialized,
Foo {
foo: Unknown::Other(DataModel(Ipld::Integer(42)))
}
);
}
{
let json = r#"{
"foo": 42.195
}"#;
assert!(from_str::<Foo>(json).is_err());
}
}
#[test]
fn unknown_try_from() {
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "$type")]
enum Foo {
#[serde(rename = "example.com#bar")]
Bar(Box<Bar>),
#[serde(rename = "example.com#baz")]
Baz(Box<Baz>),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
struct Bar {
bar: String,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
struct Baz {
baz: i32,
}
{
let unknown = Unknown::Object(BTreeMap::from_iter([
(
String::from("$type"),
DataModel(Ipld::String(String::from("example.com#bar"))),
),
(
String::from("bar"),
DataModel(Ipld::String(String::from("barbar"))),
),
]));
let bar = Bar::try_from_unknown(unknown.clone()).expect("failed to convert to Bar");
assert_eq!(
bar,
Bar {
bar: String::from("barbar")
}
);
let barbaz = Foo::try_from_unknown(unknown).expect("failed to convert to Bar");
assert_eq!(
barbaz,
Foo::Bar(Box::new(Bar {
bar: String::from("barbar")
}))
);
}
{
let unknown = Unknown::Object(BTreeMap::from_iter([
(
String::from("$type"),
DataModel(Ipld::String(String::from("example.com#baz"))),
),
(String::from("baz"), DataModel(Ipld::Integer(42))),
]));
let baz = Baz::try_from_unknown(unknown.clone()).expect("failed to convert to Baz");
assert_eq!(baz, Baz { baz: 42 });
let barbaz = Foo::try_from_unknown(unknown).expect("failed to convert to Bar");
assert_eq!(barbaz, Foo::Baz(Box::new(Baz { baz: 42 })));
}
}
}