Skip to content

Commit

Permalink
Modify structures to support newest JSON format, add JSON export.
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrej33 committed Sep 7, 2024
1 parent b5aeb00 commit fec74b5
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 50 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
57 changes: 28 additions & 29 deletions src/_impl_bma_model.rs
Original file line number Diff line number Diff line change
@@ -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<Self, String> {
Expand All @@ -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<Self, String> {
let xml_model: XmlBmaModel = serde_xml_rs::from_str(xml_str).map_err(|e| e.to_string())?;
let model = BmaModel::from(xml_model);
Expand Down Expand Up @@ -68,16 +58,14 @@ impl From<JsonBmaModel> 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
Expand All @@ -102,32 +90,37 @@ impl From<JsonBmaModel> 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
.containers
.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,
})
.unwrap_or_else(|| Layout {
variables: vec![],
containers: vec![],
description: String::default(),
zoom_level: None,
pan_x: None,
pan_y: None,
Expand Down Expand Up @@ -157,11 +150,14 @@ impl From<XmlBmaModel> 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
Expand All @@ -185,12 +181,15 @@ impl From<XmlBmaModel> 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
Expand All @@ -199,12 +198,13 @@ impl From<XmlBmaModel> 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),
Expand All @@ -213,7 +213,6 @@ impl From<XmlBmaModel> 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);

Expand Down
8 changes: 6 additions & 2 deletions src/bin/load_json.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/bin/load_xml.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
43 changes: 36 additions & 7 deletions src/bma_model.rs
Original file line number Diff line number Diff line change
@@ -1,68 +1,97 @@
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<String, String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Model {
pub name: String,
pub variables: Vec<Variable>,
pub relationships: Vec<Relationship>,
}

#[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<BmaFnNode>,
}

#[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<LayoutVariable>,
pub containers: Vec<Container>,
pub description: String, // can be empty (by default if not provided)
pub zoom_level: Option<f32>,
pub pan_x: Option<i32>,
pub pan_y: Option<i32>,
}

#[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<u32>,
pub cell_y: Option<u32>,
pub cell_x: Option<u32>, // this can be serialized to null
pub cell_y: Option<u32>, // 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<String>, // 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<S>(update_fn: &Option<BmaFnNode>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(update_fn_str) = update_fn {
s.serialize_str(update_fn_str.as_str())
} else {
s.serialize_str("")
}
}
4 changes: 4 additions & 0 deletions src/json_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ pub(crate) struct JsonLayout {
pub variables: Vec<JsonLayoutVariable>,
#[serde(rename = "Containers", alias = "containers")]
pub containers: Vec<JsonContainer>,
#[serde(rename = "Description", alias = "description")]
pub description: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand All @@ -123,6 +125,8 @@ pub(crate) struct JsonLayoutVariable {
pub cell_y: Option<u32>,
#[serde(rename = "Angle", alias = "angle")]
pub angle: f64,
#[serde(rename = "Description", alias = "description")]
pub description: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down
15 changes: 4 additions & 11 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -15,17 +15,10 @@ pub trait JsonSerde<'de>: Sized + Serialize + Deserialize<'de> {
fn from_json_str(s: &'de str) -> Result<Self, String>;
}

/// 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<Self, String>;
}

0 comments on commit fec74b5

Please sign in to comment.