fbthrift_git/
adapter.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use std::collections::BTreeMap;
18use std::collections::BTreeSet;
19use std::fmt::Debug;
20use std::marker::PhantomData;
21
22use crate::metadata::ThriftAnnotations;
23
24pub trait ThriftAdapter {
25    /// Aka the "from" type.
26    type StandardType;
27    /// Aka the "to" type.
28    type AdaptedType: Clone + Debug + PartialEq + Send + Sync;
29
30    /// The error type thrown if `from_thrift` fails during deserialization.
31    type Error: Into<anyhow::Error> + Debug;
32
33    /// Converts an instance of `StandardType` to `AdaptedType` during deserialization.
34    ///
35    /// The `Err` returned by this method will be propagated as a deserialization error.
36    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error>;
37
38    // TODO(emersonford): should we support a moved param here?
39    /// Converts from the given `AdaptedType` back to the given `StandardType` during
40    /// serialization.
41    ///
42    /// This must be an infallible operation as `serialize` is infallible.
43    ///
44    /// WARNING: you should be particularly cautious when using `.unwrap()` or any other panic-able
45    /// methods in this method. If this method panics, it will be at serialization time and not at
46    /// the Thrift struct creation time, meaning it will be extremely difficult to debug what the
47    /// true "source" of the panic.
48    ///
49    /// If your `AdaptedType` -> `StandardType` conversion is truly fallible, you probably shouldn't
50    /// use an adapter to begin with.
51    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType;
52
53    /// Method called when this adapter is used on a Thrift struct's field. Provides information
54    /// about the specific field ID in `field_id`. The type of the struct that owns this field is
55    /// passed in as `T`.
56    ///
57    /// Defaults to calling `from_thrift`.
58    fn from_thrift_field<T: ThriftAnnotations>(
59        value: Self::StandardType,
60        _field_id: i16,
61    ) -> Result<Self::AdaptedType, Self::Error> {
62        Self::from_thrift(value)
63    }
64
65    /// Method called when this adapter is used on a Thrift struct's field. Provides information
66    /// about the specific field ID in `field_id`. The type of the struct that owns this field is
67    /// passed in as `T`.
68    ///
69    /// Defaults to calling `to_thrift`.
70    fn to_thrift_field<T: ThriftAnnotations>(
71        value: &Self::AdaptedType,
72        _field_id: i16,
73    ) -> Self::StandardType {
74        Self::to_thrift(value)
75    }
76
77    /// Method called when the adapted type is not present in a field during deserialization or is
78    /// populated with `..Default::default()`. The value passed here is the default original type
79    /// value for the field. This can be used to record that the field was not present inside
80    /// of your adapted type.
81    ///
82    /// **This method must be infallible, as it will be called when `Default::default()` is used
83    /// (which is infallible).**
84    ///
85    /// WARNING: This defaults to calling `from_thrift_field` and **assumes `from_thrift_field`
86    /// will not return an `Err` for the default value**.
87    fn from_thrift_default<T: ThriftAnnotations>(
88        value: Self::StandardType,
89        field_id: i16,
90    ) -> Self::AdaptedType {
91        Self::from_thrift_field::<T>(value, field_id).unwrap_or_else(|e| {
92            panic!(
93                "`from_thrift_field` must not return an `Err` on field ID {} for its default value, but it did: '{:?}'",
94                field_id, e
95            );
96        })
97    }
98}
99
100// NOTE: we define where bounds on the structs themselves here to improve error messaging during
101// the Thrift compilation process.
102
103//// Layers multiple [ThriftTypeAdapter] together. Used when multiple Thrift typedefs with adapters
104//// are layered on each other.
105pub struct LayeredThriftAdapter<Fst, Snd>
106where
107    Fst: ThriftAdapter,
108    Snd: ThriftAdapter,
109    Fst::StandardType: Into<Snd::AdaptedType>,
110    Snd::AdaptedType: Into<Fst::StandardType>,
111{
112    _inner_first: PhantomData<Fst>,
113    _inner_second: PhantomData<Snd>,
114}
115
116impl<Fst, Snd> ThriftAdapter for LayeredThriftAdapter<Fst, Snd>
117where
118    Fst: ThriftAdapter,
119    Snd: ThriftAdapter,
120    Fst::StandardType: Into<Snd::AdaptedType>,
121    Snd::AdaptedType: Into<Fst::StandardType>,
122{
123    type StandardType = <Snd as ThriftAdapter>::StandardType;
124    type AdaptedType = <Fst as ThriftAdapter>::AdaptedType;
125
126    type Error = anyhow::Error;
127
128    #[inline]
129    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
130        <Fst as ThriftAdapter>::from_thrift(
131            <Snd as ThriftAdapter>::from_thrift(value)
132                .map_err(Into::<anyhow::Error>::into)?
133                .into(),
134        )
135        .map_err(Into::<anyhow::Error>::into)
136    }
137
138    #[inline]
139    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
140        <Snd as ThriftAdapter>::to_thrift(&<Fst as ThriftAdapter>::to_thrift(value).into())
141    }
142
143    #[inline]
144    fn from_thrift_field<T: ThriftAnnotations>(
145        value: Self::StandardType,
146        field_id: i16,
147    ) -> Result<Self::AdaptedType, Self::Error> {
148        <Fst as ThriftAdapter>::from_thrift_field::<T>(
149            <Snd as ThriftAdapter>::from_thrift_field::<T>(value, field_id)
150                .map_err(Into::<anyhow::Error>::into)?
151                .into(),
152            field_id,
153        )
154        .map_err(Into::<anyhow::Error>::into)
155    }
156
157    #[inline]
158    fn to_thrift_field<T: ThriftAnnotations>(
159        value: &Self::AdaptedType,
160        field_id: i16,
161    ) -> Self::StandardType {
162        <Snd as ThriftAdapter>::to_thrift_field::<T>(
163            &<Fst as ThriftAdapter>::to_thrift_field::<T>(value, field_id).into(),
164            field_id,
165        )
166    }
167
168    #[inline]
169    fn from_thrift_default<T: ThriftAnnotations>(
170        value: Self::StandardType,
171        field_id: i16,
172    ) -> Self::AdaptedType {
173        <Fst as ThriftAdapter>::from_thrift_default::<T>(
174            <Snd as ThriftAdapter>::from_thrift_default::<T>(value, field_id).into(),
175            field_id,
176        )
177    }
178}
179
180/// Transforms the given adapter `A` into an adapter with the signature `Vec<StandardType>`
181/// -> `Vec<AdaptedType>`. Because Rust doesn't have HKT, we cannot make this "generic" over
182/// multiple collection types.
183pub struct ListMapAdapter<A>
184where
185    A: ThriftAdapter,
186{
187    _inner_adapter: PhantomData<A>,
188}
189
190impl<A> ThriftAdapter for ListMapAdapter<A>
191where
192    A: ThriftAdapter,
193{
194    type StandardType = Vec<A::StandardType>;
195    type AdaptedType = Vec<A::AdaptedType>;
196
197    type Error = A::Error;
198
199    #[inline]
200    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
201        value
202            .into_iter()
203            .map(|elem| A::from_thrift(elem))
204            .collect::<Result<Self::AdaptedType, Self::Error>>()
205    }
206
207    #[inline]
208    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
209        value.iter().map(|elem| A::to_thrift(elem)).collect()
210    }
211
212    #[inline]
213    fn from_thrift_field<T: ThriftAnnotations>(
214        value: Self::StandardType,
215        field_id: i16,
216    ) -> Result<Self::AdaptedType, Self::Error> {
217        value
218            .into_iter()
219            .map(|elem| A::from_thrift_field::<T>(elem, field_id))
220            .collect::<Result<Self::AdaptedType, Self::Error>>()
221    }
222
223    #[inline]
224    fn to_thrift_field<T: ThriftAnnotations>(
225        value: &Self::AdaptedType,
226        field_id: i16,
227    ) -> Self::StandardType {
228        value
229            .iter()
230            .map(|elem| A::to_thrift_field::<T>(elem, field_id))
231            .collect()
232    }
233
234    #[inline]
235    fn from_thrift_default<T: ThriftAnnotations>(
236        value: Self::StandardType,
237        field_id: i16,
238    ) -> Self::AdaptedType {
239        value
240            .into_iter()
241            .map(|elem| A::from_thrift_default::<T>(elem, field_id))
242            .collect()
243    }
244}
245
246/// Transforms the given adapter `A` into an adapter with the signature `BTreeSet<StandardType>`
247/// -> `BTreeSet<AdaptedType>`. Because Rust doesn't have HKT, we cannot make this "generic" over
248/// multiple collection types.
249pub struct SetMapAdapter<A>
250where
251    A: ThriftAdapter,
252    A::StandardType: Ord + PartialEq,
253    A::AdaptedType: Ord + PartialEq,
254{
255    _inner_adapter: PhantomData<A>,
256}
257
258impl<A> ThriftAdapter for SetMapAdapter<A>
259where
260    A: ThriftAdapter,
261    A::StandardType: Ord + PartialEq,
262    A::AdaptedType: Ord + PartialEq,
263{
264    type StandardType = BTreeSet<A::StandardType>;
265    type AdaptedType = BTreeSet<A::AdaptedType>;
266
267    type Error = A::Error;
268
269    #[inline]
270    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
271        value
272            .into_iter()
273            .map(|elem| A::from_thrift(elem))
274            .collect::<Result<Self::AdaptedType, Self::Error>>()
275    }
276
277    #[inline]
278    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
279        value.iter().map(|elem| A::to_thrift(elem)).collect()
280    }
281
282    #[inline]
283    fn from_thrift_field<T: ThriftAnnotations>(
284        value: Self::StandardType,
285        field_id: i16,
286    ) -> Result<Self::AdaptedType, Self::Error> {
287        value
288            .into_iter()
289            .map(|elem| A::from_thrift_field::<T>(elem, field_id))
290            .collect::<Result<Self::AdaptedType, Self::Error>>()
291    }
292
293    #[inline]
294    fn to_thrift_field<T: ThriftAnnotations>(
295        value: &Self::AdaptedType,
296        field_id: i16,
297    ) -> Self::StandardType {
298        value
299            .iter()
300            .map(|elem| A::to_thrift_field::<T>(elem, field_id))
301            .collect()
302    }
303
304    #[inline]
305    fn from_thrift_default<T: ThriftAnnotations>(
306        value: Self::StandardType,
307        field_id: i16,
308    ) -> Self::AdaptedType {
309        value
310            .into_iter()
311            .map(|elem| A::from_thrift_default::<T>(elem, field_id))
312            .collect()
313    }
314}
315
316/// Transforms the given adapter `KA` and `KV` into an adapter with the signature
317/// `BTreeMap<KA::StandardType, KV::StandardType>` -> `BTreeMap<KA::AdaptedType, KV::AdaptedType>`.
318/// Because Rust doesn't have HKT, we cannot make this "generic" over multiple collection types.
319pub struct MapMapAdapter<KA, KV>
320where
321    KA: ThriftAdapter,
322    KV: ThriftAdapter,
323    KA::StandardType: Ord + PartialEq,
324    KA::AdaptedType: Ord + PartialEq,
325{
326    _inner_key_adapter: PhantomData<KA>,
327    _inner_val_adapter: PhantomData<KV>,
328}
329
330impl<KA, KV> ThriftAdapter for MapMapAdapter<KA, KV>
331where
332    KA: ThriftAdapter,
333    KV: ThriftAdapter,
334    KA::StandardType: Ord + PartialEq,
335    KA::AdaptedType: Ord + PartialEq,
336{
337    type StandardType = BTreeMap<KA::StandardType, KV::StandardType>;
338    type AdaptedType = BTreeMap<KA::AdaptedType, KV::AdaptedType>;
339
340    type Error = anyhow::Error;
341
342    #[inline]
343    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
344        value
345            .into_iter()
346            .map(|(key, val)| {
347                Ok((
348                    KA::from_thrift(key).map_err(Into::<anyhow::Error>::into)?,
349                    KV::from_thrift(val).map_err(Into::<anyhow::Error>::into)?,
350                ))
351            })
352            .collect::<Result<Self::AdaptedType, Self::Error>>()
353    }
354
355    #[inline]
356    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
357        value
358            .iter()
359            .map(|(key, val)| (KA::to_thrift(key), KV::to_thrift(val)))
360            .collect::<Self::StandardType>()
361    }
362
363    #[inline]
364    fn from_thrift_field<T: ThriftAnnotations>(
365        value: Self::StandardType,
366        field_id: i16,
367    ) -> Result<Self::AdaptedType, Self::Error> {
368        value
369            .into_iter()
370            .map(|(key, val)| {
371                Ok((
372                    KA::from_thrift_field::<T>(key, field_id)
373                        .map_err(Into::<anyhow::Error>::into)?,
374                    KV::from_thrift_field::<T>(val, field_id)
375                        .map_err(Into::<anyhow::Error>::into)?,
376                ))
377            })
378            .collect::<Result<Self::AdaptedType, Self::Error>>()
379    }
380
381    #[inline]
382    fn to_thrift_field<T: ThriftAnnotations>(
383        value: &Self::AdaptedType,
384        field_id: i16,
385    ) -> Self::StandardType {
386        value
387            .iter()
388            .map(|(key, val)| {
389                (
390                    KA::to_thrift_field::<T>(key, field_id),
391                    KV::to_thrift_field::<T>(val, field_id),
392                )
393            })
394            .collect::<Self::StandardType>()
395    }
396
397    #[inline]
398    fn from_thrift_default<T: ThriftAnnotations>(
399        value: Self::StandardType,
400        field_id: i16,
401    ) -> Self::AdaptedType {
402        value
403            .into_iter()
404            .map(|(key, val)| {
405                (
406                    KA::from_thrift_default::<T>(key, field_id),
407                    KV::from_thrift_default::<T>(val, field_id),
408                )
409            })
410            .collect::<Self::AdaptedType>()
411    }
412}
413
414/// No-op adapter. Used if the key of a map is an adapted type, but the value isn't, or vice versa.
415pub struct IdentityAdapter<T>
416where
417    T: Clone + Debug + Send + Sync + PartialEq,
418{
419    _inner: PhantomData<T>,
420}
421
422impl<T> ThriftAdapter for IdentityAdapter<T>
423where
424    T: Clone + Debug + Send + Sync + PartialEq,
425{
426    type StandardType = T;
427    type AdaptedType = T;
428
429    type Error = std::convert::Infallible;
430
431    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
432        Ok(value)
433    }
434
435    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
436        value.clone()
437    }
438}
439
440/// Convenience trait for newtype adapters.
441///
442/// This trait simplifies construction of adapters that newtype the standard
443/// type. On top of the standard bounds, the newtype (adapted type) needs to
444/// also have a [`From`] and [`Into`] implementation for the standard thrift
445/// type.
446///
447/// # Examples
448///
449/// ```
450/// use fbthrift::adapter::NewTypeAdapter;
451///
452/// #[derive(Clone, Debug)]
453/// struct MyInt(i64);
454///
455/// impl From<MyInt> for i64 {
456///     fn from(v: MyInt) -> Self { v.0 }
457/// }
458///
459/// impl From<i64> for MyInt {
460///     fn from(v: i64) -> Self { Self(v) }
461/// }
462///
463/// struct MyIntAdapter;
464///
465/// impl NewTypeAdapter for MyIntAdapter {
466///     type StandardType = i64;
467///     type AdaptedType = MyInt;
468/// }
469/// ```
470pub trait NewTypeAdapter {
471    /// The thrift type that the adapter interprets.
472    type StandardType;
473    /// The Rust type to adapt to.
474    type AdaptedType;
475}
476
477impl<Adapter, StandardType, AdaptedType> ThriftAdapter for Adapter
478where
479    Adapter: NewTypeAdapter<StandardType = StandardType, AdaptedType = AdaptedType>,
480    AdaptedType: From<StandardType> + Into<StandardType> + Clone + Debug + Send + Sync + PartialEq,
481{
482    type StandardType = <Self as NewTypeAdapter>::StandardType;
483    type AdaptedType = <Self as NewTypeAdapter>::AdaptedType;
484
485    type Error = <Self::AdaptedType as TryFrom<Self::StandardType>>::Error;
486
487    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
488        value.clone().into()
489    }
490
491    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
492        Self::AdaptedType::try_from(value)
493    }
494}
495
496#[cfg(test)]
497mod tests {
498    use std::any::TypeId;
499
500    use super::*;
501
502    // Represents the following flow
503    // String (Final Adapted Type) <- BoolToStringAdapter -> Bool <- I64ToBoolAdapter -> i64
504    // <- I8ToI64Adapter -> i8 (Original Thrift type)
505    struct DummyParentStruct {}
506
507    impl ThriftAnnotations for DummyParentStruct {}
508
509    struct BoolToStringAdapter {}
510
511    impl ThriftAdapter for BoolToStringAdapter {
512        type StandardType = bool;
513        type AdaptedType = String;
514
515        type Error = anyhow::Error;
516
517        fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
518            Ok(value.to_string())
519        }
520
521        fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
522            value == "true"
523        }
524
525        fn from_thrift_field<T: ThriftAnnotations>(
526            value: Self::StandardType,
527            field_id: i16,
528        ) -> Result<Self::AdaptedType, Self::Error> {
529            assert_eq!(field_id, 42);
530            assert_eq!(TypeId::of::<DummyParentStruct>(), TypeId::of::<T>());
531
532            Self::from_thrift(value)
533        }
534
535        fn to_thrift_field<T: ThriftAnnotations>(
536            value: &Self::AdaptedType,
537            field_id: i16,
538        ) -> Self::StandardType {
539            assert_eq!(field_id, 42);
540            assert_eq!(TypeId::of::<DummyParentStruct>(), TypeId::of::<T>());
541
542            Self::to_thrift(value)
543        }
544
545        fn from_thrift_default<T: ThriftAnnotations>(
546            value: Self::StandardType,
547            field_id: i16,
548        ) -> Self::AdaptedType {
549            assert_eq!(field_id, 42);
550            assert_eq!(TypeId::of::<DummyParentStruct>(), TypeId::of::<T>());
551
552            Self::from_thrift_field::<T>(value, field_id).unwrap()
553        }
554    }
555
556    struct I64ToBoolAdapter {}
557
558    impl ThriftAdapter for I64ToBoolAdapter {
559        type StandardType = i64;
560        type AdaptedType = bool;
561
562        type Error = anyhow::Error;
563
564        fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
565            match value {
566                0 => Ok(false),
567                1 => Ok(true),
568                other => anyhow::bail!("invalid num {}", other),
569            }
570        }
571
572        fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
573            if *value { 1 } else { 0 }
574        }
575
576        fn from_thrift_field<T: ThriftAnnotations>(
577            value: Self::StandardType,
578            field_id: i16,
579        ) -> Result<Self::AdaptedType, Self::Error> {
580            assert_eq!(field_id, 42);
581            assert_eq!(TypeId::of::<DummyParentStruct>(), TypeId::of::<T>());
582
583            Self::from_thrift(value)
584        }
585
586        fn to_thrift_field<T: ThriftAnnotations>(
587            value: &Self::AdaptedType,
588            field_id: i16,
589        ) -> Self::StandardType {
590            assert_eq!(field_id, 42);
591            assert_eq!(TypeId::of::<DummyParentStruct>(), TypeId::of::<T>());
592
593            Self::to_thrift(value)
594        }
595
596        fn from_thrift_default<T: ThriftAnnotations>(
597            value: Self::StandardType,
598            field_id: i16,
599        ) -> Self::AdaptedType {
600            assert_eq!(field_id, 42);
601            assert_eq!(TypeId::of::<DummyParentStruct>(), TypeId::of::<T>());
602
603            Self::from_thrift_field::<T>(value, field_id).unwrap()
604        }
605    }
606
607    struct I8ToI64Adapter {}
608
609    impl ThriftAdapter for I8ToI64Adapter {
610        type StandardType = i8;
611        type AdaptedType = i64;
612
613        type Error = anyhow::Error;
614
615        fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
616            Ok(value.into())
617        }
618
619        fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
620            (*value).try_into().unwrap()
621        }
622
623        fn from_thrift_field<T: ThriftAnnotations>(
624            value: Self::StandardType,
625            field_id: i16,
626        ) -> Result<Self::AdaptedType, Self::Error> {
627            assert_eq!(field_id, 42);
628            assert_eq!(TypeId::of::<DummyParentStruct>(), TypeId::of::<T>());
629
630            Self::from_thrift(value)
631        }
632
633        fn to_thrift_field<T: ThriftAnnotations>(
634            value: &Self::AdaptedType,
635            field_id: i16,
636        ) -> Self::StandardType {
637            assert_eq!(field_id, 42);
638            assert_eq!(TypeId::of::<DummyParentStruct>(), TypeId::of::<T>());
639
640            Self::to_thrift(value)
641        }
642
643        fn from_thrift_default<T: ThriftAnnotations>(
644            value: Self::StandardType,
645            field_id: i16,
646        ) -> Self::AdaptedType {
647            assert_eq!(field_id, 42);
648            assert_eq!(TypeId::of::<DummyParentStruct>(), TypeId::of::<T>());
649
650            Self::from_thrift_field::<T>(value, field_id).unwrap()
651        }
652    }
653
654    type TestLayeredAdapter = LayeredThriftAdapter<
655        BoolToStringAdapter,
656        LayeredThriftAdapter<I64ToBoolAdapter, I8ToI64Adapter>,
657    >;
658
659    #[test]
660    fn test_from_thrift() {
661        assert_eq!(TestLayeredAdapter::from_thrift(0_i8).unwrap(), "false");
662        assert_eq!(TestLayeredAdapter::from_thrift(1_i8).unwrap(), "true");
663    }
664
665    #[test]
666    fn test_from_thrift_error() {
667        let res = TestLayeredAdapter::from_thrift(3_i8);
668
669        assert!(res.is_err());
670        assert_eq!(res.unwrap_err().to_string(), "invalid num 3");
671    }
672
673    #[test]
674    fn test_to_thrift() {
675        assert_eq!(TestLayeredAdapter::to_thrift(&"false".to_string()), 0_i8);
676        assert_eq!(TestLayeredAdapter::to_thrift(&"true".to_string()), 1_i8);
677    }
678
679    #[test]
680    fn test_thrift_field() {
681        assert_eq!(
682            TestLayeredAdapter::from_thrift_field::<DummyParentStruct>(0_i8, 42).unwrap(),
683            "false"
684        );
685        assert_eq!(
686            TestLayeredAdapter::from_thrift_default::<DummyParentStruct>(0_i8, 42),
687            "false"
688        );
689        assert_eq!(
690            TestLayeredAdapter::to_thrift_field::<DummyParentStruct>(&"false".to_string(), 42),
691            0_i8
692        );
693    }
694}