/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use std::collections::BTreeMap; use std::collections::BTreeSet; use std::fmt::Debug; use std::marker::PhantomData; use crate::metadata::ThriftAnnotations; pub trait ThriftAdapter { /// Aka the "from" type. type StandardType; /// Aka the "to" type. type AdaptedType: Clone + Debug + PartialEq + Send + Sync; /// The error type thrown if `from_thrift` fails during deserialization. type Error: Into + Debug; /// Converts an instance of `StandardType` to `AdaptedType` during deserialization. /// /// The `Err` returned by this method will be propagated as a deserialization error. fn from_thrift(value: Self::StandardType) -> Result; // TODO(emersonford): should we support a moved param here? /// Converts from the given `AdaptedType` back to the given `StandardType` during /// serialization. /// /// This must be an infallible operation as `serialize` is infallible. /// /// WARNING: you should be particularly cautious when using `.unwrap()` or any other panic-able /// methods in this method. If this method panics, it will be at serialization time and not at /// the Thrift struct creation time, meaning it will be extremely difficult to debug what the /// true "source" of the panic. /// /// If your `AdaptedType` -> `StandardType` conversion is truly fallible, you probably shouldn't /// use an adapter to begin with. fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType; /// Method called when this adapter is used on a Thrift struct's field. Provides information /// about the specific field ID in `field_id`. The type of the struct that owns this field is /// passed in as `T`. /// /// Defaults to calling `from_thrift`. fn from_thrift_field( value: Self::StandardType, _field_id: i16, ) -> Result { Self::from_thrift(value) } /// Method called when this adapter is used on a Thrift struct's field. Provides information /// about the specific field ID in `field_id`. The type of the struct that owns this field is /// passed in as `T`. /// /// Defaults to calling `to_thrift`. fn to_thrift_field( value: &Self::AdaptedType, _field_id: i16, ) -> Self::StandardType { Self::to_thrift(value) } /// Method called when the adapted type is not present in a field during deserialization or is /// populated with `..Default::default()`. The value passed here is the default original type /// value for the field. This can be used to record that the field was not present inside /// of your adapted type. /// /// **This method must be infallible, as it will be called when `Default::default()` is used /// (which is infallible).** /// /// WARNING: This defaults to calling `from_thrift_field` and **assumes `from_thrift_field` /// will not return an `Err` for the default value**. fn from_thrift_default( value: Self::StandardType, field_id: i16, ) -> Self::AdaptedType { Self::from_thrift_field::(value, field_id).unwrap_or_else(|e| { panic!( "`from_thrift_field` must not return an `Err` on field ID {} for its default value, but it did: '{:?}'", field_id, e ); }) } } // NOTE: we define where bounds on the structs themselves here to improve error messaging during // the Thrift compilation process. /// Layers multiple [ThriftTypeAdapter] together. Used when multiple Thrift typedefs with adapters /// are layered on each other. pub struct LayeredThriftAdapter where Fst: ThriftAdapter, Snd: ThriftAdapter, Fst::StandardType: Into, Snd::AdaptedType: Into, { _inner_first: PhantomData, _inner_second: PhantomData, } impl ThriftAdapter for LayeredThriftAdapter where Fst: ThriftAdapter, Snd: ThriftAdapter, Fst::StandardType: Into, Snd::AdaptedType: Into, { type StandardType = ::StandardType; type AdaptedType = ::AdaptedType; type Error = anyhow::Error; #[inline] fn from_thrift(value: Self::StandardType) -> Result { ::from_thrift( ::from_thrift(value) .map_err(Into::::into)? .into(), ) .map_err(Into::::into) } #[inline] fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType { ::to_thrift(&::to_thrift(value).into()) } #[inline] fn from_thrift_field( value: Self::StandardType, field_id: i16, ) -> Result { ::from_thrift_field::( ::from_thrift_field::(value, field_id) .map_err(Into::::into)? .into(), field_id, ) .map_err(Into::::into) } #[inline] fn to_thrift_field( value: &Self::AdaptedType, field_id: i16, ) -> Self::StandardType { ::to_thrift_field::( &::to_thrift_field::(value, field_id).into(), field_id, ) } #[inline] fn from_thrift_default( value: Self::StandardType, field_id: i16, ) -> Self::AdaptedType { ::from_thrift_default::( ::from_thrift_default::(value, field_id).into(), field_id, ) } } /// Transforms the given adapter `A` into an adapter with the signature `Vec` /// -> `Vec`. Because Rust doesn't have HKT, we cannot make this "generic" over /// multiple collection types. pub struct ListMapAdapter where A: ThriftAdapter, { _inner_adapter: PhantomData, } impl ThriftAdapter for ListMapAdapter where A: ThriftAdapter, { type StandardType = Vec; type AdaptedType = Vec; type Error = A::Error; #[inline] fn from_thrift(value: Self::StandardType) -> Result { value .into_iter() .map(|elem| A::from_thrift(elem)) .collect::>() } #[inline] fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType { value.iter().map(|elem| A::to_thrift(elem)).collect() } #[inline] fn from_thrift_field( value: Self::StandardType, field_id: i16, ) -> Result { value .into_iter() .map(|elem| A::from_thrift_field::(elem, field_id)) .collect::>() } #[inline] fn to_thrift_field( value: &Self::AdaptedType, field_id: i16, ) -> Self::StandardType { value .iter() .map(|elem| A::to_thrift_field::(elem, field_id)) .collect() } #[inline] fn from_thrift_default( value: Self::StandardType, field_id: i16, ) -> Self::AdaptedType { value .into_iter() .map(|elem| A::from_thrift_default::(elem, field_id)) .collect() } } /// Transforms the given adapter `A` into an adapter with the signature `BTreeSet` /// -> `BTreeSet`. Because Rust doesn't have HKT, we cannot make this "generic" over /// multiple collection types. pub struct SetMapAdapter where A: ThriftAdapter, A::StandardType: Ord + PartialEq, A::AdaptedType: Ord + PartialEq, { _inner_adapter: PhantomData, } impl ThriftAdapter for SetMapAdapter where A: ThriftAdapter, A::StandardType: Ord + PartialEq, A::AdaptedType: Ord + PartialEq, { type StandardType = BTreeSet; type AdaptedType = BTreeSet; type Error = A::Error; #[inline] fn from_thrift(value: Self::StandardType) -> Result { value .into_iter() .map(|elem| A::from_thrift(elem)) .collect::>() } #[inline] fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType { value.iter().map(|elem| A::to_thrift(elem)).collect() } #[inline] fn from_thrift_field( value: Self::StandardType, field_id: i16, ) -> Result { value .into_iter() .map(|elem| A::from_thrift_field::(elem, field_id)) .collect::>() } #[inline] fn to_thrift_field( value: &Self::AdaptedType, field_id: i16, ) -> Self::StandardType { value .iter() .map(|elem| A::to_thrift_field::(elem, field_id)) .collect() } #[inline] fn from_thrift_default( value: Self::StandardType, field_id: i16, ) -> Self::AdaptedType { value .into_iter() .map(|elem| A::from_thrift_default::(elem, field_id)) .collect() } } /// Transforms the given adapter `KA` and `KV` into an adapter with the signature /// `BTreeMap` -> `BTreeMap`. /// Because Rust doesn't have HKT, we cannot make this "generic" over multiple collection types. pub struct MapMapAdapter where KA: ThriftAdapter, KV: ThriftAdapter, KA::StandardType: Ord + PartialEq, KA::AdaptedType: Ord + PartialEq, { _inner_key_adapter: PhantomData, _inner_val_adapter: PhantomData, } impl ThriftAdapter for MapMapAdapter where KA: ThriftAdapter, KV: ThriftAdapter, KA::StandardType: Ord + PartialEq, KA::AdaptedType: Ord + PartialEq, { type StandardType = BTreeMap; type AdaptedType = BTreeMap; type Error = anyhow::Error; #[inline] fn from_thrift(value: Self::StandardType) -> Result { value .into_iter() .map(|(key, val)| { Ok(( KA::from_thrift(key).map_err(Into::::into)?, KV::from_thrift(val).map_err(Into::::into)?, )) }) .collect::>() } #[inline] fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType { value .iter() .map(|(key, val)| (KA::to_thrift(key), KV::to_thrift(val))) .collect::() } #[inline] fn from_thrift_field( value: Self::StandardType, field_id: i16, ) -> Result { value .into_iter() .map(|(key, val)| { Ok(( KA::from_thrift_field::(key, field_id) .map_err(Into::::into)?, KV::from_thrift_field::(val, field_id) .map_err(Into::::into)?, )) }) .collect::>() } #[inline] fn to_thrift_field( value: &Self::AdaptedType, field_id: i16, ) -> Self::StandardType { value .iter() .map(|(key, val)| { ( KA::to_thrift_field::(key, field_id), KV::to_thrift_field::(val, field_id), ) }) .collect::() } #[inline] fn from_thrift_default( value: Self::StandardType, field_id: i16, ) -> Self::AdaptedType { value .into_iter() .map(|(key, val)| { ( KA::from_thrift_default::(key, field_id), KV::from_thrift_default::(val, field_id), ) }) .collect::() } } /// No-op adapter. Used if the key of a map is an adapted type, but the value isn't, or vice versa. pub struct IdentityAdapter where T: Clone + Debug + Send + Sync + PartialEq, { _inner: PhantomData, } impl ThriftAdapter for IdentityAdapter where T: Clone + Debug + Send + Sync + PartialEq, { type StandardType = T; type AdaptedType = T; type Error = std::convert::Infallible; fn from_thrift(value: Self::StandardType) -> Result { Ok(value) } fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType { value.clone() } } /// Convenience trait for newtype adapters. /// /// This trait simplifies construction of adapters that newtype the standard /// type. On top of the standard bounds, the newtype (adapted type) needs to /// also have a [`From`] and [`Into`] implementation for the standard thrift /// type. /// /// # Examples /// /// ``` /// use fbthrift::adapter::NewTypeAdapter; /// /// #[derive(Clone, Debug)] /// struct MyInt(i64); /// /// impl From for i64 { /// fn from(v: MyInt) -> Self { /// v.0 /// } /// } /// /// impl From for MyInt { /// fn from(v: i64) -> Self { /// Self(v) /// } /// } /// /// struct MyIntAdapter; /// /// impl NewTypeAdapter for MyIntAdapter { /// type StandardType = i64; /// type AdaptedType = MyInt; /// } /// ``` pub trait NewTypeAdapter { /// The thrift type that the adapter interprets. type StandardType; /// The Rust type to adapt to. type AdaptedType; } impl ThriftAdapter for Adapter where Adapter: NewTypeAdapter, AdaptedType: From + Into + Clone + Debug + Send + Sync + PartialEq, { type StandardType = ::StandardType; type AdaptedType = ::AdaptedType; type Error = >::Error; fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType { value.clone().into() } fn from_thrift(value: Self::StandardType) -> Result { Ok(Self::AdaptedType::from(value)) } } #[cfg(test)] mod tests { use std::any::TypeId; use super::*; // Represents the following flow // String (Final Adapted Type) <- BoolToStringAdapter -> Bool <- I64ToBoolAdapter -> i64 // <- I8ToI64Adapter -> i8 (Original Thrift type) struct DummyParentStruct {} impl ThriftAnnotations for DummyParentStruct {} struct BoolToStringAdapter {} impl ThriftAdapter for BoolToStringAdapter { type StandardType = bool; type AdaptedType = String; type Error = anyhow::Error; fn from_thrift(value: Self::StandardType) -> Result { Ok(value.to_string()) } fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType { value == "true" } fn from_thrift_field( value: Self::StandardType, field_id: i16, ) -> Result { assert_eq!(field_id, 42); assert_eq!(TypeId::of::(), TypeId::of::()); Self::from_thrift(value) } fn to_thrift_field( value: &Self::AdaptedType, field_id: i16, ) -> Self::StandardType { assert_eq!(field_id, 42); assert_eq!(TypeId::of::(), TypeId::of::()); Self::to_thrift(value) } fn from_thrift_default( value: Self::StandardType, field_id: i16, ) -> Self::AdaptedType { assert_eq!(field_id, 42); assert_eq!(TypeId::of::(), TypeId::of::()); Self::from_thrift_field::(value, field_id).unwrap() } } struct I64ToBoolAdapter {} impl ThriftAdapter for I64ToBoolAdapter { type StandardType = i64; type AdaptedType = bool; type Error = anyhow::Error; fn from_thrift(value: Self::StandardType) -> Result { match value { 0 => Ok(false), 1 => Ok(true), other => anyhow::bail!("invalid num {}", other), } } fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType { if *value { 1 } else { 0 } } fn from_thrift_field( value: Self::StandardType, field_id: i16, ) -> Result { assert_eq!(field_id, 42); assert_eq!(TypeId::of::(), TypeId::of::()); Self::from_thrift(value) } fn to_thrift_field( value: &Self::AdaptedType, field_id: i16, ) -> Self::StandardType { assert_eq!(field_id, 42); assert_eq!(TypeId::of::(), TypeId::of::()); Self::to_thrift(value) } fn from_thrift_default( value: Self::StandardType, field_id: i16, ) -> Self::AdaptedType { assert_eq!(field_id, 42); assert_eq!(TypeId::of::(), TypeId::of::()); Self::from_thrift_field::(value, field_id).unwrap() } } struct I8ToI64Adapter {} impl ThriftAdapter for I8ToI64Adapter { type StandardType = i8; type AdaptedType = i64; type Error = anyhow::Error; fn from_thrift(value: Self::StandardType) -> Result { Ok(value.into()) } fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType { (*value).try_into().unwrap() } fn from_thrift_field( value: Self::StandardType, field_id: i16, ) -> Result { assert_eq!(field_id, 42); assert_eq!(TypeId::of::(), TypeId::of::()); Self::from_thrift(value) } fn to_thrift_field( value: &Self::AdaptedType, field_id: i16, ) -> Self::StandardType { assert_eq!(field_id, 42); assert_eq!(TypeId::of::(), TypeId::of::()); Self::to_thrift(value) } fn from_thrift_default( value: Self::StandardType, field_id: i16, ) -> Self::AdaptedType { assert_eq!(field_id, 42); assert_eq!(TypeId::of::(), TypeId::of::()); Self::from_thrift_field::(value, field_id).unwrap() } } type TestLayeredAdapter = LayeredThriftAdapter< BoolToStringAdapter, LayeredThriftAdapter, >; #[test] fn test_from_thrift() { assert_eq!(TestLayeredAdapter::from_thrift(0_i8).unwrap(), "false"); assert_eq!(TestLayeredAdapter::from_thrift(1_i8).unwrap(), "true"); } #[test] fn test_from_thrift_error() { let res = TestLayeredAdapter::from_thrift(3_i8); assert!(res.is_err()); assert_eq!(res.unwrap_err().to_string(), "invalid num 3"); } #[test] fn test_to_thrift() { assert_eq!(TestLayeredAdapter::to_thrift(&"false".to_string()), 0_i8); assert_eq!(TestLayeredAdapter::to_thrift(&"true".to_string()), 1_i8); } #[test] fn test_thrift_field() { assert_eq!( TestLayeredAdapter::from_thrift_field::(0_i8, 42).unwrap(), "false" ); assert_eq!( TestLayeredAdapter::from_thrift_default::(0_i8, 42), "false" ); assert_eq!( TestLayeredAdapter::to_thrift_field::(&"false".to_string(), 42), 0_i8 ); } }