From de7d9299fd9e825df9e4d494438cf0d88e2f70c2 Mon Sep 17 00:00:00 2001 From: Finn Bear Date: Tue, 20 Sep 2022 17:57:33 -0700 Subject: [PATCH] Refactor function dispatch (#125) --- lib/src/fnc/args.rs | 284 ++++++++++++++++++++++++++---------- lib/src/fnc/array.rs | 110 +++++++------- lib/src/fnc/count.rs | 17 +-- lib/src/fnc/crypto.rs | 49 ++----- lib/src/fnc/geo.rs | 58 ++++---- lib/src/fnc/http.rs | 290 ++++++++++++++----------------------- lib/src/fnc/is.rs | 97 ++++++------- lib/src/fnc/math.rs | 121 ++++++++-------- lib/src/fnc/meta.rs | 9 +- lib/src/fnc/mod.rs | 321 ++++++++++++++++++++--------------------- lib/src/fnc/parse.rs | 58 +++----- lib/src/fnc/rand.rs | 176 ++++++++++------------ lib/src/fnc/session.rs | 16 +- lib/src/fnc/string.rs | 72 ++++----- lib/src/fnc/time.rs | 197 +++++++++++++------------ lib/src/fnc/type.rs | 142 ++++++++---------- 16 files changed, 970 insertions(+), 1047 deletions(-) diff --git a/lib/src/fnc/args.rs b/lib/src/fnc/args.rs index ba32a4e5..eadc3695 100644 --- a/lib/src/fnc/args.rs +++ b/lib/src/fnc/args.rs @@ -1,83 +1,215 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; +use crate::sql::{Number, Strand}; -pub enum Args { - None, - Any, - One, - Two, - Three, - NoneOne, - NoneTwo, - NoneOneTwo, - OneTwo, +/// Implemented by types that are commonly used, in a certain way, as arguments. +pub trait FromArg: Sized { + /// Potentially fallible conversion from a Value to an argument. Errors will be propagated + /// to the caller, although it is also possible to return a none/null Value. + fn from_arg(arg: Value) -> Result; } -pub fn check( - ctx: &Context, - name: &str, - args: Vec, - size: Args, - func: fn(&Context, Vec) -> Result, -) -> Result { - match size { - Args::None => match args.len() { - 0 => func(ctx, args), - _ => Err(Error::InvalidArguments { - name: name.to_owned(), - message: String::from("The function does not expect any arguments."), - }), - }, - Args::One => match args.len() { - 1 => func(ctx, args), - _ => Err(Error::InvalidArguments { - name: name.to_owned(), - message: String::from("The function expects 1 argument."), - }), - }, - Args::Two => match args.len() { - 2 => func(ctx, args), - _ => Err(Error::InvalidArguments { - name: name.to_owned(), - message: String::from("The function expects 2 arguments."), - }), - }, - Args::Three => match args.len() { - 3 => func(ctx, args), - _ => Err(Error::InvalidArguments { - name: name.to_owned(), - message: String::from("The function expects 3 arguments."), - }), - }, - Args::NoneOne => match args.len() { - 0 | 1 => func(ctx, args), - _ => Err(Error::InvalidArguments { - name: name.to_owned(), - message: String::from("The function expects 0 or 1 arguments."), - }), - }, - Args::NoneTwo => match args.len() { - 0 | 2 => func(ctx, args), - _ => Err(Error::InvalidArguments { - name: name.to_owned(), - message: String::from("The function expects 0 or 2 arguments."), - }), - }, - Args::NoneOneTwo => match args.len() { - 0 | 1 | 2 => func(ctx, args), - _ => Err(Error::InvalidArguments { - name: name.to_owned(), - message: String::from("The function expects 0, 1, or 2 arguments."), - }), - }, - Args::OneTwo => match args.len() { - 1 | 2 => func(ctx, args), - _ => Err(Error::InvalidArguments { - name: name.to_owned(), - message: String::from("The function expects 1 or 2 arguments."), - }), - }, - Args::Any => func(ctx, args), +impl FromArg for Value { + fn from_arg(arg: Value) -> Result { + Ok(arg) + } +} + +impl FromArg for String { + fn from_arg(arg: Value) -> Result { + Ok(arg.as_string()) + } +} + +impl FromArg for Strand { + fn from_arg(arg: Value) -> Result { + Ok(arg.as_strand()) + } +} + +impl FromArg for Number { + fn from_arg(arg: Value) -> Result { + Ok(arg.as_number()) + } +} + +impl FromArg for f64 { + fn from_arg(arg: Value) -> Result { + Ok(arg.as_float()) + } +} + +impl FromArg for i64 { + fn from_arg(arg: Value) -> Result { + Ok(arg.as_int()) + } +} + +impl FromArg for usize { + fn from_arg(arg: Value) -> Result { + Ok(arg.as_int() as usize) + } +} + +pub trait FromArgs: Sized { + /// Convert a collection of argument values into a certain argument format, failing if there are + /// too many or too few arguments, or if one of the arguments could not be converted. + fn from_args(name: &str, args: Vec) -> Result; +} + +// Take ownership of the raw arguments collection, and assume responsibility of validating the +// number of arguments and converting them as necessary. +impl FromArgs for Vec { + fn from_args(_name: &str, args: Vec) -> Result { + Ok(args) + } +} + +// Some functions take a fixed number of arguments. +// The len must match the number of type idents that follow. +macro_rules! impl_tuple { + ($len:expr, $( $T:ident ),*) => { + impl<$($T:FromArg),*> FromArgs for ($($T,)*) { + #[allow(non_snake_case)] + fn from_args(name: &str, args: Vec) -> Result { + let [$($T),*]: [Value; $len] = args.try_into().map_err(|_| Error::InvalidArguments { + name: name.to_owned(), + // This match will be optimized away. + message: match $len { + 0 => String::from("Expected no arguments."), + 1 => String::from("Expected 1 argument."), + _ => format!("Expected {} arguments.", $len), + } + })?; + Ok(($($T::from_arg($T)?,)*)) + } + } + } +} + +// It is possible to add larger sequences to support higher quantities of fixed arguments. +impl_tuple!(0,); +impl_tuple!(1, A); +impl_tuple!(2, A, B); +impl_tuple!(3, A, B, C); + +// Some functions take a single, optional argument, or no arguments at all. +impl FromArgs for (Option,) { + fn from_args(name: &str, args: Vec) -> Result { + let err = || Error::InvalidArguments { + name: name.to_owned(), + message: String::from("Expected 0 or 1 arguments."), + }; + + let mut args = args.into_iter(); + let a = match args.next() { + Some(a) => Some(A::from_arg(a)?), + None => None, + }; + if args.next().is_some() { + // Too many. + return Err(err()); + } + Ok((a,)) + } +} + +// Some functions take 1 or 2 arguments, so the second argument is optional. +impl FromArgs for (A, Option) { + fn from_args(name: &str, args: Vec) -> Result { + let err = || Error::InvalidArguments { + name: name.to_owned(), + message: String::from("Expected 1 or 2 arguments."), + }; + + let mut args = args.into_iter(); + let a = A::from_arg(args.next().ok_or_else(err)?)?; + let b = match args.next() { + Some(b) => Some(B::from_arg(b)?), + None => None, + }; + if args.next().is_some() { + // Too many. + return Err(err()); + } + Ok((a, b)) + } +} + +// Some functions take 0, 1, or 2 arguments, so both arguments are optional. +// It is safe to assume that, if the first argument is None, the second argument will also be None. +impl FromArgs for (Option, Option) { + fn from_args(name: &str, args: Vec) -> Result { + let err = || Error::InvalidArguments { + name: name.to_owned(), + message: String::from("Expected 0, 1, or 2 arguments."), + }; + + let mut args = args.into_iter(); + let a = match args.next() { + Some(a) => Some(A::from_arg(a)?), + None => None, + }; + let b = match args.next() { + Some(b) => Some(B::from_arg(b)?), + None => None, + }; + if args.next().is_some() { + // Too many. + return Err(err()); + } + Ok((a, b)) + } +} + +// Some functions optionally take 2 arguments, or don't take any at all. +impl FromArgs for (Option<(A, B)>,) { + fn from_args(name: &str, args: Vec) -> Result { + let err = || Error::InvalidArguments { + name: name.to_owned(), + message: String::from("Expected 0 or 2 arguments."), + }; + + let mut args = args.into_iter(); + let a = match args.next() { + Some(a) => Some(A::from_arg(a)?), + None => None, + }; + let b = match args.next() { + Some(b) => Some(B::from_arg(b)?), + None => None, + }; + if a.is_some() != b.is_some() || args.next().is_some() { + // One argument, or too many arguments. + return Err(err()); + } + Ok((a.zip(b),)) + } +} + +// Some functions take 1, 2, or 3 arguments. It is safe to assume that, if the second argument is +// None, the third argument will also be None. +impl FromArgs for (A, Option, Option) { + fn from_args(name: &str, args: Vec) -> Result { + let err = || Error::InvalidArguments { + name: name.to_owned(), + message: String::from("Expected 1, 2, or 3 arguments."), + }; + + let mut args = args.into_iter(); + let a = A::from_arg(args.next().ok_or_else(err)?)?; + let b = match args.next() { + Some(b) => Some(B::from_arg(b)?), + None => None, + }; + let c = match args.next() { + Some(c) => Some(C::from_arg(c)?), + None => None, + }; + if args.next().is_some() { + // Too many. + return Err(err()); + } + Ok((a, b, c)) } } diff --git a/lib/src/fnc/array.rs b/lib/src/fnc/array.rs index 1c9261ed..d5755f1c 100644 --- a/lib/src/fnc/array.rs +++ b/lib/src/fnc/array.rs @@ -1,4 +1,3 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::array::Combine; use crate::sql::array::Concat; @@ -8,9 +7,9 @@ use crate::sql::array::Union; use crate::sql::array::Uniq; use crate::sql::value::Value; -pub fn concat(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Array(v) => match args.remove(0) { +pub fn concat((left, right): (Value, Value)) -> Result { + match left { + Value::Array(v) => match right { Value::Array(w) => Ok(v.concat(w).into()), _ => Ok(Value::None), }, @@ -18,9 +17,9 @@ pub fn concat(_: &Context, mut args: Vec) -> Result { } } -pub fn combine(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Array(v) => match args.remove(0) { +pub fn combine((left, right): (Value, Value)) -> Result { + match left { + Value::Array(v) => match right { Value::Array(w) => Ok(v.combine(w).into()), _ => Ok(Value::None), }, @@ -28,9 +27,9 @@ pub fn combine(_: &Context, mut args: Vec) -> Result { } } -pub fn difference(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Array(v) => match args.remove(0) { +pub fn difference((left, right): (Value, Value)) -> Result { + match left { + Value::Array(v) => match right { Value::Array(w) => Ok(v.difference(w).into()), _ => Ok(Value::None), }, @@ -38,16 +37,16 @@ pub fn difference(_: &Context, mut args: Vec) -> Result { } } -pub fn distinct(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn distinct((arg,): (Value,)) -> Result { + match arg { Value::Array(v) => Ok(v.uniq().into()), _ => Ok(Value::None), } } -pub fn intersect(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Array(v) => match args.remove(0) { +pub fn intersect((left, right): (Value, Value)) -> Result { + match left { + Value::Array(v) => match right { Value::Array(w) => Ok(v.intersect(w).into()), _ => Ok(Value::None), }, @@ -55,59 +54,49 @@ pub fn intersect(_: &Context, mut args: Vec) -> Result { } } -pub fn len(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn len((arg,): (Value,)) -> Result { + match arg { Value::Array(v) => Ok(v.len().into()), _ => Ok(Value::None), } } -pub fn sort(_: &Context, mut args: Vec) -> Result { - match args.len() { - 2 => match args.remove(0) { - Value::Array(mut v) => match args.remove(0) { - // If "asc", sort ascending - Value::Strand(s) if s.as_str() == "asc" => { - v.sort_unstable_by(|a, b| a.cmp(b)); - Ok(v.into()) - } - // If "desc", sort descending - Value::Strand(s) if s.as_str() == "desc" => { - v.sort_unstable_by(|a, b| b.cmp(a)); - Ok(v.into()) - } - // If true, sort ascending - Value::True => { - v.sort_unstable_by(|a, b| a.cmp(b)); - Ok(v.into()) - } - // If false, sort descending - Value::False => { - v.sort_unstable_by(|a, b| b.cmp(a)); - Ok(v.into()) - } - // Sort ascending by default - _ => { - v.sort_unstable_by(|a, b| a.cmp(b)); - Ok(v.into()) - } - }, - v => Ok(v), - }, - 1 => match args.remove(0) { - Value::Array(mut v) => { +pub fn sort((array, order): (Value, Option)) -> Result { + match array { + Value::Array(mut v) => match order { + // If "asc", sort ascending + Some(Value::Strand(s)) if s.as_str() == "asc" => { + v.sort_unstable_by(|a, b| a.cmp(b)); + Ok(v.into()) + } + // If "desc", sort descending + Some(Value::Strand(s)) if s.as_str() == "desc" => { + v.sort_unstable_by(|a, b| b.cmp(a)); + Ok(v.into()) + } + // If true, sort ascending + Some(Value::True) => { + v.sort_unstable_by(|a, b| a.cmp(b)); + Ok(v.into()) + } + // If false, sort descending + Some(Value::False) => { + v.sort_unstable_by(|a, b| b.cmp(a)); + Ok(v.into()) + } + // Sort ascending by default + _ => { v.sort_unstable_by(|a, b| a.cmp(b)); Ok(v.into()) } - v => Ok(v), }, - _ => unreachable!(), + v => Ok(v), } } -pub fn union(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Array(v) => match args.remove(0) { +pub fn union((left, right): (Value, Value)) -> Result { + match left { + Value::Array(v) => match right { Value::Array(w) => Ok(v.union(w).into()), _ => Ok(Value::None), }, @@ -117,12 +106,11 @@ pub fn union(_: &Context, mut args: Vec) -> Result { pub mod sort { - use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; - pub fn asc(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { + pub fn asc((array,): (Value,)) -> Result { + match array { Value::Array(mut v) => { v.sort_unstable_by(|a, b| a.cmp(b)); Ok(v.into()) @@ -131,8 +119,8 @@ pub mod sort { } } - pub fn desc(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { + pub fn desc((array,): (Value,)) -> Result { + match array { Value::Array(mut v) => { v.sort_unstable_by(|a, b| b.cmp(a)); Ok(v.into()) diff --git a/lib/src/fnc/count.rs b/lib/src/fnc/count.rs index b90b9011..26174d2a 100644 --- a/lib/src/fnc/count.rs +++ b/lib/src/fnc/count.rs @@ -1,14 +1,11 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; -pub fn count(_: &Context, mut args: Vec) -> Result { - match args.len() { - 1 => match args.remove(0) { - Value::Array(v) => Ok(v.iter().filter(|v| v.is_truthy()).count().into()), - v => Ok((v.is_truthy() as i64).into()), - }, - 0 => Ok(1.into()), - _ => unreachable!(), - } +pub fn count((arg,): (Option,)) -> Result { + Ok(arg + .map(|val| match val { + Value::Array(v) => v.iter().filter(|v| v.is_truthy()).count().into(), + v => (v.is_truthy() as i64).into(), + }) + .unwrap_or_else(|| 1.into())) } diff --git a/lib/src/fnc/crypto.rs b/lib/src/fnc/crypto.rs index 33119363..623742fe 100644 --- a/lib/src/fnc/crypto.rs +++ b/lib/src/fnc/crypto.rs @@ -1,4 +1,3 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use md5::Digest; @@ -7,33 +6,33 @@ use sha1::Sha1; use sha2::Sha256; use sha2::Sha512; -pub fn md5(_: &Context, mut args: Vec) -> Result { +pub fn md5((arg,): (String,)) -> Result { let mut hasher = Md5::new(); - hasher.update(args.remove(0).as_string().as_str()); + hasher.update(arg.as_str()); let val = hasher.finalize(); let val = format!("{:x}", val); Ok(val.into()) } -pub fn sha1(_: &Context, mut args: Vec) -> Result { +pub fn sha1((arg,): (String,)) -> Result { let mut hasher = Sha1::new(); - hasher.update(args.remove(0).as_string().as_str()); + hasher.update(arg.as_str()); let val = hasher.finalize(); let val = format!("{:x}", val); Ok(val.into()) } -pub fn sha256(_: &Context, mut args: Vec) -> Result { +pub fn sha256((arg,): (String,)) -> Result { let mut hasher = Sha256::new(); - hasher.update(args.remove(0).as_string().as_str()); + hasher.update(arg.as_str()); let val = hasher.finalize(); let val = format!("{:x}", val); Ok(val.into()) } -pub fn sha512(_: &Context, mut args: Vec) -> Result { +pub fn sha512((arg,): (String,)) -> Result { let mut hasher = Sha512::new(); - hasher.update(args.remove(0).as_string().as_str()); + hasher.update(arg.as_str()); let val = hasher.finalize(); let val = format!("{:x}", val); Ok(val.into()) @@ -81,7 +80,6 @@ macro_rules! bounded_verify_password { pub mod argon2 { use super::COST_ALLOWANCE; - use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use argon2::{ @@ -90,9 +88,7 @@ pub mod argon2 { }; use rand::rngs::OsRng; - pub fn cmp(_: &Context, args: Vec) -> Result { - let args: [Value; 2] = args.try_into().unwrap(); - let [hash, pass] = args.map(Value::as_string); + pub fn cmp((hash, pass): (String, String)) -> Result { type Params<'a> = as PasswordHasher>::Params; Ok(PasswordHash::new(&hash) .ok() @@ -107,9 +103,8 @@ pub mod argon2 { .into()) } - pub fn gen(_: &Context, mut args: Vec) -> Result { + pub fn gen((pass,): (String,)) -> Result { let algo = Argon2::default(); - let pass = args.remove(0).as_string(); let salt = SaltString::generate(&mut OsRng); let hash = algo.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string(); Ok(hash.into()) @@ -119,7 +114,6 @@ pub mod argon2 { pub mod pbkdf2 { use super::COST_ALLOWANCE; - use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use pbkdf2::{ @@ -128,9 +122,7 @@ pub mod pbkdf2 { }; use rand::rngs::OsRng; - pub fn cmp(_: &Context, args: Vec) -> Result { - let args: [Value; 2] = args.try_into().unwrap(); - let [hash, pass] = args.map(Value::as_string); + pub fn cmp((hash, pass): (String, String)) -> Result { type Params = ::Params; Ok(PasswordHash::new(&hash) .ok() @@ -147,8 +139,7 @@ pub mod pbkdf2 { .into()) } - pub fn gen(_: &Context, mut args: Vec) -> Result { - let pass = args.remove(0).as_string(); + pub fn gen((pass,): (String,)) -> Result { let salt = SaltString::generate(&mut OsRng); let hash = Pbkdf2.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string(); Ok(hash.into()) @@ -157,7 +148,6 @@ pub mod pbkdf2 { pub mod scrypt { - use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use rand::rngs::OsRng; @@ -166,9 +156,7 @@ pub mod scrypt { Scrypt, }; - pub fn cmp(_: &Context, args: Vec) -> Result { - let args: [Value; 2] = args.try_into().unwrap(); - let [hash, pass] = args.map(Value::as_string); + pub fn cmp((hash, pass): (String, String)) -> Result { type Params = ::Params; Ok(PasswordHash::new(&hash) .ok() @@ -184,8 +172,7 @@ pub mod scrypt { .into()) } - pub fn gen(_: &Context, mut args: Vec) -> Result { - let pass = args.remove(0).as_string(); + pub fn gen((pass,): (String,)) -> Result { let salt = SaltString::generate(&mut OsRng); let hash = Scrypt.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string(); Ok(hash.into()) @@ -194,19 +181,15 @@ pub mod scrypt { pub mod bcrypt { - use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use bcrypt; - pub fn cmp(_: &Context, args: Vec) -> Result { - let args: [Value; 2] = args.try_into().unwrap(); - let [hash, pass] = args.map(Value::as_string); + pub fn cmp((hash, pass): (String, String)) -> Result { Ok(bcrypt::verify(pass, &hash).unwrap_or(false).into()) } - pub fn gen(_: &Context, mut args: Vec) -> Result { - let pass = args.remove(0).as_string(); + pub fn gen((pass,): (String,)) -> Result { let hash = bcrypt::hash(pass, bcrypt::DEFAULT_COST).unwrap(); Ok(hash.into()) } diff --git a/lib/src/fnc/geo.rs b/lib/src/fnc/geo.rs index ea56ecef..8ca90c4f 100644 --- a/lib/src/fnc/geo.rs +++ b/lib/src/fnc/geo.rs @@ -1,4 +1,3 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::geometry::Geometry; use crate::sql::value::Value; @@ -7,8 +6,8 @@ use geo::algorithm::bearing::Bearing; use geo::algorithm::centroid::Centroid; use geo::algorithm::haversine_distance::HaversineDistance; -pub fn area(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn area((arg,): (Value,)) -> Result { + match arg { Value::Geometry(v) => match v { Geometry::Point(v) => Ok(v.signed_area().into()), Geometry::Line(v) => Ok(v.signed_area().into()), @@ -24,9 +23,9 @@ pub fn area(_: &Context, mut args: Vec) -> Result { } } -pub fn bearing(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Geometry(Geometry::Point(v)) => match args.remove(0) { +pub fn bearing((a, b): (Value, Value)) -> Result { + match a { + Value::Geometry(Geometry::Point(v)) => match b { Value::Geometry(Geometry::Point(w)) => Ok(v.bearing(w).into()), _ => Ok(Value::None), }, @@ -34,8 +33,8 @@ pub fn bearing(_: &Context, mut args: Vec) -> Result { } } -pub fn centroid(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn centroid((arg,): (Value,)) -> Result { + match arg { Value::Geometry(v) => match v { Geometry::Point(v) => Ok(v.centroid().into()), Geometry::Line(v) => match v.centroid() { @@ -69,9 +68,9 @@ pub fn centroid(_: &Context, mut args: Vec) -> Result { } } -pub fn distance(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Geometry(Geometry::Point(v)) => match args.remove(0) { +pub fn distance((from, to): (Value, Value)) -> Result { + match from { + Value::Geometry(Geometry::Point(v)) => match to { Value::Geometry(Geometry::Point(w)) => Ok(v.haversine_distance(&w).into()), _ => Ok(Value::None), }, @@ -81,34 +80,29 @@ pub fn distance(_: &Context, mut args: Vec) -> Result { pub mod hash { - use crate::ctx::Context; use crate::err::Error; use crate::fnc::util::geo; use crate::sql::geometry::Geometry; use crate::sql::value::Value; - pub fn encode(_: &Context, mut args: Vec) -> Result { - match args.len() { - 2 => match args.remove(0) { - Value::Geometry(Geometry::Point(v)) => match args.remove(0).as_int() { - l if l > 0 && l <= 12 => Ok(geo::encode(v, l as usize).into()), - _ => Err(Error::InvalidArguments { - name: String::from("geo::encode"), - message: String::from("The second argument must be an integer greater than 0 and less than or equal to 12."), - }), - }, - _ => Ok(Value::None), - }, - 1 => match args.remove(0) { - Value::Geometry(Geometry::Point(v)) => Ok(geo::encode(v, 12).into()), - _ => Ok(Value::None), - }, - _ => unreachable!(), - } + pub fn encode((point, len): (Value, Option)) -> Result { + let len = match len { + Some(len) if (1..=12).contains(&len) => len, + None => 12, + _ => return Err(Error::InvalidArguments { + name: String::from("geo::encode"), + message: String::from("The second argument must be an integer greater than 0 and less than or equal to 12."), + }) + }; + + Ok(match point { + Value::Geometry(Geometry::Point(v)) => geo::encode(v, len).into(), + _ => Value::None, + }) } - pub fn decode(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { + pub fn decode((arg,): (Value,)) -> Result { + match arg { Value::Strand(v) => Ok(geo::decode(v).into()), _ => Ok(Value::None), } diff --git a/lib/src/fnc/http.rs b/lib/src/fnc/http.rs index eb89beee..1ada5d22 100644 --- a/lib/src/fnc/http.rs +++ b/lib/src/fnc/http.rs @@ -1,234 +1,156 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; +use crate::sql::Strand; #[cfg(not(feature = "http"))] -pub async fn head(_: &Context<'_>, _: Vec) -> Result { +pub async fn head((_, _): (Value, Option)) -> Result { Err(Error::HttpDisabled) } #[cfg(not(feature = "http"))] -pub async fn get(_: &Context<'_>, _: Vec) -> Result { +pub async fn get((_, _): (Value, Option)) -> Result { Err(Error::HttpDisabled) } #[cfg(not(feature = "http"))] -pub async fn put(_: &Context<'_>, _: Vec) -> Result { +pub async fn put((_, _, _): (Value, Option, Option)) -> Result { Err(Error::HttpDisabled) } #[cfg(not(feature = "http"))] -pub async fn post(_: &Context<'_>, _: Vec) -> Result { +pub async fn post((_, _, _): (Value, Option, Option)) -> Result { Err(Error::HttpDisabled) } #[cfg(not(feature = "http"))] -pub async fn patch(_: &Context<'_>, _: Vec) -> Result { +pub async fn patch((_, _, _): (Value, Option, Option)) -> Result { Err(Error::HttpDisabled) } #[cfg(not(feature = "http"))] -pub async fn delete(_: &Context<'_>, _: Vec) -> Result { +pub async fn delete((_, _): (Value, Option)) -> Result { Err(Error::HttpDisabled) } +fn try_as_uri(fn_name: &str, value: Value) -> Result { + match value { + Value::Strand(uri) => Ok(uri), + _ => Err(Error::InvalidArguments { + name: fn_name.to_owned(), + // Assumption is that URI is first argument. + message: String::from("The first argument should be a string."), + }), + } +} + #[cfg(feature = "http")] -pub async fn head(_: &Context<'_>, mut args: Vec) -> Result { - match args.len() { - 2 => match args.remove(0) { - Value::Strand(uri) => match args.remove(0) { - Value::Object(opt) => crate::fnc::util::http::head(uri, opt).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::head"), - message: String::from("The second argument should be an object."), - }), - }, - _ => Err(Error::InvalidArguments { +pub async fn head((uri, opts): (Value, Option)) -> Result { + let uri = try_as_uri("http::head", uri)?; + + let opts = match opts { + Some(Value::Object(opts)) => Some(opts), + None => None, + Some(_) => { + return Err(Error::InvalidArguments { name: String::from("http::head"), - message: String::from("The first argument should be a string."), - }), - }, - 1 => match args.remove(0) { - Value::Strand(uri) => crate::fnc::util::http::head(uri, None).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::head"), - message: String::from("The first argument should be a string."), - }), - }, - _ => Err(Error::InvalidArguments { - name: String::from("http::head"), - message: String::from("The function expects 1 or 2 arguments."), - }), - } + message: String::from("The second argument should be an object."), + }) + } + }; + + crate::fnc::util::http::head(uri, opts).await } #[cfg(feature = "http")] -pub async fn get(_: &Context<'_>, mut args: Vec) -> Result { - match args.len() { - 2 => match args.remove(0) { - Value::Strand(uri) => match args.remove(0) { - Value::Object(opt) => crate::fnc::util::http::get(uri, opt).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::get"), - message: String::from("The second argument should be an object."), - }), - }, - _ => Err(Error::InvalidArguments { +pub async fn get((uri, opts): (Value, Option)) -> Result { + let uri = try_as_uri("http::get", uri)?; + + let opts = match opts { + Some(Value::Object(opts)) => Some(opts), + None => None, + Some(_) => { + return Err(Error::InvalidArguments { name: String::from("http::get"), - message: String::from("The first argument should be a string."), - }), - }, - 1 => match args.remove(0) { - Value::Strand(uri) => crate::fnc::util::http::get(uri, None).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::get"), - message: String::from("The first argument should be a string."), - }), - }, - _ => Err(Error::InvalidArguments { - name: String::from("http::get"), - message: String::from("The function expects 1 or 2 arguments."), - }), - } + message: String::from("The second argument should be an object."), + }) + } + }; + + crate::fnc::util::http::get(uri, opts).await } #[cfg(feature = "http")] -pub async fn put(_: &Context<'_>, mut args: Vec) -> Result { - match args.len() { - 3 => match (args.remove(0), args.remove(0)) { - (Value::Strand(uri), val) => match args.remove(0) { - Value::Object(opts) => crate::fnc::util::http::put(uri, val, opts).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::put"), - message: String::from("The third argument should be an object."), - }), - }, - _ => Err(Error::InvalidArguments { +pub async fn put((uri, body, opts): (Value, Option, Option)) -> Result { + let uri = try_as_uri("http::put", uri)?; + + let opts = match opts { + Some(Value::Object(opts)) => Some(opts), + None => None, + Some(_) => { + return Err(Error::InvalidArguments { name: String::from("http::put"), - message: String::from("The first argument should be a string."), - }), - }, - 2 => match (args.remove(0), args.remove(0)) { - (Value::Strand(uri), val) => crate::fnc::util::http::put(uri, val, None).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::put"), - message: String::from("The first argument should be a string."), - }), - }, - 1 => match args.remove(0) { - Value::Strand(uri) => crate::fnc::util::http::put(uri, Value::Null, None).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::put"), - message: String::from("The first argument should be a string."), - }), - }, - _ => Err(Error::InvalidArguments { - name: String::from("http::put"), - message: String::from("The function expects 1, 2, or 3 arguments."), - }), - } + message: String::from("The third argument should be an object."), + }) + } + }; + + crate::fnc::util::http::put(uri, body.unwrap_or(Value::Null), opts).await } #[cfg(feature = "http")] -pub async fn post(_: &Context<'_>, mut args: Vec) -> Result { - match args.len() { - 3 => match (args.remove(0), args.remove(0)) { - (Value::Strand(uri), val) => match args.remove(0) { - Value::Object(opts) => crate::fnc::util::http::post(uri, val, opts).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::post"), - message: String::from("The third argument should be an object."), - }), - }, - _ => Err(Error::InvalidArguments { +pub async fn post( + (uri, body, opts): (Value, Option, Option), +) -> Result { + let uri = try_as_uri("http::post", uri)?; + + let opts = match opts { + Some(Value::Object(opts)) => Some(opts), + None => None, + Some(_) => { + return Err(Error::InvalidArguments { name: String::from("http::post"), - message: String::from("The first argument should be a string."), - }), - }, - 2 => match (args.remove(0), args.remove(0)) { - (Value::Strand(uri), val) => crate::fnc::util::http::post(uri, val, None).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::post"), - message: String::from("The first argument should be a string."), - }), - }, - 1 => match args.remove(0) { - Value::Strand(uri) => crate::fnc::util::http::post(uri, Value::Null, None).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::post"), - message: String::from("The first argument should be a string."), - }), - }, - _ => Err(Error::InvalidArguments { - name: String::from("http::post"), - message: String::from("The function expects 1, 2, or 3 arguments."), - }), - } + message: String::from("The third argument should be an object."), + }) + } + }; + + crate::fnc::util::http::post(uri, body.unwrap_or(Value::Null), opts).await } #[cfg(feature = "http")] -pub async fn patch(_: &Context<'_>, mut args: Vec) -> Result { - match args.len() { - 3 => match (args.remove(0), args.remove(0)) { - (Value::Strand(uri), val) => match args.remove(0) { - Value::Object(opts) => crate::fnc::util::http::patch(uri, val, opts).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::patch"), - message: String::from("The third argument should be an object."), - }), - }, - _ => Err(Error::InvalidArguments { +pub async fn patch( + (uri, body, opts): (Value, Option, Option), +) -> Result { + let uri = try_as_uri("http::patch", uri)?; + + let opts = match opts { + Some(Value::Object(opts)) => Some(opts), + None => None, + Some(_) => { + return Err(Error::InvalidArguments { name: String::from("http::patch"), - message: String::from("The first argument should be a string."), - }), - }, - 2 => match (args.remove(0), args.remove(0)) { - (Value::Strand(uri), val) => crate::fnc::util::http::patch(uri, val, None).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::patch"), - message: String::from("The first argument should be a string."), - }), - }, - 1 => match args.remove(0) { - Value::Strand(uri) => crate::fnc::util::http::patch(uri, Value::Null, None).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::patch"), - message: String::from("The first argument should be a string."), - }), - }, - _ => Err(Error::InvalidArguments { - name: String::from("http::patch"), - message: String::from("The function expects 1, 2, or 3 arguments."), - }), - } + message: String::from("The third argument should be an object."), + }) + } + }; + + crate::fnc::util::http::patch(uri, body.unwrap_or(Value::Null), opts).await } #[cfg(feature = "http")] -pub async fn delete(_: &Context<'_>, mut args: Vec) -> Result { - match args.len() { - 2 => match args.remove(0) { - Value::Strand(uri) => match args.remove(0) { - Value::Object(opt) => crate::fnc::util::http::delete(uri, opt).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::delete"), - message: String::from("The second argument should be an object."), - }), - }, - _ => Err(Error::InvalidArguments { +pub async fn delete((uri, opts): (Value, Option)) -> Result { + let uri = try_as_uri("http::delete", uri)?; + + let opts = match opts { + Some(Value::Object(opts)) => Some(opts), + None => None, + Some(_) => { + return Err(Error::InvalidArguments { name: String::from("http::delete"), - message: String::from("The first argument should be a string."), - }), - }, - 1 => match args.remove(0) { - Value::Strand(uri) => crate::fnc::util::http::delete(uri, None).await, - _ => Err(Error::InvalidArguments { - name: String::from("http::delete"), - message: String::from("The first argument should be a string."), - }), - }, - _ => Err(Error::InvalidArguments { - name: String::from("http::delete"), - message: String::from("The function expects 1 or 2 arguments."), - }), - } + message: String::from("The second argument should be an object."), + }) + } + }; + + crate::fnc::util::http::delete(uri, opts).await } diff --git a/lib/src/fnc/is.rs b/lib/src/fnc/is.rs index 12ec7fbe..96b826d1 100644 --- a/lib/src/fnc/is.rs +++ b/lib/src/fnc/is.rs @@ -1,4 +1,3 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use once_cell::sync::Lazy; @@ -11,58 +10,58 @@ use uuid::Uuid; #[rustfmt::skip] static LONGITUDE_RE: Lazy = Lazy::new(|| Regex::new("^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$").unwrap()); #[inline] -pub fn alphanum(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().chars().all(char::is_alphanumeric).into()) +pub fn alphanum((arg,): (String,)) -> Result { + Ok(arg.chars().all(char::is_alphanumeric).into()) } #[inline] -pub fn alpha(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().chars().all(char::is_alphabetic).into()) +pub fn alpha((arg,): (String,)) -> Result { + Ok(arg.chars().all(char::is_alphabetic).into()) } #[inline] -pub fn ascii(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().is_ascii().into()) +pub fn ascii((arg,): (String,)) -> Result { + Ok(arg.is_ascii().into()) } #[inline] -pub fn domain(_: &Context, mut args: Vec) -> Result { - Ok(addr::parse_domain_name(args.remove(0).as_string().as_str()).is_ok().into()) +pub fn domain((arg,): (String,)) -> Result { + Ok(addr::parse_domain_name(arg.as_str()).is_ok().into()) } #[inline] -pub fn email(_: &Context, mut args: Vec) -> Result { - Ok(addr::parse_email_address(args.remove(0).as_string().as_str()).is_ok().into()) +pub fn email((arg,): (String,)) -> Result { + Ok(addr::parse_email_address(arg.as_str()).is_ok().into()) } #[inline] -pub fn hexadecimal(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().chars().all(|x| char::is_ascii_hexdigit(&x)).into()) +pub fn hexadecimal((arg,): (String,)) -> Result { + Ok(arg.chars().all(|x| char::is_ascii_hexdigit(&x)).into()) } #[inline] -pub fn latitude(_: &Context, mut args: Vec) -> Result { - Ok(LATITUDE_RE.is_match(args.remove(0).as_string().as_str()).into()) +pub fn latitude((arg,): (String,)) -> Result { + Ok(LATITUDE_RE.is_match(arg.as_str()).into()) } #[inline] -pub fn longitude(_: &Context, mut args: Vec) -> Result { - Ok(LONGITUDE_RE.is_match(args.remove(0).as_string().as_str()).into()) +pub fn longitude((arg,): (String,)) -> Result { + Ok(LONGITUDE_RE.is_match(arg.as_str()).into()) } #[inline] -pub fn numeric(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().chars().all(char::is_numeric).into()) +pub fn numeric((arg,): (String,)) -> Result { + Ok(arg.chars().all(char::is_numeric).into()) } #[inline] -pub fn semver(_: &Context, mut args: Vec) -> Result { - Ok(Version::parse(args.remove(0).as_string().as_str()).is_ok().into()) +pub fn semver((arg,): (String,)) -> Result { + Ok(Version::parse(arg.as_str()).is_ok().into()) } #[inline] -pub fn uuid(_: &Context, mut args: Vec) -> Result { - Ok(match args.remove(0) { +pub fn uuid((arg,): (Value,)) -> Result { + Ok(match arg { Value::Strand(v) => Uuid::parse_str(v.as_string().as_str()).is_ok().into(), Value::Uuid(_) => true.into(), _ => false.into(), @@ -75,104 +74,104 @@ mod tests { #[test] fn alphanum() { - let value = super::alphanum(&Default::default(), vec!["abc123".into()]).unwrap(); + let value = super::alphanum((String::from("abc123"),)).unwrap(); assert_eq!(value, Value::True); - let value = super::alphanum(&Default::default(), vec!["y%*".into()]).unwrap(); + let value = super::alphanum((String::from("y%*"),)).unwrap(); assert_eq!(value, Value::False); } #[test] fn alpha() { - let value = super::alpha(&Default::default(), vec!["abc".into()]).unwrap(); + let value = super::alpha((String::from("abc"),)).unwrap(); assert_eq!(value, Value::True); - let value = super::alpha(&Default::default(), vec!["1234".into()]).unwrap(); + let value = super::alpha((String::from("1234"),)).unwrap(); assert_eq!(value, Value::False); } #[test] fn ascii() { - let value = super::ascii(&Default::default(), vec!["abc".into()]).unwrap(); + let value = super::ascii((String::from("abc"),)).unwrap(); assert_eq!(value, Value::True); - let value = super::ascii(&Default::default(), vec!["中国".into()]).unwrap(); + let value = super::ascii((String::from("中国"),)).unwrap(); assert_eq!(value, Value::False); } #[test] fn domain() { - let value = super::domain(&Default::default(), vec!["食狮.中国".into()]).unwrap(); + let value = super::domain((String::from("食狮.中国"),)).unwrap(); assert_eq!(value, Value::True); - let value = super::domain(&Default::default(), vec!["example-.com".into()]).unwrap(); + let value = super::domain((String::from("example-.com"),)).unwrap(); assert_eq!(value, Value::False); } #[test] fn email() { - let input = vec!["user@[fd79:cdcb:38cc:9dd:f686:e06d:32f3:c123]".into()]; - let value = super::email(&Default::default(), input).unwrap(); + let input = (String::from("user@[fd79:cdcb:38cc:9dd:f686:e06d:32f3:c123]"),); + let value = super::email(input).unwrap(); assert_eq!(value, Value::True); - let input = vec!["john..doe@example.com".into()]; - let value = super::email(&Default::default(), input).unwrap(); + let input = (String::from("john..doe@example.com"),); + let value = super::email(input).unwrap(); assert_eq!(value, Value::False); } #[test] fn hexadecimal() { - let value = super::hexadecimal(&Default::default(), vec!["00FF00".into()]).unwrap(); + let value = super::hexadecimal((String::from("00FF00"),)).unwrap(); assert_eq!(value, Value::True); - let value = super::hexadecimal(&Default::default(), vec!["SurrealDB".into()]).unwrap(); + let value = super::hexadecimal((String::from("SurrealDB"),)).unwrap(); assert_eq!(value, Value::False); } #[test] fn latitude() { - let value = super::latitude(&Default::default(), vec!["-0.118092".into()]).unwrap(); + let value = super::latitude((String::from("-0.118092"),)).unwrap(); assert_eq!(value, Value::True); - let value = super::latitude(&Default::default(), vec![12345.into()]).unwrap(); + let value = super::latitude((String::from("12345"),)).unwrap(); assert_eq!(value, Value::False); } #[test] fn longitude() { - let value = super::longitude(&Default::default(), vec!["51.509865".into()]).unwrap(); + let value = super::longitude((String::from("51.509865"),)).unwrap(); assert_eq!(value, Value::True); - let value = super::longitude(&Default::default(), vec![12345.into()]).unwrap(); + let value = super::longitude((String::from("12345"),)).unwrap(); assert_eq!(value, Value::False); } #[test] fn numeric() { - let value = super::numeric(&Default::default(), vec![12345.into()]).unwrap(); + let value = super::numeric((String::from("12345"),)).unwrap(); assert_eq!(value, Value::True); - let value = super::numeric(&Default::default(), vec!["abcde".into()]).unwrap(); + let value = super::numeric((String::from("abcde"),)).unwrap(); assert_eq!(value, Value::False); } #[test] fn semver() { - let value = super::semver(&Default::default(), vec!["1.0.0".into()]).unwrap(); + let value = super::semver((String::from("1.0.0"),)).unwrap(); assert_eq!(value, Value::True); - let value = super::semver(&Default::default(), vec!["1.0".into()]).unwrap(); + let value = super::semver((String::from("1.0"),)).unwrap(); assert_eq!(value, Value::False); } #[test] fn uuid() { - let input = vec!["123e4567-e89b-12d3-a456-426614174000".into()]; - let value = super::uuid(&Default::default(), input).unwrap(); + let input = (String::from("123e4567-e89b-12d3-a456-426614174000").into(),); + let value = super::uuid(input).unwrap(); assert_eq!(value, Value::True); - let input = vec!["foo-bar".into()]; - let value = super::uuid(&Default::default(), input).unwrap(); + let input = (String::from("foo-bar").into(),); + let value = super::uuid(input).unwrap(); assert_eq!(value, Value::False); } } diff --git a/lib/src/fnc/math.rs b/lib/src/fnc/math.rs index e664c2e3..68a00c6d 100644 --- a/lib/src/fnc/math.rs +++ b/lib/src/fnc/math.rs @@ -1,4 +1,3 @@ -use crate::ctx::Context; use crate::err::Error; use crate::fnc::util::math::bottom::Bottom; use crate::fnc::util::math::deviation::Deviation; @@ -16,48 +15,45 @@ use crate::fnc::util::math::variance::Variance; use crate::sql::number::Number; use crate::sql::value::Value; -pub fn abs(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_number().abs().into()) +pub fn abs((arg,): (Number,)) -> Result { + Ok(arg.abs().into()) } -pub fn bottom(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Array(v) => { - let c = args.remove(0).as_int(); - Ok(v.as_numbers().bottom(c).into()) - } +pub fn bottom((array, c): (Value, i64)) -> Result { + match array { + Value::Array(v) => Ok(v.as_numbers().bottom(c).into()), _ => Ok(Value::None), } } -pub fn ceil(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_number().ceil().into()) +pub fn ceil((arg,): (Number,)) -> Result { + Ok(arg.ceil().into()) } -pub fn fixed(_: &Context, mut args: Vec) -> Result { - let v = args.remove(0); - match args.remove(0).as_int() { - p if p > 0 => Ok(v.as_number().fixed(p as usize).into()), - _ => Err(Error::InvalidArguments { +pub fn fixed((v, p): (Number, i64)) -> Result { + if p > 0 { + Ok(v.fixed(p as usize).into()) + } else { + Err(Error::InvalidArguments { name: String::from("math::fixed"), message: String::from("The second argument must be an integer greater than 0."), - }), + }) } } -pub fn floor(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_number().floor().into()) +pub fn floor((arg,): (Number,)) -> Result { + Ok(arg.floor().into()) } -pub fn interquartile(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn interquartile((array,): (Value,)) -> Result { + match array { Value::Array(v) => Ok(v.as_numbers().interquartile().into()), _ => Ok(Value::None), } } -pub fn max(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn max((array,): (Value,)) -> Result { + match array { Value::Array(v) => match v.as_numbers().into_iter().max() { Some(v) => Ok(v.into()), None => Ok(Value::None), @@ -66,8 +62,8 @@ pub fn max(_: &Context, mut args: Vec) -> Result { } } -pub fn mean(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn mean((array,): (Value,)) -> Result { + match array { Value::Array(v) => match v.is_empty() { true => Ok(Value::None), false => Ok(v.as_numbers().mean().into()), @@ -76,8 +72,8 @@ pub fn mean(_: &Context, mut args: Vec) -> Result { } } -pub fn median(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn median((array,): (Value,)) -> Result { + match array { Value::Array(v) => match v.is_empty() { true => Ok(Value::None), false => Ok(v.as_numbers().median().into()), @@ -86,15 +82,15 @@ pub fn median(_: &Context, mut args: Vec) -> Result { } } -pub fn midhinge(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn midhinge((array,): (Value,)) -> Result { + match array { Value::Array(v) => Ok(v.as_numbers().midhinge().into()), _ => Ok(Value::None), } } -pub fn min(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn min((array,): (Value,)) -> Result { + match array { Value::Array(v) => match v.as_numbers().into_iter().min() { Some(v) => Ok(v.into()), None => Ok(Value::None), @@ -103,85 +99,82 @@ pub fn min(_: &Context, mut args: Vec) -> Result { } } -pub fn mode(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn mode((array,): (Value,)) -> Result { + match array { Value::Array(v) => Ok(v.as_numbers().mode().into()), _ => Ok(Value::None), } } -pub fn nearestrank(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Array(v) => Ok(v.as_numbers().nearestrank(args.remove(0).as_number()).into()), +pub fn nearestrank((array, n): (Value, Number)) -> Result { + match array { + Value::Array(v) => Ok(v.as_numbers().nearestrank(n).into()), _ => Ok(Value::None), } } -pub fn percentile(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Array(v) => Ok(v.as_numbers().percentile(args.remove(0).as_number()).into()), +pub fn percentile((array, n): (Value, Number)) -> Result { + match array { + Value::Array(v) => Ok(v.as_numbers().percentile(n).into()), _ => Ok(Value::None), } } -pub fn product(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn product((array,): (Value,)) -> Result { + match array { Value::Array(v) => Ok(v.as_numbers().into_iter().product::().into()), _ => Ok(Value::None), } } -pub fn round(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_number().round().into()) +pub fn round((arg,): (Number,)) -> Result { + Ok(arg.round().into()) } -pub fn spread(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn spread((array,): (Value,)) -> Result { + match array { Value::Array(v) => Ok(v.as_numbers().spread().into()), _ => Ok(Value::None), } } -pub fn sqrt(_: &Context, mut args: Vec) -> Result { - match args.remove(0).as_number() { - v if v >= Number::Int(0) => Ok(v.sqrt().into()), - _ => Ok(Value::None), - } +pub fn sqrt((arg,): (Number,)) -> Result { + Ok(match arg { + v if v >= Number::Int(0) => v.sqrt().into(), + _ => Value::None, + }) } -pub fn stddev(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn stddev((array,): (Value,)) -> Result { + match array { Value::Array(v) => Ok(v.as_numbers().deviation().into()), _ => Ok(Value::None), } } -pub fn sum(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn sum((array,): (Value,)) -> Result { + match array { Value::Array(v) => Ok(v.as_numbers().into_iter().sum::().into()), v => Ok(v.as_number().into()), } } -pub fn top(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Array(v) => { - let c = args.remove(0).as_int(); - Ok(v.as_numbers().top(c).into()) - } +pub fn top((array, c): (Value, i64)) -> Result { + match array { + Value::Array(v) => Ok(v.as_numbers().top(c).into()), _ => Ok(Value::None), } } -pub fn trimean(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn trimean((array,): (Value,)) -> Result { + match array { Value::Array(v) => Ok(v.as_numbers().trimean().into()), _ => Ok(Value::None), } } -pub fn variance(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn variance((array,): (Value,)) -> Result { + match array { Value::Array(v) => Ok(v.as_numbers().variance().into()), _ => Ok(Value::None), } diff --git a/lib/src/fnc/meta.rs b/lib/src/fnc/meta.rs index 59089175..3ae808ce 100644 --- a/lib/src/fnc/meta.rs +++ b/lib/src/fnc/meta.rs @@ -1,16 +1,15 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; -pub fn id(_: &Context, mut args: Vec) -> Result { - Ok(match args.remove(0) { +pub fn id((arg,): (Value,)) -> Result { + Ok(match arg { Value::Thing(v) => v.id.into(), _ => Value::None, }) } -pub fn tb(_: &Context, mut args: Vec) -> Result { - Ok(match args.remove(0) { +pub fn tb((arg,): (Value,)) -> Result { + Ok(match arg { Value::Thing(v) => v.tb.into(), _ => Value::None, }) diff --git a/lib/src/fnc/mod.rs b/lib/src/fnc/mod.rs index 3d8fa8f6..f3826756 100644 --- a/lib/src/fnc/mod.rs +++ b/lib/src/fnc/mod.rs @@ -1,6 +1,5 @@ use crate::ctx::Context; use crate::err::Error; -use crate::fnc::args::Args; use crate::sql::value::Value; pub mod args; @@ -24,185 +23,177 @@ pub mod time; pub mod r#type; pub mod util; -// Attempts to run any function +/// Attempts to run any function. pub async fn run(ctx: &Context<'_>, name: &str, args: Vec) -> Result { - match name { - v if v.starts_with("http") => { - // HTTP functions are asynchronous - asynchronous(ctx, name, args).await - } - _ => { - // Other functions are synchronous - synchronous(ctx, name, args) + macro_rules! dispatch { + ($name: ident, $args: ident, $($function_name: literal => $($function_path: ident)::+ $(($ctx_arg: expr))* $(.$await:tt)*,)+) => { + { + match $name { + $($function_name => $($function_path)::+($($ctx_arg,)* args::FromArgs::from_args($name, $args)?)$(.$await)*,)+ + _ => unreachable!() + } + } } } -} -// Attempts to run a synchronous function -pub fn synchronous(ctx: &Context<'_>, name: &str, args: Vec) -> Result { - match name { + // Each function is specified by its name (a string literal) followed by its path. The path + // may be followed by one parenthesized argument, e.g. ctx, which is passed to the function + // before the remainder of the arguments. The path may be followed by `.await` to signify that + // it is `async`. + dispatch!( + name, + args, + "array::combine" => array::combine, + "array::concat" => array::concat, + "array::difference" => array::difference, + "array::distinct" => array::distinct, + "array::intersect" => array::intersect, + "array::len" => array::len, + "array::sort" => array::sort, + "array::union" => array::union, + "array::sort::asc" => array::sort::asc, + "array::sort::desc" => array::sort::desc, // - "array::combine" => args::check(ctx, name, args, Args::Two, array::combine), - "array::concat" => args::check(ctx, name, args, Args::Two, array::concat), - "array::difference" => args::check(ctx, name, args, Args::Two, array::difference), - "array::distinct" => args::check(ctx, name, args, Args::One, array::distinct), - "array::intersect" => args::check(ctx, name, args, Args::Two, array::intersect), - "array::len" => args::check(ctx, name, args, Args::One, array::len), - "array::sort" => args::check(ctx, name, args, Args::OneTwo, array::sort), - "array::union" => args::check(ctx, name, args, Args::Two, array::union), - "array::sort::asc" => args::check(ctx, name, args, Args::One, array::sort::asc), - "array::sort::desc" => args::check(ctx, name, args, Args::One, array::sort::desc), + "count" => count::count, // - "count" => args::check(ctx, name, args, Args::NoneOne, count::count), + "crypto::md5" => crypto::md5, + "crypto::sha1" => crypto::sha1, + "crypto::sha256" => crypto::sha256, + "crypto::sha512" => crypto::sha512, + "crypto::argon2::compare" => crypto::argon2::cmp, + "crypto::argon2::generate" => crypto::argon2::gen, + "crypto::bcrypt::compare" => crypto::bcrypt::cmp, + "crypto::bcrypt::generate" => crypto::bcrypt::gen, + "crypto::pbkdf2::compare" => crypto::pbkdf2::cmp, + "crypto::pbkdf2::generate" => crypto::pbkdf2::gen, + "crypto::scrypt::compare" => crypto::scrypt::cmp, + "crypto::scrypt::generate" => crypto::scrypt::gen, // - "crypto::md5" => args::check(ctx, name, args, Args::One, crypto::md5), - "crypto::sha1" => args::check(ctx, name, args, Args::One, crypto::sha1), - "crypto::sha256" => args::check(ctx, name, args, Args::One, crypto::sha256), - "crypto::sha512" => args::check(ctx, name, args, Args::One, crypto::sha512), - "crypto::argon2::compare" => args::check(ctx, name, args, Args::Two, crypto::argon2::cmp), - "crypto::argon2::generate" => args::check(ctx, name, args, Args::One, crypto::argon2::gen), - "crypto::pbkdf2::compare" => args::check(ctx, name, args, Args::Two, crypto::pbkdf2::cmp), - "crypto::pbkdf2::generate" => args::check(ctx, name, args, Args::One, crypto::pbkdf2::gen), - "crypto::scrypt::compare" => args::check(ctx, name, args, Args::Two, crypto::scrypt::cmp), - "crypto::scrypt::generate" => args::check(ctx, name, args, Args::One, crypto::scrypt::gen), - "crypto::bcrypt::compare" => args::check(ctx, name, args, Args::Two, crypto::bcrypt::cmp), - "crypto::bcrypt::generate" => args::check(ctx, name, args, Args::One, crypto::bcrypt::gen), + "geo::area" => geo::area, + "geo::bearing" => geo::bearing, + "geo::centroid" => geo::centroid, + "geo::distance" => geo::distance, + "geo::hash::decode" => geo::hash::decode, + "geo::hash::encode" => geo::hash::encode, // - "geo::area" => args::check(ctx, name, args, Args::One, geo::area), - "geo::bearing" => args::check(ctx, name, args, Args::Two, geo::bearing), - "geo::centroid" => args::check(ctx, name, args, Args::One, geo::centroid), - "geo::distance" => args::check(ctx, name, args, Args::Two, geo::distance), - "geo::hash::decode" => args::check(ctx, name, args, Args::One, geo::hash::decode), - "geo::hash::encode" => args::check(ctx, name, args, Args::OneTwo, geo::hash::encode), + "http::head" => http::head.await, + "http::get" => http::get.await, + "http::put" => http::put.await, + "http::post" => http::post.await, + "http::patch" => http::patch.await, + "http::delete" => http::delete.await, // - "is::alphanum" => args::check(ctx, name, args, Args::One, is::alphanum), - "is::alpha" => args::check(ctx, name, args, Args::One, is::alpha), - "is::ascii" => args::check(ctx, name, args, Args::One, is::ascii), - "is::domain" => args::check(ctx, name, args, Args::One, is::domain), - "is::email" => args::check(ctx, name, args, Args::One, is::email), - "is::hexadecimal" => args::check(ctx, name, args, Args::One, is::hexadecimal), - "is::latitude" => args::check(ctx, name, args, Args::One, is::latitude), - "is::longitude" => args::check(ctx, name, args, Args::One, is::longitude), - "is::numeric" => args::check(ctx, name, args, Args::One, is::numeric), - "is::semver" => args::check(ctx, name, args, Args::One, is::semver), - "is::uuid" => args::check(ctx, name, args, Args::One, is::uuid), + "is::alphanum" => is::alphanum, + "is::alpha" => is::alpha, + "is::ascii" => is::ascii, + "is::domain" => is::domain, + "is::email" => is::email, + "is::hexadecimal" => is::hexadecimal, + "is::latitude" => is::latitude, + "is::longitude" => is::longitude, + "is::numeric" => is::numeric, + "is::semver" => is::semver, + "is::uuid" => is::uuid, // - "math::abs" => args::check(ctx, name, args, Args::One, math::abs), - "math::bottom" => args::check(ctx, name, args, Args::Two, math::bottom), - "math::ceil" => args::check(ctx, name, args, Args::One, math::ceil), - "math::fixed" => args::check(ctx, name, args, Args::Two, math::fixed), - "math::floor" => args::check(ctx, name, args, Args::One, math::floor), - "math::interquartile" => args::check(ctx, name, args, Args::One, math::interquartile), - "math::max" => args::check(ctx, name, args, Args::One, math::max), - "math::mean" => args::check(ctx, name, args, Args::One, math::mean), - "math::median" => args::check(ctx, name, args, Args::One, math::median), - "math::midhinge" => args::check(ctx, name, args, Args::One, math::midhinge), - "math::min" => args::check(ctx, name, args, Args::One, math::min), - "math::mode" => args::check(ctx, name, args, Args::One, math::mode), - "math::nearestrank" => args::check(ctx, name, args, Args::Two, math::nearestrank), - "math::percentile" => args::check(ctx, name, args, Args::Two, math::percentile), - "math::product" => args::check(ctx, name, args, Args::One, math::product), - "math::round" => args::check(ctx, name, args, Args::One, math::round), - "math::spread" => args::check(ctx, name, args, Args::One, math::spread), - "math::sqrt" => args::check(ctx, name, args, Args::One, math::sqrt), - "math::stddev" => args::check(ctx, name, args, Args::One, math::stddev), - "math::sum" => args::check(ctx, name, args, Args::One, math::sum), - "math::top" => args::check(ctx, name, args, Args::Two, math::top), - "math::trimean" => args::check(ctx, name, args, Args::One, math::trimean), - "math::variance" => args::check(ctx, name, args, Args::One, math::variance), + "math::abs" => math::abs, + "math::bottom" => math::bottom, + "math::ceil" => math::ceil, + "math::fixed" => math::fixed, + "math::floor" => math::floor, + "math::interquartile" => math::interquartile, + "math::max" => math::max, + "math::mean" => math::mean, + "math::median" => math::median, + "math::midhinge" => math::midhinge, + "math::min" => math::min, + "math::mode" => math::mode, + "math::nearestrank" => math::nearestrank, + "math::percentile" => math::percentile, + "math::product" => math::product, + "math::roun" => math::round, + "math::spread" => math::spread, + "math::sqrt" => math::sqrt, + "math::stddev" => math::stddev, + "math::sum" => math::sum, + "math::top" => math::top, + "math::trimean" => math::trimean, + "math::variance" => math::variance, // - "meta::id" => args::check(ctx, name, args, Args::One, meta::id), - "meta::table" => args::check(ctx, name, args, Args::One, meta::tb), - "meta::tb" => args::check(ctx, name, args, Args::One, meta::tb), + "meta::id" => meta::id, + "meta::table" => meta::tb, + "meta::tb" => meta::tb, // - "parse::email::host" => args::check(ctx, name, args, Args::One, parse::email::host), - "parse::email::user" => args::check(ctx, name, args, Args::One, parse::email::user), - "parse::url::domain" => args::check(ctx, name, args, Args::One, parse::url::domain), - "parse::url::fragment" => args::check(ctx, name, args, Args::One, parse::url::fragment), - "parse::url::host" => args::check(ctx, name, args, Args::One, parse::url::host), - "parse::url::path" => args::check(ctx, name, args, Args::One, parse::url::path), - "parse::url::port" => args::check(ctx, name, args, Args::One, parse::url::port), - "parse::url::query" => args::check(ctx, name, args, Args::One, parse::url::query), + "parse::email::host" => parse::email::host, + "parse::email::user" => parse::email::user, + "parse::url::domain" => parse::url::domain, + "parse::url::fragment" => parse::url::fragment, + "parse::url::host" => parse::url::host, + "parse::url::path" => parse::url::path, + "parse::url::port" => parse::url::port, + "parse::url::query" => parse::url::query, // - "rand::bool" => args::check(ctx, name, args, Args::None, rand::bool), - "rand::enum" => args::check(ctx, name, args, Args::Any, rand::r#enum), - "rand::float" => args::check(ctx, name, args, Args::NoneTwo, rand::float), - "rand::guid" => args::check(ctx, name, args, Args::NoneOne, rand::guid), - "rand::int" => args::check(ctx, name, args, Args::NoneTwo, rand::int), - "rand::string" => args::check(ctx, name, args, Args::NoneOneTwo, rand::string), - "rand::time" => args::check(ctx, name, args, Args::NoneTwo, rand::time), - "rand::uuid" => args::check(ctx, name, args, Args::None, rand::uuid), - "rand" => args::check(ctx, name, args, Args::None, rand::rand), + "rand::bool" => rand::bool, + "rand::enum" => rand::r#enum, + "rand::float" => rand::float, + "rand::guid" => rand::guid, + "rand::int" => rand::int, + "rand::string" => rand::string, + "rand::time" => rand::time, + "rand::uuid" => rand::uuid, + "rand" => rand::rand, // - "session::db" => args::check(ctx, name, args, Args::None, session::db), - "session::id" => args::check(ctx, name, args, Args::None, session::id), - "session::ip" => args::check(ctx, name, args, Args::None, session::ip), - "session::ns" => args::check(ctx, name, args, Args::None, session::ns), - "session::origin" => args::check(ctx, name, args, Args::None, session::origin), - "session::sc" => args::check(ctx, name, args, Args::None, session::sc), - "session::sd" => args::check(ctx, name, args, Args::None, session::sd), - "session::token" => args::check(ctx, name, args, Args::None, session::token), + "session::db" => session::db(ctx), + "session::id" => session::id(ctx), + "session::ip" => session::ip(ctx), + "session::ns" => session::ns(ctx), + "session::origin" => session::origin(ctx), + "session::sc" => session::sc(ctx), + "session::sd" => session::sd(ctx), + "session::token" => session::token(ctx), // - "string::concat" => args::check(ctx, name, args, Args::Any, string::concat), - "string::endsWith" => args::check(ctx, name, args, Args::Two, string::ends_with), - "string::join" => args::check(ctx, name, args, Args::Any, string::join), - "string::length" => args::check(ctx, name, args, Args::One, string::length), - "string::lowercase" => args::check(ctx, name, args, Args::One, string::lowercase), - "string::repeat" => args::check(ctx, name, args, Args::Two, string::repeat), - "string::replace" => args::check(ctx, name, args, Args::Three, string::replace), - "string::reverse" => args::check(ctx, name, args, Args::One, string::reverse), - "string::slice" => args::check(ctx, name, args, Args::Three, string::slice), - "string::slug" => args::check(ctx, name, args, Args::OneTwo, string::slug), - "string::split" => args::check(ctx, name, args, Args::Two, string::split), - "string::startsWith" => args::check(ctx, name, args, Args::Two, string::starts_with), - "string::trim" => args::check(ctx, name, args, Args::One, string::trim), - "string::uppercase" => args::check(ctx, name, args, Args::One, string::uppercase), - "string::words" => args::check(ctx, name, args, Args::One, string::words), + "string::concat" => string::concat, + "string::endsWith" => string::ends_with, + "string::join" => string::join, + "string::length" => string::length, + "string::lowercase" => string::lowercase, + "string::repeat" => string::repeat, + "string::replace" => string::replace, + "string::reverse" => string::reverse, + "string::slice" => string::slice, + "string::slug" => string::slug, + "string::split" => string::split, + "string::startsWith" => string::starts_with, + "string::trim" => string::trim, + "string::uppercase" => string::uppercase, + "string::words" => string::words, // - "time::day" => args::check(ctx, name, args, Args::NoneOne, time::day), - "time::floor" => args::check(ctx, name, args, Args::Two, time::floor), - "time::group" => args::check(ctx, name, args, Args::Two, time::group), - "time::hour" => args::check(ctx, name, args, Args::NoneOne, time::hour), - "time::mins" => args::check(ctx, name, args, Args::NoneOne, time::mins), - "time::month" => args::check(ctx, name, args, Args::NoneOne, time::month), - "time::nano" => args::check(ctx, name, args, Args::NoneOne, time::nano), - "time::now" => args::check(ctx, name, args, Args::None, time::now), - "time::round" => args::check(ctx, name, args, Args::Two, time::round), - "time::secs" => args::check(ctx, name, args, Args::NoneOne, time::secs), - "time::unix" => args::check(ctx, name, args, Args::NoneOne, time::unix), - "time::wday" => args::check(ctx, name, args, Args::NoneOne, time::wday), - "time::week" => args::check(ctx, name, args, Args::NoneOne, time::week), - "time::yday" => args::check(ctx, name, args, Args::NoneOne, time::yday), - "time::year" => args::check(ctx, name, args, Args::NoneOne, time::year), + "time::day" => time::day, + "time::floor" => time::floor, + "time::group" => time::group, + "time::hour" => time::hour, + "time::mins" => time::mins, + "time::month" => time::month, + "time::nano" => time::nano, + "time::now" => time::now, + "time::round" => time::round, + "time::secs" => time::secs, + "time::unix" => time::unix, + "time::wday" => time::wday, + "time::week" => time::week, + "time::yday" => time::yday, + "time::year" => time::year, // - "type::bool" => args::check(ctx, name, args, Args::One, r#type::bool), - "type::datetime" => args::check(ctx, name, args, Args::One, r#type::datetime), - "type::decimal" => args::check(ctx, name, args, Args::One, r#type::decimal), - "type::duration" => args::check(ctx, name, args, Args::One, r#type::duration), - "type::float" => args::check(ctx, name, args, Args::One, r#type::float), - "type::int" => args::check(ctx, name, args, Args::One, r#type::int), - "type::number" => args::check(ctx, name, args, Args::One, r#type::number), - "type::point" => args::check(ctx, name, args, Args::OneTwo, r#type::point), - "type::regex" => args::check(ctx, name, args, Args::One, r#type::regex), - "type::string" => args::check(ctx, name, args, Args::One, r#type::string), - "type::table" => args::check(ctx, name, args, Args::One, r#type::table), - "type::thing" => args::check(ctx, name, args, Args::OneTwo, r#type::thing), - // - _ => unreachable!(), - } -} - -// Attempts to run an asynchronous function -pub async fn asynchronous(ctx: &Context<'_>, name: &str, args: Vec) -> Result { - match name { - // - "http::head" => http::head(ctx, args).await, - "http::get" => http::get(ctx, args).await, - "http::put" => http::put(ctx, args).await, - "http::post" => http::post(ctx, args).await, - "http::patch" => http::patch(ctx, args).await, - "http::delete" => http::delete(ctx, args).await, - // - _ => unreachable!(), - } + "type::bool" => r#type::bool, + "type::datetime" => r#type::datetime, + "type::decimal" => r#type::decimal, + "type::duration" => r#type::duration, + "type::float" => r#type::float, + "type::int" => r#type::int, + "type::number" => r#type::number, + "type::point" => r#type::point, + "type::regex" => r#type::regex, + "type::string" => r#type::string, + "type::table" => r#type::table, + "type::thing" => r#type::thing, + ) } diff --git a/lib/src/fnc/parse.rs b/lib/src/fnc/parse.rs index 3ba021f6..3a20d727 100644 --- a/lib/src/fnc/parse.rs +++ b/lib/src/fnc/parse.rs @@ -1,15 +1,12 @@ pub mod email { - use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use addr::email::Host; - pub fn host(_: &Context, mut args: Vec) -> Result { - // Convert to a String - let val = args.remove(0).as_string(); + pub fn host((string,): (String,)) -> Result { // Parse the email address - match addr::parse_email_address(&val) { + match addr::parse_email_address(&string) { // Return the host part Ok(v) => match v.host() { Host::Domain(name) => Ok(name.as_str().into()), @@ -19,10 +16,9 @@ pub mod email { } } - pub fn user(_: &Context, mut args: Vec) -> Result { - let val = args.remove(0).as_string(); + pub fn user((string,): (String,)) -> Result { // Parse the email address - match addr::parse_email_address(&val) { + match addr::parse_email_address(&string) { // Return the user part Ok(v) => Ok(v.user().into()), Err(_) => Ok(Value::None), @@ -33,15 +29,15 @@ pub mod email { mod tests { #[test] fn host() { - let input = vec!["john.doe@example.com".into()]; - let value = super::host(&Default::default(), input).unwrap(); + let input = (String::from("john.doe@example.com"),); + let value = super::host(input).unwrap(); assert_eq!(value, "example.com".into()); } #[test] fn user() { - let input = vec!["john.doe@example.com".into()]; - let value = super::user(&Default::default(), input).unwrap(); + let input = (String::from("john.doe@example.com"),); + let value = super::user(input).unwrap(); assert_eq!(value, "john.doe".into()); } } @@ -49,16 +45,12 @@ pub mod email { pub mod url { - use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use url::Url; - pub fn domain(_: &Context, mut args: Vec) -> Result { - // Convert to a String - let val = args.remove(0).as_string(); - // Parse the URL - match Url::parse(&val) { + pub fn domain((string,): (String,)) -> Result { + match Url::parse(&string) { Ok(v) => match v.domain() { Some(v) => Ok(v.into()), None => Ok(Value::None), @@ -67,11 +59,9 @@ pub mod url { } } - pub fn fragment(_: &Context, mut args: Vec) -> Result { - // Convert to a String - let val = args.remove(0).as_string(); + pub fn fragment((string,): (String,)) -> Result { // Parse the URL - match Url::parse(&val) { + match Url::parse(&string) { Ok(v) => match v.fragment() { Some(v) => Ok(v.into()), None => Ok(Value::None), @@ -80,11 +70,9 @@ pub mod url { } } - pub fn host(_: &Context, mut args: Vec) -> Result { - // Convert to a String - let val = args.remove(0).as_string(); + pub fn host((string,): (String,)) -> Result { // Parse the URL - match Url::parse(&val) { + match Url::parse(&string) { Ok(v) => match v.host_str() { Some(v) => Ok(v.into()), None => Ok(Value::None), @@ -93,21 +81,17 @@ pub mod url { } } - pub fn path(_: &Context, mut args: Vec) -> Result { - // Convert to a String - let val = args.remove(0).as_string(); + pub fn path((string,): (String,)) -> Result { // Parse the URL - match Url::parse(&val) { + match Url::parse(&string) { Ok(v) => Ok(v.path().into()), Err(_) => Ok(Value::None), } } - pub fn port(_: &Context, mut args: Vec) -> Result { - // Convert to a String - let val = args.remove(0).as_string(); + pub fn port((string,): (String,)) -> Result { // Parse the URL - match Url::parse(&val) { + match Url::parse(&string) { Ok(v) => match v.port() { Some(v) => Ok(v.into()), None => Ok(Value::None), @@ -116,11 +100,9 @@ pub mod url { } } - pub fn query(_: &Context, mut args: Vec) -> Result { - // Convert to a String - let val = args.remove(0).as_string(); + pub fn query((string,): (String,)) -> Result { // Parse the URL - match Url::parse(&val) { + match Url::parse(&string) { Ok(v) => match v.query() { Some(v) => Ok(v.into()), None => Ok(Value::None), diff --git a/lib/src/fnc/rand.rs b/lib/src/fnc/rand.rs index 2ea8808a..71cfae0e 100644 --- a/lib/src/fnc/rand.rs +++ b/lib/src/fnc/rand.rs @@ -1,5 +1,4 @@ use crate::cnf::ID_CHARS; -use crate::ctx::Context; use crate::err::Error; use crate::sql::datetime::Datetime; use crate::sql::uuid::Uuid; @@ -9,15 +8,15 @@ use rand::distributions::Alphanumeric; use rand::prelude::IteratorRandom; use rand::Rng; -pub fn rand(_: &Context, _: Vec) -> Result { +pub fn rand(_: ()) -> Result { Ok(rand::random::().into()) } -pub fn bool(_: &Context, _: Vec) -> Result { +pub fn bool(_: ()) -> Result { Ok(rand::random::().into()) } -pub fn r#enum(_: &Context, mut args: Vec) -> Result { +pub fn r#enum(mut args: Vec) -> Result { Ok(match args.len() { 0 => Value::None, 1 => match args.remove(0) { @@ -28,127 +27,104 @@ pub fn r#enum(_: &Context, mut args: Vec) -> Result { }) } -pub fn float(_: &Context, mut args: Vec) -> Result { - match args.len() { - 2 => { - let min = args.remove(0).as_float(); - match args.remove(0).as_float() { - max if max < min => Ok(rand::thread_rng().gen_range(max..=min).into()), - max => Ok(rand::thread_rng().gen_range(min..=max).into()), - } +pub fn float((range,): (Option<(f64, f64)>,)) -> Result { + Ok(if let Some((min, max)) = range { + if max < min { + rand::thread_rng().gen_range(max..=min) + } else { + rand::thread_rng().gen_range(min..=max) } - 0 => Ok(rand::random::().into()), - _ => unreachable!(), + } else { + rand::random::() } + .into()) } -pub fn guid(_: &Context, mut args: Vec) -> Result { - match args.len() { - 1 => { - // Only need 53 to uniquely identify all atoms in observable universe. - const LIMIT: usize = 64; - let len = args.remove(0).as_int() as usize; - if len > LIMIT { - Err(Error::InvalidArguments { - name: String::from("rand::guid"), - message: format!("The maximum length of a GUID is {}.", LIMIT), - }) - } else { - Ok(nanoid!(len, &ID_CHARS).into()) - } +pub fn guid((len,): (Option,)) -> Result { + // Only need 53 to uniquely identify all atoms in observable universe. + const LIMIT: usize = 64; + + let len = match len { + Some(len) if len <= LIMIT => len, + None => 20, + _ => { + return Err(Error::InvalidArguments { + name: String::from("rand::guid"), + message: format!("The maximum length of a GUID is {}.", LIMIT), + }) } - 0 => Ok(nanoid!(20, &ID_CHARS).into()), - _ => unreachable!(), - } + }; + + Ok(nanoid!(len, &ID_CHARS).into()) } -pub fn int(_: &Context, mut args: Vec) -> Result { - match args.len() { - 2 => { - let min = args.remove(0).as_int(); - match args.remove(0).as_int() { - max if max < min => Ok(rand::thread_rng().gen_range(max..=min).into()), - max => Ok(rand::thread_rng().gen_range(min..=max).into()), - } +pub fn int((range,): (Option<(i64, i64)>,)) -> Result { + Ok(if let Some((min, max)) = range { + if max < min { + rand::thread_rng().gen_range(max..=min) + } else { + rand::thread_rng().gen_range(min..=max) } - 0 => Ok(rand::random::().into()), - _ => unreachable!(), + } else { + rand::random::() } + .into()) } -pub fn string(_: &Context, mut args: Vec) -> Result { +pub fn string((arg1, arg2): (Option, Option)) -> Result { // Limit how much time and bandwidth is spent. const LIMIT: i64 = 2i64.pow(16); - match args.len() { - 2 => match args.remove(0).as_int() { - min if (0..=LIMIT).contains(&min) => match args.remove(0).as_int() { - max if min <= max && max <= LIMIT => Ok(rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(rand::thread_rng().gen_range(min as usize..=max as usize)) - .map(char::from) - .collect::() - .into()), - max if max >= 0 && max <= min => Ok(rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(rand::thread_rng().gen_range(max as usize..=min as usize)) - .map(char::from) - .collect::() - .into()), - _ => Err(Error::InvalidArguments { + + let len = if let Some((min, max)) = arg1.zip(arg2) { + match min { + min if (0..=LIMIT).contains(&min) => match max { + max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min as usize..=max as usize), + max if max >= 0 && max <= min => rand::thread_rng().gen_range(max as usize..=min as usize), + _ => return Err(Error::InvalidArguments { name: String::from("rand::string"), message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {}.", LIMIT), }), }, - _ => Err(Error::InvalidArguments { + _ => return Err(Error::InvalidArguments { name: String::from("rand::string"), message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {}.", LIMIT), }), - }, - 1 => match args.remove(0).as_int() { - x if (0..=LIMIT).contains(&x) => Ok(rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(x as usize) - .map(char::from) - .collect::() - .into()), - _ => Err(Error::InvalidArguments { + } + } else if let Some(len) = arg1 { + if (0..=LIMIT).contains(&len) { + len as usize + } else { + return Err(Error::InvalidArguments { name: String::from("rand::string"), message: format!("To generate a string of X characters in length, the argument must be a positive number and no higher than {}.", LIMIT), - }), - }, - 0 => Ok(rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect::() - .into()), - _ => unreachable!(), - } + }); + } + } else { + 32 + }; + + Ok(rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(len) + .map(char::from) + .collect::() + .into()) } -pub fn time(_: &Context, mut args: Vec) -> Result { - match args.len() { - 2 => { - let min = args.remove(0).as_int(); - match args.remove(0).as_int() { - max if max < min => { - let i = rand::thread_rng().gen_range(max..=min); - Ok(Datetime::from(i).into()) - } - max => { - let i = rand::thread_rng().gen_range(min..=max); - Ok(Datetime::from(i).into()) - } - } - } - 0 => { - let i = rand::random::(); - Ok(Datetime::from(i as i64).into()) - } - _ => unreachable!(), - } +pub fn time((range,): (Option<(i64, i64)>,)) -> Result { + let i = if let Some((min, max)) = range { + let range = if max < min { + max..=min + } else { + min..=max + }; + rand::thread_rng().gen_range(range) + } else { + rand::random::() as i64 + }; + Ok(Datetime::from(i).into()) } -pub fn uuid(_: &Context, _: Vec) -> Result { +pub fn uuid(_: ()) -> Result { Ok(Uuid::new().into()) } diff --git a/lib/src/fnc/session.rs b/lib/src/fnc/session.rs index bf74e67b..e18a0fee 100644 --- a/lib/src/fnc/session.rs +++ b/lib/src/fnc/session.rs @@ -10,34 +10,34 @@ use crate::sql::paths::SD; use crate::sql::paths::TK; use crate::sql::value::Value; -pub fn db(ctx: &Context, _: Vec) -> Result { +pub fn db(ctx: &Context, _: ()) -> Result { ctx.value("session").unwrap_or(&Value::None).pick(DB.as_ref()).ok() } -pub fn id(ctx: &Context, _: Vec) -> Result { +pub fn id(ctx: &Context, _: ()) -> Result { ctx.value("session").unwrap_or(&Value::None).pick(ID.as_ref()).ok() } -pub fn ip(ctx: &Context, _: Vec) -> Result { +pub fn ip(ctx: &Context, _: ()) -> Result { ctx.value("session").unwrap_or(&Value::None).pick(IP.as_ref()).ok() } -pub fn ns(ctx: &Context, _: Vec) -> Result { +pub fn ns(ctx: &Context, _: ()) -> Result { ctx.value("session").unwrap_or(&Value::None).pick(NS.as_ref()).ok() } -pub fn origin(ctx: &Context, _: Vec) -> Result { +pub fn origin(ctx: &Context, _: ()) -> Result { ctx.value("session").unwrap_or(&Value::None).pick(OR.as_ref()).ok() } -pub fn sc(ctx: &Context, _: Vec) -> Result { +pub fn sc(ctx: &Context, _: ()) -> Result { ctx.value("session").unwrap_or(&Value::None).pick(SC.as_ref()).ok() } -pub fn sd(ctx: &Context, _: Vec) -> Result { +pub fn sd(ctx: &Context, _: ()) -> Result { ctx.value("session").unwrap_or(&Value::None).pick(SD.as_ref()).ok() } -pub fn token(ctx: &Context, _: Vec) -> Result { +pub fn token(ctx: &Context, _: ()) -> Result { ctx.value("session").unwrap_or(&Value::None).pick(TK.as_ref()).ok() } diff --git a/lib/src/fnc/string.rs b/lib/src/fnc/string.rs index 6d57627f..1bb689c0 100644 --- a/lib/src/fnc/string.rs +++ b/lib/src/fnc/string.rs @@ -1,41 +1,37 @@ -use crate::ctx::Context; use crate::err::Error; use crate::fnc::util::string; use crate::sql::value::Value; -pub fn concat(_: &Context, args: Vec) -> Result { +pub fn concat(args: Vec) -> Result { Ok(args.into_iter().map(|x| x.as_string()).collect::>().concat().into()) } -pub fn ends_with(_: &Context, args: Vec) -> Result { - let args: [Value; 2] = args.try_into().unwrap(); - let [val, chr] = args.map(Value::as_string); +pub fn ends_with((val, chr): (String, String)) -> Result { Ok(val.ends_with(&chr).into()) } -pub fn join(_: &Context, args: Vec) -> Result { +pub fn join(args: Vec) -> Result { let mut args = args.into_iter().map(Value::as_string); - let chr = args.next().unwrap(); + let chr = args.next().ok_or(Error::InvalidArguments { + name: String::from("string::join"), + message: String::from("Expected at least one argument"), + })?; // FIXME: Use intersperse to avoid intermediate allocation once stable // https://github.com/rust-lang/rust/issues/79524 let val = args.collect::>().join(&chr); Ok(val.into()) } -pub fn length(_: &Context, mut args: Vec) -> Result { - let val = args.remove(0).as_string(); - let num = val.chars().count() as i64; +pub fn length((string,): (String,)) -> Result { + let num = string.chars().count() as i64; Ok(num.into()) } -pub fn lowercase(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().to_lowercase().into()) +pub fn lowercase((string,): (String,)) -> Result { + Ok(string.to_lowercase().into()) } -pub fn repeat(_: &Context, args: Vec) -> Result { - let [val_arg, num_arg]: [Value; 2] = args.try_into().unwrap(); - let val = val_arg.as_string(); - let num = num_arg.as_int() as usize; +pub fn repeat((val, num): (String, usize)) -> Result { const LIMIT: usize = 2usize.pow(20); if val.len().saturating_mul(num) > LIMIT { Err(Error::InvalidArguments { @@ -47,50 +43,38 @@ pub fn repeat(_: &Context, args: Vec) -> Result { } } -pub fn replace(_: &Context, args: Vec) -> Result { - let args: [Value; 3] = args.try_into().unwrap(); - let [val, old, new] = args.map(Value::as_string); +pub fn replace((val, old, new): (String, String, String)) -> Result { Ok(val.replace(&old, &new).into()) } -pub fn reverse(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().chars().rev().collect::().into()) +pub fn reverse((string,): (String,)) -> Result { + Ok(string.chars().rev().collect::().into()) } -pub fn slice(_: &Context, args: Vec) -> Result { - let [val_arg, beg_arg, lim_arg]: [Value; 3] = args.try_into().unwrap(); - let val = val_arg.as_string(); - let beg = beg_arg.as_int() as usize; - let lim = lim_arg.as_int() as usize; - let val = val.chars().skip(beg).take(lim).collect::(); - Ok(val.into()) +pub fn slice((val, beg, lim): (String, usize, usize)) -> Result { + Ok(val.chars().skip(beg).take(lim).collect::().into()) } -pub fn slug(_: &Context, mut args: Vec) -> Result { - Ok(string::slug(&args.remove(0).as_string()).into()) +pub fn slug((string,): (String,)) -> Result { + Ok(string::slug(&string).into()) } -pub fn split(_: &Context, args: Vec) -> Result { - let args: [Value; 2] = args.try_into().unwrap(); - let [val, chr] = args.map(Value::as_string); - let val = val.split(&chr).collect::>(); - Ok(val.into()) +pub fn split((val, chr): (String, String)) -> Result { + Ok(val.split(&chr).collect::>().into()) } -pub fn starts_with(_: &Context, args: Vec) -> Result { - let args: [Value; 2] = args.try_into().unwrap(); - let [val, chr] = args.map(Value::as_string); +pub fn starts_with((val, chr): (String, String)) -> Result { Ok(val.starts_with(&chr).into()) } -pub fn trim(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().trim().into()) +pub fn trim((string,): (String,)) -> Result { + Ok(string.trim().into()) } -pub fn uppercase(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().to_uppercase().into()) +pub fn uppercase((string,): (String,)) -> Result { + Ok(string.to_uppercase().into()) } -pub fn words(_: &Context, mut args: Vec) -> Result { - Ok(args.remove(0).as_string().split_whitespace().collect::>().into()) +pub fn words((string,): (String,)) -> Result { + Ok(string.split_whitespace().collect::>().into()) } diff --git a/lib/src/fnc/time.rs b/lib/src/fnc/time.rs index ad3cf2f2..3c97ca70 100644 --- a/lib/src/fnc/time.rs +++ b/lib/src/fnc/time.rs @@ -1,4 +1,3 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::datetime::Datetime; use crate::sql::value::Value; @@ -8,19 +7,19 @@ use chrono::DurationRound; use chrono::Timelike; use chrono::Utc; -pub fn day(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().day().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.day().into()), - _ => Ok(Value::None), - }, - } +pub fn day((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.day().into()) } -pub fn floor(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Datetime(v) => match args.remove(0) { +pub fn floor((datetime, duration): (Value, Value)) -> Result { + match datetime { + Value::Datetime(v) => match duration { Value::Duration(w) => match chrono::Duration::from_std(*w) { Ok(d) => match v.duration_trunc(d) { Ok(v) => Ok(v.into()), @@ -34,9 +33,9 @@ pub fn floor(_: &Context, mut args: Vec) -> Result { } } -pub fn group(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Datetime(v) => match args.remove(0) { +pub fn group((datetime, strand): (Value, Value)) -> Result { + match datetime { + Value::Datetime(v) => match strand { Value::Strand(g) => match g.as_str() { "year" => Ok(Utc.ymd(v.year(), 1, 1).and_hms(0, 0, 0).into()), "month" => Ok(Utc.ymd(v.year(), v.month(), 1).and_hms(0, 0, 0).into()), @@ -67,53 +66,53 @@ pub fn group(_: &Context, mut args: Vec) -> Result { } } -pub fn hour(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().hour().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.hour().into()), - _ => Ok(Value::None), - }, - } +pub fn hour((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.hour().into()) } -pub fn mins(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().minute().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.minute().into()), - _ => Ok(Value::None), - }, - } +pub fn mins((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.minute().into()) } -pub fn month(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().month().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.month().into()), - _ => Ok(Value::None), - }, - } +pub fn month((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.month().into()) } -pub fn nano(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().timestamp_nanos().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.timestamp_nanos().into()), - _ => Ok(Value::None), - }, - } +pub fn nano((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.timestamp_nanos().into()) } -pub fn now(_: &Context, _: Vec) -> Result { +pub fn now(_: ()) -> Result { Ok(Datetime::default().into()) } -pub fn round(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { - Value::Datetime(v) => match args.remove(0) { +pub fn round((datetime, duration): (Value, Value)) -> Result { + match datetime { + Value::Datetime(v) => match duration { Value::Duration(w) => match chrono::Duration::from_std(*w) { Ok(d) => match v.duration_round(d) { Ok(v) => Ok(v.into()), @@ -127,62 +126,62 @@ pub fn round(_: &Context, mut args: Vec) -> Result { } } -pub fn secs(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().second().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.second().into()), - _ => Ok(Value::None), - }, - } +pub fn secs((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.second().into()) } -pub fn unix(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().timestamp().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.timestamp().into()), - _ => Ok(Value::None), - }, - } +pub fn unix((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.timestamp().into()) } -pub fn wday(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().weekday().number_from_monday().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.weekday().number_from_monday().into()), - _ => Ok(Value::None), - }, - } +pub fn wday((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.weekday().number_from_monday().into()) } -pub fn week(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().iso_week().week().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.iso_week().week().into()), - _ => Ok(Value::None), - }, - } +pub fn week((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.iso_week().week().into()) } -pub fn yday(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().ordinal().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.ordinal().into()), - _ => Ok(Value::None), - }, - } +pub fn yday((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.ordinal().into()) } -pub fn year(_: &Context, mut args: Vec) -> Result { - match args.len() { - 0 => Ok(Utc::now().year().into()), - _ => match args.remove(0) { - Value::Datetime(v) => Ok(v.year().into()), - _ => Ok(Value::None), - }, - } +pub fn year((datetime,): (Option,)) -> Result { + let date = match datetime { + Some(Value::Datetime(v)) => v, + None => Datetime::default(), + Some(_) => return Ok(Value::None), + }; + + Ok(date.year().into()) } diff --git a/lib/src/fnc/type.rs b/lib/src/fnc/type.rs index eb322757..b29351d2 100644 --- a/lib/src/fnc/type.rs +++ b/lib/src/fnc/type.rs @@ -1,120 +1,104 @@ -use crate::ctx::Context; use crate::err::Error; use crate::sql::geometry::Geometry; use crate::sql::number::Number; use crate::sql::table::Table; use crate::sql::thing::Thing; use crate::sql::value::Value; +use crate::sql::Strand; -pub fn bool(_: &Context, mut args: Vec) -> Result { - match args.remove(0).is_truthy() { - true => Ok(Value::True), - false => Ok(Value::False), +pub fn bool((arg,): (Value,)) -> Result { + Ok(arg.is_truthy().into()) +} + +pub fn datetime((arg,): (Value,)) -> Result { + Ok(match arg { + Value::Datetime(_) => arg, + _ => Value::Datetime(arg.as_datetime()), + }) +} + +pub fn decimal((arg,): (Value,)) -> Result { + Ok(match arg { + Value::Number(Number::Decimal(_)) => arg, + _ => Value::Number(Number::Decimal(arg.as_decimal())), + }) +} + +pub fn duration((arg,): (Value,)) -> Result { + match arg { + Value::Duration(_) => Ok(arg), + _ => Ok(Value::Duration(arg.as_duration())), } } -pub fn datetime(_: &Context, mut args: Vec) -> Result { - let val = args.remove(0); - match val { - Value::Datetime(_) => Ok(val), - _ => Ok(Value::Datetime(val.as_datetime())), +pub fn float((arg,): (Value,)) -> Result { + match arg { + Value::Number(Number::Float(_)) => Ok(arg), + _ => Ok(Value::Number(Number::Float(arg.as_float()))), } } -pub fn decimal(_: &Context, mut args: Vec) -> Result { - let val = args.remove(0); - match val { - Value::Number(Number::Decimal(_)) => Ok(val), - _ => Ok(Value::Number(Number::Decimal(val.as_decimal()))), +pub fn int((arg,): (Value,)) -> Result { + match arg { + Value::Number(Number::Int(_)) => Ok(arg), + _ => Ok(Value::Number(Number::Int(arg.as_int()))), } } -pub fn duration(_: &Context, mut args: Vec) -> Result { - let val = args.remove(0); - match val { - Value::Duration(_) => Ok(val), - _ => Ok(Value::Duration(val.as_duration())), +pub fn number((arg,): (Value,)) -> Result { + match arg { + Value::Number(_) => Ok(arg), + _ => Ok(Value::Number(arg.as_number())), } } -pub fn float(_: &Context, mut args: Vec) -> Result { - let val = args.remove(0); - match val { - Value::Number(Number::Float(_)) => Ok(val), - _ => Ok(Value::Number(Number::Float(val.as_float()))), - } -} - -pub fn int(_: &Context, mut args: Vec) -> Result { - let val = args.remove(0); - match val { - Value::Number(Number::Int(_)) => Ok(val), - _ => Ok(Value::Number(Number::Int(val.as_int()))), - } -} - -pub fn number(_: &Context, mut args: Vec) -> Result { - let val = args.remove(0); - match val { - Value::Number(_) => Ok(val), - _ => Ok(Value::Number(val.as_number())), - } -} - -pub fn point(_: &Context, mut args: Vec) -> Result { - match args.len() { - 2 => { - let x = args.remove(0); - let y = args.remove(0); - Ok((x.as_float(), y.as_float()).into()) +pub fn point((arg1, arg2): (Value, Option)) -> Result { + Ok(if let Some(y) = arg2 { + let x = arg1; + (x.as_float(), y.as_float()).into() + } else { + match arg1 { + Value::Array(v) if v.len() == 2 => v.as_point().into(), + Value::Geometry(Geometry::Point(v)) => v.into(), + _ => Value::None, } - 1 => match args.remove(0) { - Value::Array(v) if v.len() == 2 => Ok(v.as_point().into()), - Value::Geometry(Geometry::Point(v)) => Ok(v.into()), - _ => Ok(Value::None), - }, - _ => unreachable!(), - } + }) } -pub fn regex(_: &Context, mut args: Vec) -> Result { - match args.remove(0) { +pub fn regex((arg,): (Value,)) -> Result { + match arg { Value::Strand(v) => Ok(Value::Regex(v.as_str().into())), _ => Ok(Value::None), } } -pub fn string(_: &Context, mut args: Vec) -> Result { - let val = args.remove(0); - match val { - Value::Strand(_) => Ok(val), - _ => Ok(Value::Strand(val.as_strand())), - } +pub fn string((arg,): (Strand,)) -> Result { + Ok(arg.into()) } -pub fn table(_: &Context, mut args: Vec) -> Result { - Ok(Value::Table(Table(match args.remove(0) { +pub fn table((arg,): (Value,)) -> Result { + Ok(Value::Table(Table(match arg { Value::Thing(t) => t.tb, v => v.as_string(), }))) } -pub fn thing(_: &Context, mut args: Vec) -> Result { - match args.len() { - 2 => Ok(Value::Thing(Thing { - tb: args.remove(0).as_string(), - id: match args.remove(0) { +pub fn thing((arg1, arg2): (Value, Option)) -> Result { + Ok(if let Some(arg2) = arg2 { + Value::Thing(Thing { + tb: arg1.as_string(), + id: match arg2 { Value::Thing(v) => v.id, Value::Array(v) => v.into(), Value::Object(v) => v.into(), Value::Number(Number::Int(v)) => v.into(), v => v.as_string().into(), }, - })), - 1 => match args.remove(0) { - Value::Thing(v) => Ok(v.into()), - _ => Ok(Value::None), - }, - _ => unreachable!(), - } + }) + } else { + match arg1 { + Value::Thing(v) => v.into(), + _ => Value::None, + } + }) }