diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index cfff4bb8..d1331a1b 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -17,4 +17,7 @@ jobs: - name: Build run: cargo build -p atrium-api --verbose - name: Run tests - run: cargo test -p atrium-api --verbose + run: | + cargo test -p atrium-api --lib + cargo test -p atrium-api --lib --no-default-features + cargo test -p atrium-api --lib --all-features diff --git a/atrium-api/Cargo.toml b/atrium-api/Cargo.toml index 060b6112..01b6be0b 100644 --- a/atrium-api/Cargo.toml +++ b/atrium-api/Cargo.toml @@ -13,21 +13,24 @@ keywords = ["atproto", "bluesky"] [dependencies] atrium-xrpc = "0.8.0" async-trait = "0.1.68" -cid = { version = "0.10.1", features = ["serde-codec"] } http = "0.2.9" serde = { version = "1.0.160", features = ["derive"] } serde_bytes = "0.11.9" tokio = { version = "1.33.0", default-features = false, optional = true } +cid = { version = "0.10.1", optional = true } +libipld-core = { version = "0.16.0", optional = true } [features] default = ["agent"] agent = ["tokio/sync"] +dag-cbor = ["cid/serde-codec", "libipld-core/serde-codec"] [dev-dependencies] atrium-xrpc-client = "0.2.0" futures = "0.3.28" serde_json = "1.0.107" tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] } +serde_ipld_dagcbor = { git = "https://github.com/sugyan/serde_ipld_dagcbor.git", rev = "345b240" } [package.metadata.docs.rs] all-features = true diff --git a/atrium-api/benches/cid-link.rs b/atrium-api/benches/cid-link.rs new file mode 100644 index 00000000..04454c4f --- /dev/null +++ b/atrium-api/benches/cid-link.rs @@ -0,0 +1,194 @@ +#![feature(test)] + +extern crate test; +use serde::Deserialize; +use test::Bencher; + +const JSON_STR: &str = r#"{ + "$link": "bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy" +}"#; + +const CBOR_CID: [u8; 41] = [ + 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, +]; + +mod via_ipld { + use super::*; + use libipld_core::ipld::Ipld; + + #[derive(PartialEq, Eq, Debug)] + pub struct CidLink(pub cid::Cid); + + impl<'de> Deserialize<'de> for CidLink { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let ipld = Ipld::deserialize(deserializer)?; + match &ipld { + Ipld::Link(cid) => { + return Ok(Self(*cid)); + } + Ipld::Map(map) => { + if map.len() == 1 { + if let Some(Ipld::String(link)) = map.get("$link") { + return Ok(Self( + cid::Cid::try_from(link.as_str()) + .map_err(serde::de::Error::custom)?, + )); + } + } + } + _ => {} + } + Err(serde::de::Error::custom("Invalid cid-link")) + } + } +} + +mod untagged_1 { + use super::*; + + #[derive(Deserialize, PartialEq, Eq, Debug)] + #[serde(untagged)] + pub enum CidLink { + Raw(cid::Cid), + Object { + #[serde(rename = "$link")] + link: String, + }, + } +} + +mod untagged_2 { + use super::*; + + #[derive(Deserialize, PartialEq, Eq, Debug)] + #[serde(untagged)] + pub enum CidLink { + Object { + #[serde(rename = "$link")] + link: String, + }, + Raw(cid::Cid), + } +} + +mod only_json { + use super::*; + + #[derive(Deserialize, PartialEq, Eq, Debug)] + + pub struct CidLink { + #[serde(rename = "$link")] + pub link: String, + } +} + +mod only_cbor { + use super::*; + + #[derive(Deserialize, PartialEq, Eq, Debug)] + + pub struct CidLink(pub cid::Cid); +} + +fn cid() -> cid::Cid { + serde_ipld_dagcbor::from_slice::(&CBOR_CID).expect("failed to deserialize cid") +} + +#[bench] +fn bench_cbor_untagged_1(b: &mut Bencher) { + let expected = untagged_1::CidLink::Raw(cid()); + + b.iter(|| { + let result = serde_ipld_dagcbor::from_slice::(&CBOR_CID) + .expect("failed to deserialize cid_link"); + assert_eq!(result, expected); + }); +} + +#[bench] +fn bench_json_untagged_1(b: &mut Bencher) { + let expected = untagged_1::CidLink::Object { + link: cid().to_string(), + }; + + b.iter(|| { + let result = serde_json::from_str::(JSON_STR) + .expect("failed to deserialize cid_link"); + assert_eq!(result, expected); + }); +} + +#[bench] +fn bench_cbor_untagged_2(b: &mut Bencher) { + let expected = untagged_2::CidLink::Raw(cid()); + + b.iter(|| { + let result = serde_ipld_dagcbor::from_slice::(&CBOR_CID) + .expect("failed to deserialize cid_link"); + assert_eq!(result, expected); + }); +} + +#[bench] +fn bench_json_untagged_2(b: &mut Bencher) { + let expected = untagged_2::CidLink::Object { + link: cid().to_string(), + }; + + b.iter(|| { + let result = serde_json::from_str::(JSON_STR) + .expect("failed to deserialize cid_link"); + assert_eq!(result, expected); + }); +} + +#[bench] +fn bench_cbor_via_ipld(b: &mut Bencher) { + let expected = via_ipld::CidLink(cid()); + + b.iter(|| { + let result = serde_ipld_dagcbor::from_slice::(&CBOR_CID) + .expect("failed to deserialize cid_link"); + assert_eq!(result, expected); + }); +} + +#[bench] +fn bench_json_via_ipld(b: &mut Bencher) { + let expected = via_ipld::CidLink(cid()); + + b.iter(|| { + let result = serde_json::from_str::(JSON_STR) + .expect("failed to deserialize cid_link"); + assert_eq!(result, expected); + }); +} + +#[bench] +fn bench_json_only(b: &mut Bencher) { + let expected = only_json::CidLink { + link: cid().to_string(), + }; + + b.iter(|| { + let result = serde_json::from_str::(JSON_STR) + .expect("failed to deserialize cid_link"); + assert_eq!(result, expected); + }); +} + +#[bench] +fn bench_cbor_only(b: &mut Bencher) { + let expected = only_cbor::CidLink(cid()); + + b.iter(|| { + let result = serde_ipld_dagcbor::from_slice::(&CBOR_CID) + .expect("failed to deserialize cid_link"); + assert_eq!(result, expected); + }); +} diff --git a/atrium-api/src/app/bsky/actor/profile.rs b/atrium-api/src/app/bsky/actor/profile.rs index cc14d578..cf1fab26 100644 --- a/atrium-api/src/app/bsky/actor/profile.rs +++ b/atrium-api/src/app/bsky/actor/profile.rs @@ -4,9 +4,9 @@ #[serde(rename_all = "camelCase")] pub struct Record { #[serde(skip_serializing_if = "Option::is_none")] - pub avatar: Option, + pub avatar: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub banner: Option, + pub banner: Option, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/atrium-api/src/app/bsky/embed/external.rs b/atrium-api/src/app/bsky/embed/external.rs index 2f860577..50c1f42c 100644 --- a/atrium-api/src/app/bsky/embed/external.rs +++ b/atrium-api/src/app/bsky/embed/external.rs @@ -11,7 +11,7 @@ pub struct Main { pub struct External { pub description: String, #[serde(skip_serializing_if = "Option::is_none")] - pub thumb: Option, + pub thumb: Option, pub title: String, pub uri: String, } diff --git a/atrium-api/src/app/bsky/embed/images.rs b/atrium-api/src/app/bsky/embed/images.rs index e6058a04..bd0b63c1 100644 --- a/atrium-api/src/app/bsky/embed/images.rs +++ b/atrium-api/src/app/bsky/embed/images.rs @@ -19,7 +19,7 @@ pub struct Image { pub alt: String, #[serde(skip_serializing_if = "Option::is_none")] pub aspect_ratio: Option, - pub image: crate::blob::BlobRef, + pub image: crate::types::BlobRef, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] diff --git a/atrium-api/src/app/bsky/feed/generator.rs b/atrium-api/src/app/bsky/feed/generator.rs index 1f8f192a..38b0c3a5 100644 --- a/atrium-api/src/app/bsky/feed/generator.rs +++ b/atrium-api/src/app/bsky/feed/generator.rs @@ -4,7 +4,7 @@ #[serde(rename_all = "camelCase")] pub struct Record { #[serde(skip_serializing_if = "Option::is_none")] - pub avatar: Option, + pub avatar: Option, pub created_at: String, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, diff --git a/atrium-api/src/app/bsky/graph/list.rs b/atrium-api/src/app/bsky/graph/list.rs index e553d0ae..aeba26d6 100644 --- a/atrium-api/src/app/bsky/graph/list.rs +++ b/atrium-api/src/app/bsky/graph/list.rs @@ -4,7 +4,7 @@ #[serde(rename_all = "camelCase")] pub struct Record { #[serde(skip_serializing_if = "Option::is_none")] - pub avatar: Option, + pub avatar: Option, pub created_at: String, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, diff --git a/atrium-api/src/com/atproto/label/subscribe_labels.rs b/atrium-api/src/com/atproto/label/subscribe_labels.rs index 5345fa9a..eb6d4806 100644 --- a/atrium-api/src/com/atproto/label/subscribe_labels.rs +++ b/atrium-api/src/com/atproto/label/subscribe_labels.rs @@ -1,5 +1,6 @@ // This file is generated by atrium-codegen. DO NOT EDIT. //!Definitions for the `com.atproto.label.subscribeLabels` namespace. +pub const NSID: &str = "com.atproto.label.subscribeLabels"; #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Parameters { diff --git a/atrium-api/src/com/atproto/repo/upload_blob.rs b/atrium-api/src/com/atproto/repo/upload_blob.rs index 9a67ce57..9eaa3929 100644 --- a/atrium-api/src/com/atproto/repo/upload_blob.rs +++ b/atrium-api/src/com/atproto/repo/upload_blob.rs @@ -3,7 +3,7 @@ #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Output { - pub blob: crate::blob::BlobRef, + pub blob: crate::types::BlobRef, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(tag = "error", content = "message")] diff --git a/atrium-api/src/com/atproto/sync/subscribe_repos.rs b/atrium-api/src/com/atproto/sync/subscribe_repos.rs index cb248882..3f84d3de 100644 --- a/atrium-api/src/com/atproto/sync/subscribe_repos.rs +++ b/atrium-api/src/com/atproto/sync/subscribe_repos.rs @@ -1,5 +1,6 @@ // This file is generated by atrium-codegen. DO NOT EDIT. //!Definitions for the `com.atproto.sync.subscribeRepos` namespace. +pub const NSID: &str = "com.atproto.sync.subscribeRepos"; #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Parameters { @@ -16,14 +17,14 @@ pub enum Error { #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Commit { - pub blobs: Vec, + pub blobs: Vec, ///CAR file containing relevant blocks. #[serde(with = "serde_bytes")] pub blocks: Vec, - pub commit: cid::Cid, + pub commit: crate::types::CidLink, pub ops: Vec, #[serde(skip_serializing_if = "Option::is_none")] - pub prev: Option, + pub prev: Option, pub rebase: bool, pub repo: String, ///The rev of the emitted commit. @@ -65,7 +66,7 @@ pub struct Migrate { pub struct RepoOp { pub action: String, #[serde(skip_serializing_if = "Option::is_none")] - pub cid: Option, + pub cid: Option, pub path: String, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] diff --git a/atrium-api/src/lib.rs b/atrium-api/src/lib.rs index 42177886..f1fe27df 100644 --- a/atrium-api/src/lib.rs +++ b/atrium-api/src/lib.rs @@ -5,8 +5,8 @@ pub use atrium_xrpc as xrpc; #[cfg(feature = "agent")] pub mod agent; pub mod app; -pub mod blob; pub mod client; pub mod com; pub mod did_doc; pub mod records; +pub mod types; diff --git a/atrium-api/src/types.rs b/atrium-api/src/types.rs new file mode 100644 index 00000000..5e0c6b12 --- /dev/null +++ b/atrium-api/src/types.rs @@ -0,0 +1,141 @@ +//! Definitions for AT Protocol's data models. +//! + +#[cfg(feature = "dag-cbor")] +mod cid_link_ipld; +#[cfg(not(feature = "dag-cbor"))] +mod cid_link_json; + +#[cfg(feature = "dag-cbor")] +pub use cid_link_ipld::CidLink; +#[cfg(not(feature = "dag-cbor"))] +pub use cid_link_json::CidLink; + +/// Definitions for Blob types. +/// Usually a map with `$type` is used, but deprecated legacy formats are also supported for parsing. +/// +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(untagged)] +pub enum BlobRef { + Typed(TypedBlobRef), + Untyped(UnTypedBlobRef), +} + +/// Current, typed blob reference. +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(tag = "$type", rename_all = "lowercase")] +pub enum TypedBlobRef { + Blob(Blob), +} + +/// An untyped blob reference. +/// Some records in the wild still contain this format, but should never write them. +#[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, // TODO +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::{from_str, to_string}; + + const CID_LINK_JSON: &str = + r#"{"$link":"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"}"#; + + #[test] + fn test_cid_link_serde_json() { + let deserialized = + from_str::(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 test_blob_ref_typed_deserialize_json() { + let json = format!( + r#"{{"$type":"blob","ref":{},"mimeType":"text/plain","size":0}}"#, + CID_LINK_JSON + ); + let deserialized = from_str::(&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 test_blob_ref_untyped_deserialize_json() { + let json = r#"{"cid":"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy","mimeType":"text/plain"}"#; + let deserialized = from_str::(json).expect("failed to deserialize blob-ref"); + assert_eq!( + deserialized, + BlobRef::Untyped(UnTypedBlobRef { + cid: "bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy".into(), + mime_type: "text/plain".into(), + }) + ); + } + + #[test] + fn test_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 + ) + ); + } + + #[cfg(feature = "dag-cbor")] + #[test] + fn test_blob_ref_deserialize_dag_cbor() { + // {"$type": "blob", "mimeType": "text/plain", "ref": bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy, "size": 0} + 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::(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, + })) + ); + } +} diff --git a/atrium-api/src/types/cid_link_ipld.rs b/atrium-api/src/types/cid_link_ipld.rs new file mode 100644 index 00000000..f6c81935 --- /dev/null +++ b/atrium-api/src/types/cid_link_ipld.rs @@ -0,0 +1,165 @@ +use cid::{Cid, Error}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[cfg_attr(docsrs, doc(cfg(feature = "dag-cbor")))] +/// Representation of an IPLD Link. +/// This type is used when the `dag-cbor` feature is enabled. +/// Otherwise, it will be an object for JSON encoding with +/// the single key `$link` and the string-encoded CID as a value. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CidLink(pub Cid); + +#[derive(Serialize, Deserialize)] +struct Link { + #[serde(rename = "$link")] + link: String, +} + +impl Serialize for CidLink { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + Link { + link: self.0.to_string(), + } + .serialize(serializer) + } else { + self.0.serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for CidLink { + fn deserialize>(deserializer: D) -> Result { + let ipld = Ipld::deserialize(deserializer)?; + match &ipld { + Ipld::Link(cid) => { + return Ok(Self(*cid)); + } + Ipld::Map(map) => { + if map.len() == 1 { + if let Some(Ipld::String(link)) = map.get("$link") { + return Ok(Self( + cid::Cid::try_from(link.as_str()).map_err(serde::de::Error::custom)?, + )); + } + } + } + _ => {} + } + Err(serde::de::Error::custom("Invalid cid-link")) + } +} + +impl TryFrom<&str> for CidLink { + type Error = Error; + + fn try_from(s: &str) -> Result { + Ok(Self(Cid::try_from(s)?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_ipld_dagcbor::{from_slice, to_vec}; + use serde_json::{from_str, to_string}; + + const CID_LINK_JSON: &str = + r#"{"$link":"bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy"}"#; + + const CID_LINK_DAGCBOR: [u8; 41] = [ + 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, + ]; + + fn cid() -> Cid { + Cid::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy").unwrap() + } + + #[test] + fn test_cid_link_serialize_json() { + let cid_link = CidLink(cid()); + + let serialized = to_string(&cid_link).expect("failed to serialize cid-link"); + assert_eq!(serialized, CID_LINK_JSON); + } + + #[test] + fn test_cid_link_serialize_dagcbor() { + let cid_link = CidLink(cid()); + + let serialized = to_vec(&cid_link).expect("failed to serialize cid-link"); + assert_eq!(serialized, CID_LINK_DAGCBOR); + } + + #[test] + fn test_cid_link_deserialize_json() { + let deserialized = + from_str::(CID_LINK_JSON).expect("failed to deserialize cid-link"); + + assert_eq!(deserialized, CidLink(cid())); + } + + #[test] + fn test_cid_link_deserialize_dagcbor() { + let deserialized = + from_slice::(&CID_LINK_DAGCBOR).expect("failed to deserialize cid-link"); + + assert_eq!(deserialized, CidLink(cid())); + } + + #[test] + fn test_cid_link_deserialize_any_json() { + #[derive(Deserialize, Debug, PartialEq, Eq)] + #[serde(untagged)] + enum Enum { + CidLink(CidLink), + } + + let deserialized = from_str::(CID_LINK_JSON).expect("failed to deserialize cid-link"); + assert_eq!(deserialized, Enum::CidLink(CidLink(cid()))); + } + + #[test] + fn test_cid_link_deserialize_any_dagcbor() { + #[derive(Deserialize, Debug, PartialEq, Eq)] + #[serde(untagged)] + enum Enum { + CidLink(CidLink), + } + + let deserialized = + from_slice::(&CID_LINK_DAGCBOR).expect("failed to deserialize cid-link"); + assert_eq!(deserialized, Enum::CidLink(CidLink(cid()))); + } + + #[test] + fn test_cid_link_serde_json() { + // let deserialized = + // from_str::(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); + + let cid_link = CidLink(cid()); + let serialized = to_string(&cid_link).expect("failed to serialize cid-link"); + let deserialized = + from_str::(&serialized).expect("failed to deserialize cid-link"); + assert_eq!(deserialized, cid_link); + } + + #[test] + fn test_cid_link_serde_dagcbor() { + let deserialized = + from_slice::(&CID_LINK_DAGCBOR).expect("failed to deserialize cid-link"); + let serialized = to_vec(&deserialized).expect("failed to serialize cid-link"); + assert_eq!(serialized, CID_LINK_DAGCBOR); + + let cid_link = CidLink(cid()); + let serialized = to_vec(&cid_link).expect("failed to serialize cid-link"); + let deserialized = + from_slice::(&serialized).expect("failed to deserialize cid-link"); + assert_eq!(deserialized, cid_link); + } +} diff --git a/atrium-api/src/types/cid_link_json.rs b/atrium-api/src/types/cid_link_json.rs new file mode 100644 index 00000000..9e74d304 --- /dev/null +++ b/atrium-api/src/types/cid_link_json.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +/// Representation of an IPLD Link. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct CidLink { + #[serde(rename = "$link")] + pub link: String, +} + +impl TryFrom<&str> for CidLink { + type Error = (); + + fn try_from(s: &str) -> Result { + Ok(Self { + link: s.to_string(), + }) + } +} diff --git a/atrium-codegen/src/generator.rs b/atrium-codegen/src/generator.rs index 54aee08f..973948af 100644 --- a/atrium-codegen/src/generator.rs +++ b/atrium-codegen/src/generator.rs @@ -24,8 +24,15 @@ pub(crate) fn generate_schemas( if let Some(basename) = paths.pop() { let mut tokens = Vec::new(); let mut names = Vec::new(); - // main def for (name, def) in &schema.defs { + // NSID (for XrpcSubscription only) + if let LexUserType::XrpcSubscription(_) = &def { + let nsid = schema.id.clone(); + tokens.push(quote! { + pub const NSID: &str = #nsid; + }); + } + // main def if name == "main" { tokens.push(user_type(def, basename, true)?); } else { @@ -128,11 +135,12 @@ pub(crate) fn generate_client( pub(crate) fn generate_modules(outdir: &Path) -> Result, Box> { let mut paths = find_dirs(outdir)?; + paths.reverse(); paths.retain(|p| { p.as_ref() != outdir && p.as_ref() .strip_prefix(outdir) - .map_or(true, |p| !p.starts_with("agent")) + .map_or(true, |p| !p.starts_with("agent") && !p.starts_with("types")) }); let mut files = Vec::with_capacity(paths.len()); // generate ".rs" files names diff --git a/atrium-codegen/src/token_stream.rs b/atrium-codegen/src/token_stream.rs index 880633f9..bb5d7c18 100644 --- a/atrium-codegen/src/token_stream.rs +++ b/atrium-codegen/src/token_stream.rs @@ -182,7 +182,6 @@ fn lex_subscription(subscription: &LexXrpcSubscription) -> Result { } else { quote!() }; - // TODO: message let errors = xrpc_errors(&subscription.errors)?; Ok(quote! { #params @@ -328,7 +327,7 @@ fn bytes_type(bytes: &LexBytes) -> Result<(TokenStream, TokenStream)> { fn cid_link_type(cid_link: &LexCidLink) -> Result<(TokenStream, TokenStream)> { let description = description(&cid_link.description); - Ok((description, quote!(cid::Cid))) + Ok((description, quote!(crate::types::CidLink))) } fn array_type( @@ -363,7 +362,7 @@ fn array_type( fn blob_type(blob: &LexBlob) -> Result<(TokenStream, TokenStream)> { let description = description(&blob.description); - Ok((description, quote!(crate::blob::BlobRef))) + Ok((description, quote!(crate::types::BlobRef))) } fn boolean_type(boolean: &LexBoolean) -> Result<(TokenStream, TokenStream)> {