use std::fmt;
use super::BindError;
use super::BindErrorKind;
use crate::NamedValidationArgument;
use crate::ValidationArgument;
pub struct ArgumentReader<'a> {
args: &'a [NamedValidationArgument<'a>],
consumed: Vec<bool>,
}
impl<'a> ArgumentReader<'a> {
pub fn new(args: &'a [NamedValidationArgument<'a>]) -> Result<Self, BindError> {
for (index, argument) in args.iter().enumerate() {
if args[..index].iter().any(|previous| previous.name() == argument.name()) {
return Err(BindError::new(BindErrorKind::DuplicateParameter).with_parameter(argument.name()));
}
}
Ok(Self {
args,
consumed: vec![false; args.len()],
})
}
#[inline]
pub fn required_argument(&mut self, name: &str) -> Result<ValidationArgument<'a>, BindError> {
self.take(name)
}
#[inline]
pub fn required_u32(&mut self, name: &str) -> Result<u32, BindError> {
self.u32_value(name, true).map(|value| value.expect("required value"))
}
#[inline]
pub fn optional_u32(&mut self, name: &str) -> Result<Option<u32>, BindError> {
self.u32_value(name, false)
}
pub fn optional_usize(&mut self, name: &str) -> Result<Option<usize>, BindError> {
let Some(value) = self.take_optional(name)? else {
return Ok(None);
};
let converted = match value {
ValidationArgument::Unsigned(value) => usize::try_from(value),
ValidationArgument::Integer(value) if value >= 0 => usize::try_from(value as u128),
ValidationArgument::Integer(_) => return Err(Self::range_error(name)),
_ => return Err(Self::type_error(name)),
};
converted.map(Some).map_err(|_| Self::range_error(name))
}
pub fn required_str(&mut self, name: &str) -> Result<&'a str, BindError> {
match self.take(name)? {
ValidationArgument::String(value) => Ok(value),
_ => Err(Self::type_error(name)),
}
}
pub fn optional_str(&mut self, name: &str) -> Result<Option<&'a str>, BindError> {
let Some(value) = self.take_optional(name)? else {
return Ok(None);
};
match value {
ValidationArgument::String(value) => Ok(Some(value)),
_ => Err(Self::type_error(name)),
}
}
pub fn optional_bool(&mut self, name: &str) -> Result<Option<bool>, BindError> {
let Some(value) = self.take_optional(name)? else {
return Ok(None);
};
match value {
ValidationArgument::Bool(value) => Ok(Some(value)),
_ => Err(Self::type_error(name)),
}
}
pub fn finish(&self) -> Result<(), BindError> {
if let Some((_index, argument)) = self.args.iter().enumerate().find(|(index, _)| !self.consumed[*index]) {
return Err(BindError::new(BindErrorKind::UnknownParameter).with_parameter(argument.name()));
}
Ok(())
}
fn u32_value(&mut self, name: &str, required: bool) -> Result<Option<u32>, BindError> {
let Some(value) = self.take_optional(name)? else {
if required {
return Err(BindError::new(BindErrorKind::MissingParameter).with_parameter(name));
}
return Ok(None);
};
let value = match value {
ValidationArgument::Unsigned(value) => u32::try_from(value),
ValidationArgument::Integer(value) if value >= 0 => u32::try_from(value as u128),
ValidationArgument::Integer(_) => {
return Err(Self::range_error(name));
}
_ => return Err(Self::type_error(name)),
}
.map_err(|_| Self::range_error(name))?;
Ok(Some(value))
}
fn take_optional(&mut self, name: &str) -> Result<Option<ValidationArgument<'a>>, BindError> {
let Some(index) = self.args.iter().position(|argument| argument.name() == name) else {
return Ok(None);
};
if self.consumed[index] {
return Err(BindError::new(BindErrorKind::ParameterAlreadyConsumed).with_parameter(name));
}
self.consumed[index] = true;
Ok(Some(self.args[index].value()))
}
fn take(&mut self, name: &str) -> Result<ValidationArgument<'a>, BindError> {
self.take_optional(name)?
.ok_or_else(|| BindError::new(BindErrorKind::MissingParameter).with_parameter(name))
}
#[inline]
fn type_error(name: &str) -> BindError {
BindError::new(BindErrorKind::ParameterTypeMismatch).with_parameter(name)
}
#[inline]
fn range_error(name: &str) -> BindError {
BindError::new(BindErrorKind::ParameterOutOfRange).with_parameter(name)
}
}
impl fmt::Debug for ArgumentReader<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ArgumentReader")
.field("argument_count", &self.args.len())
.finish()
}
}