1use std::io;
2
3use super::serialize::{to_json_string, to_json_string_with_sym};
4use crate::{
5 encode::{DbnEncodable, EncodeDbn, EncodeRecord, EncodeRecordRef, EncodeRecordTextExt},
6 rtype_dispatch, Error, Metadata, Result,
7};
8
9pub struct Encoder<W>
11where
12 W: io::Write,
13{
14 writer: W,
15 should_pretty_print: bool,
16 use_pretty_px: bool,
17 use_pretty_ts: bool,
18}
19
20pub struct EncoderBuilder<W>
24where
25 W: io::Write,
26{
27 writer: W,
28 should_pretty_print: bool,
29 use_pretty_px: bool,
30 use_pretty_ts: bool,
31}
32
33impl<W> EncoderBuilder<W>
34where
35 W: io::Write,
36{
37 pub fn new(writer: W) -> Self {
39 Self {
40 writer,
41 should_pretty_print: false,
42 use_pretty_px: false,
43 use_pretty_ts: false,
44 }
45 }
46
47 pub fn should_pretty_print(mut self, should_pretty_print: bool) -> Self {
51 self.should_pretty_print = should_pretty_print;
52 self
53 }
54
55 pub fn use_pretty_px(mut self, use_pretty_px: bool) -> Self {
58 self.use_pretty_px = use_pretty_px;
59 self
60 }
61
62 pub fn use_pretty_ts(mut self, use_pretty_ts: bool) -> Self {
65 self.use_pretty_ts = use_pretty_ts;
66 self
67 }
68
69 pub fn build(self) -> Encoder<W> {
72 Encoder::new(
73 self.writer,
74 self.should_pretty_print,
75 self.use_pretty_px,
76 self.use_pretty_ts,
77 )
78 }
79}
80
81impl<W> Encoder<W>
82where
83 W: io::Write,
84{
85 pub fn new(
89 writer: W,
90 should_pretty_print: bool,
91 use_pretty_px: bool,
92 use_pretty_ts: bool,
93 ) -> Self {
94 Self {
95 writer,
96 should_pretty_print,
97 use_pretty_px,
98 use_pretty_ts,
99 }
100 }
101
102 pub fn builder(writer: W) -> EncoderBuilder<W> {
104 EncoderBuilder::new(writer)
105 }
106
107 pub fn encode_metadata(&mut self, metadata: &Metadata) -> Result<()> {
112 let json = to_json_string(
113 metadata,
114 self.should_pretty_print,
115 self.use_pretty_px,
116 self.use_pretty_ts,
117 );
118 let io_err = |e| Error::io(e, "writing metadata");
119 self.writer.write_all(json.as_bytes()).map_err(io_err)?;
120 self.writer.flush().map_err(io_err)?;
121 Ok(())
122 }
123
124 pub fn get_ref(&self) -> &W {
126 &self.writer
127 }
128
129 pub fn get_mut(&mut self) -> &mut W {
131 &mut self.writer
132 }
133}
134
135impl<W> EncodeRecord for Encoder<W>
136where
137 W: io::Write,
138{
139 fn encode_record<R: DbnEncodable>(&mut self, record: &R) -> Result<()> {
140 let json = to_json_string(
141 record,
142 self.should_pretty_print,
143 self.use_pretty_px,
144 self.use_pretty_ts,
145 );
146 match self.writer.write_all(json.as_bytes()) {
147 Ok(()) => Ok(()),
148 Err(e) => Err(Error::io(e, "writing record")),
149 }
150 }
151
152 fn flush(&mut self) -> Result<()> {
153 self.writer
154 .flush()
155 .map_err(|e| Error::io(e, "flushing output"))
156 }
157}
158
159impl<W> EncodeRecordRef for Encoder<W>
160where
161 W: io::Write,
162{
163 fn encode_record_ref(&mut self, record: crate::RecordRef) -> Result<()> {
164 rtype_dispatch!(record, self.encode_record())?
165 }
166
167 unsafe fn encode_record_ref_ts_out(
168 &mut self,
169 record: crate::RecordRef,
170 ts_out: bool,
171 ) -> Result<()> {
172 rtype_dispatch!(record, ts_out: ts_out, self.encode_record())?
173 }
174}
175
176impl<W> EncodeDbn for Encoder<W> where W: io::Write {}
177
178impl<W> EncodeRecordTextExt for Encoder<W>
179where
180 W: io::Write,
181{
182 fn encode_record_with_sym<R: DbnEncodable>(
183 &mut self,
184 record: &R,
185 symbol: Option<&str>,
186 ) -> Result<()> {
187 let json = to_json_string_with_sym(
188 record,
189 self.should_pretty_print,
190 self.use_pretty_px,
191 self.use_pretty_ts,
192 symbol,
193 );
194 match self.writer.write_all(json.as_bytes()) {
195 Ok(()) => Ok(()),
196 Err(e) => Err(Error::io(e, "writing record")),
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 #![allow(clippy::clone_on_copy)]
204
205 use std::{array, io::BufWriter, num::NonZeroU64, os::raw::c_char};
206
207 use super::*;
208 use crate::{
209 compat::SYMBOL_CSTR_LEN_V1,
210 encode::test_data::{BID_ASK, RECORD_HEADER},
211 enums::{
212 rtype, InstrumentClass, SType, Schema, SecurityUpdateAction, StatType,
213 StatUpdateAction, UserDefinedInstrument,
214 },
215 record::{
216 str_to_c_chars, ErrorMsg, ImbalanceMsg, InstrumentDefMsg, MboMsg, Mbp10Msg, Mbp1Msg,
217 OhlcvMsg, RecordHeader, StatMsg, StatusMsg, TradeMsg, WithTsOut,
218 },
219 test_utils::VecStream,
220 Dataset, MappingInterval, RecordRef, SymbolMapping, FIXED_PRICE_SCALE,
221 };
222
223 fn write_json_to_string<R>(
224 records: &[R],
225 should_pretty_print: bool,
226 use_pretty_px: bool,
227 use_pretty_ts: bool,
228 ) -> String
229 where
230 R: DbnEncodable,
231 {
232 let mut buffer = Vec::new();
233 Encoder::new(
234 &mut buffer,
235 should_pretty_print,
236 use_pretty_px,
237 use_pretty_ts,
238 )
239 .encode_records(records)
240 .unwrap();
241 String::from_utf8(buffer).expect("valid UTF-8")
242 }
243
244 fn write_json_stream_to_string<R>(
245 records: Vec<R>,
246 should_pretty_print: bool,
247 use_pretty_px: bool,
248 use_pretty_ts: bool,
249 ) -> String
250 where
251 R: DbnEncodable,
252 {
253 let mut buffer = Vec::new();
254 let writer = BufWriter::new(&mut buffer);
255 Encoder::new(writer, should_pretty_print, use_pretty_px, use_pretty_ts)
256 .encode_stream(VecStream::new(records))
257 .unwrap();
258 String::from_utf8(buffer).expect("valid UTF-8")
259 }
260
261 fn write_json_metadata_to_string(metadata: &Metadata, should_pretty_print: bool) -> String {
262 let mut buffer = Vec::new();
263 let writer = BufWriter::new(&mut buffer);
264 Encoder::new(writer, should_pretty_print, true, true)
265 .encode_metadata(metadata)
266 .unwrap();
267 String::from_utf8(buffer).expect("valid UTF-8")
268 }
269
270 const HEADER_JSON: &str =
271 r#""hd":{"ts_event":"1658441851000000000","rtype":4,"publisher_id":1,"instrument_id":323}"#;
272 const BID_ASK_JSON: &str = r#"{"bid_px":"372000.000000000","ask_px":"372500.000000000","bid_sz":10,"ask_sz":5,"bid_ct":5,"ask_ct":2}"#;
273
274 #[test]
275 fn test_mbo_write_json() {
276 let data = vec![MboMsg {
277 hd: RECORD_HEADER,
278 order_id: 16,
279 price: 5500,
280 size: 3,
281 flags: 128.into(),
282 channel_id: 14,
283 action: 'R' as c_char,
284 side: 'N' as c_char,
285 ts_recv: 1658441891000000000,
286 ts_in_delta: 22_000,
287 sequence: 1_002_375,
288 }];
289 let slice_res = write_json_to_string(data.as_slice(), false, true, false);
290 let stream_res = write_json_stream_to_string(data, false, true, false);
291
292 assert_eq!(slice_res, stream_res);
293 assert_eq!(
294 slice_res,
295 format!(
296 "{{{},{HEADER_JSON},{}}}\n",
297 r#""ts_recv":"1658441891000000000""#,
298 r#""action":"R","side":"N","price":"0.000005500","size":3,"channel_id":14,"order_id":"16","flags":128,"ts_in_delta":22000,"sequence":1002375"#
299 )
300 );
301 }
302
303 #[test]
304 fn test_mbp1_write_json() {
305 let data = vec![Mbp1Msg {
306 hd: RECORD_HEADER,
307 price: 5500,
308 size: 3,
309 action: 'B' as c_char,
310 side: 'B' as c_char,
311 flags: 128.into(),
312 depth: 9,
313 ts_recv: 1658441891000000000,
314 ts_in_delta: 22_000,
315 sequence: 1_002_375,
316 levels: [BID_ASK],
317 }];
318 let slice_res = write_json_to_string(data.as_slice(), false, true, true);
319 let stream_res = write_json_stream_to_string(data, false, true, true);
320
321 assert_eq!(slice_res, stream_res);
322 assert_eq!(
323 slice_res,
324 format!(
325 "{{{},{},{},{}}}\n",
326 r#""ts_recv":"2022-07-21T22:18:11.000000000Z""#,
327 r#""hd":{"ts_event":"2022-07-21T22:17:31.000000000Z","rtype":4,"publisher_id":1,"instrument_id":323}"#,
328 r#""action":"B","side":"B","depth":9,"price":"0.000005500","size":3,"flags":128,"ts_in_delta":22000,"sequence":1002375"#,
329 format_args!("\"levels\":[{BID_ASK_JSON}]")
330 )
331 );
332 }
333
334 #[test]
335 fn test_mbp10_write_json() {
336 let data = vec![Mbp10Msg {
337 hd: RECORD_HEADER,
338 price: 5500,
339 size: 3,
340 action: 'T' as c_char,
341 side: 'N' as c_char,
342 flags: 128.into(),
343 depth: 9,
344 ts_recv: 1658441891000000000,
345 ts_in_delta: 22_000,
346 sequence: 1_002_375,
347 levels: array::from_fn(|_| BID_ASK.clone()),
348 }];
349 let slice_res = write_json_to_string(data.as_slice(), false, true, true);
350 let stream_res = write_json_stream_to_string(data, false, true, true);
351
352 assert_eq!(slice_res, stream_res);
353 assert_eq!(
354 slice_res,
355 format!(
356 "{{{},{},{},{}}}\n",
357 r#""ts_recv":"2022-07-21T22:18:11.000000000Z""#,
358 r#""hd":{"ts_event":"2022-07-21T22:17:31.000000000Z","rtype":4,"publisher_id":1,"instrument_id":323}"#,
359 r#""action":"T","side":"N","depth":9,"price":"0.000005500","size":3,"flags":128,"ts_in_delta":22000,"sequence":1002375"#,
360 format_args!("\"levels\":[{BID_ASK_JSON},{BID_ASK_JSON},{BID_ASK_JSON},{BID_ASK_JSON},{BID_ASK_JSON},{BID_ASK_JSON},{BID_ASK_JSON},{BID_ASK_JSON},{BID_ASK_JSON},{BID_ASK_JSON}]")
361 )
362 );
363 }
364
365 #[test]
366 fn test_trade_write_json() {
367 let data = vec![TradeMsg {
368 hd: RECORD_HEADER,
369 price: 5500,
370 size: 3,
371 action: 'C' as c_char,
372 side: 'B' as c_char,
373 flags: 128.into(),
374 depth: 9,
375 ts_recv: 1658441891000000000,
376 ts_in_delta: 22_000,
377 sequence: 1_002_375,
378 }];
379 let slice_res = write_json_to_string(data.as_slice(), false, false, false);
380 let stream_res = write_json_stream_to_string(data, false, false, false);
381
382 assert_eq!(slice_res, stream_res);
383 assert_eq!(
384 slice_res,
385 format!(
386 "{{{},{HEADER_JSON},{}}}\n",
387 r#""ts_recv":"1658441891000000000""#,
388 r#""action":"C","side":"B","depth":9,"price":"5500","size":3,"flags":128,"ts_in_delta":22000,"sequence":1002375"#,
389 )
390 );
391 }
392
393 #[test]
394 fn test_ohlcv_write_json() {
395 let data = vec![OhlcvMsg {
396 hd: RECORD_HEADER,
397 open: 5000,
398 high: 8000,
399 low: 3000,
400 close: 6000,
401 volume: 55_000,
402 }];
403 let slice_res = write_json_to_string(data.as_slice(), false, true, false);
404 let stream_res = write_json_stream_to_string(data, false, true, false);
405
406 assert_eq!(slice_res, stream_res);
407 assert_eq!(
408 slice_res,
409 format!(
410 "{{{HEADER_JSON},{}}}\n",
411 r#""open":"0.000005000","high":"0.000008000","low":"0.000003000","close":"0.000006000","volume":"55000""#,
412 )
413 );
414 }
415
416 #[test]
417 fn test_status_write_json() {
418 let data = vec![StatusMsg {
419 hd: RECORD_HEADER,
420 ts_recv: 1658441891000000000,
421 action: 1,
422 reason: 2,
423 trading_event: 3,
424 is_trading: b'Y' as c_char,
425 is_quoting: b'Y' as c_char,
426 is_short_sell_restricted: b'~' as c_char,
427 _reserved: Default::default(),
428 }];
429 let slice_res = write_json_to_string(data.as_slice(), false, false, true);
430 let stream_res = write_json_stream_to_string(data, false, false, true);
431
432 assert_eq!(slice_res, stream_res);
433 assert_eq!(
434 slice_res,
435 format!(
436 "{{{},{},{}}}\n",
437 r#""ts_recv":"2022-07-21T22:18:11.000000000Z""#,
438 r#""hd":{"ts_event":"2022-07-21T22:17:31.000000000Z","rtype":4,"publisher_id":1,"instrument_id":323}"#,
439 r#""action":1,"reason":2,"trading_event":3,"is_trading":"Y","is_quoting":"Y","is_short_sell_restricted":"~""#,
440 )
441 );
442 }
443
444 #[test]
445 fn test_instrument_def_write_json() {
446 let data = vec![InstrumentDefMsg {
447 hd: RECORD_HEADER,
448 ts_recv: 1658441891000000000,
449 min_price_increment: 100,
450 display_factor: 1_000_000_000,
451 expiration: 1698450000000000000,
452 activation: 1697350000000000000,
453 high_limit_price: 1_000_000,
454 low_limit_price: -1_000_000,
455 max_price_variation: 0,
456 trading_reference_price: 500_000,
457 unit_of_measure_qty: 5_000_000_000,
458 min_price_increment_amount: 5,
459 price_ratio: 10,
460 inst_attrib_value: 10,
461 underlying_id: 256785,
462 raw_instrument_id: RECORD_HEADER.instrument_id,
463 market_depth_implied: 0,
464 market_depth: 13,
465 market_segment_id: 0,
466 max_trade_vol: 10_000,
467 min_lot_size: 1,
468 min_lot_size_block: 1000,
469 min_lot_size_round_lot: 100,
470 min_trade_vol: 1,
471 contract_multiplier: 0,
472 decay_quantity: 0,
473 original_contract_size: 0,
474 trading_reference_date: 0,
475 appl_id: 0,
476 maturity_year: 0,
477 decay_start_date: 0,
478 channel_id: 4,
479 currency: str_to_c_chars("USD").unwrap(),
480 settl_currency: str_to_c_chars("USD").unwrap(),
481 secsubtype: Default::default(),
482 raw_symbol: str_to_c_chars("ESZ4 C4100").unwrap(),
483 group: str_to_c_chars("EW").unwrap(),
484 exchange: str_to_c_chars("XCME").unwrap(),
485 asset: str_to_c_chars("ES").unwrap(),
486 cfi: str_to_c_chars("OCAFPS").unwrap(),
487 security_type: str_to_c_chars("OOF").unwrap(),
488 unit_of_measure: str_to_c_chars("IPNT").unwrap(),
489 underlying: str_to_c_chars("ESZ4").unwrap(),
490 strike_price_currency: str_to_c_chars("USD").unwrap(),
491 instrument_class: InstrumentClass::Call as c_char,
492 strike_price: 4_100_000_000_000,
493 match_algorithm: 'F' as c_char,
494 md_security_trading_status: 2,
495 main_fraction: 4,
496 price_display_format: 8,
497 settl_price_type: 9,
498 sub_fraction: 23,
499 underlying_product: 10,
500 security_update_action: SecurityUpdateAction::Add as c_char,
501 maturity_month: 8,
502 maturity_day: 9,
503 maturity_week: 11,
504 user_defined_instrument: UserDefinedInstrument::No,
505 contract_multiplier_unit: 0,
506 flow_schedule_type: 5,
507 tick_rule: 0,
508 _reserved: Default::default(),
509 }];
510 let slice_res = write_json_to_string(data.as_slice(), false, true, true);
511 let stream_res = write_json_stream_to_string(data, false, true, true);
512
513 assert_eq!(slice_res, stream_res);
514 assert_eq!(
515 slice_res,
516 format!(
517 "{{{},{},{}}}\n",
518 r#""ts_recv":"2022-07-21T22:18:11.000000000Z""#,
519 r#""hd":{"ts_event":"2022-07-21T22:17:31.000000000Z","rtype":4,"publisher_id":1,"instrument_id":323}"#,
520 concat!(
521 r#""raw_symbol":"ESZ4 C4100","security_update_action":"A","instrument_class":"C","min_price_increment":"0.000000100","display_factor":"1.000000000","expiration":"2023-10-27T23:40:00.000000000Z","activation":"2023-10-15T06:06:40.000000000Z","#,
522 r#""high_limit_price":"0.001000000","low_limit_price":"-0.001000000","max_price_variation":"0.000000000","trading_reference_price":"0.000500000","unit_of_measure_qty":"5.000000000","#,
523 r#""min_price_increment_amount":"0.000000005","price_ratio":"0.000000010","inst_attrib_value":10,"underlying_id":256785,"raw_instrument_id":323,"market_depth_implied":0,"#,
524 r#""market_depth":13,"market_segment_id":0,"max_trade_vol":10000,"min_lot_size":1,"min_lot_size_block":1000,"min_lot_size_round_lot":100,"min_trade_vol":1,"#,
525 r#""contract_multiplier":0,"decay_quantity":0,"original_contract_size":0,"trading_reference_date":0,"appl_id":0,"#,
526 r#""maturity_year":0,"decay_start_date":0,"channel_id":4,"currency":"USD","settl_currency":"USD","secsubtype":"","group":"EW","exchange":"XCME","asset":"ES","cfi":"OCAFPS","#,
527 r#""security_type":"OOF","unit_of_measure":"IPNT","underlying":"ESZ4","strike_price_currency":"USD","strike_price":"4100.000000000","match_algorithm":"F","md_security_trading_status":2,"main_fraction":4,"price_display_format":8,"#,
528 r#""settl_price_type":9,"sub_fraction":23,"underlying_product":10,"maturity_month":8,"maturity_day":9,"maturity_week":11,"#,
529 r#""user_defined_instrument":"N","contract_multiplier_unit":0,"flow_schedule_type":5,"tick_rule":0"#
530 )
531 )
532 );
533 }
534
535 #[test]
536 fn test_imbalance_write_json() {
537 let data = vec![ImbalanceMsg {
538 hd: RECORD_HEADER,
539 ts_recv: 1,
540 ref_price: 2,
541 auction_time: 3,
542 cont_book_clr_price: 4,
543 auct_interest_clr_price: 5,
544 ssr_filling_price: 6,
545 ind_match_price: 7,
546 upper_collar: 8,
547 lower_collar: 9,
548 paired_qty: 10,
549 total_imbalance_qty: 11,
550 market_imbalance_qty: 12,
551 unpaired_qty: 13,
552 auction_type: 'B' as c_char,
553 side: 'A' as c_char,
554 auction_status: 14,
555 freeze_status: 15,
556 num_extensions: 16,
557 unpaired_side: 'A' as c_char,
558 significant_imbalance: 'N' as c_char,
559 _reserved: [0],
560 }];
561 let slice_res = write_json_to_string(data.as_slice(), false, false, false);
562 let stream_res = write_json_stream_to_string(data, false, false, false);
563
564 assert_eq!(slice_res, stream_res);
565 assert_eq!(
566 slice_res,
567 format!(
568 "{{{},{HEADER_JSON},{}}}\n",
569 r#""ts_recv":"1""#,
570 concat!(
571 r#""ref_price":"2","auction_time":"3","cont_book_clr_price":"4","auct_interest_clr_price":"5","#,
572 r#""ssr_filling_price":"6","ind_match_price":"7","upper_collar":"8","lower_collar":"9","paired_qty":10,"#,
573 r#""total_imbalance_qty":11,"market_imbalance_qty":12,"unpaired_qty":13,"auction_type":"B","side":"A","#,
574 r#""auction_status":14,"freeze_status":15,"num_extensions":16,"unpaired_side":"A","significant_imbalance":"N""#,
575 )
576 )
577 );
578 }
579
580 #[test]
581 fn test_stat_write_json() {
582 let data = vec![StatMsg {
583 hd: RECORD_HEADER,
584 ts_recv: 1,
585 ts_ref: 2,
586 price: 3,
587 quantity: 0,
588 sequence: 4,
589 ts_in_delta: 5,
590 stat_type: StatType::OpeningPrice as u16,
591 channel_id: 7,
592 update_action: StatUpdateAction::New as u8,
593 stat_flags: 0,
594 _reserved: Default::default(),
595 }];
596 let slice_res = write_json_to_string(data.as_slice(), false, true, false);
597 let stream_res = write_json_stream_to_string(data, false, true, false);
598
599 assert_eq!(slice_res, stream_res);
600 assert_eq!(
601 slice_res,
602 format!(
603 "{{{},{HEADER_JSON},{}}}\n",
604 r#""ts_recv":"1""#,
605 concat!(
606 r#""ts_ref":"2","price":"0.000000003","quantity":0,"sequence":4,"#,
607 r#""ts_in_delta":5,"stat_type":1,"channel_id":7,"update_action":1,"stat_flags":0"#,
608 )
609 )
610 );
611 }
612
613 #[test]
614 fn test_metadata_write_json() {
615 let metadata = Metadata {
616 version: 1,
617 dataset: Dataset::GlbxMdp3.to_string(),
618 schema: Some(Schema::Ohlcv1H),
619 start: 1662734705128748281,
620 end: NonZeroU64::new(1662734720914876944),
621 limit: None,
622 stype_in: Some(SType::InstrumentId),
623 stype_out: SType::RawSymbol,
624 ts_out: false,
625 symbol_cstr_len: SYMBOL_CSTR_LEN_V1,
626 symbols: vec!["ESZ2".to_owned()],
627 partial: Vec::new(),
628 not_found: Vec::new(),
629 mappings: vec![SymbolMapping {
630 raw_symbol: "ESZ2".to_owned(),
631 intervals: vec![MappingInterval {
632 start_date: time::Date::from_calendar_date(2022, time::Month::September, 9)
633 .unwrap(),
634 end_date: time::Date::from_calendar_date(2022, time::Month::September, 10)
635 .unwrap(),
636 symbol: "ESH2".to_owned(),
637 }],
638 }],
639 };
640 let res = write_json_metadata_to_string(&metadata, false);
641 assert_eq!(
642 res,
643 "{\"version\":1,\"dataset\":\"GLBX.MDP3\",\"schema\":\"ohlcv-1h\",\"start\"\
644 :\"2022-09-09T14:45:05.128748281Z\",\"end\":\"2022-09-09T14:45:20.914876944Z\",\"limit\":null,\
645 \"stype_in\":\"instrument_id\",\"stype_out\":\"raw_symbol\",\"ts_out\":false,\"symbol_cstr_len\":22,\"symbols\"\
646 :[\"ESZ2\"],\"partial\":[],\"not_found\":[],\"mappings\":[{\"raw_symbol\":\"ESZ2\",\
647 \"intervals\":[{\"start_date\":\"2022-09-09\",\"end_date\":\"2022-09-10\",\"symbol\":\
648 \"ESH2\"}]}]}\n"
649 );
650 }
651
652 #[test]
653 fn test_encode_with_ts_out() {
654 let records = vec![WithTsOut {
655 rec: OhlcvMsg {
656 hd: RECORD_HEADER,
657 open: 5000,
658 high: 8000,
659 low: 3000,
660 close: 6000,
661 volume: 55_000,
662 },
663 ts_out: 1678481869000000000,
664 }];
665 let res = write_json_to_string(records.as_slice(), false, false, true);
666 assert_eq!(
667 res,
668 format!(
669 "{{{},{}}}\n",
670 r#""hd":{"ts_event":"2022-07-21T22:17:31.000000000Z","rtype":4,"publisher_id":1,"instrument_id":323}"#,
671 r#""open":"5000","high":"8000","low":"3000","close":"6000","volume":"55000","ts_out":"2023-03-10T20:57:49.000000000Z""#,
672 )
673 );
674 }
675
676 #[test]
677 fn test_serialize_quoted_str_to_json() {
678 let json = write_json_to_string(
679 vec![ErrorMsg::new(0, None, "\"A test", true)].as_slice(),
680 false,
681 true,
682 true,
683 );
684 assert_eq!(
685 json,
686 r#"{"hd":{"ts_event":null,"rtype":21,"publisher_id":0,"instrument_id":0},"err":"\"A test","code":255,"is_last":1}
687"#
688 );
689 }
690
691 #[test]
692 fn test_encode_ref_with_sym() {
693 let mut buffer = Vec::new();
694 const BAR: OhlcvMsg = OhlcvMsg {
695 hd: RecordHeader::new::<OhlcvMsg>(rtype::OHLCV_1H, 10, 9, 0),
696 open: 175 * FIXED_PRICE_SCALE,
697 high: 177 * FIXED_PRICE_SCALE,
698 low: 174 * FIXED_PRICE_SCALE,
699 close: 175 * FIXED_PRICE_SCALE,
700 volume: 4033445,
701 };
702 let rec_ref = RecordRef::from(&BAR);
703 let mut encoder = Encoder::new(&mut buffer, false, false, false);
704 encoder.encode_ref_with_sym(rec_ref, None).unwrap();
705 encoder.encode_ref_with_sym(rec_ref, Some("AAPL")).unwrap();
706 let res = String::from_utf8(buffer).unwrap();
707 assert_eq!(
708 res,
709 "{\"hd\":{\"ts_event\":\"0\",\"rtype\":34,\"publisher_id\":10,\"instrument_id\":9},\"open\":\"175000000000\",\"high\":\"177000000000\",\"low\":\"174000000000\",\"close\":\"175000000000\",\"volume\":\"4033445\",\"symbol\":null}\n\
710 {\"hd\":{\"ts_event\":\"0\",\"rtype\":34,\"publisher_id\":10,\"instrument_id\":9},\"open\":\"175000000000\",\"high\":\"177000000000\",\"low\":\"174000000000\",\"close\":\"175000000000\",\"volume\":\"4033445\",\"symbol\":\"AAPL\"}\n",
711 );
712 }
713}