pub mod error;
pub use error::{Error, Result};
mod context;
pub(crate) use context::LoadContext;
pub use context::{Context, Object, Reader};
#[cfg(feature = "disk")]
use std::path::Path;
use crate::{Version, save};
pub(crate) fn load<'a, T, C>(context: &'a C) -> Result<T>
where
T: Loadable<'a>,
C: LoadContext,
{
let value = context.value()?;
T::load(Context::new(context, value))
}
#[cfg(feature = "disk")]
pub fn load_from_disk<T>(metadata: &Path, dir: &Path) -> Result<T>
where
T: for<'a> Loadable<'a>,
{
let context = crate::backend::disk::DiskLoadContext::new(metadata, dir)?;
load(&context)
}
pub trait Load<'a>: Sized {
const VERSION: Version;
fn load(object: Object<'a>) -> Result<Self>;
fn load_legacy(object: Object<'a>) -> Result<Self>;
}
pub trait Loadable<'a>: Sized {
fn load(context: Context<'a>) -> Result<Self>;
}
impl<'a, T> Loadable<'a> for T
where
T: Load<'a>,
{
fn load(context: Context<'a>) -> Result<Self> {
let object = context.as_object().ok_or(error::Kind::TypeMismatch)?;
let version = object.version();
if version == T::VERSION {
T::load(object)
} else {
T::load_legacy(object)
}
}
}
#[macro_export]
macro_rules! load_fields {
(@field $object:ident, $field:ident: $T:ty) => {
let $field: $T = $object.field(stringify!($field))?;
};
(@field $object:ident, $field:ident) => {
let $field = $object.field(stringify!($field))?;
};
($object:ident, [$($field:ident $(: $ty:ty)?),+ $(,)?]) => {
$(
$crate::load_fields!(@field $object, $field $(: $ty)?);
)+
};
}
impl<'a> Loadable<'a> for &'a str {
fn load(context: Context<'a>) -> Result<Self> {
context
.as_str()
.ok_or_else(|| error::Kind::TypeMismatch.into())
}
}
impl Loadable<'_> for String {
fn load(context: Context<'_>) -> Result<Self> {
context.load::<&str>().map(|s| s.into())
}
}
impl Loadable<'_> for save::Handle {
fn load(context: Context<'_>) -> Result<Self> {
context
.as_handle()
.cloned()
.ok_or_else(|| error::Kind::TypeMismatch.into())
}
}
impl Loadable<'_> for bool {
fn load(context: Context<'_>) -> Result<Self> {
context
.as_bool()
.ok_or_else(|| error::Kind::TypeMismatch.into())
}
}
impl<'a, T> Loadable<'a> for Option<T>
where
T: Loadable<'a>,
{
fn load(context: Context<'a>) -> Result<Self> {
if context.is_null() {
Ok(None)
} else {
T::load(context).map(Some)
}
}
}
impl<'a, T> Loadable<'a> for Vec<T>
where
T: Loadable<'a>,
{
fn load(context: Context<'a>) -> Result<Self> {
match context.as_array() {
Some(array) => array.iter().map(T::load).collect(),
None => Err((error::Kind::TypeMismatch).into()),
}
}
}
macro_rules! load_number {
($T:ty) => {
impl Loadable<'_> for $T {
fn load(context: Context<'_>) -> Result<Self> {
match context.as_number() {
Some(n) => n.try_into().map_err(|_| error::Kind::NumberOutOfRange.into()),
None => Err((error::Kind::TypeMismatch).into()),
}
}
}
};
($($Ts:ty),+ $(,)?) => {
$(load_number!($Ts);)+
}
}
load_number!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64);
macro_rules! load_nonzero {
($T:ty, $Inner:ty) => {
impl Loadable<'_> for $T {
fn load(context: Context<'_>) -> Result<Self> {
let inner: $Inner = context.load()?;
<$T>::new(inner).ok_or_else(|| error::Kind::NumberOutOfRange.into())
}
}
};
}
load_nonzero!(std::num::NonZeroU32, u32);
load_nonzero!(std::num::NonZeroU64, u64);
load_nonzero!(std::num::NonZeroUsize, usize);