1use serde::{Deserialize, Serialize};
2
3use crate::websocket::subscription::{SubscriptionQuery, PROTOCOL_VERSION};
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
6#[serde(rename_all = "lowercase")]
7pub enum Mode {
8 State,
9 Append,
10 List,
11}
12
13#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
14#[serde(rename_all = "lowercase")]
15pub enum SortOrder {
16 Asc,
17 Desc,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
21pub struct SortConfig {
22 pub field: Vec<String>,
23 pub order: SortOrder,
24}
25
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
27pub struct WireFormat {
28 pub wide_int_paths: Vec<Vec<String>>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct SubscribedFrame {
34 pub protocol_version: u8,
35 pub subscription_id: String,
36 pub op: &'static str,
37 pub query: SubscriptionQuery,
38 pub mode: Mode,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub sort: Option<SortConfig>,
41}
42
43impl SubscribedFrame {
44 pub fn new(
45 subscription_id: String,
46 query: SubscriptionQuery,
47 mode: Mode,
48 sort: Option<SortConfig>,
49 ) -> Self {
50 Self {
51 protocol_version: PROTOCOL_VERSION,
52 subscription_id,
53 op: "subscribed",
54 query,
55 mode,
56 sort,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct UnsubscribedFrame {
64 pub protocol_version: u8,
65 pub subscription_id: String,
66 pub op: &'static str,
67}
68
69impl UnsubscribedFrame {
70 pub fn new(subscription_id: String) -> Self {
71 Self {
72 protocol_version: PROTOCOL_VERSION,
73 subscription_id,
74 op: "unsubscribed",
75 }
76 }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct Frame {
82 pub protocol_version: u8,
83 pub subscription_id: String,
84 pub mode: Mode,
85 #[serde(rename = "entity")]
86 pub export: String,
87 pub op: String,
88 pub key: String,
89 pub data: serde_json::Value,
90 #[serde(skip_serializing_if = "Vec::is_empty", default)]
91 pub append: Vec<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub seq: Option<String>,
94}
95
96impl Frame {
97 pub fn scoped(
98 subscription_id: impl Into<String>,
99 mode: Mode,
100 export: impl Into<String>,
101 op: impl Into<String>,
102 key: impl Into<String>,
103 data: serde_json::Value,
104 seq: Option<String>,
105 ) -> Self {
106 Self {
107 protocol_version: PROTOCOL_VERSION,
108 subscription_id: subscription_id.into(),
109 mode,
110 export: export.into(),
111 op: op.into(),
112 key: key.into(),
113 data,
114 append: vec![],
115 seq,
116 }
117 }
118
119 pub fn entity(&self) -> &str {
120 &self.export
121 }
122
123 pub fn key(&self) -> &str {
124 &self.key
125 }
126}
127
128#[derive(Debug, Clone, Serialize)]
130pub(crate) struct SourceFrame {
131 pub mode: Mode,
132 #[serde(rename = "entity")]
133 pub export: String,
134 pub op: &'static str,
135 pub key: String,
136 pub data: serde_json::Value,
137 #[serde(skip_serializing_if = "Vec::is_empty", default)]
138 pub append: Vec<String>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub seq: Option<String>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
144pub struct SnapshotEntity {
145 pub key: String,
146 pub data: serde_json::Value,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150#[serde(rename_all = "camelCase")]
151pub struct SnapshotFrame {
152 pub protocol_version: u8,
153 pub subscription_id: String,
154 pub snapshot_id: String,
155 pub authoritative: bool,
156 pub mode: Mode,
157 #[serde(rename = "entity")]
158 pub export: String,
159 pub op: &'static str,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub key: Option<String>,
162 pub data: Vec<SnapshotEntity>,
163 pub complete: bool,
164}
165
166pub fn apply_wire_format(value: &mut serde_json::Value, wire_format: &WireFormat) {
167 for path in &wire_format.wide_int_paths {
168 stringify_value_at_path(value, path);
169 }
170}
171
172fn stringify_value_at_path(value: &mut serde_json::Value, path: &[String]) {
173 if path.is_empty() {
174 stringify_wide_int_value(value);
175 return;
176 }
177
178 match value {
179 serde_json::Value::Object(map) => {
180 if let Some(child) = map.get_mut(&path[0]) {
181 stringify_value_at_path(child, &path[1..]);
182 }
183 }
184 serde_json::Value::Array(values) => {
185 for child in values {
186 stringify_value_at_path(child, path);
187 }
188 }
189 _ => {}
190 }
191}
192
193fn stringify_wide_int_value(value: &mut serde_json::Value) {
194 match value {
195 serde_json::Value::Number(number) => {
196 if let Some(unsigned) = number.as_u64() {
197 *value = serde_json::Value::String(unsigned.to_string());
198 } else if let Some(signed) = number.as_i64() {
199 *value = serde_json::Value::String(signed.to_string());
200 }
201 }
202 serde_json::Value::Array(values) => {
203 for child in values {
204 stringify_wide_int_value(child);
205 }
206 }
207 _ => {}
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use serde_json::json;
215
216 #[test]
217 fn scoped_live_frames_carry_v2_identity() {
218 let frame = Frame::scoped(
219 "rounds:1",
220 Mode::List,
221 "OreRound/latest",
222 "upsert",
223 "123",
224 json!({"id": 123}),
225 Some("10:000000000001".to_string()),
226 );
227 let value = serde_json::to_value(frame).unwrap();
228 assert_eq!(value["protocolVersion"], 2);
229 assert_eq!(value["subscriptionId"], "rounds:1");
230 assert_eq!(value["seq"], "10:000000000001");
231 }
232
233 #[test]
234 fn snapshot_serializes_conformance_metadata() {
235 let frame = SnapshotFrame {
236 protocol_version: PROTOCOL_VERSION,
237 subscription_id: "rounds:1".to_string(),
238 snapshot_id: "snapshot-1".to_string(),
239 authoritative: true,
240 mode: Mode::List,
241 export: "OreRound/latest".to_string(),
242 op: "snapshot",
243 key: None,
244 data: vec![],
245 complete: true,
246 };
247 let value = serde_json::to_value(frame).unwrap();
248 assert_eq!(value["snapshotId"], "snapshot-1");
249 assert_eq!(value["authoritative"], true);
250 assert_eq!(value["complete"], true);
251 }
252
253 #[test]
254 fn wire_format_stringifies_marked_wide_int_paths() {
255 let wire_format = WireFormat {
256 wide_int_paths: vec![
257 vec!["amount".to_string()],
258 vec!["positions".to_string(), "liquidity".to_string()],
259 ],
260 };
261 let mut value = json!({
262 "amount": 42,
263 "positions": [{"liquidity": 9}, {"liquidity": 11}],
264 "small": 5,
265 });
266 apply_wire_format(&mut value, &wire_format);
267 assert_eq!(value["amount"], "42");
268 assert_eq!(value["positions"][1]["liquidity"], "11");
269 assert_eq!(value["small"], 5);
270 }
271}