feat!: added ability to run functions & more metadata to object instances

This commit is contained in:
selene 2025-12-28 00:40:14 +01:00
parent 26309312e8
commit 7ae145be20
Signed by: sel
SSH key fingerprint: SHA256:33R/4Rx5Lu4o81LyJyXdMrmP5CJ6j7j1Soo0Dn7mKc0
10 changed files with 323 additions and 68 deletions

91
server/Cargo.lock generated
View file

@ -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",
]

View file

@ -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"

View file

@ -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<ObjectTemplate, String> {
@ -21,13 +26,50 @@ impl ObjectTemplate {
}
impl ObjectInstance {
pub fn from_template(object_type: &str, input: Value) -> Result<Self, String> {
let instance = ObjectInstance {
pub fn from_object_type(object_type: &str, input: Value) -> Result<Self, String> {
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<Self, String> {
pub fn run_transform(
obj: &ObjectInstance,
source: &str,
destination: &str
)
-> Result<Self, String>
{
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<FunctionItem>
)
-> Result<FunctionItem, String>
{
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) = &params {
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<String, FunctionTemplateObject>, 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(())
}

View file

@ -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(())
}

View file

@ -3,14 +3,15 @@ use std::collections::HashMap;
pub mod meta;
pub type FunctionItem = HashMap<String, Vec<ObjectInstance>>;
pub enum ModuleItem {
Template(&'static str),
Validator(fn(&ObjectInstance) -> Result<(), String>),
Calculator(fn(&ObjectInstance) -> Result<ObjectInstance, String>),
Function(fn(
inputs: HashMap<&str, &ObjectInstance>,
params: Option<HashMap<&str, &ObjectInstance>>
) -> Result<HashMap<String, ObjectInstance>, String>)
Function((
&'static str, // template
fn(inputs: FunctionItem, params: Option<FunctionItem>) -> Result<FunctionItem, String>
))
}
pub fn item_registry() -> HashMap<&'static str, ModuleItem> {

View file

@ -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));

View file

@ -1,4 +1,5 @@
pub const TEMPLATE: &str = r#"[input.value]
pub const TEMPLATE: &str = r#"
[input.value]
transforms = ["meta/text:value"]
subobjects = []
conditions = []

View file

@ -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<ObjectInstance, String> {
let _value = obj.input.get("value")
.ok_or("input.value must exist")?;
pub fn local(
inputs: FunctionItem,
_params: Option<FunctionItem>
)
-> Result<FunctionItem, String>
{
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)
}

View file

@ -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<ObjectInstance, String> {
let value = obj.input.get("value")
.ok_or("input.value must exist")?;
pub fn local(
inputs: FunctionItem,
_params: Option<FunctionItem>
)
-> Result<FunctionItem, String>
{
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)
}

View file

@ -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<String, TemplateProperty>,
pub local: HashMap<String, TemplateProperty>,
pub remote: HashMap<String, TemplateProperty>,
pub input: HashMap<String, ObjectTemplateProperty>,
pub local: HashMap<String, ObjectTemplateProperty>,
pub remote: HashMap<String, ObjectTemplateProperty>
}
// part of ObjectTemplate
#[derive(Deserialize, Debug, Default)]
#[serde(default)]
pub struct TemplateProperty {
pub struct ObjectTemplateProperty {
pub transforms: Vec<String>,
pub subobjects: Vec<String>,
pub conditions: Vec<[String; 2]>,
pub duplicates: bool
}
#[derive(Deserialize, Debug, Default)]
#[serde(default)]
pub struct FunctionTemplate {
pub inputs: HashMap<String, FunctionTemplateObject>,
pub params: HashMap<String, FunctionTemplateObject>,
pub outputs: HashMap<String, FunctionTemplateObject>
}
#[derive(Deserialize, Debug, Default)]
#[serde(default)]
pub struct FunctionTemplateObject {
pub required: bool,
pub object_type: String,
pub count: usize
}