use std::{ffi::CString, ptr};
use crate::{
convert::{FromZvalMut, IntoZvalDyn},
describe::{Parameter, abi},
error::{Error, Result},
ffi::{zend_internal_arg_info, zend_wrong_parameters_count_error},
flags::DataType,
types::Zval,
zend::ZendType,
};
pub type ArgInfoTables = Box<[Box<[ArgInfo]>]>;
#[must_use]
#[derive(Debug)]
pub struct Arg<'a> {
name: String,
r#type: DataType,
as_ref: bool,
allow_null: bool,
pub(crate) variadic: bool,
default_value: Option<String>,
zval: Option<&'a mut Zval>,
variadic_zvals: Vec<Option<&'a mut Zval>>,
}
impl<'a> Arg<'a> {
pub fn new<T: Into<String>>(name: T, r#type: DataType) -> Self {
Arg {
name: name.into(),
r#type,
as_ref: false,
allow_null: false,
variadic: false,
default_value: None,
zval: None,
variadic_zvals: vec![],
}
}
pub fn of<T: FromZvalMut<'a>>(name: impl Into<String>) -> Self {
Arg {
name: name.into(),
r#type: T::TYPE,
as_ref: T::BY_REF,
allow_null: T::NULLABLE,
variadic: false,
default_value: None,
zval: None,
variadic_zvals: vec![],
}
}
#[allow(clippy::wrong_self_convention)]
pub fn as_ref(mut self) -> Self {
self.as_ref = true;
self
}
pub fn is_variadic(mut self) -> Self {
self.variadic = true;
self
}
pub fn allow_null(mut self) -> Self {
self.allow_null = true;
self
}
pub fn default<T: Into<String>>(mut self, default: T) -> Self {
self.default_value = Some(default.into());
self
}
pub fn consume<T>(mut self) -> Result<T, Self>
where
for<'b> T: FromZvalMut<'b>,
{
self.zval
.as_mut()
.and_then(|zv| T::from_zval_mut(zv.dereference_mut()))
.ok_or(self)
}
pub fn val<T>(&'a mut self) -> Option<T>
where
T: FromZvalMut<'a>,
{
self.zval
.as_mut()
.and_then(|zv| T::from_zval_mut(zv.dereference_mut()))
}
pub fn variadic_vals<T>(&'a mut self) -> Vec<T>
where
T: FromZvalMut<'a>,
{
self.variadic_zvals
.iter_mut()
.filter_map(|zv| zv.as_mut())
.filter_map(|zv| T::from_zval_mut(zv.dereference_mut()))
.collect()
}
pub fn try_variadic_vals<T>(&'a mut self) -> Option<Vec<T>>
where
T: FromZvalMut<'a>,
{
self.variadic_zvals
.iter_mut()
.filter_map(|zv| zv.as_mut())
.map(|zv| T::from_zval_mut(zv.dereference_mut()))
.collect()
}
#[allow(clippy::mut_mut)]
pub fn zval(&mut self) -> Option<&mut &'a mut Zval> {
self.zval.as_mut()
}
#[allow(clippy::inline_always)]
#[inline(always)]
pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
self.zval.as_ref().ok_or(Error::Callable)?.try_call(params)
}
pub(crate) fn as_arg_info(&self, optional: bool) -> Result<ArgInfo> {
let default_value = match &self.default_value {
Some(val) => Some(val.as_str()),
None if optional && self.allow_null && !self.variadic => Some("null"),
None => None,
};
Ok(ArgInfo {
name: CString::new(self.name.as_str())?.into_raw(),
type_: ZendType::empty_from_type(
self.r#type,
self.as_ref,
self.variadic,
self.allow_null,
)
.ok_or(Error::ZvalConversion(self.r#type))?,
default_value: match default_value {
Some(val) => CString::new(val)?.into_raw(),
None => ptr::null(),
},
})
}
}
impl From<Arg<'_>> for Parameter {
fn from(val: Arg<'_>) -> Self {
Parameter {
name: val.name.into(),
ty: Some(val.r#type).into(),
nullable: val.allow_null,
variadic: val.variadic,
default: val.default_value.map(abi::RString::from).into(),
}
}
}
pub type ArgInfo = zend_internal_arg_info;
#[must_use]
pub const fn required_count(omittable: &[bool]) -> usize {
let mut required = omittable.len();
while required > 0 && omittable[required - 1] {
required -= 1;
}
required
}
#[must_use]
pub struct ArgParser<'a, 'b> {
args: Vec<&'b mut Arg<'a>>,
min_num_args: Option<usize>,
arg_zvals: Vec<Option<&'a mut Zval>>,
}
impl<'a, 'b> ArgParser<'a, 'b> {
pub fn new(arg_zvals: Vec<Option<&'a mut Zval>>) -> Self {
ArgParser {
args: vec![],
min_num_args: None,
arg_zvals,
}
}
pub fn arg(mut self, arg: &'b mut Arg<'a>) -> Self {
self.args.push(arg);
self
}
pub fn not_required(mut self) -> Self {
self.min_num_args = Some(self.args.len());
self
}
pub fn required_args(mut self, count: usize) -> Self {
self.min_num_args = Some(count);
self
}
pub fn parse(mut self) -> Result<()> {
let max_num_args = self.args.len();
let mut min_num_args = self.min_num_args.unwrap_or(max_num_args);
let num_args = self.arg_zvals.len();
let has_variadic = self.args.last().is_some_and(|arg| arg.variadic);
if has_variadic {
min_num_args = min_num_args.saturating_sub(1);
}
if num_args < min_num_args || (!has_variadic && num_args > max_num_args) {
unsafe {
zend_wrong_parameters_count_error(
min_num_args.try_into()?,
max_num_args.try_into()?,
);
};
return Err(Error::IncorrectArguments(num_args, min_num_args));
}
for (i, arg_zval) in self.arg_zvals.into_iter().enumerate() {
let arg = match self.args.get_mut(i) {
Some(arg) => Some(arg),
None => self.args.last_mut().filter(|arg| arg.variadic),
};
if let Some(arg) = arg {
if arg.variadic {
arg.variadic_zvals.push(arg_zval);
} else {
arg.zval = arg_zval;
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
#[cfg(feature = "embed")]
use crate::embed::Embed;
use super::*;
#[test]
fn required_count_keeps_omittable_args_before_a_required_one() {
assert_eq!(required_count(&[]), 0);
assert_eq!(required_count(&[false, false]), 2);
assert_eq!(required_count(&[false, true]), 1);
assert_eq!(required_count(&[true, false, true, true]), 2);
assert_eq!(required_count(&[true, true]), 0);
}
#[test]
fn of_reads_metadata_from_the_type() {
let arg = Arg::of::<Option<i64>>("maybe");
assert_eq!(arg.r#type, DataType::Long);
assert!(arg.allow_null);
assert!(!arg.as_ref);
let arg = Arg::of::<&mut Zval>("target");
assert_eq!(arg.r#type, DataType::Mixed);
assert!(!arg.allow_null);
assert!(arg.as_ref);
}
fn default_of(arg: &Arg<'_>, optional: bool) -> Option<String> {
let info = arg.as_arg_info(optional).unwrap();
unsafe { drop(CString::from_raw(info.name.cast_mut())) };
if info.default_value.is_null() {
return None;
}
let default = unsafe { CString::from_raw(info.default_value.cast_mut()) };
Some(default.to_str().unwrap().to_string())
}
#[test]
fn optional_nullable_arg_reports_a_null_default() {
assert_eq!(
default_of(&Arg::of::<Option<i64>>("a"), true).as_deref(),
Some("null")
);
assert_eq!(default_of(&Arg::of::<Option<i64>>("a"), false), None);
assert_eq!(default_of(&Arg::of::<i64>("a"), true), None);
assert_eq!(
default_of(&Arg::of::<Option<i64>>("a").is_variadic(), true),
None
);
assert_eq!(
default_of(&Arg::of::<Option<i64>>("a").default("5"), true).as_deref(),
Some("5")
);
}
#[test]
fn test_new() {
let arg = Arg::new("test", DataType::Long);
assert_eq!(arg.name, "test");
assert_eq!(arg.r#type, DataType::Long);
assert!(!arg.as_ref);
assert!(!arg.allow_null);
assert!(!arg.variadic);
assert!(arg.default_value.is_none());
assert!(arg.zval.is_none());
assert!(arg.variadic_zvals.is_empty());
}
#[test]
fn test_as_ref() {
let arg = Arg::new("test", DataType::Long).as_ref();
assert!(arg.as_ref);
}
#[test]
fn test_is_variadic() {
let arg = Arg::new("test", DataType::Long).is_variadic();
assert!(arg.variadic);
}
#[test]
fn test_allow_null() {
let arg = Arg::new("test", DataType::Long).allow_null();
assert!(arg.allow_null);
}
#[test]
fn test_default() {
let arg = Arg::new("test", DataType::Long).default("default");
assert_eq!(arg.default_value, Some("default".to_string()));
}
#[test]
fn test_consume_no_value() {
let arg = Arg::new("test", DataType::Long);
let result: Result<i32, _> = arg.consume();
assert!(result.is_err());
assert_eq!(result.unwrap_err().name, "test");
}
#[test]
#[cfg(feature = "embed")]
fn test_consume() {
let mut arg = Arg::new("test", DataType::Long);
let mut zval = Zval::from(42);
arg.zval = Some(&mut zval);
let result: Result<i32, _> = arg.consume();
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_val_no_value() {
let mut arg = Arg::new("test", DataType::Long);
let result: Option<i32> = arg.val();
assert!(result.is_none());
}
#[test]
#[cfg(feature = "embed")]
fn test_val() {
let mut arg = Arg::new("test", DataType::Long);
let mut zval = Zval::from(42);
arg.zval = Some(&mut zval);
let result: Option<i32> = arg.val();
assert_eq!(result.unwrap(), 42);
}
#[test]
#[cfg(feature = "embed")]
fn test_variadic_vals() {
let mut arg = Arg::new("test", DataType::Long).is_variadic();
let mut zval1 = Zval::from(42);
let mut zval2 = Zval::from(43);
arg.variadic_zvals.push(Some(&mut zval1));
arg.variadic_zvals.push(Some(&mut zval2));
let result: Vec<i32> = arg.variadic_vals();
assert_eq!(result, vec![42, 43]);
}
#[test]
#[cfg(feature = "embed")]
fn try_variadic_vals_fails_when_a_value_does_not_convert() {
let mut arg = Arg::new("test", DataType::Long).is_variadic();
let mut long = Zval::from(42);
let mut double = Zval::from(1.5);
arg.variadic_zvals.push(Some(&mut long));
arg.variadic_zvals.push(Some(&mut double));
assert_eq!(arg.try_variadic_vals::<i64>(), None);
}
#[test]
fn test_zval_no_value() {
let mut arg = Arg::new("test", DataType::Long);
let result = arg.zval();
assert!(result.is_none());
}
#[test]
#[cfg(feature = "embed")]
fn test_zval() {
let mut arg = Arg::new("test", DataType::Long);
let mut zval = Zval::from(42);
arg.zval = Some(&mut zval);
let result = arg.zval();
assert!(result.is_some());
assert_eq!(result.unwrap().dereference_mut().long(), Some(42));
}
#[cfg(feature = "embed")]
#[test]
fn test_try_call_no_value() {
let arg = Arg::new("test", DataType::Long);
let result = arg.try_call(vec![]);
assert!(result.is_err());
}
#[test]
#[cfg(feature = "embed")]
fn test_try_call_not_callable() {
Embed::run(|| {
let mut arg = Arg::new("test", DataType::Long);
let mut zval = Zval::from(42);
arg.zval = Some(&mut zval);
let result = arg.try_call(vec![]);
assert!(result.is_err());
});
}
#[test]
#[cfg(feature = "embed")]
fn test_as_arg_info() {
let arg = Arg::new("test", DataType::Long);
let arg_info = arg.as_arg_info(false);
assert!(arg_info.is_ok());
let arg_info = arg_info.unwrap();
assert!(arg_info.default_value.is_null());
let r#type = arg_info.type_;
assert_eq!(r#type.type_mask, 16);
unsafe { drop(CString::from_raw(arg_info.name.cast_mut())) };
}
#[test]
#[cfg(feature = "embed")]
fn test_as_arg_info_with_default() {
let arg = Arg::new("test", DataType::Long).default("default");
let arg_info = arg.as_arg_info(false);
assert!(arg_info.is_ok());
let arg_info = arg_info.unwrap();
assert!(!arg_info.default_value.is_null());
let r#type = arg_info.type_;
assert_eq!(r#type.type_mask, 16);
unsafe {
drop(CString::from_raw(arg_info.name.cast_mut()));
drop(CString::from_raw(arg_info.default_value.cast_mut()));
}
}
#[test]
fn test_param_from_arg() {
let arg = Arg::new("test", DataType::Long)
.default("default")
.allow_null();
let param: Parameter = arg.into();
assert_eq!(param.name, "test".into());
assert_eq!(param.ty, abi::Option::Some(DataType::Long));
assert!(param.nullable);
assert_eq!(param.default, abi::Option::Some("default".into()));
}
#[test]
fn test_arg_parser_new() {
let arg_zvals = vec![None, None];
let parser = ArgParser::new(arg_zvals);
assert_eq!(parser.arg_zvals.len(), 2);
assert!(parser.args.is_empty());
assert!(parser.min_num_args.is_none());
}
#[test]
fn test_arg_parser_arg() {
let arg_zvals = vec![None, None];
let mut parser = ArgParser::new(arg_zvals);
let mut arg = Arg::new("test", DataType::Long);
parser = parser.arg(&mut arg);
assert_eq!(parser.args.len(), 1);
assert_eq!(parser.args[0].name, "test");
assert_eq!(parser.args[0].r#type, DataType::Long);
}
}