//! convert an ipld_core::ipld::Ipld enum into a serde_json::value::Value in the atproto data model //! //! a specific helper is required for this as Bytes and Link have differing representations to how serde_json handles them by default //! //! in general. types are naievely converted. the following types have special cases: //! - `integer`: this could throw an error if the number is `x` in `i64::MIN < x < u64::MAX` //! - `float`: always issues a warning since this is technically illegal. If its NaN or infinity, this errors as they cant be represented in json //! - `bytes`: atproto JSON represents them as `{"$bytes": "BASE 64 NO PADDING"}`, but serde_json defaults to `[u8]` //! - `link`: atproto JSON represents them as `{"$link": "BASE 32 NO PADDING"}`, but serde_json defaults to `[u8]` use base64::{Engine, prelude::BASE64_STANDARD_NO_PAD}; use ipld_core::{cid::multibase::Base, ipld::Ipld}; use log::warn; use serde_json::{Map, Number, Value, json}; use thiserror::Error; #[derive(Error, Debug)] pub enum Error { #[error("CID error: {}", .0)] Cid(#[from] ipld_core::cid::Error), #[error("Number too big: {0} > {1} || {0} < {2}", .val, u64::MAX, i64::MIN)] IntInvalidSize { val: i128 }, #[error("{} was NaN or Infinity", .0)] FloatInvalidSize(f64), } /// Convert any decoded IPLD data into serde_json Value /// /// This can be used for `sqlx` queries or decoded into plain JSON /// /// We can't use bog standard `to_string` or `to_json` functions /// due to the fact that ATProto DAG-CBOR does not map straight /// to ATProto JSON (see: `Ipld::Bytes` and `Ipld::Link` in the function definition.) /// /// To get an atproto JSON representation in string format call /// `Value::to_string(&self)` on the Value from this function pub fn ipld_to_json_value(data: &Ipld) -> Result { Ok(match data { Ipld::Null => Value::Null, Ipld::Bool(bool) => Value::Bool(*bool), Ipld::Integer(int) => { Value::Number(Number::from_i128(*int).ok_or(Error::IntInvalidSize { val: *int })?) } Ipld::Float(float) => { warn!("Got float in IPLD data: {}", float); Value::Number(Number::from_f64(*float).ok_or(Error::FloatInvalidSize(*float))?) } Ipld::String(str) => Value::String(str.clone()), Ipld::Bytes(items) => json!({ "$bytes": BASE64_STANDARD_NO_PAD.encode(items) }), Ipld::List(iplds) => Value::Array( iplds .iter() .map(ipld_to_json_value) .collect::, _>>()?, ), Ipld::Map(map) => Value::Object( map.iter() .map(|(k, v)| Ok::<_, Error>((k.clone(), ipld_to_json_value(v)?))) .collect::, _>>()?, ), Ipld::Link(cid) => json!({ "$link": cid.to_string_of_base(Base::Base32Lower)? }), }) }