From fec74b5469c5164d7b559554628a547501d58e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Huvar?= <492849@mail.muni.cz> Date: Sat, 7 Sep 2024 14:47:49 +0200 Subject: [PATCH] Modify structures to support newest JSON format, add JSON export. --- Cargo.toml | 1 + src/_impl_bma_model.rs | 57 +++++++++++++++++++++--------------------- src/bin/load_json.rs | 8 ++++-- src/bin/load_xml.rs | 2 +- src/bma_model.rs | 43 +++++++++++++++++++++++++------ src/json_model.rs | 4 +++ src/traits.rs | 15 +++-------- 7 files changed, 80 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80142a4..f1733fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,5 +30,6 @@ rand = "0.8.5" regex = "1.10.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde_with = "3.9.0" serde-xml-rs = "0.6.0" num-rational = "0.4.2" diff --git a/src/_impl_bma_model.rs b/src/_impl_bma_model.rs index c31c5f4..ffaa234 100644 --- a/src/_impl_bma_model.rs +++ b/src/_impl_bma_model.rs @@ -1,20 +1,18 @@ use crate::bma_model::*; -use crate::enums::VariableType; use crate::json_model::JsonBmaModel; -use crate::traits::{JsonSerde, XmlSerde}; -use crate::update_fn::bma_fn_tree::BmaFnNode; +use crate::traits::{JsonSerDe, XmlDe}; use crate::update_fn::parser::parse_bma_formula; use crate::xml_model::XmlBmaModel; use biodivine_lib_param_bn::{BooleanNetwork, RegulatoryGraph}; use std::collections::HashMap; -impl<'de> JsonSerde<'de> for BmaModel { +impl<'de> JsonSerDe<'de> for BmaModel { fn to_json_str(&self) -> String { - todo!() + serde_json::to_string(self).unwrap() } fn to_pretty_json_str(&self) -> String { - todo!() + serde_json::to_string_pretty(self).unwrap() } fn from_json_str(json_str: &'de str) -> Result { @@ -24,15 +22,7 @@ impl<'de> JsonSerde<'de> for BmaModel { } } -impl<'de> XmlSerde<'de> for BmaModel { - fn to_xml_str(&self) -> String { - todo!() - } - - fn to_pretty_xml_str(&self) -> String { - todo!() - } - +impl<'de> XmlDe<'de> for BmaModel { fn from_xml_str(xml_str: &'de str) -> Result { let xml_model: XmlBmaModel = serde_xml_rs::from_str(xml_str).map_err(|e| e.to_string())?; let model = BmaModel::from(xml_model); @@ -68,16 +58,14 @@ impl From for BmaModel { name: var .name .unwrap_or(layout_var_names.get(&var.id).cloned().unwrap_or_default()), // Use the name from layout - variable_type: json_model - .layout - .as_ref() - .and_then(|layout| layout.variables.iter().find(|v| v.id == var.id)) - .map(|v| v.r#type) - .unwrap_or(VariableType::Default), // Use the type from layout if available range_from: var.range_from, range_to: var.range_to, - // todo: handle the failures and empty formulas - formula: parse_bma_formula(&var.formula).unwrap_or(BmaFnNode::mk_constant(0)), + formula: if var.formula.is_empty() { + // TODO: handle incorrectly parsed formulas + parse_bma_formula(&var.formula).ok() + } else { + None + }, }) .collect(), relationships: json_model @@ -102,12 +90,15 @@ impl From for BmaModel { .into_iter() .map(|var| LayoutVariable { id: var.id, + name: var.name.unwrap_or_default(), container_id: var.container_id, + variable_type: var.r#type, position_x: var.position_x, position_y: var.position_y, cell_x: var.cell_x, cell_y: var.cell_y, angle: var.angle, + description: var.description.unwrap_or_default(), }) .collect(), containers: layout @@ -115,12 +106,13 @@ impl From for BmaModel { .into_iter() .map(|container| Container { id: container.id, - name: container.name, + name: container.name.unwrap_or_default(), size: container.size, position_x: container.position_x, position_y: container.position_y, }) .collect(), + description: layout.description.unwrap_or_default(), zoom_level: None, pan_x: None, pan_y: None, @@ -128,6 +120,7 @@ impl From for BmaModel { .unwrap_or_else(|| Layout { variables: vec![], containers: vec![], + description: String::default(), zoom_level: None, pan_x: None, pan_y: None, @@ -157,11 +150,14 @@ impl From for BmaModel { .map(|var| Variable { id: var.id, name: var.name, - variable_type: var.r#type, range_from: var.range_from, range_to: var.range_to, - // todo: handle the failures and empty formulas - formula: parse_bma_formula(&var.formula).unwrap_or(BmaFnNode::mk_constant(0)), + formula: if var.formula.is_empty() { + // TODO: handle incorrectly parsed formulas + parse_bma_formula(&var.formula).ok() + } else { + None + }, }) .collect(), relationships: xml_model @@ -185,12 +181,15 @@ impl From for BmaModel { .into_iter() .map(|var| LayoutVariable { id: var.id, + name: var.name, + variable_type: var.r#type, container_id: var.container_id, position_x: var.position_x, position_y: var.position_y, cell_x: Some(var.cell_x), cell_y: Some(var.cell_y), angle: var.angle, + description: String::default(), }) .collect(), containers: xml_model @@ -199,12 +198,13 @@ impl From for BmaModel { .into_iter() .map(|container| Container { id: container.id, - name: Some(container.name), + name: container.name, size: container.size, position_x: container.position_x, position_y: container.position_y, }) .collect(), + description: xml_model.description, zoom_level: Some(xml_model.layout.zoom_level), pan_x: Some(xml_model.layout.pan_x), pan_y: Some(xml_model.layout.pan_y), @@ -213,7 +213,6 @@ impl From for BmaModel { // Metadata can be constructed from various XML fields let mut metadata = HashMap::new(); metadata.insert("biocheck_version".to_string(), xml_model.biocheck_version); - metadata.insert("description".to_string(), xml_model.description); metadata.insert("created_date".to_string(), xml_model.created_date); metadata.insert("modified_date".to_string(), xml_model.modified_date); diff --git a/src/bin/load_json.rs b/src/bin/load_json.rs index da27a86..75a09b0 100644 --- a/src/bin/load_json.rs +++ b/src/bin/load_json.rs @@ -1,5 +1,5 @@ use biodivine_lib_bma_data::bma_model::BmaModel; -use biodivine_lib_bma_data::traits::JsonSerde; +use biodivine_lib_bma_data::traits::JsonSerDe; use std::fs::{read_dir, read_to_string}; /// Iterate through all models and see if they are parse without error. @@ -44,12 +44,16 @@ fn test_parse_all_models_in_dir(models_dir: &str) { fn main() { // 1) first, let's just check the small example and print the internal structure - let selected_model_paths = vec!["models/json-repo/SimpleBifurcation.json"]; + let selected_model_paths = vec!["models/json-export-from-tool/ToyModelStable.json"]; for model_path in selected_model_paths { println!("Parsing selected model {:?}:", model_path); let json_data = read_to_string(model_path).expect("Unable to read file"); let model = BmaModel::from_json_str(&json_data).expect("JSON was not well-formatted"); println!("Internal BmaModel structure:\n{:?}\n", model); + println!( + "Exported JSON BmaModel structure:\n{}\n", + model.to_json_str() + ); } // 2) now let's iterate through all models and see if they at least parse without error diff --git a/src/bin/load_xml.rs b/src/bin/load_xml.rs index 472f863..809bda8 100644 --- a/src/bin/load_xml.rs +++ b/src/bin/load_xml.rs @@ -1,5 +1,5 @@ use biodivine_lib_bma_data::bma_model::BmaModel; -use biodivine_lib_bma_data::traits::XmlSerde; +use biodivine_lib_bma_data::traits::XmlDe; use std::fs::{read_dir, read_to_string}; /// Iterate through all models and see if they are parse without error. diff --git a/src/bma_model.rs b/src/bma_model.rs index 03b89e5..10dbe4a 100644 --- a/src/bma_model.rs +++ b/src/bma_model.rs @@ -1,19 +1,24 @@ use crate::enums::{RelationshipType, VariableType}; use crate::update_fn::bma_fn_tree::BmaFnNode; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, Serializer}; +use serde_with::skip_serializing_none; use std::collections::HashMap; +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] pub struct BmaModel { /// Main data with variables and relationships. pub model: Model, /// Layout information (variable positions, containers, ...). pub layout: Layout, - /// Stores additional metadata like description, biocheck_version, etc. + /// Stores additional metadata like biocheck_version that are sometimes present in XML. + #[serde(flatten)] pub metadata: HashMap, } #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] pub struct Model { pub name: String, pub variables: Vec, @@ -21,48 +26,72 @@ pub struct Model { } #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] pub struct Variable { pub id: u32, pub name: String, - pub variable_type: VariableType, // Corresponds to "Type" in JSON/XML pub range_from: u32, pub range_to: u32, - pub formula: BmaFnNode, + #[serde(serialize_with = "serialize_update_fn")] + pub formula: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] pub struct Relationship { pub id: u32, pub from_variable: u32, pub to_variable: u32, + #[serde(rename = "Type")] pub relationship_type: RelationshipType, // Corresponds to "Type" in JSON/XML } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] pub struct Layout { pub variables: Vec, pub containers: Vec, + pub description: String, // can be empty (by default if not provided) pub zoom_level: Option, pub pan_x: Option, pub pan_y: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] pub struct LayoutVariable { pub id: u32, + pub name: String, // duplicated with Variable.name, but that's what BMA does + #[serde(rename = "Type")] + pub variable_type: VariableType, // Corresponds to "Type" in JSON/XML pub container_id: u32, pub position_x: f64, pub position_y: f64, - pub cell_x: Option, - pub cell_y: Option, + pub cell_x: Option, // this can be serialized to null + pub cell_y: Option, // this can be serialized to null pub angle: f64, + pub description: String, // can be empty (by default if not provided) } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] pub struct Container { pub id: u32, - pub name: Option, // Optional, as some containers may not have a name + pub name: String, // can be empty if not provided pub size: u32, pub position_x: f64, pub position_y: f64, } + +fn serialize_update_fn(update_fn: &Option, s: S) -> Result +where + S: Serializer, +{ + if let Some(update_fn_str) = update_fn { + s.serialize_str(update_fn_str.as_str()) + } else { + s.serialize_str("") + } +} diff --git a/src/json_model.rs b/src/json_model.rs index 52e0c6a..0f0a033 100644 --- a/src/json_model.rs +++ b/src/json_model.rs @@ -97,6 +97,8 @@ pub(crate) struct JsonLayout { pub variables: Vec, #[serde(rename = "Containers", alias = "containers")] pub containers: Vec, + #[serde(rename = "Description", alias = "description")] + pub description: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -123,6 +125,8 @@ pub(crate) struct JsonLayoutVariable { pub cell_y: Option, #[serde(rename = "Angle", alias = "angle")] pub angle: f64, + #[serde(rename = "Description", alias = "description")] + pub description: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/src/traits.rs b/src/traits.rs index 60e16d5..cf7abda 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; /// utilizing [serde]. /// /// All of the structs implementing `JsonSerde` must implement traits `Serialize` and `Deserialize`. -pub trait JsonSerde<'de>: Sized + Serialize + Deserialize<'de> { +pub trait JsonSerDe<'de>: Sized + Serialize + Deserialize<'de> { /// Wrapper for json serialization. fn to_json_str(&self) -> String; @@ -15,17 +15,10 @@ pub trait JsonSerde<'de>: Sized + Serialize + Deserialize<'de> { fn from_json_str(s: &'de str) -> Result; } -/// Trait that provides methods to serialize and deserialize objects into (from) XML, -/// utilizing [serde]. +/// Trait that provides method to deserialize objects from XML utilizing [serde]. /// -/// All of the structs implementing `JsonSerde` must implement traits `Serialize` and `Deserialize`. -pub trait XmlSerde<'de>: Sized + Serialize + Deserialize<'de> { - /// Wrapper for xml serialization. - fn to_xml_str(&self) -> String; - - /// Wrapper for *pretty* xml serialization with indentation. - fn to_pretty_xml_str(&self) -> String; - +/// All of the structs implementing `JsonSerde` must implement trait `Deserialize`. +pub trait XmlDe<'de>: Sized + Deserialize<'de> { /// Wrapper for xml de-serialization. fn from_xml_str(s: &'de str) -> Result; }