use serde::{Deserialize, Serialize};
use serde_reflection::{
json_converter::{DeserializationContext, EmptyEnvironment},
Format, Registry,
};
#[derive(Serialize, Deserialize, Debug, Eq, Clone, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub struct Formats {
pub registry: Registry,
pub operation: Format,
pub response: Format,
pub message: Format,
pub event_value: Format,
}
pub trait BcsApplication {
type Abi;
fn formats() -> serde_reflection::Result<Formats>;
}
pub fn bcs_to_json(
bytes: &[u8],
format: &Format,
registry: &Registry,
) -> bcs::Result<serde_json::Value> {
let context = DeserializationContext {
format: format.clone(),
registry,
environment: &EmptyEnvironment,
};
bcs::from_bytes_seed(context, bytes)
}
impl Formats {
pub fn decode_operation(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
bcs_to_json(bytes, &self.operation, &self.registry)
}
pub fn decode_response(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
bcs_to_json(bytes, &self.response, &self.registry)
}
pub fn decode_message(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
bcs_to_json(bytes, &self.message, &self.registry)
}
pub fn decode_event_value(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
bcs_to_json(bytes, &self.event_value, &self.registry)
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_reflection::{Samples, Tracer, TracerConfig};
use super::*;
fn trace_format<T>() -> (Format, Registry)
where
T: Serialize + for<'de> Deserialize<'de>,
{
let mut tracer = Tracer::new(
TracerConfig::default()
.record_samples_for_newtype_structs(true)
.record_samples_for_tuple_structs(true),
);
let samples = Samples::new();
let (format, _) = tracer.trace_type::<T>(&samples).unwrap();
let registry = tracer.registry().unwrap();
(format, registry)
}
#[test]
fn primitive_round_trip() {
let (format, registry) = trace_format::<u64>();
let bytes = bcs::to_bytes(&42u64).unwrap();
let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
assert_eq!(value, json!(42));
}
#[test]
fn struct_round_trip() {
#[derive(Serialize, Deserialize)]
struct Point {
x: i32,
y: i32,
}
let (format, registry) = trace_format::<Point>();
let bytes = bcs::to_bytes(&Point { x: 10, y: -7 }).unwrap();
let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
assert_eq!(value, json!({ "x": 10, "y": -7 }));
}
#[test]
fn enum_unit_and_struct_variants() {
#[derive(Serialize, Deserialize)]
enum Op {
Increment,
Set { value: u64 },
Add(i64, i64),
}
let (format, registry) = trace_format::<Op>();
let bytes = bcs::to_bytes(&Op::Increment).unwrap();
let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
assert_eq!(value, json!({ "Increment": null }));
let bytes = bcs::to_bytes(&Op::Set { value: 99 }).unwrap();
let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
assert_eq!(value, json!({ "Set": { "value": 99 } }));
let bytes = bcs::to_bytes(&Op::Add(2, 3)).unwrap();
let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
assert_eq!(value, json!({ "Add": [2, 3] }));
}
#[test]
fn nested_with_option_and_seq() {
#[derive(Serialize, Deserialize)]
struct Outer {
tag: String,
items: Vec<u32>,
note: Option<String>,
}
let (format, registry) = trace_format::<Outer>();
let value = Outer {
tag: "hello".to_string(),
items: vec![1, 2, 3],
note: None,
};
let bytes = bcs::to_bytes(&value).unwrap();
let json_value = bcs_to_json(&bytes, &format, ®istry).unwrap();
assert_eq!(
json_value,
json!({ "tag": "hello", "items": [1, 2, 3], "note": null })
);
}
#[test]
fn formats_decode_helpers() {
#[derive(Serialize, Deserialize)]
enum Operation {
Ping,
Echo(String),
}
#[derive(Serialize, Deserialize)]
struct Response {
ok: bool,
}
let (operation, op_registry) = trace_format::<Operation>();
let (response, resp_registry) = trace_format::<Response>();
let mut registry = op_registry;
registry.extend(resp_registry);
let (message, _) = trace_format::<()>();
let (event_value, _) = trace_format::<()>();
let formats = Formats {
registry,
operation,
response,
message,
event_value,
};
let op_bytes = bcs::to_bytes(&Operation::Echo("hi".to_string())).unwrap();
assert_eq!(
formats.decode_operation(&op_bytes).unwrap(),
json!({ "Echo": "hi" })
);
let resp_bytes = bcs::to_bytes(&Response { ok: true }).unwrap();
assert_eq!(
formats.decode_response(&resp_bytes).unwrap(),
json!({ "ok": true })
);
let unit_bytes = bcs::to_bytes(&()).unwrap();
assert_eq!(formats.decode_message(&unit_bytes).unwrap(), json!(null));
assert_eq!(
formats.decode_event_value(&unit_bytes).unwrap(),
json!(null)
);
}
#[test]
fn malformed_bytes_return_error() {
let (format, registry) = trace_format::<u64>();
assert!(bcs_to_json(&[1, 2, 3], &format, ®istry).is_err());
}
}