sea-orm-ffi 0.1.3

Compatibility layer for Sea-ORM when crossing a Rust-to-Rust FFI boundary
Documentation
use crate::{
	string::StringPtr,
	time::{HmsNano, OrdinalDate, OrdinalDateHmsNano, OrdinalDateHmsNanoOffset},
	vec::VecPtr
};
use paste::paste;
use sea_orm::Value;
use std::mem::ManuallyDrop;
#[cfg(feature = "with-uuid")]
use uuid::Uuid;

/// `uuid::Uuid` has a guarantee'd memory layout, so we can replace it with the known
/// memory layout when the feature is disabled.
#[cfg(not(feature = "with-uuid"))]
type Uuid = [u8; 16];

#[repr(C)]
pub(crate) struct FfiValue {
	/// The inner value stored in this [`FfiValue`].
	///
	/// This is a union, and the exact value stored in here depends on the `ty` and
	/// `option` fields of this struct.
	inner: FfiValueInner,

	/// The type of this value.
	///
	/// Together with `option`, this describes what is stored in `inner`.
	ty: FfiValueTy,

	// /// If `ty` is `Array`, then this describes the inner type of the array. Otherwise,
	// /// this value can be set to any arbitrary value and must be ignored.
	// array_ty: FfiValueTy,
	/// Describes whether `inner` is inhabited or not. If this is `None`, then `inner`
	/// will always be `none: ()`.
	option: FfiValueOption
}

#[derive(Clone, Copy, Debug)]
#[repr(u8)]
enum FfiValueOption {
	None,
	Some
}

macro_rules! impl_ffi_value {
	($(
		$(#[un $unbox:tt box])?
		$(#[drop $manually_drop:tt manually])?
		$(#[cfg($($cfg:tt)*)])?
		$variant:ident($ty:ty)
	),*) => {
		#[derive(Clone, Copy, Debug)]
		#[repr(u8)]
		enum FfiValueTy {
			$(
				$(#[cfg_attr(not($($cfg)*), allow(dead_code))])?
				$variant
			),*
		}

		paste! {
			#[repr(C)]
			union FfiValueInner {
				none: (),
				$(
					$(#[cfg_attr(not($($cfg)*), allow(dead_code))])?
					[< $variant:snake >]: $ty
				),*
			}
		}

		impl Drop for FfiValue {
			fn drop(&mut self) {
				if matches!(self.option, FfiValueOption::None) {
					return;
				}
				match self.ty { $(
					$(#[cfg($($cfg)*)])?
					FfiValueTy::$variant => {
						$(unsafe {
							_ = stringify!($manually_drop);
							ManuallyDrop::drop(paste! {
								&mut self.inner.[< $variant:snake >]
							});
						})?
					},
					$(#[cfg(not($($cfg)*))]
					FfiValueTy::$variant => {
						panic!(
							"Value::{} is only supported with cfg({})",
							stringify!($variant),
							stringify!($($cfg)*)
						);
					})?
				)* }
			}
		}

		impl From<Value> for FfiValue {
			fn from(value: Value) -> Self {
				paste! {
					match value {
						$($(#[cfg($($cfg)*)])? Value::$variant(None) => Self {
							inner: FfiValueInner { none: () },
							ty: FfiValueTy::$variant,
							option: FfiValueOption::None
						},)*

						$($(#[cfg($($cfg)*)])? Value::$variant(Some(value)) => {
							$(
								_ = stringify!($unbox);
								let value = *value;
							)?
							let value = value.into();
							$(
								_ = stringify!($manually_drop);
								let value = ManuallyDrop::new(value);
							)?
							Self {
								inner: FfiValueInner { [< $variant:snake >]: value },
								ty: FfiValueTy::$variant,
								option: FfiValueOption::Some
							}
						},)*

						#[allow(unreachable_patterns)]
						_ => panic!(concat!(
							"sea-orm's Value enum gains variants with enabled features. ",
							"Some of those are supported if sea-orm-ffi is compiled ",
							"with the corresponding feature flag. ",
							"Otherwise, you found one that isn't implemented by sea-orm-ffi."
						))
					}
				}
			}
		}

		impl From<FfiValue> for Value {
			fn from(value: FfiValue) -> Self {
				// We have some values in `inner` that are `ManuallyDrop` and that would
				//  be manually dropped by the `Drop` impl of `FfiValue`.
				// That is a problem, because we want to move those types. And we can't
				//  move types out of `FfiValue` because it has a `Drop` impl.
				// Therefore, we inhibit that impl, and call `ManuallyDrop::take` on the
				//  inner values.
				let mut value = ManuallyDrop::new(value);

				paste! {
					match (value.ty, value.option) {
						$($(#[cfg($($cfg)*)])? (FfiValueTy::$variant, FfiValueOption::None) => {
							Self::$variant(None)
						},)*

						$($(#[cfg($($cfg)*)])? (FfiValueTy::$variant, FfiValueOption::Some) => {
							// Take &mut because value is wrapped in ManuallyDrop.
							let value = unsafe { &mut value.inner.[< $variant:snake >] };
							// If the value is ManuallyDrop, move it somewhere where the
							//  *value expression doesn't interfere with it.
							$(
								_ = stringify!($manually_drop);
								let manually_drop = value;
								let value = &();
								#[allow(unused_variables)]
							)?
							// Copy the value (or copy `()` if using ManuallyDrop).
							let value = *value;
							// If the value itself is wrapped in ManuallyDrop, take it.
							// This is safe, because we don't access the ManuallyDrop
							//  thereafter, and we have inhibited the Drop impl.
							$(
								_ = stringify!($manually_drop);
								let value = unsafe { ManuallyDrop::take(manually_drop) };
							)?
							// Now is a good point to call .into();
							let value = value.into();
							// Add a layer of Box if necessary.
							$(
								_ = stringify!($unbox);
								let value = Box::new(value);
							)?
							Self::$variant(Some(value))
						},)*

						$($(#[cfg(not($($cfg)*))] (FfiValueTy::$variant, _) => panic!(
							"Value::{} is only supported with cfg({})",
							stringify!($variant),
							stringify!($($cfg)*)
						),)?)*
					}
				}
			}
		}
	};
}

// DO NOT REORDER. APPEND ONLY.
impl_ffi_value! {
	Bool(bool),

	TinyInt(i8),
	SmallInt(i16),
	Int(i32),
	BigInt(i64),

	TinyUnsigned(u8),
	SmallUnsigned(u16),
	Unsigned(u32),
	BigUnsigned(u64),

	Float(f32),
	Double(f64),

	#[un-box]
	#[drop-manually]
	String(ManuallyDrop<StringPtr>),

	Char(char),

	#[un-box]
	#[drop-manually]
	Bytes(ManuallyDrop<VecPtr<u8>>),

	#[un-box]
	#[cfg(feature = "with-time")]
	TimeDate(OrdinalDate),
	#[un-box]
	#[cfg(feature = "with-time")]
	TimeTime(HmsNano),
	#[un-box]
	#[cfg(feature = "with-time")]
	TimeDateTime(OrdinalDateHmsNano),
	#[un-box]
	#[cfg(feature = "with-time")]
	TimeDateTimeWithTimeZone(OrdinalDateHmsNanoOffset),

	#[un-box]
	#[cfg(feature = "with-uuid")]
	Uuid(Uuid)
}