This commit is contained in:
Sandipsinh Rathod 2024-12-21 23:15:51 -05:00
parent fe3ec74e35
commit 5eeb1b6002
14 changed files with 624 additions and 106 deletions

33
Cargo.lock generated

@ -23,6 +23,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "beef"
version = "0.5.2"
@ -176,6 +182,7 @@ name = "foo"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"convert_case",
"lazy_static",
"oapi",
@ -188,6 +195,7 @@ dependencies = [
"serde_json",
"serde_yaml 0.9.34+deprecated",
"sppparse",
"strum_macros",
"tailcall-valid",
"wit-parser",
]
@ -236,6 +244,12 @@ version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "http"
version = "1.2.0"
@ -875,6 +889,12 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustversion"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]]
name = "ryu"
version = "1.0.18"
@ -1075,6 +1095,19 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum_macros"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.90",
]
[[package]]
name = "syn"
version = "1.0.109"

@ -19,4 +19,5 @@ serde = { version = "1.0.216", features = ["derive"] }
tailcall-valid = "0.1.3"
wit-parser = "0.222.0"
lazy_static = "1.5.0"
base64 = "0.22.1"
strum_macros = "0.26.4"

@ -88,25 +88,25 @@ components:
type: object
properties:
data:
type: string
# items:
# $ref: '#/components/schemas/Todo'
type: array
items:
$ref: '#/components/schemas/Todo'
metadata:
type: string
# properties:
# total:
# type: integer
# minimum: 0
# limit:
# type: integer
# minimum: 1
# offset:
# type: integer
# minimum: 0
# required:
# - total
# - limit
# - offset
type: object
properties:
total:
type: integer
minimum: 0
limit:
type: integer
minimum: 1
offset:
type: integer
minimum: 0
required:
- total
- limit
- offset
required:
- data
- metadata
@ -115,8 +115,7 @@ components:
type: object
properties:
data:
type: string
# $ref: '#/components/schemas/Todo'
$ref: '#/components/schemas/Todo'
required:
- data
@ -124,34 +123,34 @@ components:
type: object
properties:
error:
type: string
# properties:
# code:
# type: string
# enum:
# - VALIDATION_ERROR
# - UNAUTHORIZED
# - FORBIDDEN
# - NOT_FOUND
# - RATE_LIMIT_EXCEEDED
# - INTERNAL_ERROR
# message:
# type: string
# details:
# type: array
# items:
# type: object
# properties:
# field:
# type: string
# message:
# type: string
# required:
# - field
# - message
# required:
# - code
# - message
type: object
properties:
code:
type: string
enum:
- VALIDATION_ERROR
- UNAUTHORIZED
- FORBIDDEN
- NOT_FOUND
- RATE_LIMIT_EXCEEDED
- INTERNAL_ERROR
message:
type: string
details:
type: array
items:
type: object
properties:
field:
type: string
message:
type: string
required:
- field
- message
required:
- code
- message
parameters:
TodoId:

