diff --git a/server/Cargo.lock b/server/Cargo.lock index b650552..7e94b15 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -2,18 +2,78 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "indexmap" version = "2.12.0" @@ -30,6 +90,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + [[package]] name = "memchr" version = "2.7.6" @@ -112,6 +178,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "syn" version = "2.0.110" @@ -162,18 +239,32 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wgu" version = "0.0.1" dependencies = [ + "hex", "serde", "serde_json", + "sha2", "toml", ] diff --git a/server/Cargo.toml b/server/Cargo.toml index 9ef1a1f..c6307df 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -5,6 +5,8 @@ version = "0.0.1" edition = "2024" [dependencies] +hex = "0.4.3" serde = { version = "*", features = ["derive"] } serde_json = "1.0.145" +sha2 = "0.10.9" toml = "0.9.8" diff --git a/server/src/lib.rs b/server/src/lib.rs index a06d9a2..9bc8443 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,10 +1,15 @@ pub mod modules; pub mod types; -use crate::modules::{ModuleItem, item_registry}; -use crate::types::{ObjectInstance, ObjectTemplate}; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::collections::HashMap; + +use crate::modules::{ModuleItem, FunctionItem, item_registry}; +use crate::types::{FunctionTemplate, FunctionTemplateObject, ObjectInstance, ObjectTemplate}; use serde_json::{Value, json}; +use sha2::{Sha256, Digest}; +use hex; impl ObjectTemplate { fn from_object_type(object_type: &str) -> Result { @@ -21,13 +26,50 @@ impl ObjectTemplate { } impl ObjectInstance { - pub fn from_template(object_type: &str, input: Value) -> Result { - let instance = ObjectInstance { + pub fn from_object_type(object_type: &str, input: Value) -> Result { + let mut instance = ObjectInstance { object_type: object_type.to_string(), input: json!(input), ..ObjectInstance::default() }; + // https://stackoverflow.com/questions/26593387/how-can-i-get-the-current-time-in-milliseconds + instance.created = format!("{:?}", SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("time should go forward") + .as_millis()); + + instance.edited = format!("{:?}", SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("time should go forward") + .as_millis()); + + instance.hashed = format!("{:?}", SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("time should go forward") + .as_millis()); + + let input_sha256_bytes = Sha256::digest( + format!("{}+{}", + instance.object_type, + json!(instance.input) + ) + ); + + instance.input_sha256 = hex::encode(input_sha256_bytes); + + let full_sha256_bytes = Sha256::digest( + format!("{}+{}+{}+{}+{}", + instance.object_type, + instance.hashed, + json!(instance.input), + json!(instance.local), + json!(instance.remote) + ) + ); + + instance.full_sha256 = hex::encode(full_sha256_bytes); + ObjectInstance::validate(&instance)?; Ok(instance) } @@ -66,7 +108,13 @@ impl ObjectInstance { Ok(()) } - pub fn transform(obj: &ObjectInstance, source: &str, destination: &str) -> Result { + pub fn run_transform( + obj: &ObjectInstance, + source: &str, + destination: &str + ) + -> Result + { let object_type = &obj.object_type; let template = ObjectTemplate::from_object_type(object_type)?; @@ -100,13 +148,75 @@ impl ObjectInstance { }; if template_property.transforms.contains(&destination.to_string()) { - let result = ObjectInstance::from_template(destination_parts[0], json!({ + let result = ObjectInstance::from_object_type(destination_parts[0], json!({ destination_parts[1]: source_instance_category.get(source_parts[1]) }))?; Ok(result) } else { - Err(format!("invalid transform destination {}", destination)) + Err(format!("invalid destination {}", destination)) } } + + pub fn run_function( + function: &str, + inputs: FunctionItem, + params: Option + ) + -> Result + { + let modules = item_registry(); + + // same thing as in ObjectTemplate::from_object_type + let (template, function): (FunctionTemplate, _) = match modules.get(function) { + Some(ModuleItem::Function((template_str, function))) => (toml::from_str(template_str).unwrap(), function), + Some(_) => return Err(format!("the ModuleItem `{}` is not a ModuleItem::Function", function)), + None => return Err(format!("the ModuleItem `{}` doesn't exist", function)) + }; + + validate_function_data(&inputs, &template.inputs, "input")?; + + if let Some(params) = ¶ms { + validate_function_data(params, &template.params, "parameter")?; + } + + let result = function(inputs.clone(), params.clone())?; + validate_function_data(&result, &template.outputs, "output")?; + + Ok(result) + } +} + +// helpers + +fn validate_function_data(data: &FunctionItem, template: &HashMap, category: &str) -> Result<(), String> { + for (name, template_object) in template { + if let None = data.get(name) && template_object.required { + return Err(format!("required {} {} doesn't exist", category, name)) + } + } + + for (name, instances) in data { + let template_object = match template.get(name) { + Some(template_object) => template_object, + None => return Err(format!("{} {} doesn't exist in the template", "input", name)) + }; + + let expected_count = &template_object.count; + let expected_object_type = &template_object.object_type; + + if instances.len() != *expected_count { + return Err(format!("the amount of object instances in the {} {} ({}) doesn't match the expected amount ({})", category, name, instances.len(), expected_count)); + } + + for instance in instances { + if instance.object_type != *expected_object_type { + return Err(format!("the object type of one or more object instances in the {} {} ({}) doesn't match the expected type ({})", category, name, instance.object_type, expected_object_type)) + } + + ObjectInstance::validate(&instance)?; + } + } + + Ok(()) } diff --git a/server/src/main.rs b/server/src/main.rs index 79d64ee..58e4d92 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,48 +1,33 @@ +use std::collections::HashMap; + use serde_json::json; use wgu::types::ObjectInstance; -use wgu::modules::{ModuleItem, item_registry}; fn main() -> Result<(), String> { - let modules = item_registry(); - // change these! - // for step 1 + // 1 | creating a new object instance let object_type = "meta/number"; let input = json!({ "value": 1312 }); - // for step 2 - let function = "local"; - - // for step 3 - let transform_source = "local.length"; - let transform_destination = "meta/number:value"; - - // this creates a new object instance let some_object = ObjectInstance::from_object_type(object_type, input)?; println!("this is an object:"); println!("{:#?}", some_object); println!(""); - // this runs a function on it to calculate other properties - let some_object_with_data = match modules.get(format!("{}:func:{}", object_type, function).as_str()) { - Some(ModuleItem::Calculator(calc)) => calc(&some_object)?, - Some(_) => return Err(format!("if you're trying to run a ModuleItem::Function and not a ModuleItem::Calculator, you'll have to add the match arm for that")), - None => { - return Err(format!("the ModuleItem `{}:func:{}` doesn't exist. this is not necessarily bad in this example (if you changed `object_type`)", object_type, function)); - } - }; + // 2 | running a function on it + let function = "meta/number:func:local"; + let mut function_input = HashMap::new(); + function_input.insert(format!("main"), vec![some_object.clone()]); - println!("this is the same object with data:"); - println!("{:#?}", some_object_with_data); - println!(""); + println!("running function {}", function); + println!("input: {:#?}", function_input); + + let some_object_with_data = ObjectInstance::run_function(function, function_input, None)?; - let transformed = ObjectInstance::transform(&some_object_with_data, transform_source, transform_destination)?; - - println!("this is what happens when we transform the {} property into a new {}:", transform_source, transform_destination); - println!("{:#?}", transformed); + println!("output: {:#?}", some_object_with_data); Ok(()) } diff --git a/server/src/modules.rs b/server/src/modules.rs index feae52c..266aec6 100644 --- a/server/src/modules.rs +++ b/server/src/modules.rs @@ -3,14 +3,15 @@ use std::collections::HashMap; pub mod meta; +pub type FunctionItem = HashMap>; + pub enum ModuleItem { Template(&'static str), Validator(fn(&ObjectInstance) -> Result<(), String>), - Calculator(fn(&ObjectInstance) -> Result), - Function(fn( - inputs: HashMap<&str, &ObjectInstance>, - params: Option> - ) -> Result, String>) + Function(( + &'static str, // template + fn(inputs: FunctionItem, params: Option) -> Result + )) } pub fn item_registry() -> HashMap<&'static str, ModuleItem> { diff --git a/server/src/modules/meta.rs b/server/src/modules/meta.rs index de0070d..6c90840 100644 --- a/server/src/modules/meta.rs +++ b/server/src/modules/meta.rs @@ -14,12 +14,12 @@ pub fn registry() -> HashMap<&'static str, ModuleItem> { // meta/text map.insert("meta/text", ModuleItem::Template(text::TEMPLATE)); map.insert("meta/text:func:validator", ModuleItem::Validator(text::validate)); - map.insert("meta/text:func:local", ModuleItem::Calculator(text::local)); + map.insert("meta/text:func:local", ModuleItem::Function((text::TEMPLATE_LOCAL, text::local))); // meta/number map.insert("meta/number", ModuleItem::Template(number::TEMPLATE)); map.insert("meta/number:func:validator", ModuleItem::Validator(number::validate)); - map.insert("meta/number:func:local", ModuleItem::Calculator(number::local)); + map.insert("meta/number:func:local", ModuleItem::Function((number::TEMPLATE_LOCAL, number::local))); // meta/bool map.insert("meta/bool", ModuleItem::Template(bool::TEMPLATE)); diff --git a/server/src/modules/meta/dummy.rs b/server/src/modules/meta/dummy.rs index 31d0885..5a3f8a2 100644 --- a/server/src/modules/meta/dummy.rs +++ b/server/src/modules/meta/dummy.rs @@ -1,4 +1,5 @@ -pub const TEMPLATE: &str = r#"[input.value] +pub const TEMPLATE: &str = r#" +[input.value] transforms = ["meta/text:value"] subobjects = [] conditions = [] diff --git a/server/src/modules/meta/number.rs b/server/src/modules/meta/number.rs index f6330a2..93e5c1b 100644 --- a/server/src/modules/meta/number.rs +++ b/server/src/modules/meta/number.rs @@ -1,5 +1,7 @@ +use std::collections::HashMap; use serde_json::json; -use crate::ObjectInstance; + +use crate::{modules::FunctionItem, types::ObjectInstance}; pub const TEMPLATE: &str = r#" [input.value] @@ -15,6 +17,18 @@ conditions = [] duplicates = false "#; +pub const TEMPLATE_LOCAL: &str = r#" +[inputs.main] +required = true +object_type = "meta/number" +count = 1 + +[outputs.main] +required = true +object_type = "meta/number" +count = 1 +"#; + pub fn validate(obj: &ObjectInstance) -> Result<(), String> { let value = obj.input.get("value") .ok_or("input.value must exist")?; @@ -25,18 +39,28 @@ pub fn validate(obj: &ObjectInstance) -> Result<(), String> { Ok(()) } -pub fn local(obj: &ObjectInstance) -> Result { - let _value = obj.input.get("value") - .ok_or("input.value must exist")?; +pub fn local( + inputs: FunctionItem, + _params: Option +) + -> Result +{ + let Some(main) = inputs.get("main") + else { unreachable!() }; - let value = _value.as_number() - .ok_or("input.value must be a number")?; + let main_obj = &main[0]; - let mut new = obj.clone(); - new.local = json!({ + let Some(value) = &main_obj.input.get("value") + else { unreachable!() }; + + let mut result_obj = ObjectInstance::from_object_type("meta/number", main_obj.input.clone())?; + + result_obj.local = json!({ "length": value.to_string().len() }); - Ok(new) -} + let mut result = HashMap::new(); + result.insert(format!("main"), vec![result_obj]); + Ok(result) +} diff --git a/server/src/modules/meta/text.rs b/server/src/modules/meta/text.rs index d3270fb..84c73b4 100644 --- a/server/src/modules/meta/text.rs +++ b/server/src/modules/meta/text.rs @@ -1,5 +1,7 @@ +use std::collections::HashMap; use serde_json::json; -use crate::ObjectInstance; + +use crate::{modules::FunctionItem, types::ObjectInstance}; pub const TEMPLATE: &str = r#" [input.value] @@ -15,6 +17,19 @@ conditions = [] duplicates = false "#; + +pub const TEMPLATE_LOCAL: &str = r#" +[inputs.main] +required = true +object_type = "meta/text" +count = 1 + +[outputs.main] +required = true +object_type = "meta/text" +count = 1 +"#; + pub fn validate(obj: &ObjectInstance) -> Result<(), String> { let value = obj.input.get("value") .ok_or("input.value must exist")?; @@ -25,17 +40,28 @@ pub fn validate(obj: &ObjectInstance) -> Result<(), String> { Ok(()) } -pub fn local(obj: &ObjectInstance) -> Result { - let value = obj.input.get("value") - .ok_or("input.value must exist")?; +pub fn local( + inputs: FunctionItem, + _params: Option +) + -> Result +{ + let Some(main) = inputs.get("main") + else { unreachable!() }; - let value = value.as_str() - .ok_or("input.value must be a string")?; + let main_obj = &main[0]; - let mut new = obj.clone(); - new.local = json!({ - "length": value.len() + let Some(value) = &main_obj.input.get("value") + else { unreachable!() }; + + let mut result_obj = ObjectInstance::from_object_type("meta/text", main_obj.input.clone())?; + + result_obj.local = json!({ + "length": value.to_string().len() }); - Ok(new) + let mut result = HashMap::new(); + result.insert(format!("main"), vec![result_obj]); + + Ok(result) } diff --git a/server/src/types.rs b/server/src/types.rs index ef57cb9..89179ba 100644 --- a/server/src/types.rs +++ b/server/src/types.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct ObjectInstance { - pub variant: u32, + pub variant: usize, pub plotted: bool, pub created: String, pub edited: String, @@ -24,24 +24,39 @@ pub struct Log { level: String, variant: String, timestamp: String, - message: String, + message: String } #[derive(Deserialize, Debug, Default)] #[serde(default)] pub struct ObjectTemplate { - pub input: HashMap, - pub local: HashMap, - pub remote: HashMap, + pub input: HashMap, + pub local: HashMap, + pub remote: HashMap } // part of ObjectTemplate #[derive(Deserialize, Debug, Default)] #[serde(default)] -pub struct TemplateProperty { +pub struct ObjectTemplateProperty { pub transforms: Vec, pub subobjects: Vec, pub conditions: Vec<[String; 2]>, pub duplicates: bool } +#[derive(Deserialize, Debug, Default)] +#[serde(default)] +pub struct FunctionTemplate { + pub inputs: HashMap, + pub params: HashMap, + pub outputs: HashMap +} + +#[derive(Deserialize, Debug, Default)] +#[serde(default)] +pub struct FunctionTemplateObject { + pub required: bool, + pub object_type: String, + pub count: usize +}