Merge branch 'main' into feat/provider_portal

# Conflicts:
#	src/app.rs
This commit is contained in:
meraghon committed 2025-12-22 07:35:06 -05:00
commit c6f0a2e5e5
32 files changed
+2544 -138

No files matched your search

+115
View File
@@ -0,0 +1,115 @@
use super::{
Comparison, MapListComparison, NestedListComparison, NumberComparison, NumberListComparison,
TextComparison, TextListComparison,
};
use crate::common::{ExpandableInfo, ValueType};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ComparisonData {
/// Each comparison will have a unique ID.
pub id: String,
/// The member from the Student structure to match against.
pub member: String,
/// The comparison to perform.
pub comparison: ComparisonType,
/// The value to match against.
pub target_value: ValueType,
/// The display category for this comparison. Affects where this comparison
/// will be displayed in the provider side of the application.
pub category: String,
/// The text that will be displayed on the provider side.
pub display_text: String,
}
impl ComparisonData {
pub fn new(
id: impl Into<String>,
member: impl Into<String>,
comparison: ComparisonType,
target_value: ValueType,
category: impl Into<String>,
display_text: impl Into<String>,
) -> Self {
Self {
id: id.into(),
member: member.into(),
comparison,
target_value,
category: category.into(),
display_text: display_text.into(),
}
}
pub fn compare(&self, student_data: &ExpandableInfo) -> Result<bool, String> {
let value = student_data.data.get(&self.member).expect(
format!(
"Couldn't find member {:?}, current student data: {:?}",
self.member, student_data
)
.as_str(),
);
self.comparison.evaluate(value, &self.target_value)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ComparisonType {
Number(NumberComparison),
Text(TextComparison),
TextList(TextListComparison),
NumberList(NumberListComparison),
MapList(MapListComparison),
NestedList(NestedListComparison),
}
impl Comparison for ComparisonType {
fn evaluate(&self, comp_value: &ValueType, target_value: &ValueType) -> Result<bool, String> {
match self {
ComparisonType::Number(n) => n.evaluate(comp_value, target_value),
ComparisonType::Text(t) => t.evaluate(comp_value, target_value),
ComparisonType::NumberList(l) => l.evaluate(comp_value, target_value),
ComparisonType::MapList(m) => m.evaluate(comp_value, target_value),
ComparisonType::TextList(tl) => tl.evaluate(comp_value, target_value),
ComparisonType::NestedList(nl) => nl.evaluate(comp_value, target_value),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::comparison::test_utils::create_student_data;
#[test]
fn test_student_data() {
let student_data = create_student_data();
// Should succeed every time.
let text_comp_success = ComparisonData {
id: "first_name_success".to_string(),
member: "first_name".to_string(),
comparison: ComparisonType::Text(TextComparison::Matches),
target_value: ValueType::String(Some("John".to_string())),
category: "Basic Checks".to_string(),
display_text: "First Name is John".to_string(),
};
// Uses an incorrect target_value type.
let text_comp_fail = ComparisonData {
id: "first_name_fail".to_string(),
member: "first_name".to_string(),
comparison: ComparisonType::Text(TextComparison::Matches),
target_value: ValueType::Number(Some("Jane".to_string())),
category: "Basic Checks".to_string(),
display_text: "First Name is Jane".to_string(),
};
let mut result = text_comp_success.compare(&student_data);
assert!(result.is_ok());
assert_eq!(result.unwrap(), true);
result = text_comp_fail.compare(&student_data);
assert!(result.is_err());
}
}
+477
View File
@@ -0,0 +1,477 @@
use super::{Comparison, NumberComparison};
use crate::common::ValueType;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum NumberListComparison {
/// Sums the values in the list and checks them using the given [`NumberComparison`].
/// Requires the list to contain only numbers.
Sum(Box<NumberComparison>),
/// Checks if the list contains a specific value.
Contains,
/// Checks if the list does not contain a specific value.
NotContains,
}
impl Comparison for NumberListComparison {
fn evaluate(&self, comp_value: &ValueType, target_value: &ValueType) -> Result<bool, String> {
match self {
NumberListComparison::Sum(num_comp) => {
// Requires the list to contain only numbers. Get this list or throw an error.
let student_list = match comp_value {
ValueType::List(Some(list)) => list,
_ => return Err("Expected a list".into()),
};
let sum = student_list.iter().try_fold(0.0, |acc, item| match item {
ValueType::Number(Some(num_str)) => {
let num = num_str.parse::<f64>().unwrap();
Ok(acc + num)
}
_ => Err("Expected a number".into()),
});
// If the sum is not a number, return an error.
let sum = match sum {
Ok(sum) => sum,
Err(err) => return Err(err),
};
// Finally perform the comparison using this sum... we don't have a function for this yet.
match num_comp.evaluate(&ValueType::Number(Some(sum.to_string())), target_value) {
Ok(result) => Ok(result),
Err(err) => Err(err),
}
}
NumberListComparison::Contains => {
// Get list and check if it contains the target value.
// This will check the type and the value, in that order. If the type does not match,
// this will return false.
let list = match comp_value {
ValueType::List(Some(vec)) => vec,
_ => return Err("Expected a list".into()),
};
// Check if the list contains the target value.
Ok(list.contains(target_value))
}
_ => Err("Not implemented".into()),
}
}
}
/// Represents all available comparisons for a text list. All comparisons are comparable to a
/// single value.
///
/// For example, using `TextListComparison::Contains` checks if a specific value is contained
/// in the list.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum TextListComparison {
/// Checks if the list contains the target value.
Contains,
/// Checks if the list does not contain the target value.
NotContains,
/// Checks if the list is empty.
IsEmpty,
/// Checks if the list is not empty.
IsNotEmpty,
}
impl Comparison for TextListComparison {
fn evaluate(&self, comp_value: &ValueType, target_value: &ValueType) -> Result<bool, String> {
match self {
TextListComparison::Contains => {
let list_opt = match comp_value {
ValueType::List(list) => list,
_ => return Err("Expected a list".into()),
};
// If the list exists, check if it contains the target value.
// Otherwise, return false. Note that this is not a strongly typed comparison - if
// the list contains multiple different types, no error is thrown.
let check = match list_opt {
Some(list) => list.contains(target_value),
None => false,
};
Ok(check)
}
TextListComparison::NotContains => {
let list_opt = match comp_value {
ValueType::List(list) => list,
_ => return Err("Expected a list".into()),
};
// If the list exists, check if it does not contain the target value.
// Otherwise, return true.
let check = match list_opt {
Some(list) => !list.contains(target_value),
None => true,
};
Ok(check)
}
TextListComparison::IsEmpty => {
let list_opt = match comp_value {
ValueType::List(list) => list,
_ => return Err("Expected a list".into()),
};
// If the list exists, check if it is empty.
// Otherwise, return false.
let check = match list_opt {
Some(list) => list.is_empty(),
None => true,
};
Ok(check)
}
TextListComparison::IsNotEmpty => {
let list_opt = match comp_value {
ValueType::List(list) => list,
_ => return Err("Expected a list".into()),
};
// If the list exists, check if it is not empty.
// Otherwise, return true.
let check = match list_opt {
Some(list) => !list.is_empty(),
None => false,
};
Ok(check)
}
}
}
}
/// For right now, nested lists should be flattened into a list of a single type before they
/// are compared. The plan is to create a more robust framework that eventually be able to
/// query specific lists and perform comparisons on them.
///
/// For example:
/// ```
/// NestedListComparison::FlattenToTextList(Box::new(TextListComparison::Contains));
/// ```
///
/// This will flatten a nested list into a single list of `ValueType::String` enums and then
/// will check if the list contains the indicated target value.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum NestedListComparison {
/// Flattens a nested list into a single text list, then performs the given comparison.
FlattenToTextList(Box<TextListComparison>),
}
impl Comparison for NestedListComparison {
fn evaluate(&self, comp_value: &ValueType, target_value: &ValueType) -> Result<bool, String> {
match self {
NestedListComparison::FlattenToTextList(t) => {
// Check the comp_value for a series of lists. We'll then iterate over each list and flatten it to a text list.
// The end type should be a ValueType::List(Option<Vec<T>>), where T is ValueType::String.
let list = match comp_value {
ValueType::List(Some(list)) => list,
_ => return Err("Expected a list of values".to_owned()),
};
let mut out: Vec<ValueType> = Vec::new();
for item in list {
// Every item should be another list.
let sub_list = match item {
ValueType::List(Some(sub_list)) => sub_list,
_ => return Err("Expected a list of lists".to_owned()),
};
for sub_item in sub_list {
match sub_item {
s @ ValueType::String(_) => out.push(s.clone()),
_ => return Err("Expected a list of strings".to_owned()),
}
}
}
t.evaluate(&ValueType::List(Some(out)), target_value)
}
}
}
}
/// Lists of maps must be flattened to a list of some type before they can be compared.
/// Flattening maps simply extracts the values from a specific key from all maps in the list.
/// This makes comparison much simpler to implement and understand.
///
/// All flattening operations also contain a `String` key (the map's member to flatten)
/// and some `Comparison` enum that matches the type of the flattened values. Flattening will
/// fail if any single map in the list does not contain the specified key or if the value
/// is not of the expected type.
///
/// For example, to flatten a list of maps by using the `name` key, we would use the following
/// structure:
/// ```
/// MapListComparison::FlattenToTextList("name".to_owned(), Box::new(TextListComparison::Equal));
/// ```
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum MapListComparison {
/// Flattens the map list to a text list and then performs the given comparison.
FlattenToTextList(String, Box<TextListComparison>),
/// Flattens the map list to a number list and then performs the given comparison.
FlattenToNumberList(String, Box<NumberListComparison>),
/// Flattens the map list to a nested list and then performs the given comparison.
FlattenToNestedList(String, Box<NestedListComparison>),
}
impl Comparison for MapListComparison {
fn evaluate(&self, comp_value: &ValueType, target_value: &ValueType) -> Result<bool, String> {
// Define base list - this must be present otherwise we cannot proceed.
let list = match comp_value {
ValueType::List(Some(list)) => list,
ValueType::List(None) => &Vec::new(),
_ => return Err("Expected a list".to_owned()),
};
match self {
MapListComparison::FlattenToTextList(key, comp) => {
let mut out = Vec::new();
for item in list {
let map = match item {
ValueType::Map(Some(map)) => map,
_ => return Err("Expected a map".to_string()),
};
// Ignore any non-string values.
if let Some(text @ ValueType::String(_)) = map.get(key) {
out.push(text.clone());
}
}
let flattened = ValueType::List(Some(out));
comp.evaluate(&flattened, target_value)
}
MapListComparison::FlattenToNumberList(key, comp) => {
let mut out = Vec::new();
for item in list {
let map = match item {
ValueType::Map(Some(map)) => map,
_ => return Err("Expected a map".to_string()),
};
// Ignore any non-number values.
if let Some(number @ ValueType::Number(_)) = map.get(key) {
out.push(number.clone());
}
}
let flattened = ValueType::List(Some(out));
comp.evaluate(&flattened, target_value)
}
MapListComparison::FlattenToNestedList(key, comp) => {
let mut out = Vec::new();
for item in list {
let map = match item {
ValueType::Map(Some(map)) => map,
_ => return Err("Expected a map".to_string()),
};
// Ignore any non-list values.
if let Some(list @ ValueType::List(_)) = map.get(key) {
out.push(list.clone());
}
}
let flattened = ValueType::List(Some(out));
comp.evaluate(&flattened, target_value)
}
}
}
}
#[cfg(test)]
mod tests {
use super::super::test_utils::create_map_list_helper;
use super::*;
use crate::common::ComparisonType;
use crate::common::comparison::test_utils::{
create_empty_lists, create_number_list, create_text_list,
};
#[test]
fn map_list_flatten_to_text_list() {
let map_list = create_map_list_helper();
// Define our comparison, paying close attention to the target values.
let first_name_comp = ComparisonType::MapList(MapListComparison::FlattenToTextList(
"first_name".to_string(),
Box::new(TextListComparison::Contains),
));
let last_name_comp = ComparisonType::MapList(MapListComparison::FlattenToTextList(
"last_name".to_string(),
Box::new(TextListComparison::Contains),
));
let gender_comp = ComparisonType::MapList(MapListComparison::FlattenToTextList(
"gender".to_string(),
Box::new(TextListComparison::Contains),
));
let first_name_result =
first_name_comp.evaluate(&map_list, &ValueType::String(Some("Jane".to_string())));
let last_name_result =
last_name_comp.evaluate(&map_list, &ValueType::String(Some("Fails".to_string())));
let gender_result =
gender_comp.evaluate(&map_list, &ValueType::String(Some("Female".to_string())));
// We have one map that contains the target value (map_two.first_name)
assert_eq!(first_name_result.unwrap(), true);
assert_eq!(last_name_result.unwrap(), false);
assert_eq!(gender_result.unwrap(), true);
}
#[test]
fn map_list_flatten_to_number_list() {
let map_list = create_map_list_helper();
let sum_comparison = ComparisonType::MapList(MapListComparison::FlattenToNumberList(
"sat_score".to_string(),
Box::new(NumberListComparison::Sum(Box::new(NumberComparison::Equal))),
));
let result = sum_comparison.evaluate(&map_list, &ValueType::Number(Some(2200.to_string())));
let failed = sum_comparison.evaluate(&map_list, &ValueType::Number(Some(2201.to_string())));
assert_eq!(result.unwrap(), true);
assert_eq!(failed.unwrap(), false);
}
#[test]
fn map_list_flatten_to_nested_list() {
let map_list = create_map_list_helper();
let comparison = ComparisonType::MapList(MapListComparison::FlattenToNestedList(
"grades_participated".to_string(),
Box::new(NestedListComparison::FlattenToTextList(Box::new(
TextListComparison::Contains,
))),
));
let result = comparison.evaluate(&map_list, &ValueType::String(Some("9".to_string())));
let failed = comparison.evaluate(&map_list, &ValueType::String(Some("13".to_string())));
assert_eq!(result.unwrap(), true);
assert_eq!(failed.unwrap(), false);
}
#[test]
fn number_list_sum_comparison() {
let list = create_number_list();
let comparison = ComparisonType::NumberList(NumberListComparison::Sum(Box::new(
NumberComparison::Equal,
)));
let result = comparison.evaluate(&list, &ValueType::Number(Some(15.to_string())));
assert_eq!(result.unwrap(), true);
}
#[test]
fn number_list_contains_all_types() {
let num_list = create_number_list();
let string_list = ValueType::List(Some(vec![
ValueType::String(Some("a".to_string())),
ValueType::String(Some("b".to_string())),
ValueType::String(Some("c".to_string())),
ValueType::String(Some("d".to_string())),
ValueType::String(Some("e".to_string())),
]));
let mixed_list = ValueType::List(Some(vec![
ValueType::Number(Some(1.to_string())),
ValueType::String(Some("b".to_string())),
ValueType::Number(Some(3.to_string())),
ValueType::String(Some("d".to_string())),
ValueType::Number(Some(5.to_string())),
]));
let comparison = ComparisonType::NumberList(NumberListComparison::Contains);
let num_result = comparison.evaluate(&num_list, &ValueType::Number(Some(3.to_string())));
let string_result =
comparison.evaluate(&string_list, &ValueType::String(Some("a".to_string())));
let mixed_result =
comparison.evaluate(&mixed_list, &ValueType::String(Some("b".to_string())));
let incorrect_type =
comparison.evaluate(&string_list, &ValueType::Number(Some(1.to_string())));
let incorrect_value =
comparison.evaluate(&string_list, &ValueType::String(Some("f".to_string())));
let mixed_incorrect_type =
comparison.evaluate(&string_list, &ValueType::String(Some("1".to_string())));
assert_eq!(num_result.unwrap(), true);
assert_eq!(string_result.unwrap(), true);
assert_eq!(mixed_result.unwrap(), true);
assert_eq!(incorrect_type.unwrap(), false);
assert_eq!(incorrect_value.unwrap(), false);
assert_eq!(mixed_incorrect_type.unwrap(), false);
}
#[test]
fn text_list_contains() {
let (none_list, empty_list) = create_empty_lists();
let num_list = create_number_list();
let text_list = create_text_list();
// Define comparisons here
let comparison = ComparisonType::TextList(TextListComparison::Contains);
let target_value = ValueType::String(Some("one".to_string()));
let none_result = comparison.evaluate(&none_list, &target_value);
let empty_result = comparison.evaluate(&empty_list, &target_value);
let num_result = comparison.evaluate(&num_list, &target_value);
let text_list = comparison.evaluate(&text_list, &target_value);
assert_eq!(none_result.unwrap(), false);
assert_eq!(empty_result.unwrap(), false);
assert_eq!(num_result.unwrap(), false);
assert_eq!(text_list.unwrap(), true);
}
#[test]
fn text_list_empty() {
let none_list = ValueType::List(None);
let empty_list = ValueType::List(Some(vec![]));
let long_list = ValueType::List(Some(vec![ValueType::String(None)]));
let empty_comp = ComparisonType::TextList(TextListComparison::IsEmpty);
let not_empty_comp = ComparisonType::TextList(TextListComparison::IsNotEmpty);
// This comparison does not use a target value, as it should just check if the list
// either doesn't exist or is empty.
let mut result = empty_comp.evaluate(&empty_list, &ValueType::List(None));
assert!(result.is_ok());
assert_eq!(result.unwrap(), true);
result = empty_comp.evaluate(&none_list, &ValueType::List(None));
assert!(result.is_ok());
assert_eq!(result.unwrap(), true);
result = empty_comp.evaluate(&long_list, &ValueType::List(None));
assert!(result.is_ok());
assert_eq!(result.unwrap(), false);
result = not_empty_comp.evaluate(&empty_list, &ValueType::List(None));
assert!(result.is_ok());
assert_eq!(result.unwrap(), false);
result = not_empty_comp.evaluate(&none_list, &ValueType::List(None));
assert!(result.is_ok());
assert_eq!(result.unwrap(), false);
result = not_empty_comp.evaluate(&long_list, &ValueType::List(None));
assert!(result.is_ok());
assert_eq!(result.unwrap(), true);
}
}
+144
View File
@@ -0,0 +1,144 @@
mod base;
mod lists;
mod number;
mod text;
mod traits;
mod utils;
pub use self::{base::*, lists::*, number::*, text::*, traits::*};
#[cfg(test)]
mod test_utils {
use crate::common::{ExpandableInfo, ValueType};
use std::collections::HashMap;
pub fn create_student_data() -> ExpandableInfo {
let map = create_map_helper(
vec![
"first_name".to_string(),
"last_name".to_string(),
"gender".to_string(),
"sat_score".to_string(),
"athletic_participation".to_string(),
"community_involvement".to_string(),
],
vec![
ValueType::String(Some("John".to_string())),
ValueType::String(Some("Doe".to_string())),
ValueType::String(Some("Male".to_string())),
ValueType::Number(Some(1190.to_string())),
ValueType::Map(Some(create_map_helper(
vec![
"sport_name".to_string(),
"grades_participated".to_string(),
"special_achievement".to_string(),
],
vec![
ValueType::String(Some("Football".to_string())),
ValueType::List(Some(vec![
ValueType::String(Some(9.to_string())),
ValueType::String(Some(10.to_string())),
])),
ValueType::String(Some("N/A".to_string())),
],
))),
ValueType::Map(Some(create_map_helper(
vec!["activity_name".to_string(), "total_hours".to_string()],
vec![
ValueType::String(Some("Raking".to_string())),
ValueType::Number(Some(30.to_string())),
],
))),
],
);
let mut info = ExpandableInfo::new("test");
info.data = map;
info
}
pub fn create_map_helper(
keys: Vec<String>,
values: Vec<ValueType>,
) -> HashMap<String, ValueType> {
keys.iter()
.cloned()
.zip(values.iter().cloned())
.collect::<HashMap<String, ValueType>>()
}
pub fn create_map_list_helper() -> ValueType {
let map_one = create_map_helper(
vec![
"first_name".to_string(),
"last_name".to_string(),
"gender".to_string(),
"sat_score".to_string(),
"grades_participated".to_string(),
],
vec![
ValueType::String(Some("John".to_string())),
ValueType::String(Some("Doe".to_string())),
ValueType::String(Some("Male".to_string())),
ValueType::Number(Some(1200.to_string())),
ValueType::List(Some(vec![
ValueType::String(Some("9".to_string())),
ValueType::String(Some("10".to_string())),
])),
],
);
let map_two = create_map_helper(
vec![
"first_name".to_string(),
"last_name".to_string(),
"gender".to_string(),
"sat_score".to_string(),
"grades_participated".to_string(),
],
vec![
ValueType::String(Some("Jane".to_string())),
ValueType::String(Some("Doe".to_string())),
ValueType::String(Some("Female".to_string())),
ValueType::Number(Some(1000.to_string())),
ValueType::List(Some(vec![
ValueType::String(Some("11".to_string())),
ValueType::String(Some("12".to_string())),
])),
],
);
ValueType::List(Some(vec![
ValueType::Map(Some(map_one)),
ValueType::Map(Some(map_two)),
]))
}
pub fn create_empty_lists() -> (ValueType, ValueType) {
let none_list = ValueType::List(None);
let empty_list = ValueType::List(Some(vec![]));
(none_list, empty_list)
}
pub fn create_number_list() -> ValueType {
ValueType::List(Some(vec![
ValueType::Number(Some(1.to_string())),
ValueType::Number(Some(2.to_string())),
ValueType::Number(Some(3.to_string())),
ValueType::Number(Some(4.to_string())),
ValueType::Number(Some(5.to_string())),
]))
}
pub fn create_text_list() -> ValueType {
ValueType::List(Some(vec![
ValueType::String(Some("one".to_string())),
ValueType::String(Some("two".to_string())),
ValueType::String(Some("three".to_string())),
ValueType::String(Some("four".to_string())),
ValueType::String(Some("five".to_string())),
]))
}
}
+44
View File
@@ -0,0 +1,44 @@
use super::Comparison;
use crate::common::ValueType;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum NumberComparison {
GreaterThan,
LessThan,
Equal,
NotEqual,
GreaterThanOrEqual,
LessThanOrEqual,
}
impl Comparison for NumberComparison {
fn evaluate(&self, comp_value: &ValueType, target_value: &ValueType) -> Result<bool, String> {
// The prerequisites for all number comparisons is that the target value is Some(number) and the
// comparison member points to Some(number) in the student_info.
let student_value = match comp_value {
ValueType::Number(Some(num)) => num.parse::<i32>().unwrap(),
_ => {
return Err("Student value is not a number".into());
}
};
let target = match target_value {
ValueType::Number(Some(num)) => num.parse::<i32>().unwrap(),
_ => {
return Err("Target value is not a number".into());
}
};
let matched = match self {
NumberComparison::GreaterThan => student_value > target,
NumberComparison::LessThan => student_value < target,
NumberComparison::Equal => student_value == target,
NumberComparison::NotEqual => student_value != target,
NumberComparison::GreaterThanOrEqual => student_value >= target,
NumberComparison::LessThanOrEqual => student_value <= target,
};
Ok(matched)
}
}
+36
View File
@@ -0,0 +1,36 @@
use super::Comparison;
use crate::common::ValueType;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum TextComparison {
Matches,
NotMatches,
Contains,
NotContains,
}
impl Comparison for TextComparison {
fn evaluate(&self, comp_value: &ValueType, target_value: &ValueType) -> Result<bool, String> {
// All text comparisons require that the comp.member points to a Some(string) in the student_info,
// and that the target_value is also a Some(string).
let student_value = match comp_value {
ValueType::String(Some(value)) => value,
_ => return Err("Student value is not a string".into()),
};
let target_value = match target_value {
ValueType::String(Some(value)) => value,
_ => return Err("Target value is not a string".into()),
};
let matched = match self {
TextComparison::Matches => student_value == target_value,
TextComparison::NotMatches => student_value != target_value,
TextComparison::Contains => student_value.contains(target_value),
TextComparison::NotContains => !student_value.contains(target_value),
};
Ok(matched)
}
}
+5
View File
@@ -0,0 +1,5 @@
use crate::common::ValueType;
pub trait Comparison {
fn evaluate(&self, comp_value: &ValueType, target_value: &ValueType) -> Result<bool, String>;
}
+28
View File
@@ -0,0 +1,28 @@
use crate::common::ValueType;
/// This function is a work in progress. It's designed to flatten a list of maps by using the
/// given key and verifier function, however it currently doesn't work and just needs to be
/// planned out a little more.
fn flatten_map_helper<F>(
list: &Vec<ValueType>,
key: &String,
verifier: F,
) -> Result<Vec<ValueType>, String>
where
F: Fn(&ValueType) -> Option<&ValueType>,
{
let mut out = Vec::new();
for item in list {
let map = match item {
ValueType::Map(Some(map)) => map,
_ => return Err("Expected a map".to_owned()),
};
// Get the key, and verify that the value is the correct type. If so, push it.
if let Some(value) = map.get(key).and_then(|v| verifier(v)) {
out.push(value.clone());
}
}
Ok(out)
}