Remove ciborium in favor of custom deserialization logic
Unfortunately, looks like serde is not flexible enough to support everything CBOR does, so a lot of messages cannot be deserialized properly. Other serde-based CBOR libraries suffer from the same problem. So now we have a bunch of boring deserialization logic supported by sk-cbor
This commit is contained in:
parent
ffccdc40fe
commit
642a3d57cc
|
@ -437,33 +437,6 @@ dependencies = [
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ciborium"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
|
|
||||||
dependencies = [
|
|
||||||
"ciborium-io",
|
|
||||||
"ciborium-ll",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ciborium-io"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ciborium-ll"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
|
|
||||||
dependencies = [
|
|
||||||
"ciborium-io",
|
|
||||||
"half",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cid"
|
name = "cid"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
@ -1035,12 +1008,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "half"
|
|
||||||
version = "1.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
@ -2341,7 +2308,6 @@ dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"chat-gpt-lib-rs",
|
"chat-gpt-lib-rs",
|
||||||
"chrono",
|
"chrono",
|
||||||
"ciborium",
|
|
||||||
"clap",
|
"clap",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
@ -2354,6 +2320,7 @@ dependencies = [
|
||||||
"scooby",
|
"scooby",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_ipld_dagcbor",
|
"serde_ipld_dagcbor",
|
||||||
|
"sk-cbor",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
|
@ -3091,6 +3058,12 @@ dependencies = [
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sk-cbor"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e94879a793aba6e65d691f345cfd172c4cc924a78259d5f9612a2cbfb78847a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
|
|
@ -14,7 +14,6 @@ atrium-xrpc = "0.4.0"
|
||||||
axum = "0.6.20"
|
axum = "0.6.20"
|
||||||
chat-gpt-lib-rs = "0.2.1"
|
chat-gpt-lib-rs = "0.2.1"
|
||||||
chrono = "0.4.31"
|
chrono = "0.4.31"
|
||||||
ciborium = "0.2.1"
|
|
||||||
clap = { version = "4.4.4", features = ["derive"] }
|
clap = { version = "4.4.4", features = ["derive"] }
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
|
@ -27,6 +26,7 @@ rs-car = "0.4.1"
|
||||||
scooby = "0.5.0"
|
scooby = "0.5.0"
|
||||||
serde = "1.0.188"
|
serde = "1.0.188"
|
||||||
serde_ipld_dagcbor = "0.4.2"
|
serde_ipld_dagcbor = "0.4.2"
|
||||||
|
sk-cbor = "0.1.2"
|
||||||
sqlx = { version = "0.7.1", default-features = false, features = ["postgres", "runtime-tokio-native-tls", "chrono"] }
|
sqlx = { version = "0.7.1", default-features = false, features = ["postgres", "runtime-tokio-native-tls", "chrono"] }
|
||||||
tokio = { version = "1.32.0", features = ["full"] }
|
tokio = { version = "1.32.0", features = ["full"] }
|
||||||
tokio-tungstenite = { version = "0.20.0", features = ["native-tls"] }
|
tokio-tungstenite = { version = "0.20.0", features = ["native-tls"] }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
mod client;
|
mod client;
|
||||||
mod proto;
|
mod proto;
|
||||||
mod streaming;
|
mod streaming;
|
||||||
|
mod decode;
|
||||||
|
|
||||||
pub use client::Bluesky;
|
pub use client::Bluesky;
|
||||||
pub use streaming::{CommitDetails, CommitProcessor, Operation};
|
pub use streaming::{CommitDetails, CommitProcessor, Operation};
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
use sk_cbor::Value;
|
||||||
|
use anyhow::{Result, Error, anyhow};
|
||||||
|
|
||||||
|
type CborMap = Vec<(Value, Value)>;
|
||||||
|
|
||||||
|
pub struct PostRecord {
|
||||||
|
pub langs: Option<Vec<String>>,
|
||||||
|
pub text: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&CborMap> for PostRecord {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(root: &CborMap) -> Result<Self> {
|
||||||
|
let mut text: Option<&str> = None;
|
||||||
|
let mut langs: Option<Vec<&str>> = None;
|
||||||
|
|
||||||
|
for (key, value) in iter_string_keys(&root) {
|
||||||
|
match key {
|
||||||
|
"text" => { text = Some(string(value)?) },
|
||||||
|
"langs" => { langs = Some(array_of_strings(value)?) },
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PostRecord {
|
||||||
|
text: text.ok_or_else(|| anyhow!("Missing field: text"))?.to_owned(),
|
||||||
|
langs: langs.map(|v| v.into_iter().map(str::to_owned).collect()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LikeRecord {
|
||||||
|
pub subject: Subject,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&CborMap> for LikeRecord {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(root: &CborMap) -> Result<Self> {
|
||||||
|
let mut subject = None;
|
||||||
|
|
||||||
|
for (key, value) in iter_string_keys(&root) {
|
||||||
|
match key {
|
||||||
|
"subject" => { subject = Some(map(value)?.try_into()?) },
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LikeRecord {
|
||||||
|
subject: subject.ok_or_else(|| anyhow!("Missing field: subject"))?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Subject {
|
||||||
|
pub cid: String,
|
||||||
|
pub uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&CborMap> for Subject {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(root: &CborMap) -> Result<Self> {
|
||||||
|
let mut cid = None;
|
||||||
|
let mut uri = None;
|
||||||
|
|
||||||
|
for (key, value) in iter_string_keys(&root) {
|
||||||
|
match key {
|
||||||
|
"cid" => { cid = Some(string(value)?) },
|
||||||
|
"uri" => { uri = Some(string(value)?) },
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Subject {
|
||||||
|
cid: cid.ok_or_else(|| anyhow!("Missing field: cid"))?.to_owned(),
|
||||||
|
uri: uri.ok_or_else(|| anyhow!("Missing field: uri"))?.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FollowRecord {
|
||||||
|
pub subject: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&CborMap> for FollowRecord {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(root: &CborMap) -> Result<Self> {
|
||||||
|
let mut subject = None;
|
||||||
|
|
||||||
|
for (key, value) in iter_string_keys(&root) {
|
||||||
|
match key {
|
||||||
|
"subject" => { subject = Some(string(value)?) },
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FollowRecord {
|
||||||
|
subject: subject.ok_or_else(|| anyhow!("Missing field: subject"))?.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_record<T: for<'a> TryFrom<&'a CborMap, Error = Error>>(bytes: &[u8]) -> Result<T> {
|
||||||
|
let root = match sk_cbor::read(bytes) {
|
||||||
|
Err(_) => return Err(anyhow!("Could not decode anything")),
|
||||||
|
Ok(v) => v
|
||||||
|
};
|
||||||
|
|
||||||
|
let root_map = match root {
|
||||||
|
Value::Map(m) => m,
|
||||||
|
_ => return Err(anyhow!("Expected root object to be a map")),
|
||||||
|
};
|
||||||
|
|
||||||
|
(&root_map).try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_string_keys(map: &CborMap) -> impl Iterator<Item = (&str, &Value)> {
|
||||||
|
map.into_iter().flat_map(|(k, v)| {
|
||||||
|
match k {
|
||||||
|
Value::TextString(k) => Some((k.as_str(), v)),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map(value: &Value) -> Result<&CborMap> {
|
||||||
|
match value {
|
||||||
|
Value::Map(m) => Ok(m),
|
||||||
|
_ => Err(anyhow!("Expected a map")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string(value: &Value) -> Result<&str> {
|
||||||
|
match value {
|
||||||
|
Value::TextString(value) => Ok(value.as_str()),
|
||||||
|
_ => Err(anyhow!("Expected string"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn array_of_strings(value: &Value) -> Result<Vec<&str>> {
|
||||||
|
match value {
|
||||||
|
Value::Array(vec) => {
|
||||||
|
let mut res = Vec::with_capacity(vec.len());
|
||||||
|
for vec_value in vec {
|
||||||
|
res.push(string(&vec_value)?)
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
_ => Err(anyhow!("Expected array"))
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use atrium_api::com::atproto::sync::subscribe_repos::{Commit, Message};
|
use atrium_api::com::atproto::sync::subscribe_repos::{Commit, Message};
|
||||||
|
|
||||||
use super::proto::Frame;
|
use super::{proto::Frame, decode::{read_record, PostRecord, LikeRecord, FollowRecord}};
|
||||||
|
|
||||||
const COLLECTION_POST: &str = "app.bsky.feed.post";
|
const COLLECTION_POST: &str = "app.bsky.feed.post";
|
||||||
const COLLECTION_LIKE: &str = "app.bsky.feed.like";
|
const COLLECTION_LIKE: &str = "app.bsky.feed.like";
|
||||||
|
@ -109,9 +109,7 @@ async fn extract_operations(commit: &Commit) -> Result<Vec<Operation>> {
|
||||||
|
|
||||||
match collection {
|
match collection {
|
||||||
COLLECTION_POST => {
|
COLLECTION_POST => {
|
||||||
use atrium_api::app::bsky::feed::post::Record;
|
let record: PostRecord = read_record(&block)?;
|
||||||
|
|
||||||
let record: Record = ciborium::from_reader(&mut block.as_slice())?;
|
|
||||||
|
|
||||||
Operation::CreatePost {
|
Operation::CreatePost {
|
||||||
author_did: commit.repo.clone(),
|
author_did: commit.repo.clone(),
|
||||||
|
@ -122,9 +120,7 @@ async fn extract_operations(commit: &Commit) -> Result<Vec<Operation>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
COLLECTION_LIKE => {
|
COLLECTION_LIKE => {
|
||||||
use atrium_api::app::bsky::feed::like::Record;
|
let record: LikeRecord = read_record(&block)?;
|
||||||
|
|
||||||
let record: Record = ciborium::from_reader(&mut block.as_slice())?;
|
|
||||||
|
|
||||||
Operation::CreateLike {
|
Operation::CreateLike {
|
||||||
author_did: commit.repo.clone(),
|
author_did: commit.repo.clone(),
|
||||||
|
@ -135,9 +131,7 @@ async fn extract_operations(commit: &Commit) -> Result<Vec<Operation>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
COLLECTION_FOLLOW => {
|
COLLECTION_FOLLOW => {
|
||||||
use atrium_api::app::bsky::graph::follow::Record;
|
let record: FollowRecord = read_record(&block)?;
|
||||||
|
|
||||||
let record: Record = ciborium::from_reader(&mut block.as_slice())?;
|
|
||||||
|
|
||||||
Operation::CreateFollow {
|
Operation::CreateFollow {
|
||||||
author_did: commit.repo.clone(),
|
author_did: commit.repo.clone(),
|
||||||
|
|
Loading…
Reference in New Issue