From 5eeb1b600207a3d888a2769059a5da0cfed4a619 Mon Sep 17 00:00:00 2001 From: Sandipsinh Rathod Date: Sat, 21 Dec 2024 23:15:51 -0500 Subject: [PATCH] partial --- Cargo.lock | 33 ++++ Cargo.toml | 3 +- openapi.yaml | 95 ++++++------ src/config/config.rs | 1 + src/config/fixargs.rs | 143 ++++++++++++++++++ src/config/handle_files.rs | 48 ++++++ src/config/mod.rs | 4 +- src/config/to_wit.rs | 2 +- src/config/wit_types.rs | 30 +++- src/main.rs | 3 + src/proto.rs | 59 ++------ src/transform/mod.rs | 0 src/transformer.rs | 7 + src/tryfold.rs | 302 +++++++++++++++++++++++++++++++++++++ 14 files changed, 624 insertions(+), 106 deletions(-) create mode 100644 src/config/fixargs.rs create mode 100644 src/config/handle_files.rs create mode 100644 src/transform/mod.rs create mode 100644 src/transformer.rs create mode 100644 src/tryfold.rs diff --git a/Cargo.lock b/Cargo.lock index 53feb16..90d11f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 18c7d55..34fa354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/openapi.yaml b/openapi.yaml index eb95303..7fd9cd0 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -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: diff --git a/src/config/config.rs b/src/config/config.rs index 34e53bb..9444300 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -27,6 +27,7 @@ pub struct Interface { pub struct Record { pub name: String, pub fields: Vec, + pub added_fields: Vec, } #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct UseStatement { diff --git a/src/config/fixargs.rs b/src/config/fixargs.rs new file mode 100644 index 0000000..b12c953 --- /dev/null +++ b/src/config/fixargs.rs @@ -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 { + 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::>(); + 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 + // 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::>(); + 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 { + 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)), + } +} +*/ \ No newline at end of file diff --git a/src/config/handle_files.rs b/src/config/handle_files.rs new file mode 100644 index 0000000..b0d6363 --- /dev/null +++ b/src/config/handle_files.rs @@ -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 { + 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)) + }) +} diff --git a/src/config/mod.rs b/src/config/mod.rs index a56f658..193fee1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,5 @@ pub mod config; pub mod wit_types; -pub mod to_wit; \ No newline at end of file +pub mod to_wit; +pub mod fixargs; +pub mod handle_files; diff --git a/src/config/to_wit.rs b/src/config/to_wit.rs index 68c3fd6..ab658f1 100644 --- a/src/config/to_wit.rs +++ b/src/config/to_wit.rs @@ -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::>() .join(", ") ), diff --git a/src/config/wit_types.rs b/src/config/wit_types.rs index 86cf23e..85910a0 100644 --- a/src/config/wit_types.rs +++ b/src/config/wit_types.rs @@ -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), // (T1, T2, ...) // Custom Types - Record(Vec<(String, String)>), // Record { field_name: Type } + Record(Vec<(String, WitType)>), // Record { field_name: Type } Variant(Vec<(String, Option)>), // Variant { name: Option } Enum(Vec), // Enum { name1, name2, ... } Flags(Vec), // 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), ]) ); } diff --git a/src/main.rs b/src/main.rs index 87b8aac..86a3363 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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}; diff --git a/src/proto.rs b/src/proto.rs index 317ad4b..45e4290 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -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 { - 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 { 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); diff --git a/src/transform/mod.rs b/src/transform/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/transformer.rs b/src/transformer.rs new file mode 100644 index 0000000..e243721 --- /dev/null +++ b/src/transformer.rs @@ -0,0 +1,7 @@ +use tailcall_valid::Valid; + +pub trait Transform { + type Value; + type Error; + fn transform(&self, value: Self::Value) -> Valid; +} \ No newline at end of file diff --git a/src/tryfold.rs b/src/tryfold.rs new file mode 100644 index 0000000..972c060 --- /dev/null +++ b/src/tryfold.rs @@ -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 Valid + '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 { + (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 + 'a) -> Self { + TryFold(Box::new(f)) + } + + /// Transforms a TryFold to TryFold 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 that applies the transformations. + pub fn transform( + 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 to TryFold 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. + /// - `down`: A function that uses O1 to create a new Valid. + /// + /// # Returns + /// Returns a new TryFold that applies the transformations. + pub fn transform_valid( + self, + up: impl Fn(O, O1) -> Valid + 'a, + down: impl Fn(O1) -> Valid + '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> for TryFold<'a, I, O, E> { + fn from_iter>>(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::::new(|a: &i32, b: i32| Valid::succeed(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(); + 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::::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::().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::().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::::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); + } +}