use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum Mode {
State,
Append,
List,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SortOrder {
Asc,
Desc,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SortConfig {
pub field: Vec<String>,
pub order: SortOrder,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscribedFrame {
pub op: &'static str,
pub view: String,
pub mode: Mode,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort: Option<SortConfig>,
}
impl SubscribedFrame {
pub fn new(view: String, mode: Mode, sort: Option<SortConfig>) -> Self {
Self {
op: "subscribed",
view,
mode,
sort,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Frame {
pub mode: Mode,
#[serde(rename = "entity")]
pub export: String,
pub op: &'static str,
pub key: String,
pub data: serde_json::Value,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub append: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seq: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotEntity {
pub key: String,
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotFrame {
pub mode: Mode,
#[serde(rename = "entity")]
pub export: String,
pub op: &'static str,
pub data: Vec<SnapshotEntity>,
#[serde(default = "default_complete")]
pub complete: bool,
}
fn default_complete() -> bool {
true
}
pub fn transform_large_u64_to_strings(value: &mut serde_json::Value) {
const MAX_SAFE_INTEGER: u64 = 9007199254740991;
match value {
serde_json::Value::Object(map) => {
for (_, v) in map.iter_mut() {
transform_large_u64_to_strings(v);
}
}
serde_json::Value::Array(arr) => {
for v in arr.iter_mut() {
transform_large_u64_to_strings(v);
}
}
serde_json::Value::Number(n) => {
if let Some(n_u64) = n.as_u64() {
if n_u64 > MAX_SAFE_INTEGER {
*value = serde_json::Value::String(n_u64.to_string());
}
} else if let Some(n_i64) = n.as_i64() {
const MIN_SAFE_INTEGER: i64 = -(MAX_SAFE_INTEGER as i64);
if n_i64 < MIN_SAFE_INTEGER {
*value = serde_json::Value::String(n_i64.to_string());
}
}
}
_ => {}
}
}
impl Frame {
pub fn entity(&self) -> &str {
&self.export
}
pub fn key(&self) -> &str {
&self.key
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_entity_key_accessors() {
let frame = Frame {
mode: Mode::List,
export: "SettlementGame/list".to_string(),
op: "upsert",
key: "123".to_string(),
data: serde_json::json!({}),
append: vec![],
seq: None,
};
assert_eq!(frame.entity(), "SettlementGame/list");
assert_eq!(frame.key(), "123");
}
#[test]
fn test_frame_serialization() {
let frame = Frame {
mode: Mode::List,
export: "SettlementGame/list".to_string(),
op: "upsert",
key: "123".to_string(),
data: serde_json::json!({"gameId": "123"}),
append: vec![],
seq: None,
};
let json = serde_json::to_value(&frame).unwrap();
assert_eq!(json["op"], "upsert");
assert_eq!(json["mode"], "list");
assert_eq!(json["entity"], "SettlementGame/list");
assert_eq!(json["key"], "123");
}
#[test]
fn test_frame_with_seq() {
let frame = Frame {
mode: Mode::List,
export: "SettlementGame/list".to_string(),
op: "upsert",
key: "123".to_string(),
data: serde_json::json!({"gameId": "123"}),
append: vec![],
seq: Some("123456789:000000000042".to_string()),
};
let json = serde_json::to_value(&frame).unwrap();
assert_eq!(json["op"], "upsert");
assert_eq!(json["seq"], "123456789:000000000042");
}
#[test]
fn test_frame_seq_skipped_when_none() {
let frame = Frame {
mode: Mode::List,
export: "SettlementGame/list".to_string(),
op: "upsert",
key: "123".to_string(),
data: serde_json::json!({"gameId": "123"}),
append: vec![],
seq: None,
};
let json = serde_json::to_value(&frame).unwrap();
assert!(json.get("seq").is_none());
}
#[test]
fn test_snapshot_frame_complete_serialization() {
let frame = SnapshotFrame {
mode: Mode::List,
export: "tokens/list".to_string(),
op: "snapshot",
data: vec![SnapshotEntity {
key: "abc".to_string(),
data: serde_json::json!({"id": "abc"}),
}],
complete: false,
};
let json = serde_json::to_value(&frame).unwrap();
assert_eq!(json["complete"], false);
assert_eq!(json["op"], "snapshot");
}
#[test]
fn test_snapshot_frame_complete_defaults_to_true_on_deserialize() {
#[derive(Debug, Deserialize)]
struct TestSnapshotFrame {
#[allow(dead_code)]
mode: Mode,
#[allow(dead_code)]
#[serde(rename = "entity")]
export: String,
#[allow(dead_code)]
op: String,
#[allow(dead_code)]
data: Vec<SnapshotEntity>,
#[serde(default = "super::default_complete")]
complete: bool,
}
let json_without_complete = serde_json::json!({
"mode": "list",
"entity": "tokens/list",
"op": "snapshot",
"data": []
});
let frame: TestSnapshotFrame = serde_json::from_value(json_without_complete).unwrap();
assert!(frame.complete);
}
#[test]
fn test_snapshot_frame_batching_fields() {
let first_batch = SnapshotFrame {
mode: Mode::List,
export: "tokens/list".to_string(),
op: "snapshot",
data: vec![],
complete: false,
};
let final_batch = SnapshotFrame {
mode: Mode::List,
export: "tokens/list".to_string(),
op: "snapshot",
data: vec![],
complete: true,
};
assert!(!first_batch.complete);
assert!(final_batch.complete);
}
}