use std::collections::HashMap;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
pub type DefaultFieldErrorMetadata = ();
pub type DefaultErrorPayload = IvoErrorPayload<DefaultFieldErrorMetadata>;
#[derive(Debug, Clone)]
pub struct FieldError<Metadata: Clone = DefaultFieldErrorMetadata> {
pub reason: String,
pub metadata: Option<Metadata>,
}
pub type IvoErrorPayload<Metadata> = HashMap<String, FieldError<Metadata>>;
pub async fn run_resolver<T, Ctx, Opts, F>(ctx: Ctx, opts: &Opts, resolver: F) -> T
where
F: AsyncFn(Ctx, &Opts) -> T,
{
resolver(ctx, opts).await
}
pub async fn run_sanitizer<T, Ctx, Opts, F>(value: T, ctx: &Ctx, opts: &Opts, sanitizer: F) -> T
where
F: AsyncFn(T, &Ctx, &Opts) -> T,
{
sanitizer(value, ctx, opts).await
}
pub async fn run_validator<T, Ctx, Opts, F, Metadata>(
value: T,
ctx: &Ctx,
opts: &Opts,
validator: F,
) -> Result<Option<T>, FieldError<Metadata>>
where
Metadata: Clone,
F: AsyncFn(T, &Ctx, &Opts) -> Result<Option<T>, (String, Option<Metadata>)>,
{
match validator(value, ctx, opts).await {
Ok(v) => Ok(v),
Err((reason, metadata)) => Err(FieldError { reason, metadata }),
}
}
pub async fn run_boolean_resolver<Ctx, Opts, F>(ctx: &Ctx, opts: &Opts, resolver: F) -> bool
where
F: AsyncFn(&Ctx, &Opts) -> bool,
{
resolver(ctx, opts).await
}
pub async fn run_required_resolver<Ctx, Opts, F>(
ctx: &Ctx,
opts: &Opts,
resolver: F,
) -> Option<String>
where
F: AsyncFn(&Ctx, &Opts) -> Option<String>,
{
resolver(ctx, opts).await
}
pub async fn run_grouped_required_resolver<Ctx, Opts, E, F>(
ctx: &Ctx,
opts: &Opts,
resolver: F,
) -> Option<E>
where
F: AsyncFn(&Ctx, &Opts) -> Option<E>,
{
resolver(ctx, opts).await
}
pub async fn run_hook<Ctx, Opts, F>(ctx: Ctx, opts: &Opts, handler: F)
where
F: AsyncFn(Ctx, &Opts),
{
handler(ctx, opts).await
}
pub fn run_resolver_sync<T, Ctx, Opts, F>(ctx: Ctx, opts: &Opts, resolver: F) -> T
where
F: Fn(Ctx, &Opts) -> T,
{
resolver(ctx, opts)
}
pub fn run_sanitizer_sync<T, Ctx, Opts, F>(value: T, ctx: &Ctx, opts: &Opts, sanitizer: F) -> T
where
F: Fn(T, &Ctx, &Opts) -> T,
{
sanitizer(value, ctx, opts)
}
pub fn run_validator_sync<T, Ctx, Opts, F, Metadata>(
value: T,
ctx: &Ctx,
opts: &Opts,
validator: F,
) -> Result<Option<T>, FieldError<Metadata>>
where
Metadata: Clone,
F: Fn(T, &Ctx, &Opts) -> Result<Option<T>, (String, Option<Metadata>)>,
{
match validator(value, ctx, opts) {
Ok(v) => Ok(v),
Err((reason, metadata)) => Err(FieldError { reason, metadata }),
}
}
pub fn run_boolean_resolver_sync<Ctx, Opts, F>(ctx: &Ctx, opts: &Opts, resolver: F) -> bool
where
F: Fn(&Ctx, &Opts) -> bool,
{
resolver(ctx, opts)
}
pub fn run_required_resolver_sync<Ctx, Opts, F>(
ctx: &Ctx,
opts: &Opts,
resolver: F,
) -> Option<String>
where
F: Fn(&Ctx, &Opts) -> Option<String>,
{
resolver(ctx, opts)
}
pub fn run_grouped_required_resolver_sync<Ctx, Opts, E, F>(
ctx: &Ctx,
opts: &Opts,
resolver: F,
) -> Option<E>
where
F: Fn(&Ctx, &Opts) -> Option<E>,
{
resolver(ctx, opts)
}
pub fn run_hook_sync<Ctx, Opts, F>(ctx: Ctx, opts: &Opts, handler: F)
where
F: Fn(Ctx, &Opts),
{
handler(ctx, opts)
}
pub fn run_callback_sync<F>(handler: F)
where
F: Fn(),
{
handler()
}
pub fn run_value_resolver_sync<T, F>(resolver: F) -> T
where
F: Fn() -> T,
{
resolver()
}
pub async fn run_post_validator<Ctx, Opts, F, Partial, Errors>(
ctx: Ctx,
opts: Opts,
validator: F,
) -> Result<Option<Partial>, Errors>
where
F: AsyncFn(Ctx, Opts) -> Result<Option<Partial>, Errors>,
{
validator(ctx, opts).await
}
pub fn run_post_validator_sync<Ctx, Opts, F, Partial, Errors>(
ctx: Ctx,
opts: Opts,
validator: F,
) -> Result<Option<Partial>, Errors>
where
F: Fn(Ctx, Opts) -> Result<Option<Partial>, Errors>,
{
validator(ctx, opts)
}
pub trait IvoErrorSanitizer<CtxOptions> {
type Metadata: Clone + Send + Sync;
type Payload;
fn sanitize(
payload: IvoErrorPayload<Self::Metadata>,
ctx_options: &CtxOptions,
) -> Self::Payload;
}
#[derive(Debug)]
pub struct DefaultErrorSanitizer<Metadata: Clone = DefaultFieldErrorMetadata> {
_marker: std::marker::PhantomData<Metadata>,
}
impl<CtxOptions, Metadata: Clone + Send + Sync> IvoErrorSanitizer<CtxOptions>
for DefaultErrorSanitizer<Metadata>
{
type Metadata = Metadata;
type Payload = IvoErrorPayload<Self::Metadata>;
fn sanitize(payload: IvoErrorPayload<Self::Metadata>, _: &CtxOptions) -> Self::Payload {
payload
}
}
pub trait WithPartialStruct {
type Partial: Default + Clone + Send + Sync + 'static;
}
pub trait WithPartialErrors<Metadata: Send + Sync> {
type PartialErrors: Send + Sync;
}
pub trait IvoStruct: WithPartialStruct + Into<Self::Partial> + Send + Sync + 'static {
fn append_updates(&mut self, updates: &Self::Partial);
}
pub trait IvoInputStruct<CtxOptions, ErrorSanitizer: IvoErrorSanitizer<CtxOptions>>:
IvoStruct + WithPartialErrors<ErrorSanitizer::Metadata>
{
}
#[derive(Clone)]
pub struct IvoContext<I, O: WithPartialStruct> {
input: I,
raw_input: I,
values: O,
changes: Option<O::Partial>,
previous_values: Option<O>,
}
impl<I, O: WithPartialStruct> IvoContext<I, O> {
pub fn new(
input: I,
raw_input: I,
values: O,
changes: Option<O::Partial>,
previous_values: Option<O>,
) -> Self {
Self {
input,
raw_input,
values,
changes,
previous_values,
}
}
pub fn input(&self) -> &I {
&self.input
}
pub fn raw_input(&self) -> &I {
&self.raw_input
}
pub fn values(&self) -> &O {
&self.values
}
pub fn changes(&self) -> Option<&O::Partial> {
self.changes.as_ref()
}
pub fn previous_values(&self) -> Option<&O> {
self.previous_values.as_ref()
}
pub fn is_update(&self) -> bool {
self.previous_values.is_some()
}
}
pub struct IvoCtxOptions<CtxOptions>(pub std::sync::Arc<async_lock::RwLock<CtxOptions>>);
impl<CtxOptions> IvoCtxOptions<CtxOptions> {
pub fn new(rw: &IvoRwCtxOptions<CtxOptions>) -> Self {
Self(rw.0.clone())
}
pub fn read(&self) -> impl Future<Output = async_lock::RwLockReadGuard<'_, CtxOptions>> + '_ {
self.0.read()
}
#[cfg(not(target_family = "wasm"))]
pub fn read_sync(&self) -> async_lock::RwLockReadGuard<'_, CtxOptions> {
self.0.read_blocking()
}
#[cfg(target_family = "wasm")]
pub fn read_sync(&self) -> async_lock::RwLockReadGuard<'_, CtxOptions> {
self.0.try_read().expect(
"IvoCtxOptions::read_sync() found the lock contended on wasm32, where it can only \
ever poll once (no thread to block on) -- see the doc comment on the native \
`read_sync` for why this should never actually happen",
)
}
}
impl<CtxOptions> Clone for IvoCtxOptions<CtxOptions> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
pub struct IvoRwCtxOptions<CtxOptions>(pub std::sync::Arc<async_lock::RwLock<CtxOptions>>);
impl<CtxOptions> IvoRwCtxOptions<CtxOptions> {
pub fn new(opts: CtxOptions) -> Self {
Self(std::sync::Arc::new(async_lock::RwLock::new(opts)))
}
pub fn read(&self) -> impl Future<Output = async_lock::RwLockReadGuard<'_, CtxOptions>> + '_ {
self.0.read()
}
pub fn write(&self) -> impl Future<Output = async_lock::RwLockWriteGuard<'_, CtxOptions>> + '_ {
self.0.write()
}
#[cfg(not(target_family = "wasm"))]
pub fn read_sync(&self) -> async_lock::RwLockReadGuard<'_, CtxOptions> {
self.0.read_blocking()
}
#[cfg(target_family = "wasm")]
pub fn read_sync(&self) -> async_lock::RwLockReadGuard<'_, CtxOptions> {
self.0.try_read().expect(
"IvoRwCtxOptions::read_sync() found the lock contended on wasm32, where it can only \
ever poll once (no thread to block on) -- see the doc comment on the native \
`read_sync` for why this should never actually happen",
)
}
#[cfg(not(target_family = "wasm"))]
pub fn write_sync(&self) -> async_lock::RwLockWriteGuard<'_, CtxOptions> {
self.0.write_blocking()
}
#[cfg(target_family = "wasm")]
pub fn write_sync(&self) -> async_lock::RwLockWriteGuard<'_, CtxOptions> {
self.0.try_write().expect(
"IvoRwCtxOptions::write_sync() found the lock contended on wasm32, where it can \
only ever poll once (no thread to block on) -- see the doc comment on the native \
`write_sync` for why this should never actually happen",
)
}
#[doc(hidden)]
pub fn __read_only(&self) -> IvoCtxOptions<CtxOptions> {
IvoCtxOptions(self.0.clone())
}
}
impl<CtxOptions> Clone for IvoRwCtxOptions<CtxOptions> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<CtxOptions> fmt::Debug for IvoCtxOptions<CtxOptions> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IvoCtxOptions").finish()
}
}
impl<CtxOptions> fmt::Debug for IvoRwCtxOptions<CtxOptions> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IvoRwCtxOptions").finish()
}
}
pub type IvoSyncTrigger = Box<dyn FnOnce() + Send>;
pub type IvoAsyncTrigger = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>;
pub fn ivo_sync_trigger<F>(handler: F) -> IvoSyncTrigger
where
F: FnOnce() + Send + 'static,
{
Box::new(handler)
}
pub fn ivo_trigger<F>(future: F) -> IvoAsyncTrigger
where
F: Future<Output = ()> + Send + 'static,
{
Box::new(move || Box::pin(future) as Pin<Box<dyn Future<Output = ()> + Send>>)
}