this repo has no description
1//! convert an ipld_core::ipld::Ipld enum into a serde_json::value::Value in the atproto data model
2//!
3//! a specific helper is required for this as Bytes and Link have differing representations to how serde_json handles them by default
4//!
5//! in general. types are naievely converted. the following types have special cases:
6//! - `integer`: this could throw an error if the number is `x` in `i64::MIN < x < u64::MAX`
7//! - `float`: always issues a warning since this is technically illegal. If its NaN or infinity, this errors as they cant be represented in json
8//! - `bytes`: atproto JSON represents them as `{"$bytes": "BASE 64 NO PADDING"}`, but serde_json defaults to `[u8]`
9//! - `link`: atproto JSON represents them as `{"$link": "BASE 32 NO PADDING"}`, but serde_json defaults to `[u8]`
10
11use base64::{Engine, prelude::BASE64_STANDARD_NO_PAD};
12use ipld_core::{cid::multibase::Base, ipld::Ipld};
13use log::warn;
14use serde_json::{Map, Number, Value, json};
15use thiserror::Error;
16
17#[derive(Error, Debug)]
18pub enum Error {
19 #[error("CID error: {}", .0)]
20 Cid(#[from] ipld_core::cid::Error),
21 #[error("Number too big: {0} > {1} || {0} < {2}", .val, u64::MAX, i64::MIN)]
22 IntInvalidSize { val: i128 },
23 #[error("{} was NaN or Infinity", .0)]
24 FloatInvalidSize(f64),
25}
26
27/// Convert any decoded IPLD data into serde_json Value
28///
29/// This can be used for `sqlx` queries or decoded into plain JSON
30///
31/// We can't use bog standard `to_string` or `to_json` functions
32/// due to the fact that ATProto DAG-CBOR does not map straight
33/// to ATProto JSON (see: `Ipld::Bytes` and `Ipld::Link` in the function definition.)
34///
35/// To get an atproto JSON representation in string format call
36/// `Value::to_string(&self)` on the Value from this function
37pub fn ipld_to_json_value(data: &Ipld) -> Result<Value, Error> {
38 Ok(match data {
39 Ipld::Null => Value::Null,
40 Ipld::Bool(bool) => Value::Bool(*bool),
41 Ipld::Integer(int) => {
42 Value::Number(Number::from_i128(*int).ok_or(Error::IntInvalidSize { val: *int })?)
43 }
44 Ipld::Float(float) => {
45 warn!("Got float in IPLD data: {}", float);
46 Value::Number(Number::from_f64(*float).ok_or(Error::FloatInvalidSize(*float))?)
47 }
48 Ipld::String(str) => Value::String(str.clone()),
49 Ipld::Bytes(items) => json!({ "$bytes": BASE64_STANDARD_NO_PAD.encode(items) }),
50 Ipld::List(iplds) => Value::Array(
51 iplds
52 .iter()
53 .map(ipld_to_json_value)
54 .collect::<Result<Vec<_>, _>>()?,
55 ),
56 Ipld::Map(map) => Value::Object(
57 map.iter()
58 .map(|(k, v)| Ok::<_, Error>((k.clone(), ipld_to_json_value(v)?)))
59 .collect::<Result<Map<String, Value>, _>>()?,
60 ),
61 Ipld::Link(cid) => json!({
62 "$link": cid.to_string_of_base(Base::Base32Lower)?
63 }),
64 })
65}