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

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
}