Skip to content

Commit

Permalink
implement and test validator support
Browse files Browse the repository at this point in the history
  • Loading branch information
GlenDC committed Apr 18, 2024
1 parent 98aaf50 commit c4f2926
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 12 deletions.
57 changes: 46 additions & 11 deletions venndb-macros/src/generate_db.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
use crate::field::{FieldInfo, StructField};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::Ident;
use syn::{Ident, Path};

/// Generate the venndb logic
pub fn generate_db(
name: &Ident,
name_db: &Ident,
validator: Option<&Path>,
vis: &syn::Visibility,
fields: &[StructField],
) -> TokenStream {
let fields: Vec<_> = fields.iter().filter_map(StructField::info).collect();

let db_error = DbError::new(&fields[..]);
let db_error = DbError::new(validator, &fields[..]);

let db_struct = generate_db_struct(name, name_db, vis, &fields[..]);
let db_struct_methods = generate_db_struct_methods(name, name_db, vis, &db_error, &fields[..]);
let db_struct_methods =
generate_db_struct_methods(name, name_db, validator, vis, &db_error, &fields[..]);

let db_query = generate_query_struct(name, name_db, vis, &fields[..]);

Expand Down Expand Up @@ -92,6 +94,7 @@ fn generate_db_struct(
fn generate_db_struct_methods(
name: &Ident,
name_db: &Ident,
validator: Option<&Path>,
vis: &syn::Visibility,
db_error: &DbError,
fields: &[FieldInfo],
Expand All @@ -101,7 +104,8 @@ fn generate_db_struct_methods(
let method_from_rows =
generate_db_struct_method_from_rows(name, name_db, vis, db_error, fields);
let field_methods = generate_db_struct_field_methods(name, name_db, vis, fields);
let method_append = generate_db_struct_method_append(name, name_db, vis, db_error, fields);
let method_append =
generate_db_struct_method_append(name, name_db, validator, vis, db_error, fields);

quote! {
#[allow(clippy::unused_unit)]
Expand Down Expand Up @@ -315,6 +319,7 @@ fn generate_db_struct_method_from_rows(
fn generate_db_struct_method_append(
name: &Ident,
name_db: &Ident,
validator: Option<&Path>,
vis: &syn::Visibility,
db_error: &DbError,
fields: &[FieldInfo],
Expand All @@ -325,6 +330,18 @@ fn generate_db_struct_method_append(
name
);

let validator_check = match validator {
Some(validator) => {
let err = DbError::generate_invalid_row_error_kind_creation(name_db);
quote! {
if !#validator(&data) {
return Err(#err);
}
}
}
None => quote! {},
};

let db_field_insert_checks: Vec<_> = fields
.iter()
.filter_map(|info| match info {
Expand Down Expand Up @@ -538,6 +555,7 @@ fn generate_db_struct_method_append(
}

fn append_internal(&mut self, data: &#name, index: usize) -> #append_kind_return_type {
#validator_check
#(#db_field_insert_checks)*
#(#db_field_insert_commits)*
#append_return_output
Expand Down Expand Up @@ -956,6 +974,7 @@ struct DbError {
#[derive(Debug)]
enum DbErrorKind {
DuplicateKey,
InvalidRow,
}

impl ToTokens for DbErrorKind {
Expand All @@ -967,18 +986,27 @@ impl ToTokens for DbErrorKind {
DuplicateKey,
});
}
Self::InvalidRow => {
tokens.extend(quote! {
/// The error kind for when the row to be inserted is invalid.
InvalidRow,
});
}
}
}
}

impl DbError {
fn new(fields: &[FieldInfo]) -> Self {
let error_duplicate_key = fields.iter().any(|info| matches!(info, FieldInfo::Key(_)));
let error_kinds = if error_duplicate_key {
vec![DbErrorKind::DuplicateKey]
} else {
Vec::new()
};
fn new(validator: Option<&Path>, fields: &[FieldInfo]) -> Self {
let mut error_kinds = Vec::new();

if validator.is_some() {
error_kinds.push(DbErrorKind::InvalidRow);
}

if fields.iter().any(|info| matches!(info, FieldInfo::Key(_))) {
error_kinds.push(DbErrorKind::DuplicateKey);
}

Self { error_kinds }
}
Expand All @@ -990,6 +1018,13 @@ impl DbError {
}
}

fn generate_invalid_row_error_kind_creation(name_db: &Ident) -> TokenStream {
let ident_error_kind = format_ident!("{}ErrorKind", name_db);
quote! {
#ident_error_kind::InvalidRow
}
}

fn generate_fn_error_kind_usage(
&self,
name_db: &Ident,
Expand Down
8 changes: 7 additions & 1 deletion venndb-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,13 @@ fn impl_from_args_struct(
None => format_ident!("{}DB", name),
};

let db_code = generate_db::generate_db(name, &name_db, vis, &fields[..]);
let db_code = generate_db::generate_db(
name,
&name_db,
type_attrs.validator.as_ref(),
vis,
&fields[..],
);

quote! {
#db_code
Expand Down
156 changes: 156 additions & 0 deletions venndb-usage/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use venndb::{Any, VennDB};

#[derive(Debug, VennDB)]
#[venndb(validator = employee_validator)]
pub struct Employee {
#[venndb(key)]
id: u32,
Expand All @@ -15,6 +16,10 @@ pub struct Employee {
department: Department,
}

fn employee_validator(employee: &Employee) -> bool {
employee.id > 0
}

#[derive(Debug)]
pub struct L1Engineer {
id: u32,
Expand Down Expand Up @@ -1064,6 +1069,7 @@ mod tests_v0_4 {
use super::*;

#[derive(Debug, VennDB)]
#[venndb(validator = worker_validator)]
pub struct Worker {
#[venndb(key)]
id: u32,
Expand All @@ -1073,6 +1079,10 @@ mod tests_v0_4 {
department: Option<Department>,
}

fn worker_validator(worker: &Worker) -> bool {
worker.id > 0 && (worker.is_active.unwrap_or_default() || !worker.is_admin)
}

#[test]
fn test_any_filter_map() {
let db = EmployeeDB::from_rows(vec![
Expand Down Expand Up @@ -1194,4 +1204,150 @@ mod tests_v0_4 {
let employee = query.execute().unwrap().any();
assert_eq!(employee.id, 2);
}

#[test]
fn test_worker_db_valid_rows_append() {
let mut db = WorkerDB::default();

db.append(Worker {
id: 1,
is_admin: false,
is_active: Some(true),
department: Some(Department::Engineering),
})
.unwrap();

db.append(Worker {
id: 2,
is_admin: false,
is_active: None,
department: None,
})
.unwrap();

db.append(Worker {
id: 3,
is_admin: false,
is_active: Some(true),
department: Some(Department::Any),
})
.unwrap();

db.append(Worker {
id: 4,
is_admin: true,
is_active: Some(true),
department: Some(Department::HR),
})
.unwrap();
}

#[test]
fn test_worker_db_valid_rows_from_iter() {
WorkerDB::from_iter([
Worker {
id: 1,
is_admin: false,
is_active: Some(true),
department: Some(Department::Engineering),
},
Worker {
id: 2,
is_admin: false,
is_active: None,
department: None,
},
Worker {
id: 3,
is_admin: false,
is_active: Some(true),
department: Some(Department::Any),
},
Worker {
id: 4,
is_admin: true,
is_active: Some(true),
department: Some(Department::HR),
},
])
.unwrap();
}

#[test]
fn test_worker_db_invalid_rows_append() {
let mut db = WorkerDB::default();

assert_eq!(
WorkerDBErrorKind::InvalidRow,
db.append(Worker {
id: 0,
is_admin: false,
is_active: None,
department: Some(Department::Engineering),
})
.unwrap_err()
.kind()
);

assert_eq!(
WorkerDBErrorKind::InvalidRow,
db.append(Worker {
id: 1,
is_admin: true,
is_active: Some(false),
department: Some(Department::Engineering),
})
.unwrap_err()
.kind()
);

assert_eq!(
WorkerDBErrorKind::InvalidRow,
db.append(Worker {
id: 2,
is_admin: true,
is_active: None,
department: Some(Department::Engineering),
})
.unwrap_err()
.kind()
);
}

#[test]
fn test_worker_db_invalid_rows_from_iter() {
assert_eq!(
WorkerDBErrorKind::InvalidRow,
WorkerDB::from_iter(
[Worker {
id: 0,
is_admin: false,
is_active: None,
department: Some(Department::Engineering),
},]
.into_iter(),
)
.unwrap_err()
.kind()
);
}

#[test]
fn test_employee_db_append_invalid_row() {
let mut db = EmployeeDB::default();

assert_eq!(
EmployeeDBErrorKind::InvalidRow,
db.append(Employee {
id: 0,
name: "Alice".to_string(),
is_manager: true,
is_admin: false,
is_active: true,
department: Department::Engineering,
})
.unwrap_err()
.kind()
);
}
}

0 comments on commit c4f2926

Please sign in to comment.