partial
This commit is contained in:
parent
fe3ec74e35
commit
5eeb1b6002
33
Cargo.lock
generated
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"
|
||||
|
95
openapi.yaml
95
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:
|
||||
|
@ -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
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)),
|
||||
}
|
||||
}
|
||||
*/
|
48
src/config/handle_files.rs
Normal file
48
src/config/handle_files.rs
Normal file
@ -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 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};
|
||||
|
59
src/proto.rs
59
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<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
0
src/transform/mod.rs
Normal file
7
src/transformer.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
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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user