1pub use crate::protocol::binary::metadata::{BinaryFieldMeta, BinarySchemaMeta, BinaryType};
15pub use crate::protocol::binary::reader::BinaryObjectReader;
16pub use crate::protocol::binary::value::BinaryObject;
17pub use crate::protocol::binary::writer::BinaryObjectBuilder;
18pub use crate::protocol::binary::{field_id, schema_id, type_id};
19pub use crate::protocol::types::type_code;
20pub use ignite_client_derive::IgniteBinary;
21
22use bigdecimal::BigDecimal;
23use uuid::Uuid;
24
25use crate::Result;
26use crate::protocol::error::{ProtocolError, value_type_name};
27use crate::protocol::types::IgniteValue;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct IgniteChar(pub u16);
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
42pub struct IgniteDate(pub i64);
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub struct IgniteTime(pub i64);
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
51pub struct IgniteTimestamp(pub i64, pub i32);
52
53pub trait WriteBinary {
57 fn type_name() -> &'static str;
59
60 fn write(&self, b: BinaryObjectBuilder) -> BinaryObjectBuilder;
63
64 fn to_binary(&self) -> BinaryObject {
66 self.write(BinaryObjectBuilder::new(Self::type_name()))
67 .build()
68 }
69
70 fn binary_type() -> BinaryType;
73}
74
75pub trait FieldWrite {
77 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder;
79
80 fn field_type_code() -> i32;
82}
83
84pub trait ReadBinary: Sized {
86 fn read(r: &BinaryObjectReader) -> Result<Self>;
87}
88
89pub trait FieldRead: Sized {
91 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self>;
92}
93
94thread_local! {
117 static NESTED_SCHEMAS: std::cell::RefCell<std::collections::HashMap<(i32, i32), Vec<i32>>> =
118 std::cell::RefCell::new(std::collections::HashMap::new());
119}
120
121pub(crate) fn with_nested_schemas<F, R>(
132 schemas: std::collections::HashMap<(i32, i32), Vec<i32>>,
133 f: F,
134) -> R
135where
136 F: FnOnce() -> R,
137{
138 NESTED_SCHEMAS.with(|cell| *cell.borrow_mut() = schemas);
139 let result = f();
140 NESTED_SCHEMAS.with(|cell| cell.borrow_mut().clear());
141 result
142}
143
144pub fn nested_object_reader(bytes: bytes::Bytes) -> Result<BinaryObjectReader> {
155 match BinaryObjectReader::new(bytes.clone()) {
156 Ok(r) => Ok(r),
157 Err(ProtocolError::CompactFooterNeedsSchema) => {
158 let mut hb = bytes.clone();
159 let header = crate::protocol::binary::header::BinaryHeader::read(&mut hb)?;
160 let key = (header.type_id, header.schema_id);
161 let schema = NESTED_SCHEMAS
162 .with(|cell| cell.borrow().get(&key).cloned())
163 .ok_or(ProtocolError::CompactFooterNeedsSchema)?;
164 Ok(BinaryObjectReader::with_schema(bytes, &schema)?)
165 }
166 Err(e) => Err(e.into()),
167 }
168}
169
170macro_rules! leaf_field {
181 ($rust_ty:ty, $variant:ident, $code:path, $expected:literal) => {
182 impl FieldWrite for $rust_ty {
183 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
184 b.set_value(name, &IgniteValue::$variant(*self))
185 }
186
187 fn field_type_code() -> i32 {
188 $code as i32
189 }
190 }
191
192 impl FieldRead for $rust_ty {
193 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
194 match r.field(name)? {
195 Some(IgniteValue::$variant(v)) => Ok(v),
196 Some(other) => Err(ProtocolError::TypeMismatch {
197 expected: $expected,
198 got: value_type_name(&other),
199 }
200 .into()),
201 None => Err(ProtocolError::UnexpectedNull.into()),
202 }
203 }
204 }
205 };
206}
207
208leaf_field!(bool, Bool, type_code::BOOL, "Bool");
209leaf_field!(i8, Byte, type_code::BYTE, "Byte");
210leaf_field!(i16, Short, type_code::SHORT, "Short");
211leaf_field!(i32, Int, type_code::INT, "Int");
212leaf_field!(i64, Long, type_code::LONG, "Long");
213leaf_field!(f32, Float, type_code::FLOAT, "Float");
214leaf_field!(f64, Double, type_code::DOUBLE, "Double");
215
216macro_rules! newtype_field {
219 ($newtype:ty, $variant:ident, $code:path, $expected:literal) => {
220 impl FieldWrite for $newtype {
221 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
222 b.set_value(name, &IgniteValue::$variant(self.0))
223 }
224
225 fn field_type_code() -> i32 {
226 $code as i32
227 }
228 }
229
230 impl FieldRead for $newtype {
231 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
232 match r.field(name)? {
233 Some(IgniteValue::$variant(v)) => Ok(Self(v)),
234 Some(other) => Err(ProtocolError::TypeMismatch {
235 expected: $expected,
236 got: value_type_name(&other),
237 }
238 .into()),
239 None => Err(ProtocolError::UnexpectedNull.into()),
240 }
241 }
242 }
243 };
244}
245
246newtype_field!(IgniteChar, Char, type_code::CHAR, "Char");
247newtype_field!(IgniteDate, Date, type_code::DATE, "Date");
248newtype_field!(IgniteTime, Time, type_code::TIME, "Time");
249
250impl FieldWrite for IgniteTimestamp {
253 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
254 b.set_value(name, &IgniteValue::Timestamp(self.0, self.1))
255 }
256
257 fn field_type_code() -> i32 {
258 type_code::TIMESTAMP as i32
259 }
260}
261
262impl FieldRead for IgniteTimestamp {
263 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
264 match r.field(name)? {
265 Some(IgniteValue::Timestamp(ms, ns)) => Ok(Self(ms, ns)),
266 Some(other) => Err(ProtocolError::TypeMismatch {
267 expected: "Timestamp",
268 got: value_type_name(&other),
269 }
270 .into()),
271 None => Err(ProtocolError::UnexpectedNull.into()),
272 }
273 }
274}
275
276impl FieldWrite for String {
277 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
278 b.set_value(name, &IgniteValue::String(self.clone()))
279 }
280
281 fn field_type_code() -> i32 {
282 type_code::STRING as i32
283 }
284}
285
286impl FieldRead for String {
287 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
288 match r.field(name)? {
289 Some(IgniteValue::String(v)) => Ok(v),
290 Some(other) => Err(ProtocolError::TypeMismatch {
291 expected: "String",
292 got: value_type_name(&other),
293 }
294 .into()),
295 None => Err(ProtocolError::UnexpectedNull.into()),
296 }
297 }
298}
299
300impl FieldWrite for Uuid {
301 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
302 b.set_value(name, &IgniteValue::Uuid(*self))
303 }
304
305 fn field_type_code() -> i32 {
306 type_code::UUID as i32
307 }
308}
309
310impl FieldRead for Uuid {
311 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
312 match r.field(name)? {
313 Some(IgniteValue::Uuid(v)) => Ok(v),
314 Some(other) => Err(ProtocolError::TypeMismatch {
315 expected: "Uuid",
316 got: value_type_name(&other),
317 }
318 .into()),
319 None => Err(ProtocolError::UnexpectedNull.into()),
320 }
321 }
322}
323
324impl FieldWrite for BigDecimal {
325 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
326 b.set_value(name, &IgniteValue::Decimal(self.clone()))
327 }
328
329 fn field_type_code() -> i32 {
330 type_code::DECIMAL as i32
331 }
332}
333
334impl FieldRead for BigDecimal {
335 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
336 match r.field(name)? {
337 Some(IgniteValue::Decimal(v)) => Ok(v),
338 Some(other) => Err(ProtocolError::TypeMismatch {
339 expected: "Decimal",
340 got: value_type_name(&other),
341 }
342 .into()),
343 None => Err(ProtocolError::UnexpectedNull.into()),
344 }
345 }
346}
347
348impl<T: FieldWrite> FieldWrite for Option<T> {
352 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
353 match self {
354 Some(v) => v.write_field(b, name),
355 None => b.set_value(name, &IgniteValue::Null),
356 }
357 }
358
359 fn field_type_code() -> i32 {
360 T::field_type_code()
361 }
362}
363
364impl<T: FieldRead> FieldRead for Option<T> {
365 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
366 match r.field(name)? {
367 None | Some(IgniteValue::Null) => Ok(None),
368 Some(_) => Ok(Some(T::read_field(r, name)?)),
369 }
370 }
371}
372
373impl FieldWrite for Vec<i32> {
385 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
386 b.set_value(name, &IgniteValue::IntArray(self.clone()))
387 }
388
389 fn field_type_code() -> i32 {
390 type_code::INT_ARRAY as i32
391 }
392}
393
394impl FieldRead for Vec<i32> {
395 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
396 match r.field(name)? {
397 Some(IgniteValue::IntArray(v)) => Ok(v),
398 Some(other) => Err(ProtocolError::TypeMismatch {
399 expected: "IntArray",
400 got: value_type_name(&other),
401 }
402 .into()),
403 None => Err(ProtocolError::UnexpectedNull.into()),
404 }
405 }
406}
407
408impl FieldWrite for Vec<String> {
415 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
416 let arr: Vec<Option<String>> = self.iter().cloned().map(Some).collect();
417 b.set_value(name, &IgniteValue::StringArray(arr))
418 }
419
420 fn field_type_code() -> i32 {
421 type_code::STRING_ARRAY as i32
422 }
423}
424
425impl FieldRead for Vec<String> {
426 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
427 match r.field(name)? {
428 Some(IgniteValue::StringArray(v)) => v
429 .into_iter()
430 .map(|e| {
431 e.ok_or_else(|| {
432 ProtocolError::TypeMismatch {
433 expected: "String",
434 got: "Null",
435 }
436 .into()
437 })
438 })
439 .collect(),
440 Some(other) => Err(ProtocolError::TypeMismatch {
441 expected: "StringArray",
442 got: value_type_name(&other),
443 }
444 .into()),
445 None => Err(ProtocolError::UnexpectedNull.into()),
446 }
447 }
448}
449
450impl FieldWrite for std::collections::HashMap<String, i32> {
451 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
452 let pairs: Vec<(IgniteValue, IgniteValue)> = self
453 .iter()
454 .map(|(k, v)| (IgniteValue::String(k.clone()), IgniteValue::Int(*v)))
455 .collect();
456 b.set_value(name, &IgniteValue::Map(1, pairs))
460 }
461
462 fn field_type_code() -> i32 {
463 type_code::MAP as i32
464 }
465}
466
467impl FieldRead for std::collections::HashMap<String, i32> {
468 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
469 match r.field(name)? {
470 Some(IgniteValue::Map(_, pairs)) => pairs
471 .into_iter()
472 .map(|(k, v)| {
473 let key = match k {
474 IgniteValue::String(s) => s,
475 other => {
476 return Err(ProtocolError::TypeMismatch {
477 expected: "String",
478 got: value_type_name(&other),
479 }
480 .into());
481 }
482 };
483 let val = match v {
484 IgniteValue::Int(i) => i,
485 other => {
486 return Err(ProtocolError::TypeMismatch {
487 expected: "Int",
488 got: value_type_name(&other),
489 }
490 .into());
491 }
492 };
493 Ok((key, val))
494 })
495 .collect(),
496 Some(other) => Err(ProtocolError::TypeMismatch {
497 expected: "Map",
498 got: value_type_name(&other),
499 }
500 .into()),
501 None => Err(ProtocolError::UnexpectedNull.into()),
502 }
503 }
504}
505
506#[derive(Debug, Clone, PartialEq)]
517pub struct IgniteList<T>(pub Vec<T>);
518
519impl FieldWrite for IgniteList<i32> {
520 fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
521 let vals: Vec<IgniteValue> = self.0.iter().map(|x| IgniteValue::Int(*x)).collect();
522 b.set_value(name, &IgniteValue::Collection(2, vals))
524 }
525
526 fn field_type_code() -> i32 {
527 type_code::COLLECTION as i32
528 }
529}
530
531impl FieldRead for IgniteList<i32> {
532 fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
533 match r.field(name)? {
534 Some(IgniteValue::Collection(_, vals)) => {
535 let items: Vec<i32> = vals
536 .into_iter()
537 .map(|v| match v {
538 IgniteValue::Int(i) => Ok(i),
539 other => Err(ProtocolError::TypeMismatch {
540 expected: "Int",
541 got: value_type_name(&other),
542 }
543 .into()),
544 })
545 .collect::<Result<Vec<i32>>>()?;
546 Ok(IgniteList(items))
547 }
548 Some(other) => Err(ProtocolError::TypeMismatch {
549 expected: "Collection",
550 got: value_type_name(&other),
551 }
552 .into()),
553 None => Err(ProtocolError::UnexpectedNull.into()),
554 }
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561 use crate::protocol::binary::{reader::BinaryObjectReader, writer::BinaryObjectBuilder};
562
563 #[test]
564 fn field_write_read_i32_and_string() {
565 let b = BinaryObjectBuilder::new("t.T");
566 let b = 42i32.write_field(b, "n");
567 let b = "hi".to_string().write_field(b, "s");
568 let obj = b.build();
569 let r = BinaryObjectReader::new(obj.bytes).unwrap();
570 assert_eq!(i32::read_field(&r, "n").unwrap(), 42);
571 assert_eq!(String::read_field(&r, "s").unwrap(), "hi");
572 }
573
574 #[test]
575 fn field_write_read_option_none() {
576 let b = Option::<i32>::None.write_field(BinaryObjectBuilder::new("t.T"), "n");
577 let r = BinaryObjectReader::new(b.build().bytes).unwrap();
578 assert_eq!(Option::<i32>::read_field(&r, "n").unwrap(), None);
579 }
580
581 #[test]
582 fn field_write_read_option_some() {
583 let b = Some(7i32).write_field(BinaryObjectBuilder::new("t.T"), "n");
584 let r = BinaryObjectReader::new(b.build().bytes).unwrap();
585 assert_eq!(Option::<i32>::read_field(&r, "n").unwrap(), Some(7));
586 }
587
588 #[test]
589 fn field_read_option_missing_field_is_none() {
590 let obj = BinaryObjectBuilder::new("t.T").set_i32("other", 1).build();
595 let r = BinaryObjectReader::new(obj.bytes).unwrap();
596 assert_eq!(Option::<i32>::read_field(&r, "n").unwrap(), None);
597 }
598
599 #[test]
600 fn field_write_read_all_numeric_leaves() {
601 let b = BinaryObjectBuilder::new("t.T");
602 let b = true.write_field(b, "bo");
603 let b = 1i8.write_field(b, "i8");
604 let b = 2i16.write_field(b, "i16");
605 let b = 3i32.write_field(b, "i32");
606 let b = 4i64.write_field(b, "i64");
607 let b = 5.5f32.write_field(b, "f32");
608 let b = 6.5f64.write_field(b, "f64");
609 let obj = b.build();
610 let r = BinaryObjectReader::new(obj.bytes).unwrap();
611 assert_eq!(bool::read_field(&r, "bo").unwrap(), true);
612 assert_eq!(i8::read_field(&r, "i8").unwrap(), 1);
613 assert_eq!(i16::read_field(&r, "i16").unwrap(), 2);
614 assert_eq!(i32::read_field(&r, "i32").unwrap(), 3);
615 assert_eq!(i64::read_field(&r, "i64").unwrap(), 4);
616 assert_eq!(f32::read_field(&r, "f32").unwrap(), 5.5);
617 assert_eq!(f64::read_field(&r, "f64").unwrap(), 6.5);
618 }
619
620 #[test]
621 fn field_write_read_newtypes() {
622 let b = BinaryObjectBuilder::new("t.T");
623 let b = IgniteChar(65).write_field(b, "c");
624 let b = IgniteDate(1_705_276_800_000).write_field(b, "d");
625 let b = IgniteTime(1_234_567_890).write_field(b, "t");
626 let b = IgniteTimestamp(1_700_000_000_000, 123).write_field(b, "ts");
627 let obj = b.build();
628 let r = BinaryObjectReader::new(obj.bytes).unwrap();
629 assert_eq!(IgniteChar::read_field(&r, "c").unwrap(), IgniteChar(65));
630 assert_eq!(
631 IgniteDate::read_field(&r, "d").unwrap(),
632 IgniteDate(1_705_276_800_000)
633 );
634 assert_eq!(
635 IgniteTime::read_field(&r, "t").unwrap(),
636 IgniteTime(1_234_567_890)
637 );
638 assert_eq!(
639 IgniteTimestamp::read_field(&r, "ts").unwrap(),
640 IgniteTimestamp(1_700_000_000_000, 123)
641 );
642 }
643
644 #[test]
645 fn field_write_read_uuid_and_decimal() {
646 use std::str::FromStr;
647
648 let u = Uuid::new_v4();
649 let d = BigDecimal::from_str("12.34").unwrap();
650 let b = BinaryObjectBuilder::new("t.T");
651 let b = u.write_field(b, "u");
652 let b = d.write_field(b, "d");
653 let obj = b.build();
654 let r = BinaryObjectReader::new(obj.bytes).unwrap();
655 assert_eq!(Uuid::read_field(&r, "u").unwrap(), u);
656 assert_eq!(
657 BigDecimal::read_field(&r, "d").unwrap().normalized(),
658 d.normalized()
659 );
660 }
661
662 #[test]
663 fn field_read_missing_required_field_errors() {
664 let obj = BinaryObjectBuilder::new("t.T").build();
665 let r = BinaryObjectReader::new(obj.bytes).unwrap();
666 let err = i32::read_field(&r, "missing").unwrap_err();
667 assert!(matches!(
668 err,
669 crate::IgniteError::Protocol(ProtocolError::UnexpectedNull)
670 ));
671 }
672
673 #[test]
674 fn field_read_wrong_variant_is_type_mismatch() {
675 let obj = BinaryObjectBuilder::new("t.T").set_i32("n", 1).build();
676 let r = BinaryObjectReader::new(obj.bytes).unwrap();
677 let err = String::read_field(&r, "n").unwrap_err();
678 assert!(matches!(
679 err,
680 crate::IgniteError::Protocol(ProtocolError::TypeMismatch {
681 expected: "String",
682 got: "Int"
683 })
684 ));
685 }
686
687 #[test]
690 fn field_write_read_vec_i32() {
691 let b = vec![1i32, -2, 3].write_field(BinaryObjectBuilder::new("t.T"), "a");
692 let r = BinaryObjectReader::new(b.build().bytes).unwrap();
693 assert_eq!(<Vec<i32>>::read_field(&r, "a").unwrap(), vec![1, -2, 3]);
694 assert_eq!(<Vec<i32>>::field_type_code(), type_code::INT_ARRAY as i32);
695 }
696
697 #[test]
698 fn field_write_read_vec_i32_empty() {
699 let b = Vec::<i32>::new().write_field(BinaryObjectBuilder::new("t.T"), "a");
700 let r = BinaryObjectReader::new(b.build().bytes).unwrap();
701 assert_eq!(<Vec<i32>>::read_field(&r, "a").unwrap(), Vec::<i32>::new());
702 }
703
704 #[test]
705 fn field_write_read_vec_string() {
706 let v = vec!["foo".to_string(), "bar".to_string()];
707 let b = v.write_field(BinaryObjectBuilder::new("t.T"), "s");
708 let r = BinaryObjectReader::new(b.build().bytes).unwrap();
709 assert_eq!(<Vec<String>>::read_field(&r, "s").unwrap(), v);
710 assert_eq!(
711 <Vec<String>>::field_type_code(),
712 type_code::STRING_ARRAY as i32
713 );
714 }
715
716 #[test]
717 fn field_read_vec_string_null_element_errors() {
718 let obj = BinaryObjectBuilder::new("t.T")
722 .set_string_array("s", &[Some("a".to_string()), None])
723 .build();
724 let r = BinaryObjectReader::new(obj.bytes).unwrap();
725 let err = <Vec<String>>::read_field(&r, "s").unwrap_err();
726 assert!(matches!(
727 err,
728 crate::IgniteError::Protocol(ProtocolError::TypeMismatch {
729 expected: "String",
730 got: "Null"
731 })
732 ));
733 }
734
735 #[test]
736 fn field_write_read_hashmap_string_i32() {
737 use std::collections::HashMap;
738
739 let mut m = HashMap::new();
740 m.insert("one".to_string(), 1i32);
741 m.insert("two".to_string(), 2i32);
742 let b = m.write_field(BinaryObjectBuilder::new("t.T"), "m");
743 let r = BinaryObjectReader::new(b.build().bytes).unwrap();
744 assert_eq!(<HashMap<String, i32>>::read_field(&r, "m").unwrap(), m);
745 assert_eq!(
746 <HashMap<String, i32>>::field_type_code(),
747 type_code::MAP as i32
748 );
749 }
750
751 #[test]
752 fn field_write_read_ignite_list_i32() {
753 let list = IgniteList(vec![10i32, 20, 30]);
754 let b = list.write_field(BinaryObjectBuilder::new("t.T"), "l");
755 let r = BinaryObjectReader::new(b.build().bytes).unwrap();
756 assert_eq!(IgniteList::<i32>::read_field(&r, "l").unwrap(), list);
757 assert_eq!(
758 IgniteList::<i32>::field_type_code(),
759 type_code::COLLECTION as i32
760 );
761 }
762
763 #[test]
764 fn field_read_vec_i32_missing_field_errors() {
765 let obj = BinaryObjectBuilder::new("t.T").build();
766 let r = BinaryObjectReader::new(obj.bytes).unwrap();
767 let err = <Vec<i32>>::read_field(&r, "missing").unwrap_err();
768 assert!(matches!(
769 err,
770 crate::IgniteError::Protocol(ProtocolError::UnexpectedNull)
771 ));
772 }
773
774 #[test]
775 fn field_read_hashmap_wrong_variant_is_type_mismatch() {
776 let obj = BinaryObjectBuilder::new("t.T").set_i32("m", 1).build();
777 let r = BinaryObjectReader::new(obj.bytes).unwrap();
778 use std::collections::HashMap;
779 let err = <HashMap<String, i32>>::read_field(&r, "m").unwrap_err();
780 assert!(matches!(
781 err,
782 crate::IgniteError::Protocol(ProtocolError::TypeMismatch {
783 expected: "Map",
784 got: "Int"
785 })
786 ));
787 }
788}