@ -27,6 +27,7 @@ pub struct Interface {
pub struct Record {
pub name: String,
pub fields: Vec<Field>,
pub added_fields: Vec<Field>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct UseStatement {

143
src/config/fixargs.rs Normal file

@ -0,0 +1,143 @@
use base64::Engine;
use tailcall_valid::{Valid, Validator};
use crate::config::config::{Config, Field, Interface, Record};
use crate::config::wit_types::WitType;
use crate::tryfold::TryFold;
pub fn fix_args(config: Config) -> Valid<Config, anyhow::Error, anyhow::Error> {
Valid::succeed(config)
.and_then(|mut config| {
Valid::from_iter(config.interfaces.iter(), |interface| {
Valid::from_iter(interface.records.iter(), |record| {
Valid::from_iter(record.fields.iter(), |field| {
if !field.field_type.is_kind_of_primitive() {
fix_field()
.try_fold(&(&config, interface, record, &field, &field.field_type), interface.clone())
.and_then(|new_interface| {
Valid::succeed(new_interface)
})
} else {
Valid::succeed(interface.clone())
}
})
})
}).and_then(|x| {
let x = x.into_iter().flatten().into_iter().flatten().collect::<Vec<_>>();
config.interfaces = x;
Valid::succeed(config)
})
})
}
fn fix_field<'a>() -> TryFold<'a, (&'a Config, &'a Interface, &'a Record, &'a Field, &'a WitType), Interface, anyhow::Error> {
TryFold::<(&Config, &Interface, &Record, &Field, &WitType), Interface, anyhow::Error>::new(move |(config, interface, rec, field, wit), mut o| {
match &wit {
WitType::Option(x) => {
return fix_field().try_fold(&(*config, *interface, *rec, *field, x), o);
}
WitType::Result(a, b) => {
// TODO: this impl needs check.
// eg: -> result<record A {}, record B {}>
// this does not make sense to resolve with rec.name-field.name-A/B
return if !a.is_primitive() {
fix_field().try_fold(&(*config, *interface, *rec, *field, a), o)
} else if !b.is_primitive() {
fix_field().try_fold(&(*config, *interface, *rec, *field, b), o)
} else {
// TODO: Fix the possible conflicts here
fix_field().try_fold(&(*config, *interface, *rec, *field, a), o)
.and_then(|new_interface| fix_field().try_fold(&(*config, *interface, *rec, *field, b), new_interface))
};
}
WitType::List(x) => fix_field().try_fold(&(*config, *interface, *rec, *field, x.as_ref()), o),
WitType::Tuple(x) => {
// TODO: Fix the possible conflicts here
return Valid::from_iter(x.iter(), |x| fix_field().try_fold(&(*config, *interface, *rec, *field, x), o.clone()))
.and_then(|v| Valid::succeed(v.into_iter().fold((*interface).clone(), |mut a, b| {
a.records.extend(b.records);
a
})));
}
WitType::Record(records) => {
for (arg_name, ty) in records {
let arg_name = if arg_name.is_empty() {
// TODO: maybe we can introduce some transformers
// which uses AI to generate better names
base64::prelude::BASE64_STANDARD.encode(arg_name.as_bytes())
} else {
arg_name.clone()
};
let name = format!("{}-{}-{}", rec.name, field.name, arg_name);
let new_field = Field {
name,
field_type: ty.clone(),
};
let mut rec = (*rec).clone();
rec.added_fields.push(new_field);
let mut filtered_records = o.records.into_iter().filter(|r| r.name == rec.name).collect::<Vec<_>>();
filtered_records.push(rec);
o.records = filtered_records;
}
Valid::succeed(o)
/* Valid::from_iter(records.iter(), |(_, b)| {
fix_field()
.try_fold(&(*config, *interface, *rec, *field, b), o.clone())
}).and_then(|v| {
o.records = v;
Valid::succeed(o)
}).and_then(|mut o| {
})*/
}
// Ideally this case should never happen,
// but we should always throw an error to avoid infinite recursion
_ => return Valid::fail(anyhow::anyhow!("Unknown type: {:?}", field.field_type)),
}
// Valid::succeed(o)
})
}
/*fn fix_field1(ty: &WitType, mut record: Record, interface: Interface) -> Valid<Record, anyhow::Error, anyhow::Error> {
match ty {
WitType::Option(x) => {
fix_field1(x, record)
}
WitType::Result(x, y) => {
if !x.is_primitive() && !y.is_primitive() {
fix_field1(x, record).and_then(|x| fix_field1(y, x))
} else if !x.is_primitive() {
fix_field1(x, record)
} else {
fix_field1(y, record)
}
}
WitType::List(x) => {
fix_field1(x, record)
}
WitType::Tuple(x) => {
Valid::from_iter(x.iter(), |x| fix_field1(x, record))
.and_then(|v| Valid::succeed(v.into_iter().fold(Record::default(), |mut a, b| {
a.added_fields.extend(b.added_fields);
a
})))
}
WitType::Record(val) => {
let mut new_field = Field::default();
for (k, v) in val {
new_field.field_type = v.clone();
}
record.added_fields.push(new_field);
Valid::succeed(record)
}
// Ideally this case should never happen
// but we should always throw an error to avoid infinite recursion
_ => Valid::fail(anyhow::anyhow!("Unknown type: {:?}", ty)),
}
}
*/

@ -0,0 +1,48 @@
use anyhow::{anyhow, Error};
use tailcall_valid::{Valid, Validator};
use crate::config::config::{Config, Field, Interface, Record};
use crate::config::wit_types::WitType;
use crate::ser::OpenApiSpec;
pub fn handle_types(config: Config, spec: &OpenApiSpec) -> Valid<Config, Error, Error> {
Valid::succeed(config).and_then(|mut config: Config| {
Valid::from_option(spec.components.as_ref(), anyhow!("Components are required"))
.and_then(|v| Valid::from_option(v.schemas.as_ref(), anyhow!("Schemas are required")))
.and_then(|schemas| {
Valid::from_iter(schemas.iter(), |(record_name, v)| {
Valid::from_option(v.type_.as_ref(), anyhow!("Type is required"))
.and_then(|type_| {
Valid::from_option(v.properties.as_ref(), anyhow!("Properties are required"))
.and_then(|a| {
Valid::from_iter(a, |(field_name, v)| {
Valid::from(WitType::from_schema(v, spec))
.and_then(|wit_type| {
let field = Field {
name: field_name.clone(),
field_type: wit_type,
};
Valid::succeed(field)
})
})
})
.and_then(|fields| {
let record = Record {
name: record_name.clone(),
fields,
added_fields: vec![],
};
Valid::succeed(record)
})
})
}).and_then(|records| {
let interface = Interface {
name: "types".to_string(),
records,
..Default::default()
};
config.interfaces.push(interface);
Valid::succeed(())
})
}).and_then(|_| Valid::succeed(config))
})
}

@ -1,3 +1,5 @@
pub mod config;
pub mod wit_types;
pub mod to_wit;
pub mod fixargs;
pub mod handle_files;

@ -239,7 +239,7 @@ impl ToWit for WitType {
"{{ {} }}",
fields
.iter()
.map(|(name, ty)| format!("{}: {}", generate_wit_name(name), ty))
.map(|(name, ty)| format!("{}: {}", generate_wit_name(name), ty.to_wit()))
.collect::<Vec<_>>()
.join(", ")
),

@ -3,7 +3,7 @@ use crate::config::to_wit::ToWit;
use crate::ser::{OpenApiSpec, Schema};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[derive(Debug, Clone, Default, PartialEq, Eq, strum_macros::Display)]
pub enum WitType {
// Primitive Types
Bool,
@ -28,7 +28,7 @@ pub enum WitType {
Tuple(Vec<WitType>), // (T1, T2, ...)
// Custom Types
Record(Vec<(String, String)>), // Record { field_name: Type }
Record(Vec<(String, WitType)>), // Record { field_name: Type }
Variant(Vec<(String, Option<WitType>)>), // Variant { name: Option<Type> }
Enum(Vec<String>), // Enum { name1, name2, ... }
Flags(Vec<String>), // Flags { flag1, flag2, ... }
@ -39,6 +39,26 @@ pub enum WitType {
}
impl WitType {
pub fn is_primitive(&self) -> bool {
match self {
WitType::Bool | WitType::U8 | WitType::U16 | WitType::U32 | WitType::U64 | WitType::S8 | WitType::S16 | WitType::S32 | WitType::S64 | WitType::Float32 | WitType::Float64 | WitType::Char | WitType::String => true,
_ => false,
}
}
pub fn is_kind_of_primitive(&self) -> bool {
match self {
WitType::Option(x) => x.is_primitive(),
WitType::Result(a, b) => a.is_primitive() && b.is_primitive(),
WitType::List(x) => x.is_primitive(),
WitType::Tuple(x) => x.iter().all(|x| x.is_primitive()),
WitType::Variant(_) => true,
WitType::Enum(_) => true,
WitType::Flags(_) => true,
WitType::Handle(_) => true,
WitType::TypeAlias(_, _) => true,
v => v.is_primitive(),
}
}
pub fn from_schema(
schema: &Schema,
openapi: &OpenApiSpec,
@ -66,7 +86,7 @@ impl WitType {
Valid::from_option(schema.properties.as_ref(), anyhow::anyhow!("Properties are required"))
.and_then(|properties| {
Valid::from_iter(properties.iter(), |(name, schema)| {
Valid::from(WitType::from_schema(schema, openapi)).map(|ty| (name.clone(), ty.to_wit()))
Valid::from(WitType::from_schema(schema, openapi)).map(|ty| (name.clone(), ty))
})
}
)
@ -175,8 +195,8 @@ mod tests {
assert_eq!(
result,
WitType::Record(vec![
("id".to_string(), WitType::S32.to_wit()),
("name".to_string(), WitType::String.to_wit()),
("id".to_string(), WitType::S32),
("name".to_string(), WitType::String),
])
);
}

@ -2,6 +2,9 @@ mod value;
mod proto;
mod ser;
mod config;
mod transformer;
mod transform;
mod tryfold;
use serde_json::{Map, Value};
use anyhow::{Result, bail};

@ -1,65 +1,24 @@
use anyhow::{anyhow, Error};
use anyhow::Error;
use tailcall_valid::{Valid, Validator};
use crate::config::config::{Config, Field, Interface, Record};
use crate::config::wit_types::WitType;
use crate::config::config::Config;
use crate::config::fixargs::fix_args;
use crate::config::handle_files::handle_types;
use crate::ser::OpenApiSpec;
fn handle_types(config: Config, spec: &OpenApiSpec) -> Valid<Config, Error, Error> {
Valid::succeed(config).and_then(|mut config: Config| {
Valid::from_option(spec.components.as_ref(), anyhow!("Components are required"))
.and_then(|v| Valid::from_option(v.schemas.as_ref(), anyhow!("Schemas are required")))
.and_then(|schemas| {
Valid::from_iter(schemas.iter(), |(record_name, v)| {
Valid::from_option(v.type_.as_ref(), anyhow!("Type is required"))
.and_then(|type_| {
Valid::from_option(v.properties.as_ref(), anyhow!("Properties are required"))
.and_then(|a| {
Valid::from_iter(a, |(field_name, v)| {
Valid::from(WitType::from_schema(v, spec))
.and_then(|wit_type| {
let field = Field {
name: field_name.clone(),
field_type: wit_type,
};
Valid::succeed(field)
})
})
})
.and_then(|fields| {
let record = Record {
name: record_name.clone(),
fields,
};
Valid::succeed(record)
})
})
}).and_then(|records| {
let interface = Interface {
name: "types".to_string(),
records,
..Default::default()
};
config.interfaces.push(interface);
Valid::succeed(())
})
}).and_then(|_| Valid::succeed(config))
})
}
fn to_config(spec: OpenApiSpec) -> Valid<Config, Error, Error> {
Valid::succeed(Config::default())
.and_then(|config| handle_types(config, &spec))
.and_then(|config| fix_args(config))
}
#[cfg(test)]
mod t {
use tailcall_valid::Validator;
use wit_parser::Resolve;
use crate::config::to_wit::ToWit;
use crate::proto::to_config;
use crate::ser::OpenApiSpec;
use crate::config::to_wit::ToWit;
#[test]
fn tp() {
@ -72,9 +31,9 @@ mod t {
Ok(v) => {
v.package = "api:todos@1.0.0".to_string();
println!("{}", v.to_wit());
let mut resolve = Resolve::new();
resolve.push_str("foox.wit", &v.to_wit()).expect("TODO: panic message`");
println!("{:#?}", resolve);
// let mut resolve = Resolve::new();
// resolve.push_str("foox.wit", &v.to_wit()).expect("TODO: panic message`");
// println!("{:#?}", resolve);
}
Err(e) => {
println!("err: {:?}", e);

0
src/transform/mod.rs Normal file

7
src/transformer.rs Normal file

@ -0,0 +1,7 @@
use tailcall_valid::Valid;
pub trait Transform {
type Value;
type Error;
fn transform(&self, value: Self::Value) -> Valid<Self::Value, Self::Error, Self::Error>;
}

302
src/tryfold.rs Normal file

@ -0,0 +1,302 @@
use tailcall_valid::{Valid, Validator};
/// Trait for types that support a "try fold" operation.
///
/// `TryFolding` describes a composable folding operation that can potentially
/// fail. It can optionally consume an input to transform the provided value.
type TryFoldFn<'a, I, O, E> = Box<dyn Fn(&I, O) -> Valid<O, E, E> + 'a>;
pub struct TryFold<'a, I: 'a, O: 'a, E: 'a>(TryFoldFn<'a, I, O, E>);
impl<'a, I, O: Clone + 'a, E> TryFold<'a, I, O, E> {
/// Try to fold the value with the input.
///
/// # Parameters
/// - `input`: The input used in the folding operation.
/// - `value`: The value to be folded.
///
/// # Returns
/// Returns a `Valid` value, which can be either a success with the folded
/// value or an error.
pub fn try_fold(&self, input: &I, state: O) -> Valid<O, E, E> {
(self.0)(input, state)
}
/// Combine two `TryFolding` implementors into a sequential operation.
///
/// This method allows for chaining two `TryFolding` operations, where the
/// result of the first operation (if successful) will be used as the
/// input for the second operation.
///
/// # Parameters
/// - `other`: Another `TryFolding` implementor.
///
/// # Returns
/// Returns a combined `And` structure that represents the sequential
/// folding operation.
pub fn and(self, other: TryFold<'a, I, O, E>) -> Self {
TryFold(Box::new(move |input, state| {
self.try_fold(input, state.clone()).fold(
|state| other.try_fold(input, state),
|| other.try_fold(input, state),
)
}))
}
/// Create a new `TryFold` with a specified folding function.
///
/// # Parameters
/// - `f`: The folding function.
///
/// # Returns
/// Returns a new `TryFold` instance.
pub fn new(f: impl Fn(&I, O) -> Valid<O, E, E> + 'a) -> Self {
TryFold(Box::new(f))
}
/// Transforms a TryFold<I, O, E> to TryFold<I, O1, E> by applying
/// transformations. Check `transform_valid` if you want to return a
/// `Valid` instead of an `O1`.
///
/// # Parameters
/// - `up`: A function that uses O and O1 to create a new O1.
/// - `down`: A function that uses O1 to create a new O.
///
/// # Returns
/// Returns a new TryFold<I, O1, E> that applies the transformations.
pub fn transform<O1: Clone>(
self,
up: impl Fn(O, O1) -> O1 + 'a,
down: impl Fn(O1) -> O + 'a,
) -> TryFold<'a, I, O1, E> {
self.transform_valid(
move |o, o1| Valid::succeed(up(o, o1)),
move |o1| Valid::succeed(down(o1)),
)
}
/// Transforms a TryFold<I, O, E> to TryFold<I, O1, E> by applying
/// transformations. Check `transform` if you want to return an `O1`
/// instead of a `Valid`.
///
/// # Parameters
/// - `up`: A function that uses O and O1 to create a new Valid<O1, E>.
/// - `down`: A function that uses O1 to create a new Valid<O, E>.
///
/// # Returns
/// Returns a new TryFold<I, O1, E> that applies the transformations.
pub fn transform_valid<O1: Clone>(
self,
up: impl Fn(O, O1) -> Valid<O1, E, E> + 'a,
down: impl Fn(O1) -> Valid<O, E, E> + 'a,
) -> TryFold<'a, I, O1, E> {
TryFold(Box::new(move |i, o1| {
down(o1.clone())
.and_then(|o| self.try_fold(i, o))
.and_then(|o| up(o, o1))
}))
}
pub fn update(self, f: impl Fn(O) -> O + 'a) -> TryFold<'a, I, O, E> {
self.transform(move |o, _| f(o), |o| o)
}
/// Create a `TryFold` that doesn't do anything.
///
/// # Returns
/// Returns a `TryFold` that doesn't do anything.
pub fn empty() -> Self {
TryFold::new(|_, o| Valid::succeed(o))
}
}
impl<'a, I, O: Clone, E> FromIterator<TryFold<'a, I, O, E>> for TryFold<'a, I, O, E> {
fn from_iter<T: IntoIterator<Item = TryFold<'a, I, O, E>>>(iter: T) -> Self {
let mut iter = iter.into_iter();
let head = iter.next();
if let Some(head) = head {
head.and(TryFold::from_iter(iter))
} else {
TryFold::empty()
}
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use tailcall_valid::{Cause, Valid, Validator};
use super::TryFold;
impl<'a, I, O: Clone + 'a, E> TryFold<'a, I, O, E> {
/// Create a `TryFold` that always succeeds with the provided state.
///
/// # Parameters
/// - `state`: The state to succeed with.
///
/// # Returns
/// Returns a `TryFold` that always succeeds with the provided state.
pub fn succeed(f: impl Fn(&I, O) -> O + 'a) -> Self {
TryFold(Box::new(move |i, o| Valid::succeed(f(i, o))))
}
}
#[test]
fn test_and() {
let t1 = TryFold::<i32, i32, ()>::new(|a: &i32, b: i32| Valid::succeed(a + b));
let t2 = TryFold::<i32, i32, ()>::new(|a: &i32, b: i32| Valid::succeed(a * b));
let t = t1.and(t2);
let actual = t.try_fold(&2, 3).to_result().unwrap();
let expected = 10;
assert_eq!(actual, expected)
}
#[test]
fn test_one_failure() {
let t1 = TryFold::new(|a: &i32, b: i32| Valid::fail(a + b));
let t2 = TryFold::new(|a: &i32, b: i32| Valid::succeed(a * b));
let t = t1.and(t2);
let actual = t.try_fold(&2, 3).to_result().unwrap_err();
let expected = vec![Cause::new(5)];
assert_eq!(actual, expected)
}
/* #[test]
fn test_both_failure() {
let t1 = TryFold::new(|a: &i32, b: i32| Valid::fail(a + b));
let t2 = TryFold::new(|a: &i32, b: i32| Valid::fail(a * b));
let t = t1.and(t2);
let actual = t.try_fold(&2, 3).to_result().unwrap_err();
let expected = ValidationError::new(5).combine(ValidationError::new(6));
assert_eq!(actual, expected)
}
*/
#[test]
fn test_order() {
let calls = RefCell::new(Vec::new());
let t1 = TryFold::<i32, i32, ()>::new(|a: &i32, b: i32| {
calls.borrow_mut().push(1);
Valid::succeed(a + b)
}); // 2 + 3
let t2 = TryFold::new(|a: &i32, b: i32| {
calls.borrow_mut().push(2);
Valid::succeed(a * b)
}); // 2 * 3
let t3 = TryFold::new(|a: &i32, b: i32| {
calls.borrow_mut().push(3);
Valid::succeed(a * b * 100)
}); // 2 * 6
let _t = t1.and(t2).and(t3).try_fold(&2, 3);
assert_eq!(*calls.borrow(), vec![1, 2, 3]);
}
/* #[test]
fn test_1_3_failure_left() {
let t1 = TryFold::new(|a: &i32, b: i32| Valid::fail(a + b)); // 2 + 3
let t2 = TryFold::new(|a: &i32, b: i32| Valid::succeed(a * b)); // 2 * 3
let t3 = TryFold::new(|a: &i32, b: i32| Valid::fail(a * b * 100)); // 2 * 6
let t = t1.and(t2).and(t3);
let actual = t.try_fold(&2, 3).to_result().unwrap_err();
let expected = ValidationError::new(5).combine(ValidationError::new(600));
assert_eq!(actual, expected)
}
#[test]
fn test_1_3_failure_right() {
let t1 = TryFold::new(|a: &i32, b: i32| Valid::fail(a + b)); // 2 + 3
let t2 = TryFold::new(|a: &i32, b: i32| Valid::succeed(a * b)); // 2 * 3
let t3 = TryFold::new(|a: &i32, b: i32| Valid::fail(a * b * 100)); // 2 * 6
let t = t1.and(t2.and(t3));
let actual = t.try_fold(&2, 3).to_result().unwrap_err();
let expected = ValidationError::new(5).combine(ValidationError::new(1200));
assert_eq!(actual, expected)
}
#[test]
fn test_2_3_failure() {
let t1 = TryFold::new(|a: &i32, b: i32| Valid::succeed(a + b));
let t2 = TryFold::new(|a: &i32, b: i32| Valid::fail(a * b));
let t3 = TryFold::new(|a: &i32, b: i32| Valid::fail(a * b * 100));
let t = t1.and(t2.and(t3));
let actual = t.try_fold(&2, 3).to_result().unwrap_err();
let expected = ValidationError::new(10).combine(ValidationError::new(1000));
assert_eq!(actual, expected)
}
#[test]
fn test_try_all() {
let t1 = TryFold::new(|a: &i32, b: i32| Valid::succeed(a + b));
let t2 = TryFold::new(|a: &i32, b: i32| Valid::fail(a * b));
let t3 = TryFold::new(|a: &i32, b: i32| Valid::fail(a * b * 100));
let t = TryFold::from_iter(vec![t1, t2, t3]);
let actual = t.try_fold(&2, 3).to_result().unwrap_err();
let expected = ValidationError::new(10).combine(ValidationError::new(1000));
assert_eq!(actual, expected)
}
#[test]
fn test_try_all_1_3_fail() {
let t1 = TryFold::new(|a: &i32, b: i32| Valid::fail(a + b));
let t2 = TryFold::new(|a: &i32, b: i32| Valid::succeed(a * b));
let t3 = TryFold::new(|a: &i32, b: i32| Valid::fail(a * b * 100));
let t = TryFold::from_iter(vec![t1, t2, t3]);
let actual = t.try_fold(&2, 3).to_result().unwrap_err();
let expected = ValidationError::new(5).combine(ValidationError::new(1200));
assert_eq!(actual, expected)
}*/
#[test]
fn test_transform() {
let t: TryFold<'_, i32, String, ()> = TryFold::succeed(|a: &i32, b: i32| a + b).transform(
|v: i32, _| v.to_string(),
|v: String| v.parse::<i32>().unwrap(),
);
let actual = t.try_fold(&2, "3".to_string()).to_result().unwrap();
let expected = "5".to_string();
assert_eq!(actual, expected)
}
#[test]
fn test_transform_valid() {
let t: TryFold<'_, i32, String, ()> = TryFold::succeed(|a: &i32, b: i32| a + b)
.transform_valid(
|v: i32, _| Valid::succeed(v.to_string()),
|v: String| Valid::succeed(v.parse::<i32>().unwrap()),
);
let actual = t.try_fold(&2, "3".to_string()).to_result().unwrap();
let expected = "5".to_string();
assert_eq!(actual, expected)
}
#[test]
fn test_update() {
let t = TryFold::<i32, i32, String>::succeed(|a: &i32, b: i32| a + b).update(|a| a + 1);
let actual = t.try_fold(&2, 3).to_result().unwrap();
let expected = 6;
assert_eq!(actual, expected);
}
}