use futures::future::try_join_all;
use reblessive::tree::Stk;
use crate::catalog::Permission;
use crate::catalog::providers::DatabaseProvider;
use crate::ctx::{Context, FrozenContext};
use crate::dbs::Options;
use crate::doc::CursorDoc;
use crate::exe::FlowResultExt as _;
use crate::exec::Error as ExecError;
use crate::expr::function::{Function, FunctionCall};
use crate::expr::{ControlFlow, Error as ExprError, FlowResult, Kind, ModuleExecutable};
use crate::fnc;
use crate::iam::{Action, AuthLimit};
use crate::val::Value;
#[instrument(level = "trace", name = "Function::compute", skip_all)]
pub(crate) async fn function_compute(
this: &Function,
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
doc: Option<&CursorDoc>,
args: Vec<Value>,
) -> FlowResult<Value> {
match this {
Function::Normal(s) => {
ctx.check_allowed_function(s)?;
Ok(fnc::run(stk, ctx, opt, doc, s, args).await?)
}
#[cfg_attr(not(feature = "scripting"), expect(unused_variables))]
Function::Script(s) => {
#[cfg(feature = "scripting")]
{
ctx.check_allowed_scripting()?;
fnc::script::run(ctx, opt, doc, &s.0, args).await.map_err(ControlFlow::Err)
}
#[cfg(not(feature = "scripting"))]
{
Err(ControlFlow::Err(anyhow::Error::new(ExecError::InvalidScript {
message: String::from("Embedded functions are not enabled."),
})))
}
}
Function::Model(m) => crate::legacy::model_compute(m, stk, ctx, opt, doc, args).await,
Function::Custom(s) => {
let name = format!("fn::{s}");
ctx.check_allowed_function(name.as_str())?;
let (ns, db) = ctx.expect_ns_db_ids(opt).await?;
let val = ctx.tx().get_db_function(ns, db, s, opt.version).await?;
let opt = opt.limited_by(&AuthLimit::try_from(&val.auth_limit)?);
if ctx.check_perms(&opt, Action::View)? {
check_perms(stk, ctx, &opt, doc, &name, &val.permissions).await?;
}
validate_args(
&name,
&args,
&val.args.iter().map(|(_, k)| k.clone()).collect::<Vec<Kind>>(),
)?;
let mut ctx = Context::new_isolated(ctx);
for (val, (param_name, kind)) in args.into_iter().zip(&val.args) {
ctx.add_value(
param_name.clone(),
val.coerce_to_kind(kind)
.map_err(|e| ExprError::InvalidFunctionArguments {
name: name.clone(),
message: format!("Failed to coerce argument `${param_name}`: {e}"),
})
.map_err(anyhow::Error::new)?
.into(),
);
}
let ctx = ctx.freeze();
let result = stk
.run(|stk| crate::legacy::block_compute(&val.block, stk, &ctx, &opt, doc))
.await
.catch_return()?;
validate_return(name.as_str(), val.returns.as_ref(), result)
}
Function::Module(module, sub) => {
let mod_name = format!("mod::{module}");
let fnc_name = match sub {
Some(sub) => format!("{mod_name}::{sub}"),
None => mod_name.clone(),
};
ctx.check_allowed_function(fnc_name.as_str())?;
let (ns, db) = ctx.expect_ns_db_ids(opt).await?;
let val = ctx.tx().get_db_module(ns, db, mod_name.as_str(), opt.version).await?;
if ctx.check_perms(opt, Action::View)? {
check_perms(stk, ctx, opt, doc, &mod_name, &val.permissions).await?;
}
let executable: ModuleExecutable = val.executable.clone().into();
let signature = crate::legacy::module_executable_signature(
&executable,
ctx,
&ns,
&db,
sub.as_deref(),
)
.await?;
validate_args(&fnc_name, &args, &signature.args)?;
let result = crate::legacy::module_executable_run(
&executable,
stk,
ctx,
opt,
doc,
args,
sub.as_deref(),
)
.await?;
validate_return(fnc_name.as_str(), signature.returns.as_ref(), result)
}
Function::Silo {
org,
pkg,
major,
minor,
patch,
sub,
} => {
let mod_name = format!("silo::{org}::{pkg}::<{major}.{minor}.{patch}>");
let fnc_name = match sub {
Some(sub) => format!("{mod_name}::{sub}"),
None => mod_name.clone(),
};
ctx.check_allowed_function(fnc_name.as_str())?;
let (ns, db) = ctx.expect_ns_db_ids(opt).await?;
let val = ctx.tx().get_db_module(ns, db, mod_name.as_str(), opt.version).await?;
if ctx.check_perms(opt, Action::View)? {
check_perms(stk, ctx, opt, doc, &mod_name, &val.permissions).await?;
}
let executable: ModuleExecutable = val.executable.clone().into();
let signature = crate::legacy::module_executable_signature(
&executable,
ctx,
&ns,
&db,
sub.as_deref(),
)
.await?;
validate_args(&fnc_name, &args, &signature.args)?;
let result = crate::legacy::module_executable_run(
&executable,
stk,
ctx,
opt,
doc,
args,
sub.as_deref(),
)
.await?;
validate_return(fnc_name.as_str(), signature.returns.as_ref(), result)
}
}
}
#[instrument(level = "trace", name = "FunctionCall::compute", skip_all)]
pub(crate) async fn function_call_compute(
this: &FunctionCall,
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
doc: Option<&CursorDoc>,
) -> FlowResult<Value> {
let args = stk
.scope(|scope| {
try_join_all(
this.arguments
.iter()
.map(|v| scope.run(|stk| crate::legacy::expr_compute(v, stk, ctx, opt, doc))),
)
})
.await?;
crate::legacy::function_compute(&this.receiver, stk, ctx, opt, doc, args).await
}
pub(crate) async fn check_perms(
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
doc: Option<&CursorDoc>,
name: &str,
permissions: &Permission,
) -> FlowResult<()> {
match permissions {
Permission::Full => Ok(()),
Permission::None => {
Err(ControlFlow::from(anyhow::Error::new(ExecError::FunctionPermissions {
name: name.to_string(),
})))
}
Permission::Specific(e) => {
let opt = &opt.new_for_permission_predicate();
if !stk.run(|stk| crate::legacy::expr_compute(e, stk, ctx, opt, doc)).await?.is_truthy()
{
Err(ControlFlow::from(anyhow::Error::new(ExecError::FunctionPermissions {
name: name.to_string(),
})))
} else {
Ok(())
}
}
}
}
pub(crate) fn validate_args(name: &str, args: &[Value], sig: &[Kind]) -> FlowResult<()> {
let max_args_len = sig.len();
let min_args_len = sig.iter().rev().fold(0, |acc, kind| {
if kind.can_be_none() {
if acc == 0 {
0
} else {
acc + 1
}
} else {
acc + 1
}
});
if !(min_args_len..=max_args_len).contains(&args.len()) {
return Err(ControlFlow::from(anyhow::Error::new(ExprError::InvalidFunctionArguments {
name: name.to_string(),
message: match (min_args_len, max_args_len) {
(1, 1) => String::from("The function expects 1 argument."),
(r, t) if r == t => format!("The function expects {r} arguments."),
(r, t) => format!("The function expects {r} to {t} arguments."),
},
})));
}
Ok(())
}
pub(crate) fn validate_return(
name: &str,
return_kind: Option<&Kind>,
result: Value,
) -> FlowResult<Value> {
match return_kind {
Some(kind) => result
.coerce_to_kind(kind)
.map_err(|e| ExecError::ReturnCoerce {
name: name.to_string(),
error: Box::new(e),
})
.map_err(anyhow::Error::new)
.map_err(ControlFlow::from),
None => Ok(result),
}
}