Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 259 additions & 2 deletions core/station/impl/src/core/validation.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
use std::{hash::Hash, sync::Arc};
use std::{
fmt::{Display, Formatter},
hash::Hash,
sync::Arc,
};

#[cfg(test)]
use std::cell::RefCell;

use crate::{
errors::{ExternalCanisterValidationError, RecordValidationError},
errors::{
ExternalCanisterValidationError, FieldValidationError, RecordValidationError,
ValidationError,
},
models::{
resource::{Resource, ResourceId, ResourceIds},
AccountKey, AddressBookEntryKey, NamedRuleKey, NotificationKey, RequestKey, TokenStandard,
Expand Down Expand Up @@ -267,6 +274,256 @@ impl EnsureIdExists<UUID> for EnsureNamedRule {

impl EnsureResourceIdExists for EnsureNamedRule {}

pub trait ValidateField<T>: Send + Sync {
fn validate_field(&self, value: &T) -> Result<(), ValidationError>;
}

#[derive(Clone)]
pub enum StringCharacterSet {
Alphanumeric,
}

impl StringCharacterSet {
pub fn validate(&self, str: &str) -> bool {
match self {
StringCharacterSet::Alphanumeric => str.chars().all(|c| c.is_alphanumeric()),
}
}
}

#[derive(Clone)]
pub struct StringFieldValidator {
field_name: String,
min_length: Option<usize>,
max_length: Option<usize>,
char_set: Option<StringCharacterSet>,
}

pub struct StringFieldValidatorBuilder {
validator: StringFieldValidator,
}

impl Display for StringCharacterSet {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
StringCharacterSet::Alphanumeric => write!(f, "alphanumeric"),
}
}
}

impl StringFieldValidatorBuilder {
pub fn new(field_name: String) -> Self {
Self {
validator: StringFieldValidator {
field_name,
min_length: None,
max_length: None,
char_set: None,
},
}
}

pub fn min_length(mut self, min_length: usize) -> Self {
self.validator.min_length = Some(min_length);
self
}

pub fn max_length(mut self, max_length: usize) -> Self {
self.validator.max_length = Some(max_length);
self
}

pub fn char_set(mut self, char_set: StringCharacterSet) -> Self {
self.validator.char_set = Some(char_set);
self
}

pub fn build(self) -> StringFieldValidator {
self.validator
}
}

impl ValidateField<String> for StringFieldValidator {
fn validate_field(&self, value: &String) -> Result<(), ValidationError> {
if let Some(min_length) = self.min_length {
if value.len() < min_length {
return Err(ValidationError::FieldValidationError(
FieldValidationError::InvalidRecord {
field_name: self.field_name.clone(),
error: format!("Length cannot be shorter than {}", min_length),
},
));
}
}

if let Some(max_length) = self.max_length {
if value.len() > max_length {
return Err(ValidationError::FieldValidationError(
FieldValidationError::InvalidRecord {
field_name: self.field_name.clone(),
error: format!("Length cannot be longer than {}", max_length),
},
));
}
}

if let Some(char_set) = &self.char_set {
if !char_set.validate(value) {
return Err(ValidationError::FieldValidationError(
FieldValidationError::InvalidRecord {
field_name: self.field_name.clone(),
error: format!("Allowed characters: {}", char_set),
},
));
}
}

Ok(())
}
}

pub struct NumberFieldValidatorBuilder<T: PartialOrd + Display> {
validator: NumberFieldValidator<T>,
}

impl<T: PartialOrd + Display> NumberFieldValidatorBuilder<T> {
pub fn new(field_name: String) -> Self {
Self {
validator: NumberFieldValidator::new(field_name),
}
}

pub fn min(mut self, min: T) -> Self {
self.validator.min = Some(min);
self
}

pub fn max(mut self, max: T) -> Self {
self.validator.max = Some(max);
self
}

pub fn build(self) -> NumberFieldValidator<T> {
self.validator
}
}

pub struct NumberFieldValidator<T: PartialOrd + Display> {
field_name: String,
min: Option<T>,
max: Option<T>,
}

impl<T: PartialOrd + Display> NumberFieldValidator<T> {
pub fn new(field_name: String) -> Self {
Self {
field_name,
min: None,
max: None,
}
}

pub fn validate_field(&self, value: T) -> Result<(), ValidationError> {
if let Some(min) = &self.min {
if &value < min {
return Err(ValidationError::FieldValidationError(
FieldValidationError::InvalidRecord {
field_name: self.field_name.clone(),
error: format!("Cannot be less than {}", min),
},
));
}
}

if let Some(max) = &self.max {
if &value > max {
return Err(ValidationError::FieldValidationError(
FieldValidationError::InvalidRecord {
field_name: self.field_name.clone(),
error: format!("Cannot be greater than {}", max),
},
));
}
}

Ok(())
}
}

pub struct VecFieldValidator<T> {
field_name: String,
min_length: Option<usize>,
max_length: Option<usize>,
item_validator: Arc<dyn ValidateField<T>>,
}

pub struct VecFieldValidatorBuilder<T> {
validator: VecFieldValidator<T>,
}

impl<T> VecFieldValidator<T> {
pub fn new(field_name: String, item_validator: Arc<dyn ValidateField<T>>) -> Self {
Self {
field_name,
min_length: None,
max_length: None,
item_validator,
}
}

pub fn validate_field(&self, value: &Vec<T>) -> Result<(), ValidationError> {
if let Some(min_length) = self.min_length {
if value.len() < min_length {
return Err(ValidationError::FieldValidationError(
FieldValidationError::InvalidRecord {
field_name: self.field_name.clone(),
error: format!("Cannot have fewer than {} items", min_length),
},
));
}
}

if let Some(max_length) = self.max_length {
if value.len() > max_length {
return Err(ValidationError::FieldValidationError(
FieldValidationError::InvalidRecord {
field_name: self.field_name.clone(),
error: format!("Cannot have more than {} items", max_length),
},
));
}
}

for item in value {
self.item_validator.validate_field(item)?;
}

Ok(())
}
}

impl<T> VecFieldValidatorBuilder<T> {
pub fn new(field_name: String, item_validator: Arc<dyn ValidateField<T>>) -> Self {
Self {
validator: VecFieldValidator::new(field_name, item_validator),
}
}

pub fn min_length(mut self, min_length: usize) -> Self {
self.validator.min_length = Some(min_length);
self
}

pub fn max_length(mut self, max_length: usize) -> Self {
self.validator.max_length = Some(max_length);
self
}

pub fn build(self) -> VecFieldValidator<T> {
self.validator
}
}

#[cfg(test)]
mod test {
use std::collections::BTreeMap;
Expand Down
34 changes: 28 additions & 6 deletions core/station/impl/src/errors/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use orbit_essentials::api::DetailableError;
use std::collections::HashMap;
use thiserror::Error;

use super::ValidationError;

/// Container for account errors.
#[derive(Error, Debug, Eq, PartialEq, Clone)]
pub enum AccountError {
Expand All @@ -25,11 +27,6 @@ pub enum AccountError {
r#"The account address is out of range, it must be between {min_length} and {max_length}."#
)]
InvalidAddressLength { min_length: u8, max_length: u8 },
/// The account name is out of range.
#[error(
r#"The account name is out of range, it must be between {min_length} and {max_length}."#
)]
InvalidNameLength { min_length: u8, max_length: u8 },
/// The address format is unknown to the system.
#[error(r#"The given address format is unknown to the system."#)]
UnknownAddressFormat { address_format: String },
Expand Down Expand Up @@ -108,7 +105,32 @@ impl DetailableError for AccountError {
details.insert("max".to_string(), max.to_string());
Some(details)
}
_ => None,
AccountError::AssetDoesNotExist { id } => {
details.insert("id".to_string(), id.to_string());
Some(details)
}
AccountError::Forbidden => Some(details),
AccountError::UnknownAddressFormat { address_format } => {
details.insert("address_format".to_string(), address_format.to_string());
Some(details)
}
AccountError::InvalidAddress {
address,
address_format,
} => {
details.insert("address".to_string(), address.to_string());
details.insert("address_format".to_string(), address_format.to_string());
Some(details)
}
AccountError::AccountNameAlreadyExists => Some(details),
}
}
}

impl From<ValidationError> for AccountError {
fn from(err: ValidationError) -> Self {
AccountError::ValidationError {
info: err.to_string(),
}
}
}
Loading
Loading