diff --git a/Cargo.lock b/Cargo.lock index fc18cb9..aa896e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1562,6 +1562,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_arbitrary" version = "1.4.1" @@ -1900,6 +1911,7 @@ dependencies = [ "colored", "criterion", "crossbeam", + "derivative", "derive_builder", "egobox-ego", "env_logger 0.11.8", @@ -1945,6 +1957,7 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tsify-next", + "uuid 1.18.1", "variantly", "wasm-bindgen", "wasm-bindgen-test", @@ -4807,7 +4820,7 @@ dependencies = [ "polars-utils", "rayon", "smartstring", - "uuid 1.16.0", + "uuid 1.18.1", "version_check", ] @@ -6836,11 +6849,13 @@ dependencies = [ [[package]] name = "uuid" -version = "1.16.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -6850,7 +6865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" dependencies = [ "outref", - "uuid 1.16.0", + "uuid 1.18.1", "vsimd", ] diff --git a/Cargo.toml b/Cargo.toml index 2434d4d..a5f03c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,8 @@ quick-xml = { version = "0.38.0", features = [ "serde-types", "overlapped-lists", ], optional = true } +uuid = { version = "1.18.1", features = ["v4"] } +derivative = "2.2.0" [profile.release] opt-level = 3 diff --git a/src/macros/unit_macro.rs b/src/macros/unit_macro.rs index 04fe8d5..2c10426 100644 --- a/src/macros/unit_macro.rs +++ b/src/macros/unit_macro.rs @@ -22,6 +22,8 @@ macro_rules! build_base_unit { ($prefix:tt, $unit:ident, $exponent:expr) => { $crate::prelude::BaseUnit { + jsonld: $crate::prelude::default_baseunit_jsonld_header(), + additional_properties: None, scale: Some( $crate::macros::unit_maps::PREFIX_MAPPING .get(stringify!($prefix)) @@ -75,6 +77,8 @@ macro_rules! collect_base_units { macro_rules! inverse_unit { ($([$prefix:tt $unit:ident])+) => { $crate::prelude::UnitDefinition { + jsonld: $crate::prelude::default_unitdefinition_jsonld_header(), + additional_properties: None, id: None, name: format!("1 / {}", $crate::build_unit_name!($([$prefix $unit])+)).into(), base_units: $crate::collect_base_units!(-1; $([$prefix $unit])+), @@ -87,6 +91,8 @@ macro_rules! inverse_unit { macro_rules! ratio_unit { ($([$num_prefix:tt $num_unit:ident])+; $([$den_prefix:tt $den_unit:ident])+) => { $crate::prelude::UnitDefinition { + jsonld: $crate::prelude::default_unitdefinition_jsonld_header(), + additional_properties: None, id: None, name: format!( "{} / {}", @@ -107,6 +113,8 @@ macro_rules! ratio_unit { macro_rules! simple_unit { ($([$prefix:tt $unit:ident])+) => { $crate::prelude::UnitDefinition { + jsonld: $crate::prelude::default_unitdefinition_jsonld_header(), + additional_properties: None, id: None, name: $crate::build_unit_name!($([$prefix $unit])+).into(), base_units: $crate::collect_base_units!(1; $([$prefix $unit])+), @@ -140,6 +148,7 @@ mod tests { #[test] fn test_unit_ratio_macro() { let unit = unit!([_ mole] / [_ liter]); + assert_eq!(unit.additional_properties, None); assert_eq!(unit.name, Some("mole / liter".to_string())); assert_eq!(unit.base_units.len(), 2); assert_eq!(unit.base_units[0].kind, UnitType::Mole); @@ -153,6 +162,7 @@ mod tests { #[test] fn test_unit_ratio_macro_with_prefix() { let unit = unit!([m mol] / [_ liter]); + assert_eq!(unit.additional_properties, None); assert_eq!(unit.name, Some("mmol / liter".to_string())); assert_eq!(unit.base_units.len(), 2); assert_eq!(unit.base_units[0].kind, UnitType::Mole); diff --git a/src/sbml/reader.rs b/src/sbml/reader.rs index 4ac6b51..12cb6da 100644 --- a/src/sbml/reader.rs +++ b/src/sbml/reader.rs @@ -317,6 +317,7 @@ impl TryFrom<&Compartment<'_>> for Vessel { volume: compartment.size().unwrap_or(1.0), unit, constant: compartment.constant().unwrap_or(true), + ..Default::default() }) } } @@ -357,6 +358,7 @@ impl TryFrom<&Species<'_>> for SmallMolecule { inchikey: None, synonymous_names: vec![], references: vec![], + ..Default::default() }; annotation.apply(&mut smallmol); @@ -401,6 +403,7 @@ impl TryFrom<&Species<'_>> for Protein { ecnumber: None, organism: None, organism_tax_id: None, + ..Default::default() }; annotation.apply(&mut protein); @@ -441,6 +444,7 @@ impl TryFrom<&Species<'_>> for Complex { constant: species.constant(), vessel_id: species.compartment(), participants: vec![], + ..Default::default() }; annotation.apply(&mut complex); @@ -468,6 +472,7 @@ impl TryFrom<&KineticLaw<'_>> for Equation { equation: kinetic_law.formula(), equation_type: EquationType::RateLaw, variables: vec![], + ..Default::default() }) } } @@ -533,6 +538,7 @@ impl TryFrom<&SBMLReaction<'_>> for Reaction { products, modifiers, kinetic_law, + ..Default::default() }) } } @@ -554,6 +560,7 @@ impl TryFrom<&SpeciesReference<'_>> for ReactionElement { Ok(ReactionElement { species_id: species_reference.species(), stoichiometry: species_reference.stoichiometry(), + ..Default::default() }) } } @@ -577,6 +584,7 @@ impl TryFrom<&ModifierSpeciesReference<'_>> for ModifierElement { Ok(ModifierElement { species_id: modifier_species_reference.species(), role: ModifierRole::Biocatalyst, + ..Default::default() }) } } @@ -619,6 +627,7 @@ impl TryFrom<&SBMLParameter<'_>> for Parameter { stderr: None, constant: parameter.constant(), fit: None, + ..Default::default() }; annotation.apply(&mut parameter); @@ -706,6 +715,7 @@ impl TryFrom<&Rule<'_>> for Equation { equation: rate_rule.formula(), equation_type, variables: vec![], + ..Default::default() }) } } diff --git a/src/sbml/units.rs b/src/sbml/units.rs index a71578c..df24fef 100644 --- a/src/sbml/units.rs +++ b/src/sbml/units.rs @@ -115,6 +115,7 @@ impl TryFrom<&SBMLUnitDefinition<'_>> for UnitDefinition { id: Some(unit_definition.id()), name: unit_definition.name(), base_units, + ..Default::default() }) } } @@ -153,6 +154,7 @@ impl TryFrom<&SBMLUnit<'_>> for BaseUnit { exponent: unit.exponent().into(), multiplier: unit.multiplier().into(), scale: Some(unit.scale().into()), + ..Default::default() }) } } diff --git a/src/sbml/v1/extract.rs b/src/sbml/v1/extract.rs index 9a100f7..da52363 100644 --- a/src/sbml/v1/extract.rs +++ b/src/sbml/v1/extract.rs @@ -301,6 +301,7 @@ fn create_measurement_data( time: vec![], data_type, is_simulated: None, + ..Default::default() }; // Extract time-series data if available diff --git a/src/sbml/v2/extract.rs b/src/sbml/v2/extract.rs index 9772b9d..71e4d31 100644 --- a/src/sbml/v2/extract.rs +++ b/src/sbml/v2/extract.rs @@ -362,6 +362,7 @@ impl From<&MeasurementAnnot> for Measurement { ph: conditions.ph.and_then(|ph| ph.value), temperature: conditions.temperature.and_then(|temp| temp.value), temperature_unit: None, + ..Default::default() } } } @@ -400,6 +401,7 @@ impl From<&SpeciesDataAnnot> for MeasurementData { time_unit: None, data_type: Some(species_data.data_type.clone()), is_simulated: None, + ..Default::default() } } } diff --git a/src/versions/v2.rs b/src/versions/v2.rs index 044030b..b7ab9db 100644 --- a/src/versions/v2.rs +++ b/src/versions/v2.rs @@ -3,9 +3,13 @@ //! WARNING: This is an auto-generated file. //! Do not edit directly - any changes will be overwritten. +use derivative::Derivative; use derive_builder::Builder; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use uuid; // // Type definitions @@ -18,94 +22,122 @@ use serde::{Deserialize, Serialize}; /// comprehensive information about the experimental setup, including /// reaction vessels, proteins, complexes, small molecules, reactions, /// measurements, equations, and parameters. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct EnzymeMLDocument { - /// Title of the EnzymeML Document. - /// - #[builder(setter(into))] - pub name: String, + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_enzymemldocument_jsonld_header()")] + #[derivative(Default(value = "default_enzymemldocument_jsonld_header()"))] + pub jsonld: Option, /// The version of the EnzymeML Document. - #[serde(default)] + #[builder(default = "2.0.to_string().into()", setter(into))] + #[derivative(Default(value = "\"2.0\".to_string()"))] pub version: String, /// Description of the EnzymeML Document. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub description: Option, + /// Title of the EnzymeML Document. + + #[builder(setter(into))] + #[derivative(Default)] + pub name: String, + /// Date the EnzymeML Document was created. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub created: Option, /// Date the EnzymeML Document was modified. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub modified: Option, /// Contains descriptions of all authors that are part of the experiment. - /// + #[builder(default, setter(into, each(name = "to_creators")))] + #[derivative(Default)] pub creators: Vec, /// Contains descriptions of all vessels that are part of the experiment. - /// + #[builder(default, setter(into, each(name = "to_vessels")))] + #[derivative(Default)] pub vessels: Vec, /// Contains descriptions of all proteins that are part of the experiment /// that may be referenced in reactions, measurements, and /// equations. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_proteins")))] + #[derivative(Default)] pub proteins: Vec, /// Contains descriptions of all complexes that are part of the experiment /// that may be referenced in reactions, measurements, and /// equations. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_complexes")))] + #[derivative(Default)] pub complexes: Vec, /// Contains descriptions of all reactants that are part of the experiment /// that may be referenced in reactions, measurements, and /// equations. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_small_molecules")))] + #[derivative(Default)] pub small_molecules: Vec, /// Contains descriptions of all reactions that are part of the /// experiment. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_reactions")))] + #[derivative(Default)] pub reactions: Vec, /// Contains descriptions of all measurements that are part of the /// experiment. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_measurements")))] + #[derivative(Default)] pub measurements: Vec, /// Contains descriptions of all equations that are part of the /// experiment. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_equations")))] + #[derivative(Default)] pub equations: Vec, /// Contains descriptions of all parameters that are part of the /// experiment and may be used in equations. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_parameters")))] + #[derivative(Default)] pub parameters: Vec, /// Contains references to publications, databases, and arbitrary links to /// the web. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_references")))] + #[derivative(Default)] pub references: Vec, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// The Creator object represents an individual author or contributor who @@ -113,246 +145,362 @@ pub struct EnzymeMLDocument { /// It captures essential personal information such as their name /// and contact details, allowing proper attribution and enabling /// communication with the document's creators. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct Creator { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_creator_jsonld_header()")] + #[derivative(Default(value = "default_creator_jsonld_header()"))] + pub jsonld: Option, + /// Given name of the author or contributor. - /// + #[builder(setter(into))] + #[derivative(Default)] pub given_name: String, /// Family name of the author or contributor. - /// + #[builder(setter(into))] + #[derivative(Default)] pub family_name: String, /// Email address of the author or contributor. - /// + #[builder(setter(into))] + #[derivative(Default)] pub mail: String, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// The Vessel object represents containers used to conduct experiments, /// such as reaction vessels, microplates, or bioreactors. It captures /// key properties like volume and whether the volume remains constant /// during the experiment. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct Vessel { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_vessel_jsonld_header()")] + #[derivative(Default(value = "default_vessel_jsonld_header()"))] + pub jsonld: Option, + /// Unique identifier of the vessel. - /// + #[builder(setter(into))] + #[derivative(Default)] pub id: String, /// Name of the used vessel. - /// + #[builder(setter(into))] + #[derivative(Default)] pub name: String, /// Volumetric value of the vessel. - /// + #[builder(setter(into))] + #[derivative(Default)] pub volume: f64, /// Volumetric unit of the vessel. - /// + #[builder(setter(into))] + #[derivative(Default)] pub unit: UnitDefinition, /// Whether the volume of the vessel is constant or not. Default is True. - #[serde(default)] + #[builder(default = "true.into()", setter(into))] + #[derivative(Default(value = "true"))] pub constant: bool, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// The Protein object represents enzymes and other proteins involved in /// the experiment. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct Protein { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_protein_jsonld_header()")] + #[derivative(Default(value = "default_protein_jsonld_header()"))] + pub jsonld: Option, + /// Identifier of the protein, such as a UniProt ID, or a custom /// identifier. - /// + #[builder(setter(into))] + #[derivative(Default)] pub id: String, /// Name of the protein. - /// + #[builder(setter(into))] + #[derivative(Default)] pub name: String, /// Whether the concentration of the protein is constant through the /// experiment or not. Default is True. - #[serde(default)] + #[builder(default = "true.into()", setter(into))] + #[derivative(Default(value = "true"))] pub constant: bool, /// Amino acid sequence of the protein - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub sequence: Option, /// Identifier of the vessel this protein has been applied to. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub vessel_id: Option, /// EC number of the protein. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub ecnumber: Option, /// Expression host organism of the protein. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub organism: Option, /// Taxonomy identifier of the expression host. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub organism_tax_id: Option, /// List of references to publications, database entries, etc. that /// describe or reference the protein. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_references")))] + #[derivative(Default)] pub references: Vec, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// The Complex object allows the grouping of multiple species using /// their . This enables the representation of protein-small molecule /// complexes (e.g., enzyme-substrate complexes) as well as buffer or /// solvent mixtures (combinations of SmallMolecule species). -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct Complex { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_complex_jsonld_header()")] + #[derivative(Default(value = "default_complex_jsonld_header()"))] + pub jsonld: Option, + /// Unique identifier of the complex. - /// + #[builder(setter(into))] + #[derivative(Default)] pub id: String, /// Name of the complex. - /// + #[builder(setter(into))] + #[derivative(Default)] pub name: String, /// Whether the concentration of the complex is constant through the /// experiment or not. Default is False. - /// + #[builder(setter(into))] + #[derivative(Default)] pub constant: bool, /// Unique identifier of the vessel this complex has been used in. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub vessel_id: Option, /// Array of IDs the complex contains - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_participants")))] + #[derivative(Default)] pub participants: Vec, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// The SmallMolecule object represents small chemical compounds that /// participate in the experiment as substrates, products, or /// modifiers. It captures key molecular identifiers like SMILES and /// InChI. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct SmallMolecule { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_smallmolecule_jsonld_header()")] + #[derivative(Default(value = "default_smallmolecule_jsonld_header()"))] + pub jsonld: Option, + /// Identifier of the small molecule, such as a Pubchem ID, ChEBI ID, or a /// custom identifier. - /// + #[builder(setter(into))] + #[derivative(Default)] pub id: String, /// Name of the small molecule. - /// + #[builder(setter(into))] + #[derivative(Default)] pub name: String, /// Whether the concentration of the small molecule is constant through /// the experiment or not. Default is False. - /// + #[builder(setter(into))] + #[derivative(Default)] pub constant: bool, /// Identifier of the vessel this small molecule has been used in. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub vessel_id: Option, /// Canonical Simplified Molecular-Input Line-Entry System (SMILES) /// encoding of the small molecule. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub canonical_smiles: Option, /// International Chemical Identifier (InChI) encoding of the small /// molecule. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub inchi: Option, /// Hashed International Chemical Identifier (InChIKey) encoding of the /// small molecule. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub inchikey: Option, /// List of synonymous names for the small molecule. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_synonymous_names")))] + #[derivative(Default)] pub synonymous_names: Vec, /// List of references to publications, database entries, etc. that /// describe or reference the small molecule. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_references")))] + #[derivative(Default)] pub references: Vec, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// The Reaction object represents a chemical or enzymatic reaction and /// holds the different species and modifiers that are part of the /// reaction. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct Reaction { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_reaction_jsonld_header()")] + #[derivative(Default(value = "default_reaction_jsonld_header()"))] + pub jsonld: Option, + /// Unique identifier of the reaction. - /// + #[builder(setter(into))] + #[derivative(Default)] pub id: String, /// Name of the reaction. - /// + #[builder(setter(into))] + #[derivative(Default)] pub name: String, /// Whether the reaction is reversible or irreversible. Default is False. - /// + #[builder(setter(into))] + #[derivative(Default)] pub reversible: bool, /// Mathematical expression of the reaction. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub kinetic_law: Option, /// List of reactants that are part of the reaction. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_reactants")))] + #[derivative(Default)] pub reactants: Vec, /// List of products that are part of the reaction. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_products")))] + #[derivative(Default)] pub products: Vec, /// List of reaction elements that are not part of the reaction but /// influence it. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_modifiers")))] + #[derivative(Default)] pub modifiers: Vec, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// This object is part of the object and describes a species @@ -361,64 +509,111 @@ pub struct Reaction { /// whereas negative values indicate that the species is a reactant /// and positive values indicate that the species is a product of /// the reaction. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct ReactionElement { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_reactionelement_jsonld_header()")] + #[derivative(Default(value = "default_reactionelement_jsonld_header()"))] + pub jsonld: Option, + /// Internal identifier to either a protein or reactant defined in the /// EnzymeML Document. - /// + #[builder(setter(into))] + #[derivative(Default)] pub species_id: String, /// Float number representing the associated stoichiometry. - #[serde(default)] + #[builder(default = "1.0.into()", setter(into))] + #[derivative(Default(value = "1.0"))] pub stoichiometry: f64, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// The ModifierElement object represents a species that is not part of /// the reaction but influences it. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct ModifierElement { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_modifierelement_jsonld_header()")] + #[derivative(Default(value = "default_modifierelement_jsonld_header()"))] + pub jsonld: Option, + /// Internal identifier to either a protein or reactant defined in the /// EnzymeML Document. - /// + #[builder(setter(into))] + #[derivative(Default)] pub species_id: String, /// Role of the modifier in the reaction. - /// + #[builder(setter(into))] + #[derivative(Default)] pub role: ModifierRole, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// The Equation object describes a mathematical equation used to model /// parts of a reaction system. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct Equation { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_equation_jsonld_header()")] + #[derivative(Default(value = "default_equation_jsonld_header()"))] + pub jsonld: Option, + /// Identifier of a defined species (SmallMolecule, Protein, Complex). /// Represents the left hand side of the equation. - /// + #[builder(setter(into))] + #[derivative(Default)] pub species_id: String, /// Mathematical expression of the equation. Represents the right hand /// side of the equation. - /// + #[builder(setter(into))] + #[derivative(Default)] pub equation: String, /// Type of the equation. - /// + #[builder(setter(into))] + #[derivative(Default)] pub equation_type: EquationType, /// List of variables that are part of the equation - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_variables")))] + #[derivative(Default)] pub variables: Vec, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// This object describes a variable that is part of an equation. @@ -426,23 +621,39 @@ pub struct Equation { /// quantities that appear in mathematical expressions. Each variable /// must have a unique identifier, name, and symbol that is used in /// equations. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct Variable { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_variable_jsonld_header()")] + #[derivative(Default(value = "default_variable_jsonld_header()"))] + pub jsonld: Option, + /// Identifier of the variable. - /// + #[builder(setter(into))] + #[derivative(Default)] pub id: String, /// Name of the variable. - /// + #[builder(setter(into))] + #[derivative(Default)] pub name: String, /// Equation symbol of the variable. - /// + #[builder(setter(into))] + #[derivative(Default)] pub symbol: String, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// This object describes parameters used in kinetic models, including @@ -450,66 +661,90 @@ pub struct Variable { /// Parameters can represent rate constants, binding constants, or /// other numerical values that appear in rate equations or other /// mathematical expressions. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct Parameter { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_parameter_jsonld_header()")] + #[derivative(Default(value = "default_parameter_jsonld_header()"))] + pub jsonld: Option, + /// Identifier of the parameter. - /// + #[builder(setter(into))] + #[derivative(Default)] pub id: String, /// Name of the parameter. - /// + #[builder(setter(into))] + #[derivative(Default)] pub name: String, /// Equation symbol of the parameter. - /// + #[builder(setter(into))] + #[derivative(Default)] pub symbol: String, /// Numerical value of the estimated parameter. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub value: Option, /// Unit of the estimated parameter. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub unit: Option, /// Initial value that was used for the parameter estimation. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub initial_value: Option, /// Upper bound for the parameter value that was used for the parameter /// estimation - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub upper_bound: Option, /// Lower bound for the parameter value that was used for the parameter /// estimation - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub lower_bound: Option, /// Whether this parameter should be varied or not in the context of an /// optimization. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "true.into()", setter(into))] + #[derivative(Default(value = "true.into()"))] pub fit: Option, /// Standard error of the estimated parameter. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub stderr: Option, /// Specifies if this parameter is constant. Default is True. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "true.into()", setter(into))] + #[derivative(Default(value = "true.into()"))] pub constant: Option, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// This object describes a single measurement, which includes time @@ -518,44 +753,64 @@ pub struct Parameter { /// the experiment. Multiple measurements can be grouped together /// using the group_id field to indicate they are part of the same /// experimental series. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct Measurement { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_measurement_jsonld_header()")] + #[derivative(Default(value = "default_measurement_jsonld_header()"))] + pub jsonld: Option, + /// Unique identifier of the measurement. - /// + #[builder(setter(into))] + #[derivative(Default)] pub id: String, /// Name of the measurement - /// + #[builder(setter(into))] + #[derivative(Default)] pub name: String, /// Measurement data of all species that were part of the measurement. A /// species refers to a Protein, Complex, or SmallMolecule. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_species_data")))] + #[derivative(Default)] pub species_data: Vec, /// User-defined group ID to signal relationships between measurements. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub group_id: Option, /// pH value of the measurement. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub ph: Option, /// Temperature of the measurement. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub temperature: Option, /// Unit of the temperature of the measurement. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub temperature_unit: Option, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// This object describes a single entity of a measurement, which @@ -564,12 +819,21 @@ pub struct Measurement { /// amount, prepared amount, and measured data points over time. /// Endpoint data is treated as a time course data point with only one /// data point. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct MeasurementData { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_measurementdata_jsonld_header()")] + #[derivative(Default(value = "default_measurementdata_jsonld_header()"))] + pub jsonld: Option, + /// The identifier for the described reactant. - /// + #[builder(setter(into))] + #[derivative(Default)] pub species_id: String, /// Amount of the the species before starting the measurement. This field @@ -577,91 +841,137 @@ pub struct MeasurementData { /// in the reaction mix. Not to be confused with , specifying /// the concentration of a species at the first data point from /// the array. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub prepared: Option, /// Initial amount of the measurement data. This must be the same as the /// first data point in the array. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub initial: Option, /// SI unit of the data that was measured. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub data_unit: Option, /// Data that was measured. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_data")))] + #[derivative(Default)] pub data: Vec, /// Corresponding time points of the . - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_time")))] + #[derivative(Default)] pub time: Vec, /// Unit of the time points of the . - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub time_unit: Option, /// Type of data that was measured (e.g. concentration, absorbance, etc.) - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub data_type: Option, /// Whether or not the data has been generated by simulation. Default /// is False. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub is_simulated: Option, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// Represents a unit definition that is based on the SI unit system. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct UnitDefinition { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_unitdefinition_jsonld_header()")] + #[derivative(Default(value = "default_unitdefinition_jsonld_header()"))] + pub jsonld: Option, + /// Unique identifier of the unit definition. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub id: Option, /// Common name of the unit definition. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub name: Option, /// Base units that define the unit. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] #[builder(default, setter(into, each(name = "to_base_units")))] + #[derivative(Default)] pub base_units: Vec, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } /// Represents a base unit in the unit definition. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder, Derivative)] +#[derivative(Default)] +#[serde(default)] #[allow(non_snake_case)] pub struct BaseUnit { + /// JSON-LD header + #[serde(flatten)] + #[builder(default = "default_baseunit_jsonld_header()")] + #[derivative(Default(value = "default_baseunit_jsonld_header()"))] + pub jsonld: Option, + /// Kind of the base unit (e.g., meter, kilogram, second). - /// + #[builder(setter(into))] + #[derivative(Default)] pub kind: UnitType, /// Exponent of the base unit in the unit definition. - /// + #[builder(setter(into))] + #[derivative(Default)] pub exponent: i64, /// Multiplier of the base unit in the unit definition. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub multiplier: Option, /// Scale of the base unit in the unit definition. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[builder(default, setter(into))] + #[derivative(Default)] pub scale: Option, + + /// Additional properties outside of the schema + #[serde(flatten)] + #[builder(default)] + pub additional_properties: Option>, } // @@ -848,3 +1158,906 @@ pub enum UnitType { #[serde(rename = "weber")] Weber, } + +// Default JSON-LD header function for each object + +pub fn default_enzymemldocument_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "name".to_string(), + TermDef::Simple("schema:title".to_string()), + ); + context.terms.insert( + "created".to_string(), + TermDef::Simple("schema:dateCreated".to_string()), + ); + context.terms.insert( + "modified".to_string(), + TermDef::Simple("schema:dateModified".to_string()), + ); + context.terms.insert( + "creators".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:creator".to_string()), + type_: None, + container: Some("@list".to_string()), + context: None, + }), + ); + context.terms.insert( + "references".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:citation".to_string()), + type_: Some("@id".to_string()), + container: Some("@list".to_string()), + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:EnzymeMLDocument/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:EnzymeMLDocument".to_string()])), + }) +} + +pub fn default_creator_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "given_name".to_string(), + TermDef::Simple("schema:givenName".to_string()), + ); + context.terms.insert( + "family_name".to_string(), + TermDef::Simple("schema:familyName".to_string()), + ); + context.terms.insert( + "mail".to_string(), + TermDef::Simple("schema:email".to_string()), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:Creator/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec![ + "enzml:Creator".to_string(), + "schema:person".to_string(), + ])), + }) +} + +pub fn default_vessel_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + context.terms.insert( + "name".to_string(), + TermDef::Simple("schema:name".to_string()), + ); + context.terms.insert( + "volume".to_string(), + TermDef::Simple("OBO:OBI_0002139".to_string()), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:Vessel/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec![ + "enzml:Vessel".to_string(), + "OBO:OBI_0400081".to_string(), + ])), + }) +} + +pub fn default_protein_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + context.terms.insert( + "name".to_string(), + TermDef::Simple("schema:name".to_string()), + ); + context.terms.insert( + "sequence".to_string(), + TermDef::Simple("OBO:GSSO_007262".to_string()), + ); + context.terms.insert( + "vessel_id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + context.terms.insert( + "organism".to_string(), + TermDef::Simple("OBO:OBI_0100026".to_string()), + ); + context.terms.insert( + "organism_tax_id".to_string(), + TermDef::Detailed(TermDetail { + id: None, + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + context.terms.insert( + "references".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:citation".to_string()), + type_: Some("@id".to_string()), + container: Some("@list".to_string()), + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:Protein/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec![ + "enzml:Protein".to_string(), + "OBO:PR_000000001".to_string(), + ])), + }) +} + +pub fn default_complex_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + context.terms.insert( + "name".to_string(), + TermDef::Simple("schema:name".to_string()), + ); + context.terms.insert( + "vessel_id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + context.terms.insert( + "participants".to_string(), + TermDef::Detailed(TermDetail { + id: None, + type_: Some("@id".to_string()), + container: Some("@list".to_string()), + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:Complex/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:Complex".to_string()])), + }) +} + +pub fn default_smallmolecule_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + context.terms.insert( + "name".to_string(), + TermDef::Simple("schema:name".to_string()), + ); + context.terms.insert( + "vessel_id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + context.terms.insert( + "references".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:citation".to_string()), + type_: Some("@id".to_string()), + container: Some("@list".to_string()), + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:SmallMolecule/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:SmallMolecule".to_string()])), + }) +} + +pub fn default_reaction_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:Reaction/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:Reaction".to_string()])), + }) +} + +pub fn default_reactionelement_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "species_id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:ReactionElement/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:ReactionElement".to_string()])), + }) +} + +pub fn default_modifierelement_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "species_id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:ModifierElement/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:ModifierElement".to_string()])), + }) +} + +pub fn default_equation_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "species_id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:Equation/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:Equation".to_string()])), + }) +} + +pub fn default_variable_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:Variable/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:Variable".to_string()])), + }) +} + +pub fn default_parameter_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:Parameter/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:Parameter".to_string()])), + }) +} + +pub fn default_measurement_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "id".to_string(), + TermDef::Detailed(TermDetail { + id: Some("schema:identifier".to_string()), + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + context.terms.insert( + "group_id".to_string(), + TermDef::Detailed(TermDetail { + id: None, + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:Measurement/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:Measurement".to_string()])), + }) +} + +pub fn default_measurementdata_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + context.terms.insert( + "species_id".to_string(), + TermDef::Detailed(TermDetail { + id: None, + type_: Some("@id".to_string()), + container: None, + context: None, + }), + ); + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:MeasurementData/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:MeasurementData".to_string()])), + }) +} + +pub fn default_unitdefinition_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:UnitDefinition/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:UnitDefinition".to_string()])), + }) +} + +pub fn default_baseunit_jsonld_header() -> Option { + let mut context = SimpleContext::default(); + + // Add main prefix and repository URL + context.terms.insert( + "enzml".to_string(), + TermDef::Simple("http://www.enzymeml.org/v2/".to_string()), + ); + + // Add configured prefixes + context.terms.insert( + "OBO".to_string(), + TermDef::Simple("http://purl.obolibrary.org/obo/".to_string()), + ); + context.terms.insert( + "schema".to_string(), + TermDef::Simple("https://schema.org/".to_string()), + ); + + // Add attribute terms + + Some(JsonLdHeader { + context: Some(JsonLdContext::Object(context)), + id: Some(format!("enzml:BaseUnit/{}", uuid::Uuid::new_v4())), + type_: Some(TypeOrVec::Multi(vec!["enzml:BaseUnit".to_string()])), + }) +} + +/// JSON-LD Header +/// +/// JSON-LD (JavaScript Object Notation for Linked Data) provides a way to express +/// linked data using JSON syntax, enabling semantic web technologies and structured +/// data interchange with context and meaning preservation. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] +pub struct JsonLdHeader { + /// JSON-LD context (IRI, object, or array) + #[serde(rename = "@context", skip_serializing_if = "Option::is_none")] + pub context: Option, + + /// Node identifier (IRI or blank node) + #[serde(rename = "@id", skip_serializing_if = "Option::is_none")] + pub id: Option, + + /// Type IRI(s) for the node, e.g. schema:Person + #[serde(rename = "@type", skip_serializing_if = "Option::is_none")] + pub type_: Option, +} + +impl Default for JsonLdHeader { + /// Returns the default JSON-LD header. + fn default() -> Self { + Self { + context: None, + id: None, + type_: None, + } + } +} + +impl JsonLdHeader { + /// Adds a new term definition to the JSON-LD context, creating a context object if none exists. + /// + /// This method provides a convenient way to extend the JSON-LD context with additional term + /// mappings, allowing for semantic annotation of properties and values within the document. + /// If the header does not already contain a context, a new SimpleContext object will be + /// created automatically to hold the term definition. + /// + /// # Arguments + /// + /// * `name` - The term name to be defined in the context + /// * `term` - The term definition, either a simple IRI mapping or a detailed definition + /// + /// # Example + /// + /// ```no_compile + /// let mut header = JsonLdHeader::default(); + /// header.add_term("name", TermDef::Simple("https://schema.org/name".to_string())); + /// ``` + pub fn add_term(&mut self, name: &str, term: TermDef) { + let context = self + .context + .get_or_insert_with(|| JsonLdContext::Object(SimpleContext::default())); + + if let JsonLdContext::Object(object) = context { + object.terms.insert(name.to_string(), term); + } + } + + /// Updates an existing term definition in the JSON-LD context or adds it if it doesn't exist. + /// + /// This method functions similarly to add_term but provides clearer semantics when the + /// intention is to modify an existing term definition. The behavior is identical to add_term + /// as HashMap::insert will overwrite existing entries with the same key, but this method + /// name makes the intent more explicit in code that is updating rather than initially + /// defining terms. + /// + /// # Arguments + /// + /// * `name` - The term name to be updated in the context + /// * `term` - The new term definition to replace any existing definition + /// + /// # Example + /// + /// ```no_compile + /// let mut header = JsonLdHeader::default(); + /// header.add_term("name", TermDef::Simple("https://schema.org/name".to_string())); + /// header.update_term("name", TermDef::Simple("https://example.org/fullName".to_string())); + /// ``` + pub fn update_term(&mut self, name: &str, term: TermDef) { + let context = self + .context + .get_or_insert_with(|| JsonLdContext::Object(SimpleContext::default())); + + if let JsonLdContext::Object(object) = context { + object.terms.insert(name.to_string(), term); + } + } + + /// Removes a term definition from the JSON-LD context if it exists. + /// + /// This method allows for the removal of previously defined terms from the JSON-LD context, + /// which can be useful when dynamically managing context definitions or when certain terms + /// are no longer needed in the semantic annotation of the document. The method will only + /// attempt removal if the context exists and is an object type; it will silently do nothing + /// if the context is missing or is not an object. + /// + /// # Arguments + /// + /// * `name` - The term name to be removed from the context + /// + /// # Returns + /// + /// Returns `true` if the term was found and removed, `false` if the term was not present + /// or if the context is not an object type. + /// + /// # Example + /// + /// ```no_compile + /// let mut header = JsonLdHeader::default(); + /// header.add_term("name", TermDef::Simple("https://schema.org/name".to_string())); + /// let was_removed = header.remove_term("name"); + /// assert!(was_removed); + /// ``` + pub fn remove_term(&mut self, name: &str) -> bool { + if let Some(JsonLdContext::Object(object)) = &mut self.context { + object.terms.remove(name).is_some() + } else { + false + } + } +} + +/// Accept either a single type IRI or an array of them. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] +#[serde(untagged)] +pub enum TypeOrVec { + Single(String), + Multi(Vec), +} + +/// JSON-LD Context: +/// - a single IRI (remote context) +/// - an inline context object +/// - or an array of these (merged sequentially) +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] +#[serde(untagged)] +pub enum JsonLdContext { + Iri(String), + Object(SimpleContext), + Array(Vec), +} + +/// A simple inline @context object with essential global keys and term definitions. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default, Eq, PartialEq)] +pub struct SimpleContext { + /// Base IRI used for relative resolution. + #[serde(rename = "@base", skip_serializing_if = "Option::is_none")] + pub base: Option, + + /// Default vocabulary IRI for terms without explicit IRIs. + #[serde(rename = "@vocab", skip_serializing_if = "Option::is_none")] + pub vocab: Option, + + /// Default language for string literals. + #[serde(rename = "@language", skip_serializing_if = "Option::is_none")] + pub language: Option, + + /// Mapping of term → IRI or detailed definition. + #[serde(flatten, skip_serializing_if = "HashMap::is_empty", default)] + pub terms: HashMap, +} + +/// Term definition can be a simple mapping (string → IRI) or a detailed object. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Eq, PartialEq)] +#[serde(untagged)] +pub enum TermDef { + /// Simple alias: `"name": "https://schema.org/name"` + Simple(String), + /// Expanded form with type coercion, container behavior, or nested context. + Detailed(TermDetail), +} + +/// Detailed term definition (subset of JSON-LD 1.1 features). +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default, Eq, PartialEq)] +pub struct TermDetail { + /// Absolute or relative IRI that the term expands to, or a keyword like "@id". + #[serde(rename = "@id", skip_serializing_if = "Option::is_none")] + pub id: Option, + + /// Type coercion or value type ("@id", "@vocab", or datatype IRI). + #[serde(rename = "@type", skip_serializing_if = "Option::is_none")] + pub type_: Option, + + /// Container behavior ("@list", "@set", "@index", etc.). + #[serde(rename = "@container", skip_serializing_if = "Option::is_none")] + pub container: Option, + + /// Optional nested (scoped) context. + #[serde(rename = "@context", skip_serializing_if = "Option::is_none")] + pub context: Option>, +}