1use std::collections::HashMap;
7
8use once_cell::sync::Lazy;
9
10pub type FieldMapping = HashMap<&'static str, &'static str>;
12
13pub type MethodFieldMappings = HashMap<&'static str, FieldMapping>;
15
16pub static CEL_FIELD_MAPPINGS: Lazy<MethodFieldMappings> = Lazy::new(|| {
18 let mut mappings = HashMap::new();
19
20 mappings.insert(
22 "subscribe_wallet_balance",
23 HashMap::from([
24 ("walletAddress", "a"),
25 ("tokenAddress", "ta"),
26 ("tokenPriceInUsd", "tpiu"),
27 ("balance", "b"),
28 ("timestamp", "t"),
29 ]),
30 );
31
32 let prediction_activity_fields = HashMap::from([
33 ("activityId", "a.id"),
34 ("amount", "a.amt"),
35 ("assetIds", "a.as"),
36 ("blockNumber", "a.bn"),
37 ("conditionId", "a.cid"),
38 ("eventSlug", "a.es"),
39 ("logIndex", "a.li"),
40 ("marketIcon", "a.mi"),
41 ("marketId", "a.mid"),
42 ("marketQuestion", "a.mq"),
43 ("outcome", "a.oc"),
44 ("outcomes", "a.ocs"),
45 ("price", "a.p"),
46 ("quantity", "a.q"),
47 ("seqIndex", "a.seq"),
48 ("source", "a.src"),
49 ("taker", "a.tk"),
50 ("takerAge", "a.ta"),
51 ("takerImage", "a.ti"),
52 ("takerName", "a.tn"),
53 ("takerOrderHash", "a.toh"),
54 ("takerPseudonym", "a.tp"),
55 ("takerTags", "a.tt"),
56 ("timestamp", "a.ts"),
57 ("tokenId", "a.tid"),
58 ("txHash", "a.tx"),
59 ("type", "a.ty"),
60 ]);
61 mappings.insert(
62 "subscribe_prediction_event_activities",
63 prediction_activity_fields.clone(),
64 );
65 mappings.insert(
66 "subscribe_prediction_token_activities",
67 prediction_activity_fields,
68 );
69
70 mappings.insert(
72 "subscribe_token_candles",
73 HashMap::from([
74 ("open", "o"),
75 ("close", "c"),
76 ("high", "h"),
77 ("low", "l"),
78 ("volume", "v"),
79 ("resolution", "r"),
80 ("time", "t"),
81 ("number", "n"),
82 ]),
83 );
84
85 mappings.insert(
87 "subscribe_token_stats",
88 HashMap::from([
89 ("address", "a"),
90 ("timestamp", "t"),
91 ("buys1m", "b1m"),
92 ("sells1m", "s1m"),
93 ("buyers1m", "be1m"),
94 ("sellers1m", "se1m"),
95 ("buyVolumeInUsd1m", "bviu1m"),
96 ("sellVolumeInUsd1m", "sviu1m"),
97 ("price1m", "p1m"),
98 ("openInUsd1m", "oiu1m"),
99 ("closeInUsd1m", "ciu1m"),
100 ("buys5m", "b5m"),
101 ("sells5m", "s5m"),
102 ("buyers5m", "be5m"),
103 ("sellers5m", "se5m"),
104 ("buyVolumeInUsd5m", "bviu5m"),
105 ("sellVolumeInUsd5m", "sviu5m"),
106 ("price5m", "p5m"),
107 ("openInUsd5m", "oiu5m"),
108 ("closeInUsd5m", "ciu5m"),
109 ("buys15m", "b15m"),
110 ("sells15m", "s15m"),
111 ("buyers15m", "be15m"),
112 ("sellers15m", "se15m"),
113 ("buyVolumeInUsd15m", "bviu15m"),
114 ("sellVolumeInUsd15m", "sviu15m"),
115 ("price15m", "p15m"),
116 ("openInUsd15m", "oiu15m"),
117 ("closeInUsd15m", "ciu15m"),
118 ("buys30m", "b30m"),
119 ("sells30m", "s30m"),
120 ("buyers30m", "be30m"),
121 ("sellers30m", "se30m"),
122 ("buyVolumeInUsd30m", "bviu30m"),
123 ("sellVolumeInUsd30m", "sviu30m"),
124 ("price30m", "p30m"),
125 ("openInUsd30m", "oiu30m"),
126 ("closeInUsd30m", "ciu30m"),
127 ("buys1h", "b1h"),
128 ("sells1h", "s1h"),
129 ("buyers1h", "be1h"),
130 ("sellers1h", "se1h"),
131 ("buyVolumeInUsd1h", "bviu1h"),
132 ("sellVolumeInUsd1h", "sviu1h"),
133 ("price1h", "p1h"),
134 ("openInUsd1h", "oiu1h"),
135 ("closeInUsd1h", "ciu1h"),
136 ("buys4h", "b4h"),
137 ("sells4h", "s4h"),
138 ("buyers4h", "be4h"),
139 ("sellers4h", "se4h"),
140 ("buyVolumeInUsd4h", "bviu4h"),
141 ("sellVolumeInUsd4h", "sviu4h"),
142 ("price4h", "p4h"),
143 ("openInUsd4h", "oiu4h"),
144 ("closeInUsd4h", "ciu4h"),
145 ("buys24h", "b24h"),
146 ("sells24h", "s24h"),
147 ("buyers24h", "be24h"),
148 ("sellers24h", "se24h"),
149 ("buyVolumeInUsd24h", "bviu24h"),
150 ("sellVolumeInUsd24h", "sviu24h"),
151 ("price24h", "p24h"),
152 ("price", "p"),
153 ("openInUsd24h", "oiu24h"),
154 ("closeInUsd24h", "ciu24h"),
155 ]),
156 );
157
158 mappings.insert(
160 "subscribe_token_holders",
161 HashMap::from([
162 ("tokenAddress", "a"),
163 ("holders", "h"),
164 ("top100Amount", "t100a"),
165 ("top10Amount", "t10a"),
166 ("top100Holders", "t100h"),
167 ("top10Holders", "t10h"),
168 ("top100Ratio", "t100r"),
169 ("top10Ratio", "t10r"),
170 ("timestamp", "ts"),
171 ]),
172 );
173
174 mappings.insert(
176 "subscribe_new_token",
177 HashMap::from([
178 ("tokenAddress", "a"),
179 ("name", "n"),
180 ("symbol", "s"),
181 ("decimals", "dec"),
182 ("imageUrl", "iu"),
183 ("description", "de"),
184 ("createdAtMs", "cts"),
185 ("coingeckoCoinId", "cgi"),
186 ("socialMedia.twitter", "sm.tw"),
187 ("socialMedia.telegram", "sm.tg"),
188 ("socialMedia.website", "sm.w"),
189 ("socialMedia.tiktok", "sm.tt"),
190 ("socialMedia.discord", "sm.dc"),
191 ("socialMedia.facebook", "sm.fb"),
192 ("socialMedia.github", "sm.gh"),
193 ("socialMedia.instagram", "sm.ig"),
194 ("socialMedia.linkedin", "sm.li"),
195 ("socialMedia.medium", "sm.md"),
196 ("socialMedia.reddit", "sm.rd"),
197 ("socialMedia.youtube", "sm.yt"),
198 ("socialMedia.bitbucket", "sm.bb"),
199 ("launchFrom.programAddress", "lf.pa"),
200 ("launchFrom.protocolFamily", "lf.pf"),
201 ("launchFrom.protocolName", "lf.pn"),
202 ("migratedTo.programAddress", "mt.pa"),
203 ("migratedTo.protocolFamily", "mt.pf"),
204 ("migratedTo.protocolName", "mt.pn"),
205 ]),
206 );
207
208 mappings.insert(
210 "subscribe_token_supply",
211 HashMap::from([("tokenAddress", "a"), ("supply", "s"), ("timestamp", "ts")]),
212 );
213
214 mappings.insert(
216 "subscribe_dex_pool_balance",
217 HashMap::from([
218 ("poolAddress", "a"),
219 ("tokenAAddress", "taa"),
220 ("tokenALiquidityInUsd", "taliu"),
221 ("tokenBAddress", "tba"),
222 ("tokenBLiquidityInUsd", "tbliu"),
223 ]),
224 );
225
226 mappings.insert(
228 "subscribe_token_liquidity",
229 HashMap::from([
230 ("tokenAddress", "a"),
231 ("metricType", "t"),
232 ("value", "v"),
233 ("timestamp", "ts"),
234 ]),
235 );
236
237 mappings.insert(
239 "subscribe_new_tokens_metadata",
240 HashMap::from([
241 ("tokenAddress", "a"),
242 ("name", "n"),
243 ("decimals", "dec"),
244 ("symbol", "s"),
245 ("imageUrl", "iu"),
246 ("description", "de"),
247 ("createdAtMs", "cts"),
248 ("coingeckoCoinId", "cgi"),
249 ("socialMedia.twitter", "sm.tw"),
250 ("socialMedia.telegram", "sm.tg"),
251 ("socialMedia.website", "sm.w"),
252 ("socialMedia.tiktok", "sm.tt"),
253 ("socialMedia.discord", "sm.dc"),
254 ("socialMedia.facebook", "sm.fb"),
255 ("socialMedia.github", "sm.gh"),
256 ("socialMedia.instagram", "sm.ig"),
257 ("socialMedia.linkedin", "sm.li"),
258 ("socialMedia.medium", "sm.md"),
259 ("socialMedia.reddit", "sm.rd"),
260 ("socialMedia.youtube", "sm.yt"),
261 ("socialMedia.bitbucket", "sm.bb"),
262 ("launchFrom.programAddress", "lf.pa"),
263 ("launchFrom.protocolFamily", "lf.pf"),
264 ("launchFrom.protocolName", "lf.pn"),
265 ("migratedTo.programAddress", "mt.pa"),
266 ("migratedTo.protocolFamily", "mt.pf"),
267 ("migratedTo.protocolName", "mt.pn"),
268 ]),
269 );
270
271 mappings.insert(
273 "subscribe_new_tokens",
274 HashMap::from([
275 ("tokenAddress", "a"),
276 ("name", "n"),
277 ("decimals", "dec"),
278 ("symbol", "s"),
279 ("imageUrl", "iu"),
280 ("description", "de"),
281 ("createdAtMs", "cts"),
282 ("coingeckoCoinId", "cgi"),
283 ("socialMedia.twitter", "sm.tw"),
284 ("socialMedia.telegram", "sm.tg"),
285 ("socialMedia.website", "sm.w"),
286 ("socialMedia.tiktok", "sm.tt"),
287 ("socialMedia.discord", "sm.dc"),
288 ("socialMedia.facebook", "sm.fb"),
289 ("socialMedia.github", "sm.gh"),
290 ("socialMedia.instagram", "sm.ig"),
291 ("socialMedia.linkedin", "sm.li"),
292 ("socialMedia.medium", "sm.md"),
293 ("socialMedia.reddit", "sm.rd"),
294 ("socialMedia.youtube", "sm.yt"),
295 ("socialMedia.bitbucket", "sm.bb"),
296 ("launchFrom.programAddress", "lf.pa"),
297 ("launchFrom.protocolFamily", "lf.pf"),
298 ("launchFrom.protocolName", "lf.pn"),
299 ("migratedTo.programAddress", "mt.pa"),
300 ("migratedTo.protocolFamily", "mt.pf"),
301 ("migratedTo.protocolName", "mt.pn"),
302 ]),
303 );
304
305 mappings.insert(
307 "subscribe_token_trades",
308 HashMap::from([
309 ("tokenAddress", "a"),
310 ("timestamp", "t"),
311 ("kind", "k"),
312 ("buyAmount", "ba"),
313 ("buyAmountInUsd", "baiu"),
314 ("buyTokenAddress", "btma"),
315 ("buyTokenName", "btn"),
316 ("buyTokenSymbol", "bts"),
317 ("buyWalletAddress", "bwa"),
318 ("sellAmount", "sa"),
319 ("sellAmountInUsd", "saiu"),
320 ("sellTokenAddress", "stma"),
321 ("sellTokenName", "stn"),
322 ("sellTokenSymbol", "sts"),
323 ("sellWalletAddress", "swa"),
324 ("txHash", "h"),
325 ]),
326 );
327
328 mappings.insert(
330 "subscribe_wallet_pnl",
331 HashMap::from([
332 ("walletAddress", "a"),
333 ("tokenAddress", "ta"),
334 ("tokenPriceInUsd", "tpiu"),
335 ("timestamp", "t"),
336 ("opentime", "ot"),
337 ("lasttime", "lt"),
338 ("closetime", "ct"),
339 ("buyAmount", "ba"),
340 ("buyAmountInUsd", "baiu"),
341 ("buyCount", "bs"),
342 ("buyCount30d", "bs30d"),
343 ("buyCount7d", "bs7d"),
344 ("sellAmount", "sa"),
345 ("sellAmountInUsd", "saiu"),
346 ("sellCount", "ss"),
347 ("sellCount30d", "ss30d"),
348 ("sellCount7d", "ss7d"),
349 ("heldDurationTimestamp", "hdts"),
350 ("averageBuyPriceInUsd", "abpiu"),
351 ("averageSellPriceInUsd", "aspiu"),
352 ("unrealizedProfitInUsd", "upiu"),
353 ("unrealizedProfitRatio", "upr"),
354 ("realizedProfitInUsd", "rpiu"),
355 ("realizedProfitRatio", "rpr"),
356 ("totalRealizedProfitInUsd", "trpiu"),
357 ("totalRealizedProfitRatio", "trr"),
358 ]),
359 );
360
361 mappings.insert(
363 "subscribe_wallet_trade",
364 HashMap::from([
365 ("tokenAddress", "a"),
366 ("timestamp", "t"),
367 ("kind", "k"),
368 ("buyAmount", "ba"),
369 ("buyAmountInUsd", "baiu"),
370 ("buyTokenAddress", "btma"),
371 ("buyTokenName", "btn"),
372 ("buyTokenSymbol", "bts"),
373 ("buyWalletAddress", "bwa"),
374 ("sellAmount", "sa"),
375 ("sellAmountInUsd", "saiu"),
376 ("sellTokenAddress", "stma"),
377 ("sellTokenName", "stn"),
378 ("sellTokenSymbol", "sts"),
379 ("sellWalletAddress", "swa"),
380 ("txHash", "h"),
381 ]),
382 );
383
384 mappings.insert(
386 "subscribe_token_max_liquidity",
387 HashMap::from([
388 ("tokenAddress", "a"),
389 ("poolAddress", "p"),
390 ("liquidityInUsd", "liu"),
391 ("liquidityInNative", "lin"),
392 ("timestamp", "ts"),
393 ]),
394 );
395
396 mappings.insert(
398 "subscribe_token_total_liquidity",
399 HashMap::from([
400 ("tokenAddress", "a"),
401 ("liquidityInUsd", "liu"),
402 ("liquidityInNative", "lin"),
403 ("poolCount", "pc"),
404 ("timestamp", "ts"),
405 ]),
406 );
407
408 mappings
409});
410
411pub fn get_field_mappings(method_name: &str) -> Option<&FieldMapping> {
413 CEL_FIELD_MAPPINGS.get(method_name)
414}
415
416pub fn replace_filter_fields(filter: &str, method_name: &str) -> String {
420 if filter.is_empty() {
421 return filter.to_string();
422 }
423
424 let field_mappings = match get_field_mappings(method_name) {
425 Some(m) => m,
426 None => return filter.to_string(),
427 };
428
429 let mut result = filter.to_string();
430
431 let mut entries: Vec<_> = field_mappings.iter().collect();
433 entries.sort_by_key(|entry| std::cmp::Reverse(entry.0.len()));
434
435 for (long_field, short_field) in entries {
436 let pattern1 = format!(r"\bmeta\.{}\b", regex::escape(long_field));
439 if let Ok(re) = regex::Regex::new(&pattern1) {
440 result = re
441 .replace_all(&result, format!("meta.{}", short_field))
442 .to_string();
443 }
444
445 let pattern2 = format!(r"\b{}\b", regex::escape(long_field));
447 if let Ok(re) = regex::Regex::new(&pattern2) {
448 result = re
449 .replace_all(&result, format!("meta.{}", short_field))
450 .to_string();
451 }
452 }
453
454 result
455}
456
457pub fn get_available_fields(method_name: &str) -> Vec<&'static str> {
459 match get_field_mappings(method_name) {
460 Some(mappings) => mappings.keys().copied().collect(),
461 None => Vec::new(),
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn test_get_field_mappings() {
471 let mappings = get_field_mappings("subscribe_token_candles");
472 assert!(mappings.is_some());
473 let mappings = mappings.unwrap();
474 assert_eq!(mappings.get("open"), Some(&"o"));
475 assert_eq!(mappings.get("close"), Some(&"c"));
476 }
477
478 #[test]
479 fn test_replace_filter_fields() {
480 let filter = "open > 100 && close < 200";
481 let result = replace_filter_fields(filter, "subscribe_token_candles");
482 assert!(result.contains("meta.o"));
483 assert!(result.contains("meta.c"));
484 }
485
486 #[test]
487 fn test_get_available_fields() {
488 let fields = get_available_fields("subscribe_token_candles");
489 assert!(fields.contains(&"open"));
490 assert!(fields.contains(&"close"));
491 assert!(fields.contains(&"high"));
492 assert!(fields.contains(&"low"));
493 }
494
495 #[test]
496 fn test_prediction_activity_filter_fields_cover_full_payload() {
497 let mappings = get_field_mappings("subscribe_prediction_event_activities").unwrap();
498 assert_eq!(mappings.get("marketIcon"), Some(&"a.mi"));
499 assert_eq!(mappings.get("marketQuestion"), Some(&"a.mq"));
500 assert_eq!(mappings.get("source"), Some(&"a.src"));
501 assert_eq!(mappings.get("takerAge"), Some(&"a.ta"));
502 assert_eq!(mappings.get("takerImage"), Some(&"a.ti"));
503 assert_eq!(mappings.get("takerName"), Some(&"a.tn"));
504 assert_eq!(mappings.get("takerOrderHash"), Some(&"a.toh"));
505 assert_eq!(mappings.get("takerPseudonym"), Some(&"a.tp"));
506 assert_eq!(mappings.get("takerTags"), Some(&"a.tt"));
507
508 let filter = "marketIcon != '' && takerTags.contains('kol')";
509 let result = replace_filter_fields(filter, "subscribe_prediction_event_activities");
510 assert!(result.contains("meta.a.mi"));
511 assert!(result.contains("meta.a.tt"));
512 }
513}