use crate::Value;
use std::fmt;
use std::iter;
use std::ops;
use std::slice;
pub type Func = fn(FuncArgs) -> Result<Value, String>;
#[derive(Debug, Clone)]
pub enum ParamType {
Any,
Bool,
Number,
String,
Array(Box<ParamType>),
Object(Box<ParamType>),
OneOf(Vec<ParamType>),
Nullable(Box<ParamType>),
}
impl ParamType {
pub fn array_of(element: ParamType) -> Self {
ParamType::Array(Box::new(element))
}
pub fn object_of(element: ParamType) -> Self {
ParamType::Object(Box::new(element))
}
pub fn one_of<I>(alternatives: I) -> Self
where
I: IntoIterator<Item = ParamType>,
{
ParamType::OneOf(alternatives.into_iter().collect())
}
pub fn nullable(non_null: ParamType) -> Self {
ParamType::Nullable(Box::new(non_null))
}
pub(super) fn is_satisfied_by(&self, value: &Value) -> bool {
match self {
ParamType::Any => true,
ParamType::Bool => value.is_boolean(),
ParamType::Number => value.is_number(),
ParamType::String => value.is_string(),
ParamType::Array(elem_type) => value
.as_array()
.is_some_and(|array| array.iter().all(|elem| elem_type.is_satisfied_by(elem))),
ParamType::Object(elem_type) => value
.as_object()
.is_some_and(|object| object.values().all(|elem| elem_type.is_satisfied_by(elem))),
ParamType::Nullable(elem_type) => value.is_null() || elem_type.is_satisfied_by(value),
ParamType::OneOf(elem_types) => elem_types
.iter()
.any(|elem_type| elem_type.is_satisfied_by(value)),
}
}
}
impl fmt::Display for ParamType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParamType::Any => f.write_str("`any`"),
ParamType::Bool => f.write_str("`bool`"),
ParamType::Number => f.write_str("`number`"),
ParamType::String => f.write_str("`string`"),
ParamType::Array(elem_type) => write!(f, "`array({elem_type})`"),
ParamType::Object(elem_type) => write!(f, "`object({elem_type})`"),
ParamType::Nullable(elem_type) => write!(f, "`nullable({elem_type})`"),
ParamType::OneOf(elem_types) => match elem_types.len() {
0 => f.write_str("`any`"),
1 => fmt::Display::fmt(&elem_types[0], f),
n => {
for (i, elem_type) in elem_types.iter().enumerate() {
if i == n - 1 {
f.write_str(" or ")?;
} else if i > 0 {
f.write_str(", ")?;
}
fmt::Display::fmt(elem_type, f)?;
}
Ok(())
}
},
}
}
}
#[derive(Debug, Clone)]
pub struct FuncDef {
func: Func,
params: Vec<ParamType>,
variadic_param: Option<ParamType>,
}
impl FuncDef {
pub fn new<P>(func: Func, params: P) -> FuncDef
where
P: IntoIterator<Item = ParamType>,
{
FuncDef::builder().params(params).build(func)
}
pub fn builder() -> FuncDefBuilder {
FuncDefBuilder {
params: Vec::new(),
variadic_param: None,
}
}
pub(super) fn call(&self, args: Vec<Value>) -> Result<Value, String> {
let params_len = self.params.len();
let args_len = args.len();
if args_len < params_len || (self.variadic_param.is_none() && args_len > params_len) {
return Err(format!(
"expected {params_len} positional arguments, got {args_len}"
));
}
let (pos_args, var_args) = args.split_at(params_len);
for (pos, (arg, param)) in pos_args.iter().zip(self.params.iter()).enumerate() {
if !param.is_satisfied_by(arg) {
return Err(format!(
"expected argument at position {pos} to be of type {param}, got `{arg}`",
));
}
}
if let Some(var_param) = &self.variadic_param {
for (pos, arg) in var_args.iter().enumerate() {
if !var_param.is_satisfied_by(arg) {
return Err(format!(
"expected variadic argument at position {} to be of type {}, got `{}`",
params_len + pos,
var_param,
arg
));
}
}
}
let func_args = FuncArgs::new(args, params_len);
(self.func)(func_args)
}
}
#[derive(Debug)]
pub struct FuncDefBuilder {
params: Vec<ParamType>,
variadic_param: Option<ParamType>,
}
impl FuncDefBuilder {
pub fn param(mut self, param: ParamType) -> FuncDefBuilder {
self.params.push(param);
self
}
pub fn params<I>(mut self, params: I) -> FuncDefBuilder
where
I: IntoIterator<Item = ParamType>,
{
self.params.extend(params);
self
}
pub fn variadic_param(mut self, param: ParamType) -> FuncDefBuilder {
self.variadic_param = Some(param);
self
}
pub fn build(self, func: Func) -> FuncDef {
FuncDef {
func,
params: self.params,
variadic_param: self.variadic_param,
}
}
}
#[derive(Debug, Clone)]
pub struct FuncArgs {
values: Vec<Value>,
pos_args_len: usize,
}
impl FuncArgs {
pub(super) fn new(values: Vec<Value>, pos_args_len: usize) -> FuncArgs {
FuncArgs {
values,
pos_args_len,
}
}
pub fn into_values(self) -> Vec<Value> {
self.values
}
pub fn positional_args(&self) -> PositionalArgs<'_> {
PositionalArgs {
iter: self.values.iter().take(self.pos_args_len),
}
}
pub fn variadic_args(&self) -> VariadicArgs<'_> {
VariadicArgs {
iter: self.values.iter().skip(self.pos_args_len),
}
}
}
impl ops::Deref for FuncArgs {
type Target = Vec<Value>;
fn deref(&self) -> &Self::Target {
&self.values
}
}
#[derive(Debug, Clone)]
pub struct PositionalArgs<'a> {
iter: iter::Take<slice::Iter<'a, Value>>,
}
impl<'a> Iterator for PositionalArgs<'a> {
type Item = &'a Value;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
#[derive(Debug, Clone)]
pub struct VariadicArgs<'a> {
iter: iter::Skip<slice::Iter<'a, Value>>,
}
impl<'a> Iterator for VariadicArgs<'a> {
type Item = &'a Value;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn param_type() {
let string = Value::from("a string");
let number = Value::from(42);
let boolean = Value::from(true);
let string_array = Value::from_iter(["foo", "bar"]);
let number_array = Value::from_iter([1, 2, 3]);
let object_of_strings = Value::from_iter([("foo", "bar"), ("baz", "qux")]);
let object_of_numbers = Value::from_iter([("foo", 1), ("bar", 2)]);
let param = ParamType::String;
assert!(param.is_satisfied_by(&string));
assert!(!param.is_satisfied_by(&number));
let param = ParamType::Any;
assert!(param.is_satisfied_by(&string));
assert!(param.is_satisfied_by(&number));
let param = ParamType::nullable(ParamType::String);
assert!(param.is_satisfied_by(&string));
assert!(param.is_satisfied_by(&Value::Null));
assert!(!param.is_satisfied_by(&number));
let param = ParamType::one_of([ParamType::String, ParamType::Number]);
assert!(param.is_satisfied_by(&string));
assert!(param.is_satisfied_by(&number));
assert!(!param.is_satisfied_by(&boolean));
let param = ParamType::array_of(ParamType::String);
assert!(param.is_satisfied_by(&string_array));
assert!(!param.is_satisfied_by(&number_array));
let param = ParamType::object_of(ParamType::String);
assert!(param.is_satisfied_by(&object_of_strings));
assert!(!param.is_satisfied_by(&object_of_numbers));
}
}