From 51083143f7c4c429a3f4719528eef8796e9f6889 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Fri, 14 Mar 2025 18:05:22 +0100 Subject: [PATCH 01/15] Experimental YAML format for model. --- Cargo.lock | 16 +++++ examples/Cargo.toml | 1 + examples/src/compatibility/level_2/2_0001.yml | 59 +++++++++++++++++++ examples/src/compatibility/level_2/a.md | 1 + examples/src/lib.rs | 15 +++++ 5 files changed, 92 insertions(+) create mode 100644 examples/src/compatibility/level_2/2_0001.yml create mode 100644 examples/src/compatibility/level_2/a.md diff --git a/Cargo.lock b/Cargo.lock index 89e131e6..656bd46e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -648,6 +648,7 @@ name = "dsntk-examples" version = "0.3.0-dev" dependencies = [ "walkdir", + "yaml-rust", ] [[package]] @@ -1341,6 +1342,12 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "litemap" version = "0.7.5" @@ -2672,6 +2679,15 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yoke" version = "0.7.5" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4050e61a..6c6fe33f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -12,3 +12,4 @@ edition = { workspace = true } [dev-dependencies] walkdir = { workspace = true } +yaml-rust = "0.4.5" diff --git a/examples/src/compatibility/level_2/2_0001.yml b/examples/src/compatibility/level_2/2_0001.yml new file mode 100644 index 00000000..2c4bce01 --- /dev/null +++ b/examples/src/compatibility/level_2/2_0001.yml @@ -0,0 +1,59 @@ +model: + namespace: https://dsntk.io/2_0001/ + name: 2_001 + id: _2_0001 + xmlns: https://www.omg.org/spec/DMN/20191111/MODEL/ + description: | + Compliance level 2: Test 0001 + + The decision named **Greeting Message** has a label defined in diagram definition. + In the diagram this decision is depicted as **GREETING MESSAGE**. + + The output variable name remains **Greeting Message**. + +definitions: + + - decision: "Greeting Message" + id: _75b3add2-4d36-4a19-a76c-268b49b2f436 + description: | + This decision prepares a greeting message. + 'Hello' is prepended to the value of the input variable named 'Full Name'. + question: What is the greeting suitable for our customer? + allowedAnswers: | + The proper greeting is in the format: + Hello {customer's full name} + variable: + name: "Greeting Message" + label: "GREETING MESSAGE" + typeRef: string + informationRequirement: + id: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + requiredInput: + href: #_cba86e4d-e91c-46a2-9176-e9adf88e15db + literalExpression: + text: | + "Hello " + Full Name + + - decision: "kuku" + description: | + This decision prepares a greeting message. + 'Hello' is prepended to the value of the input variable named 'Full Name'. + decision-table: + unicode: | + + unicode-file: a.uni + markdown: | + + markdown-file: b.md + + - input-data: "Full Name" + id: _cba86e4d-e91c-46a2-9176-e9adf88e15db + description: | + Full name of the customer provided by calling service. + variable: + typeRef: string + name: "Full Name" + label: "Customer's name" + id: _id_variable_full_name + description: | + Full name of the person that will be sent greetings from this decision model. diff --git a/examples/src/compatibility/level_2/a.md b/examples/src/compatibility/level_2/a.md new file mode 100644 index 00000000..ddad7e07 --- /dev/null +++ b/examples/src/compatibility/level_2/a.md @@ -0,0 +1 @@ +kuku diff --git a/examples/src/lib.rs b/examples/src/lib.rs index a4a7653b..ac159ceb 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -21,7 +21,9 @@ pub use full_model::*; mod utilities { use std::collections::BTreeSet; use std::fmt::Write; + use std::fs; use walkdir::WalkDir; + use yaml_rust::YamlLoader; /// Generates multiple decision table variants. #[test] @@ -124,4 +126,17 @@ mod utilities { } results } + + #[test] + fn a() { + let content = fs::read_to_string("./src/compatibility/level_2/2_0001.yml").expect("failed to load yaml input file"); + //println!("{}", content); + let docs = YamlLoader::load_from_str(&content).unwrap(); + let doc = &docs[0]; + if let Some(definitions) = doc["definitions"].as_vec() { + for definition in definitions { + println!("{:?}", definition); + } + } + } } From 56d158b398050842ea663082ce4cb8318235dc76 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Fri, 14 Mar 2025 18:18:28 +0100 Subject: [PATCH 02/15] Updates. --- bbt/tests/cli/noargs/0001_OK/expected | 2 +- bbt/tests/cli/version/long-version/expected | 2 +- bbt/tests/cli/version/short-version/expected | 2 +- bbt/tests/server/action_none/expected | 2 +- dsntk/src/actions.rs | 8 ++--- gendoc/src/tests/ascii_model.rs | 6 ++-- gendoc/src/tests/mod.rs | 2 +- model-evaluator/benches/compatibility/mod.rs | 8 ++--- model-evaluator/src/input_data.rs | 4 +-- model-evaluator/src/input_data_context.rs | 2 +- model-evaluator/src/item_definition.rs | 2 +- .../src/item_definition_context.rs | 2 +- model-evaluator/src/item_definition_type.rs | 2 +- .../compatibility/non_compliant/dmn_n_0088.rs | 2 +- model-evaluator/src/tests/mod.rs | 8 ++--- model/src/lib.rs | 4 +-- model/src/tests/parser/full_model.rs | 6 ++-- model/src/tests/parser/invalid_models.rs | 34 +++++++++---------- .../model_validator/item_definition_cycles.rs | 6 ++-- model/src/{parser.rs => xml_parser.rs} | 2 +- workspace/src/builder.rs | 2 +- 21 files changed, 54 insertions(+), 54 deletions(-) rename model/src/{parser.rs => xml_parser.rs} (99%) diff --git a/bbt/tests/cli/noargs/0001_OK/expected b/bbt/tests/cli/noargs/0001_OK/expected index 672a28b9..6b4b22b9 100644 --- a/bbt/tests/cli/noargs/0001_OK/expected +++ b/bbt/tests/cli/noargs/0001_OK/expected @@ -1,3 +1,3 @@ -dsntk | DecisionToolkit | 0.2.0 +dsntk | DecisionToolkit | 0.3.0-dev Try 'dsntk --help' to see all available commands. For more information, visit https://decision-toolkit.org diff --git a/bbt/tests/cli/version/long-version/expected b/bbt/tests/cli/version/long-version/expected index 0ea3a944..d5109100 100644 --- a/bbt/tests/cli/version/long-version/expected +++ b/bbt/tests/cli/version/long-version/expected @@ -1 +1 @@ -0.2.0 +0.3.0-dev diff --git a/bbt/tests/cli/version/short-version/expected b/bbt/tests/cli/version/short-version/expected index 0ea3a944..d5109100 100644 --- a/bbt/tests/cli/version/short-version/expected +++ b/bbt/tests/cli/version/short-version/expected @@ -1 +1 @@ -0.2.0 +0.3.0-dev diff --git a/bbt/tests/server/action_none/expected b/bbt/tests/server/action_none/expected index 672a28b9..6b4b22b9 100644 --- a/bbt/tests/server/action_none/expected +++ b/bbt/tests/server/action_none/expected @@ -1,3 +1,3 @@ -dsntk | DecisionToolkit | 0.2.0 +dsntk | DecisionToolkit | 0.3.0-dev Try 'dsntk --help' to see all available commands. For more information, visit https://decision-toolkit.org diff --git a/dsntk/src/actions.rs b/dsntk/src/actions.rs index 2e0772e7..a31a5ab4 100644 --- a/dsntk/src/actions.rs +++ b/dsntk/src/actions.rs @@ -997,7 +997,7 @@ fn export_decision_table(dectab_file_name: &str, html_file_name: &str, dectab_fi /// Parses DMN model loaded from XML file and prints ASCII report. fn parse_dmn_model(dmn_file_name: &str, cm: ColorMode) { match fs::read_to_string(dmn_file_name) { - Ok(dmn_file_content) => match dsntk_model::parse(&dmn_file_content) { + Ok(dmn_file_content) => match dsntk_model::from_xml(&dmn_file_content) { Ok(definitions) => { dsntk_gendoc::print_model(definitions, cm); } @@ -1012,7 +1012,7 @@ fn evaluate_dmn_model(input_file_name: &str, dmn_file_name: &str, invocable_name match fs::read_to_string(dmn_file_name) { Ok(dmn_file_content) => match fs::read_to_string(input_file_name) { Ok(input_file_content) => match dsntk_evaluator::evaluate_context(&FeelScope::default(), &input_file_content) { - Ok(input_data) => match dsntk_model::parse(&dmn_file_content) { + Ok(input_data) => match dsntk_model::from_xml(&dmn_file_content) { Ok(definitions) => { let model_namespace = definitions.namespace().to_string(); let model_name = definitions.name().to_string(); @@ -1043,7 +1043,7 @@ fn test_dmn_model(test_file_name: &str, dmn_file_name: &str, invocable_name: &st return; } }; - let definitions = match dsntk_model::parse(&dmn_file_content) { + let definitions = match dsntk_model::from_xml(&dmn_file_content) { Ok(definitions) => definitions, Err(reason) => { eprintln!("parsing model file failed with reason: {reason}"); @@ -1085,7 +1085,7 @@ fn test_dmn_model(test_file_name: &str, dmn_file_name: &str, invocable_name: &st /// Exports DMN model loaded from `XML` file to `HTML` output file. fn export_dmn_model(dmn_file_name: &str, html_file_name: &str) { match fs::read_to_string(dmn_file_name) { - Ok(dmn_file_content) => match dsntk_model::parse(&dmn_file_content) { + Ok(dmn_file_content) => match dsntk_model::from_xml(&dmn_file_content) { Ok(definitions) => { let html_output = dsntk_gendoc::dmn_model_to_html(&definitions); if let Err(reason) = fs::write(html_file_name, html_output) { diff --git a/gendoc/src/tests/ascii_model.rs b/gendoc/src/tests/ascii_model.rs index 0143ce4d..34058fdb 100644 --- a/gendoc/src/tests/ascii_model.rs +++ b/gendoc/src/tests/ascii_model.rs @@ -8,7 +8,7 @@ macro_rules! test_print_model { #[test] #[allow(clippy::redundant_clone)] fn $test_name() { - let definitions = dsntk_model::parse(dsntk_examples::$model_name).expect("parsing model failed"); + let definitions = dsntk_model::from_xml(dsntk_examples::$model_name).expect("parsing model failed"); print_model(definitions.clone(), ColorMode::On); let expected = format!("{:?}", definitions); let actual = format!("{:?}", definitions); @@ -151,12 +151,12 @@ test_print_model!(_3_1130, DMN_3_1130); #[test] fn test_single_model() { - let definitions = dsntk_model::parse(dsntk_examples::DMN_3_1108).expect("parsing model failed"); + let definitions = dsntk_model::from_xml(dsntk_examples::DMN_3_1108).expect("parsing model failed"); print_model(definitions, ColorMode::On); } #[test] fn test_full_model() { - let definitions = dsntk_model::parse(dsntk_examples::DMN_FULL).expect("parsing model failed"); + let definitions = dsntk_model::from_xml(dsntk_examples::DMN_FULL).expect("parsing model failed"); print_model(definitions, ColorMode::On); } diff --git a/gendoc/src/tests/mod.rs b/gendoc/src/tests/mod.rs index d012ef5d..d44a33ad 100644 --- a/gendoc/src/tests/mod.rs +++ b/gendoc/src/tests/mod.rs @@ -15,7 +15,7 @@ const TARGET_DIR: &str = "../target/gendoc"; /// Utility function for generating HTML file for decision table defined as text. fn gen_html_from_model(model: &str, output_file_name: &str) { - let definitions = dsntk_model::parse(model).expect("parsing model failed"); + let definitions = dsntk_model::from_xml(model).expect("parsing model failed"); let html = crate::dmn_model_to_html(&definitions); assert_eq!("", &html[0..15]); fs::create_dir_all(TARGET_DIR).expect("creating target directories failed"); diff --git a/model-evaluator/benches/compatibility/mod.rs b/model-evaluator/benches/compatibility/mod.rs index edcfe9d1..c45e622a 100644 --- a/model-evaluator/benches/compatibility/mod.rs +++ b/model-evaluator/benches/compatibility/mod.rs @@ -69,7 +69,7 @@ use {from_examples, iter, model_evaluator_from_examples, model_name_from_example /// Utility function that builds a model evaluator from a single DMN model. fn build_model_evaluator(model_content: &str) -> Arc { - let definitions = dsntk_model::parse(model_content).unwrap(); + let definitions = dsntk_model::from_xml(model_content).unwrap(); ModelEvaluator::new(&[definitions]).unwrap() } @@ -77,20 +77,20 @@ fn build_model_evaluator(model_content: &str) -> Arc { fn build_model_evaluators(model_content: &[&str]) -> Arc { let mut definitions = vec![]; for content in model_content { - definitions.push(dsntk_model::parse(content).unwrap()); + definitions.push(dsntk_model::from_xml(content).unwrap()); } ModelEvaluator::new(&definitions).unwrap() } /// Utility function that returns a model namespace from a single DMN model. fn build_model_namespace(model_content: &str) -> String { - let definitions = dsntk_model::parse(model_content).unwrap(); + let definitions = dsntk_model::from_xml(model_content).unwrap(); definitions.namespace().to_string() } /// Utility function that returns a model namespace from a single DMN model. fn build_model_name(model_content: &str) -> String { - let definitions = dsntk_model::parse(model_content).unwrap(); + let definitions = dsntk_model::from_xml(model_content).unwrap(); definitions.name().to_string() } diff --git a/model-evaluator/src/input_data.rs b/model-evaluator/src/input_data.rs index c8eb2ce3..6fc91128 100644 --- a/model-evaluator/src/input_data.rs +++ b/model-evaluator/src/input_data.rs @@ -55,8 +55,8 @@ mod tests { /// Utility function for building input data evaluator from definitions, /// and item definition evaluator from definitions. - fn build_evaluators(xml: &str) -> (InputDataEvaluator, ItemDefinitionEvaluator) { - let definitions = dsntk_model::parse(xml).unwrap(); + fn build_evaluators(content: &str) -> (InputDataEvaluator, ItemDefinitionEvaluator) { + let definitions = dsntk_model::from_xml(content).unwrap(); let mut def_definitions = DefDefinitions::default(); def_definitions.add_model(&definitions); (InputDataEvaluator::new(&def_definitions), ItemDefinitionEvaluator::new(&def_definitions).unwrap()) diff --git a/model-evaluator/src/input_data_context.rs b/model-evaluator/src/input_data_context.rs index e8bf2d72..b3cf6bff 100644 --- a/model-evaluator/src/input_data_context.rs +++ b/model-evaluator/src/input_data_context.rs @@ -69,7 +69,7 @@ mod tests { /// Utility function for building input data context evaluator from definitions, /// and item definition context evaluator from definitions. fn build_evaluators(xml: &str) -> (InputDataContextEvaluator, ItemDefinitionContextEvaluator) { - let definitions = dsntk_model::parse(xml).unwrap(); + let definitions = dsntk_model::from_xml(xml).unwrap(); let mut def_definitions = DefDefinitions::default(); def_definitions.add_model(&definitions); let input_data_context_evaluator = InputDataContextEvaluator::new(&def_definitions); diff --git a/model-evaluator/src/item_definition.rs b/model-evaluator/src/item_definition.rs index b2538cb4..4edfe28f 100644 --- a/model-evaluator/src/item_definition.rs +++ b/model-evaluator/src/item_definition.rs @@ -458,7 +458,7 @@ mod tests { /// Utility function for building item definition evaluator from definitions. fn build_evaluator(xml: &str) -> ItemDefinitionEvaluator { - let definitions = dsntk_model::parse(xml).unwrap(); + let definitions = dsntk_model::from_xml(xml).unwrap(); let mut def_definitions = DefDefinitions::default(); def_definitions.add_model(&definitions); ItemDefinitionEvaluator::new(&def_definitions).unwrap() diff --git a/model-evaluator/src/item_definition_context.rs b/model-evaluator/src/item_definition_context.rs index f0219f96..fd56f3e2 100644 --- a/model-evaluator/src/item_definition_context.rs +++ b/model-evaluator/src/item_definition_context.rs @@ -173,7 +173,7 @@ mod tests { /// Utility function for building item definition evaluator from definitions. fn build_evaluator(xml: &str) -> ItemDefinitionContextEvaluator { - let definitions = dsntk_model::parse(xml).unwrap(); + let definitions = dsntk_model::from_xml(xml).unwrap(); let mut def_definitions = DefDefinitions::default(); def_definitions.add_model(&definitions); ItemDefinitionContextEvaluator::new(&def_definitions).unwrap() diff --git a/model-evaluator/src/item_definition_type.rs b/model-evaluator/src/item_definition_type.rs index 4437e090..2b1943e7 100644 --- a/model-evaluator/src/item_definition_type.rs +++ b/model-evaluator/src/item_definition_type.rs @@ -190,7 +190,7 @@ mod tests { /// Utility function for building item definition type evaluator from definitions. fn build_evaluator(xml: &str) -> ItemDefinitionTypeEvaluator { - let definitions = dsntk_model::parse(xml).unwrap(); + let definitions = dsntk_model::from_xml(xml).unwrap(); let mut def_definitions = DefDefinitions::default(); def_definitions.add_model(&definitions); ItemDefinitionTypeEvaluator::new(&def_definitions).unwrap() diff --git a/model-evaluator/src/tests/compatibility/non_compliant/dmn_n_0088.rs b/model-evaluator/src/tests/compatibility/non_compliant/dmn_n_0088.rs index 0c712000..bf38558f 100644 --- a/model-evaluator/src/tests/compatibility/non_compliant/dmn_n_0088.rs +++ b/model-evaluator/src/tests/compatibility/non_compliant/dmn_n_0088.rs @@ -4,6 +4,6 @@ use dsntk_examples::DMN_N_0088; fn _0001() { assert_eq!( " cyclic dependency between item definitions", - dsntk_model::parse(DMN_N_0088).err().unwrap().to_string() + dsntk_model::from_xml(DMN_N_0088).err().unwrap().to_string() ); } diff --git a/model-evaluator/src/tests/mod.rs b/model-evaluator/src/tests/mod.rs index 5539a875..caad0e39 100644 --- a/model-evaluator/src/tests/mod.rs +++ b/model-evaluator/src/tests/mod.rs @@ -73,7 +73,7 @@ pub fn context(input: &str) -> FeelContext { /// Utility function that builds a model evaluator from single XML model definitions. fn build_model_evaluator(model_content: &str) -> Arc { - let definitions = dsntk_model::parse(model_content).unwrap(); + let definitions = dsntk_model::from_xml(model_content).unwrap(); ModelEvaluator::new(&[definitions]).unwrap() } @@ -81,20 +81,20 @@ fn build_model_evaluator(model_content: &str) -> Arc { fn build_model_evaluators(model_content: &[&str]) -> Arc { let mut definitions = vec![]; for content in model_content { - definitions.push(dsntk_model::parse(content).unwrap()); + definitions.push(dsntk_model::from_xml(content).unwrap()); } ModelEvaluator::new(&definitions).unwrap() } /// Utility function that returns a model namespace from a single DMN model. fn build_model_namespace(model_content: &str) -> String { - let definitions = dsntk_model::parse(model_content).unwrap(); + let definitions = dsntk_model::from_xml(model_content).unwrap(); definitions.namespace().to_string() } /// Utility function that returns a model names from a single DMN model. fn build_model_name(model_content: &str) -> String { - let definitions = dsntk_model::parse(model_content).unwrap(); + let definitions = dsntk_model::from_xml(model_content).unwrap(); definitions.name().to_string() } diff --git a/model/src/lib.rs b/model/src/lib.rs index 4e30734e..e9a9a401 100644 --- a/model/src/lib.rs +++ b/model/src/lib.rs @@ -4,10 +4,10 @@ extern crate dsntk_macros; mod errors; mod mapper; mod model; -mod parser; mod tests; mod validators; +mod xml_parser; mod xml_utils; pub use model::*; -pub use parser::parse; +pub use xml_parser::from_xml; diff --git a/model/src/tests/parser/full_model.rs b/model/src/tests/parser/full_model.rs index 01b22f4b..3608cac3 100644 --- a/model/src/tests/parser/full_model.rs +++ b/model/src/tests/parser/full_model.rs @@ -1,10 +1,10 @@ +use crate::from_xml; use crate::model::DmnElement; -use crate::parse; use dsntk_examples::DMN_FULL; #[test] fn _0001() { - let definitions = parse(DMN_FULL).unwrap(); + let definitions = from_xml(DMN_FULL).unwrap(); assert_eq!("_id_definitions", definitions.id()); //------------------------------------------------------------------------------------------------ // ITEM DEFINITIONS @@ -30,7 +30,7 @@ fn _0001() { #[test] #[allow(clippy::redundant_clone)] fn _0002() { - let definitions = parse(DMN_FULL).unwrap(); + let definitions = from_xml(DMN_FULL).unwrap(); let cloned_definitions = definitions.clone(); assert_eq!("_id_definitions", cloned_definitions.id()); let expected = format!("{definitions:?}"); diff --git a/model/src/tests/parser/invalid_models.rs b/model/src/tests/parser/invalid_models.rs index 5fa70a8c..f1cd3aaf 100644 --- a/model/src/tests/parser/invalid_models.rs +++ b/model/src/tests/parser/invalid_models.rs @@ -1,9 +1,9 @@ -use crate::parse; +use crate::from_xml; use crate::tests::parser::input_files::*; #[test] fn _0001() { - let definitions = parse(T_DMN_0001); + let definitions = from_xml(T_DMN_0001); assert!(definitions.is_err()); assert_eq!( r#" 'Python' is not a valid function kind, accepted values are: 'FEEL', 'Java', 'PMML'"#, @@ -13,7 +13,7 @@ fn _0001() { #[test] fn _0002() { - let definitions = parse(T_DMN_0002); + let definitions = from_xml(T_DMN_0002); assert!(definitions.is_err()); assert_eq!( r#" 'LAST' is not a valid hit policy, allowed values are: 'UNIQUE', 'FIRST', 'PRIORITY', 'ANY', 'COLLECT', 'RULE ORDER', 'OUTPUT ORDER'"#, @@ -23,7 +23,7 @@ fn _0002() { #[test] fn _0003() { - let definitions = parse(T_DMN_0003); + let definitions = from_xml(T_DMN_0003); assert!(definitions.is_err()); assert_eq!( r#" 'AVG' is not a valid aggregation, allowed values are: 'COUNT', 'SUM', 'MIN', 'MAX'"#, @@ -33,7 +33,7 @@ fn _0003() { #[test] fn _0004() { - let definitions = parse(T_DMN_0004); + let definitions = from_xml(T_DMN_0004); assert!(definitions.is_err()); assert_eq!( r#" required input expression in decision table's input clause is missing"#, @@ -43,14 +43,14 @@ fn _0004() { #[test] fn _0005() { - let definitions = parse(T_DMN_0005); + let definitions = from_xml(T_DMN_0005); assert!(definitions.is_err()); assert_eq!(r#" required expression instance is missing"#, format!("{}", definitions.err().unwrap())) } #[test] fn _0006() { - let definitions = parse(T_DMN_0006); + let definitions = from_xml(T_DMN_0006); assert!(definitions.is_err()); assert_eq!( r#" number of elements in a row differs from the number of columns defined in a relation"#, @@ -60,7 +60,7 @@ fn _0006() { #[test] fn _0007() { - let definitions = parse(T_DMN_0007); + let definitions = from_xml(T_DMN_0007); assert!(definitions.is_err()); assert_eq!( r#" parsing model from XML failed with reason: the root node was opened but never closed"#, @@ -70,7 +70,7 @@ fn _0007() { #[test] fn _0008() { - let definitions = parse(T_DMN_0008); + let definitions = from_xml(T_DMN_0008); assert!(definitions.is_err()); assert_eq!( r#" unexpected XML node, expected: definitions, actual: definition"#, @@ -80,7 +80,7 @@ fn _0008() { #[test] fn _0009() { - let definitions = parse(T_DMN_0009); + let definitions = from_xml(T_DMN_0009); assert!(definitions.is_err()); assert_eq!( r#" expected value for mandatory attribute 'namespace' in node 'definitions' at [2:1]"#, @@ -90,7 +90,7 @@ fn _0009() { #[test] fn _0010() { - let definitions = parse(T_DMN_0010); + let definitions = from_xml(T_DMN_0010); assert!(definitions.is_err()); assert_eq!( r#" expected value for mandatory attribute 'name' in node 'decision' at [11:5]"#, @@ -100,7 +100,7 @@ fn _0010() { #[test] fn _0011() { - let definitions = parse(T_DMN_0011); + let definitions = from_xml(T_DMN_0011); assert!(definitions.is_err()); assert_eq!( r#" expected mandatory text content in node 'text'"#, @@ -110,7 +110,7 @@ fn _0011() { #[test] fn _0012() { - let definitions = parse(T_DMN_0012); + let definitions = from_xml(T_DMN_0012); assert!(definitions.is_err()); assert_eq!( r#" conversion to valid color value failed with reason: number too large to fit in target type"#, @@ -120,7 +120,7 @@ fn _0012() { #[test] fn _0013() { - let definitions = parse(T_DMN_0013); + let definitions = from_xml(T_DMN_0013); assert!(definitions.is_err()); assert_eq!( r#" conversion to valid double value failed with reason: invalid float literal"#, @@ -130,7 +130,7 @@ fn _0013() { #[test] fn _0014() { - let definitions = parse(T_DMN_0014); + let definitions = from_xml(T_DMN_0014); assert!(definitions.is_err()); assert_eq!( r#" expected mandatory child node 'text' in parent node 'outputEntry' at [31:17]"#, @@ -140,13 +140,13 @@ fn _0014() { #[test] fn _0015() { - let definitions = parse(T_DMN_0015); + let definitions = from_xml(T_DMN_0015); assert!(definitions.is_ok()); } #[test] fn _0016() { - let definitions = parse(T_DMN_0016); + let definitions = from_xml(T_DMN_0016); assert!(definitions.is_err()); assert_eq!( r#" required child node 'Bounds' in parent node 'DMNShape' is missing"#, diff --git a/model/src/tests/validators/model_validator/item_definition_cycles.rs b/model/src/tests/validators/model_validator/item_definition_cycles.rs index 93bf41eb..cb60eb9d 100644 --- a/model/src/tests/validators/model_validator/item_definition_cycles.rs +++ b/model/src/tests/validators/model_validator/item_definition_cycles.rs @@ -1,17 +1,17 @@ //! # Test cases for cyclic dependencies between item definitions use super::test_files::*; -use crate::parse; +use crate::from_xml; #[test] fn _0001() { - assert!(parse(DMN_0001).is_ok()); + assert!(from_xml(DMN_0001).is_ok()); } #[test] fn _0002() { assert_eq!( " cyclic dependency between item definitions", - parse(DMN_1001).err().unwrap().to_string() + from_xml(DMN_1001).err().unwrap().to_string() ); } diff --git a/model/src/parser.rs b/model/src/xml_parser.rs similarity index 99% rename from model/src/parser.rs rename to model/src/xml_parser.rs index b134eb2f..973660d1 100644 --- a/model/src/parser.rs +++ b/model/src/xml_parser.rs @@ -9,7 +9,7 @@ use dsntk_feel::{Name, FEEL_TYPE_NAME_ANY}; use roxmltree::{Node, NodeType}; /// Parses the XML input document containing DMN model into [Definitions]. -pub fn parse(input: &str) -> Result { +pub fn from_xml(input: &str) -> Result { // parse document match roxmltree::Document::parse(input) { Ok(document) => { diff --git a/workspace/src/builder.rs b/workspace/src/builder.rs index 22bc4df7..96a7f8ec 100644 --- a/workspace/src/builder.rs +++ b/workspace/src/builder.rs @@ -125,7 +125,7 @@ impl WorkspaceBuilder { /// Loads decision model from specified file. fn load_model(&mut self, workspace_name: &str, file: &Path) { match fs::read_to_string(file) { - Ok(xml) => match dsntk_model::parse(&xml) { + Ok(xml) => match dsntk_model::from_xml(&xml) { Ok(definitions) => { let namespace = definitions.namespace().to_string(); if to_rdnn(&namespace).is_some() { From 14c9d471aa22dfa6a4494533214e72980da47c2e Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Mon, 17 Mar 2025 19:12:14 +0100 Subject: [PATCH 03/15] Updates. --- Cargo.lock | 2 +- Cargo.toml | 1 + Taskfile.yml | 5 + common/src/href.rs | 2 +- common/src/lib.rs | 2 + common/src/text_utils.rs | 4 + examples/Cargo.toml | 1 - examples/src/compatibility/level_2/2_0001.dmn | 83 +++-- examples/src/compatibility/level_2/2_0001.yml | 139 +++++---- examples/src/compatibility/level_2/mod.rs | 1 + examples/src/lib.rs | 15 - model/Cargo.toml | 5 +- model/src/errors.rs | 8 + model/src/lib.rs | 3 + model/src/model.rs | 60 ++-- model/src/xml_parser.rs | 124 ++++---- model/src/xml_utils.rs | 12 +- model/src/yaml_parser.rs | 289 ++++++++++++++++++ model/src/yaml_utils.rs | 106 +++++++ model/tests/compatibility/level_2/mod.rs | 1 + .../tests/compatibility/level_2/yml_2_0001.rs | 9 + model/tests/compatibility/mod.rs | 1 + model/tests/invalid_models/mod.rs | 1 + model/tests/invalid_models/yaml/basic.rs | 37 +++ model/tests/invalid_models/yaml/mod.rs | 1 + model/tests/mod.rs | 2 + 26 files changed, 705 insertions(+), 209 deletions(-) create mode 100644 common/src/text_utils.rs create mode 100644 model/src/yaml_parser.rs create mode 100644 model/src/yaml_utils.rs create mode 100644 model/tests/compatibility/level_2/mod.rs create mode 100644 model/tests/compatibility/level_2/yml_2_0001.rs create mode 100644 model/tests/compatibility/mod.rs create mode 100644 model/tests/invalid_models/mod.rs create mode 100644 model/tests/invalid_models/yaml/basic.rs create mode 100644 model/tests/invalid_models/yaml/mod.rs create mode 100644 model/tests/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 656bd46e..0a9fabbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -648,7 +648,6 @@ name = "dsntk-examples" version = "0.3.0-dev" dependencies = [ "walkdir", - "yaml-rust", ] [[package]] @@ -767,6 +766,7 @@ dependencies = [ "dsntk-recognizer", "petgraph", "roxmltree", + "yaml-rust", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 27125dd2..b1625e88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ uriparse = "0.6.4" url = "2.5.4" urlencoding = "2.1.3" walkdir = "2.5.0" +yaml-rust = "0.4.5" dsntk-common = { path = "common" } dsntk-evaluator = { path = "evaluator" } dsntk-examples = { path = "examples" } diff --git a/Taskfile.yml b/Taskfile.yml index 4ba305e6..30648a1c 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -233,6 +233,11 @@ tasks: cmds: - cmd: cargo +stable test -p dsntk-feel-parser + test-model: + desc: Runs tests in debug mode + cmds: + - cmd: cargo +stable test -p dsntk-model + test-recognizer: desc: Runs tests in debug mode cmds: diff --git a/common/src/href.rs b/common/src/href.rs index 05ae06fa..a1028d85 100644 --- a/common/src/href.rs +++ b/common/src/href.rs @@ -10,7 +10,7 @@ use crate::DsntkError; use uriparse::URIReference; /// URI reference used for utilizing `href` attribute. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct HRef { /// Namespace built from URI's path components. namespace: Option, diff --git a/common/src/lib.rs b/common/src/lib.rs index 7cdb41b4..3055f203 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -8,6 +8,7 @@ mod href; mod idents; mod jsonify; mod namespace; +mod text_utils; mod uri; pub use errors::{DsntkError, Result, ToErrorMessage}; @@ -15,4 +16,5 @@ pub use href::HRef; pub use idents::gen_id; pub use jsonify::Jsonify; pub use namespace::to_rdnn; +pub use text_utils::trim_multiline; pub use uri::{encode_segments, to_uri, Uri}; diff --git a/common/src/text_utils.rs b/common/src/text_utils.rs new file mode 100644 index 00000000..78ef0382 --- /dev/null +++ b/common/src/text_utils.rs @@ -0,0 +1,4 @@ +/// Trims whitespaces before and after each line, preserves empty lines. +pub fn trim_multiline(input: String) -> String { + input.trim().lines().map(|line| line.trim().to_string()).collect::>().join("\n") +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 6c6fe33f..4050e61a 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -12,4 +12,3 @@ edition = { workspace = true } [dev-dependencies] walkdir = { workspace = true } -yaml-rust = "0.4.5" diff --git a/examples/src/compatibility/level_2/2_0001.dmn b/examples/src/compatibility/level_2/2_0001.dmn index 991085bb..6db3b37d 100644 --- a/examples/src/compatibility/level_2/2_0001.dmn +++ b/examples/src/compatibility/level_2/2_0001.dmn @@ -1,6 +1,6 @@ - Compliance level 2: Test 0001 - - The decision named **Greeting Message** has a label defined in diagram definition. - - In the diagram this decision is depicted as **GREETING MESSAGE**. - - The output variable name remains **Greeting Message**. + + The decision named **Greeting Message** has a label defined in diagram definition. + In the diagram this decision is depicted as **GREETING MESSAGE**. + The output variable name remains **Greeting Message**. - + This decision prepares a greeting message. - 'Hello' is prepended to the value of the input variable named 'Full Name'. + 'Hello' is prepended to the value of the input variable named 'Full Name'. What is the greeting suitable for our customer? - The proper greeting is in the format: - Hello {customer's full name} + The proper greeting is in the format: Hello {customer's full name} - - + + - + "Hello " + Full Name - + Full name of the customer provided by calling service. @@ -45,31 +42,31 @@ Full name of the person that will be sent greetings from this decision model. - - - - - - - - GREETING MESSAGE - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/src/compatibility/level_2/2_0001.yml b/examples/src/compatibility/level_2/2_0001.yml index 2c4bce01..dce719ca 100644 --- a/examples/src/compatibility/level_2/2_0001.yml +++ b/examples/src/compatibility/level_2/2_0001.yml @@ -1,59 +1,90 @@ -model: - namespace: https://dsntk.io/2_0001/ - name: 2_001 - id: _2_0001 - xmlns: https://www.omg.org/spec/DMN/20191111/MODEL/ - description: | - Compliance level 2: Test 0001 - - The decision named **Greeting Message** has a label defined in diagram definition. - In the diagram this decision is depicted as **GREETING MESSAGE**. - - The output variable name remains **Greeting Message**. +namespace: https://decision-toolkit.org/2_0001/ +name: 2_0001 +id: _2_0001 +xmlns: https://www.omg.org/spec/DMN/20191111/MODEL/ +description: | + Compliance level 2: Test 0001 + + The decision named **Greeting Message** has a label defined in diagram definition. + In the diagram this decision is depicted as **GREETING MESSAGE**. + The output variable name remains **Greeting Message**. definitions: - - - decision: "Greeting Message" - id: _75b3add2-4d36-4a19-a76c-268b49b2f436 - description: | - This decision prepares a greeting message. - 'Hello' is prepended to the value of the input variable named 'Full Name'. - question: What is the greeting suitable for our customer? - allowedAnswers: | - The proper greeting is in the format: - Hello {customer's full name} - variable: + - decision: name: "Greeting Message" - label: "GREETING MESSAGE" - typeRef: string - informationRequirement: - id: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b - requiredInput: - href: #_cba86e4d-e91c-46a2-9176-e9adf88e15db - literalExpression: - text: | - "Hello " + Full Name - - - decision: "kuku" - description: | - This decision prepares a greeting message. - 'Hello' is prepended to the value of the input variable named 'Full Name'. - decision-table: - unicode: | - - unicode-file: a.uni - markdown: | - - markdown-file: b.md - - - input-data: "Full Name" - id: _cba86e4d-e91c-46a2-9176-e9adf88e15db - description: | - Full name of the customer provided by calling service. - variable: - typeRef: string + id: _75b3add2-4d36-4a19-a76c-268b49b2f436 + description: | + This decision prepares a greeting message. + 'Hello' is prepended to the value of the input variable named 'Full Name'. + question: | + What is the greeting suitable for our customer? + allowedAnswers: | + The proper greeting is in the format: Hello {customer's full name} + variable: + name: "Greeting Message" + label: "GREETING MESSAGE" + typeRef: string + id: _decision_greeting_message + informationRequirement: + - id: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + requiredInput: + href: "#_cba86e4d-e91c-46a2-9176-e9adf88e15db" + literalExpression: + id: _literal_expression + text: | + "Hello " + Full Name + - inputData: name: "Full Name" - label: "Customer's name" - id: _id_variable_full_name + id: _cba86e4d-e91c-46a2-9176-e9adf88e15db description: | - Full name of the person that will be sent greetings from this decision model. + Full name of the customer provided by calling service. + variable: + name: "Full Name" + label: "Customer's name" + typeRef: string + id: _id_variable_full_name + description: | + Full name of the person that will be sent greetings from this decision model. + +diagrams: + - diagram: + name: Decision Requirement Diagram + resolution: 300 + id: _d3a3312e-5924-4f7b-ac0e-232ef9203ff6 + size: 190.0 240.0 + shapes: + - id: _ebf33cfc-0ee3-4708-af8b-91c52237b7d6 + dmnElementRef: _75b3add2-4d36-4a19-a76c-268b49b2f436 + bounds: 20.0 20.0 150.0 60.0 + label: + text: GREETING MESSAGE + sharedStyle: style1 + sharedStyle: style1 + - id: _48ea7a1d-2575-4cb7-8b63-8baa4cb3b371 + dmnElementRef: _cba86e4d-e91c-46a2-9176-e9adf88e15db + bounds: 20.0 160.0 150.0 60.0 + sharedStyle: style2 + edges: + - id: _e9a73517-0ba2-4b31-b308-82279ae21591 + dmnElementRef: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + waypoints: + - waypoint: 95.0 160.0 + - waypoint: 95.0 80.0 + styles: + - id: style1 + fontFamily: "Arial,Helvetica,sans-serif" + fontSize: 18 + fontBold: true + fontUnderline: true + fontItalic: true + fontStrikeThrough: true + labelVerticalAlignment: start + fillColor: 10 255 255 + strokeColor: 255 0 0 + fontColor: 0 200 0 + - id: style2 + fontFamily: "Arial,sans-serif" + fontSize: 12 + fontBold: true + fontUnderline: true + strokeColor: 255 0 0 diff --git a/examples/src/compatibility/level_2/mod.rs b/examples/src/compatibility/level_2/mod.rs index 198ad116..51dba324 100644 --- a/examples/src/compatibility/level_2/mod.rs +++ b/examples/src/compatibility/level_2/mod.rs @@ -1,6 +1,7 @@ //! # Decision models for compatibility tests level 2 pub const DMN_2_0001: &str = include_str!("2_0001.dmn"); +pub const YAML_2_0001: &str = include_str!("2_0001.yml"); pub const DMN_2_0002: &str = include_str!("2_0002.dmn"); pub const DMN_2_0003: &str = include_str!("2_0003.dmn"); pub const DMN_2_0004: &str = include_str!("2_0004.dmn"); diff --git a/examples/src/lib.rs b/examples/src/lib.rs index ac159ceb..a4a7653b 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -21,9 +21,7 @@ pub use full_model::*; mod utilities { use std::collections::BTreeSet; use std::fmt::Write; - use std::fs; use walkdir::WalkDir; - use yaml_rust::YamlLoader; /// Generates multiple decision table variants. #[test] @@ -126,17 +124,4 @@ mod utilities { } results } - - #[test] - fn a() { - let content = fs::read_to_string("./src/compatibility/level_2/2_0001.yml").expect("failed to load yaml input file"); - //println!("{}", content); - let docs = YamlLoader::load_from_str(&content).unwrap(); - let doc = &docs[0]; - if let Some(definitions) = doc["definitions"].as_vec() { - for definition in definitions { - println!("{:?}", definition); - } - } - } } diff --git a/model/Cargo.toml b/model/Cargo.toml index 00b1d5c7..80f28ab0 100644 --- a/model/Cargo.toml +++ b/model/Cargo.toml @@ -13,9 +13,12 @@ edition = { workspace = true } [dependencies] petgraph = { workspace = true } roxmltree = { workspace = true } +yaml-rust = { workspace = true } dsntk-common = { workspace = true } -dsntk-examples = { workspace = true } dsntk-feel = { workspace = true } dsntk-feel-parser = { workspace = true } dsntk-macros = { workspace = true } dsntk-recognizer = { workspace = true } + +[dev-dependencies] +dsntk-examples = { workspace = true } diff --git a/model/src/errors.rs b/model/src/errors.rs index ce72d608..030f9616 100644 --- a/model/src/errors.rs +++ b/model/src/errors.rs @@ -94,6 +94,14 @@ pub fn err_xml_expected_mandatory_text_content(s: &str) -> DsntkError { ModelParserError(format!("expected mandatory text content in node '{s}'")).into() } +pub fn err_yaml_parsing_model_failed(s: &str) -> DsntkError { + ModelParserError(format!("parsing model from YAML failed with reason: {s}")).into() +} + +pub fn err_yaml_expected_mandatory_attribute(node_name: &str, attr_name: &str) -> DsntkError { + ModelParserError(format!("expected value for mandatory attribute '{attr_name}' in node {node_name}")).into() +} + /// Errors related with validating the decision model. #[derive(ToErrorMessage)] struct ModelValidatorError(String); diff --git a/model/src/lib.rs b/model/src/lib.rs index e9a9a401..5304a789 100644 --- a/model/src/lib.rs +++ b/model/src/lib.rs @@ -8,6 +8,9 @@ mod tests; mod validators; mod xml_parser; mod xml_utils; +mod yaml_parser; +mod yaml_utils; pub use model::*; pub use xml_parser::from_xml; +pub use yaml_parser::from_yaml; diff --git a/model/src/model.rs b/model/src/model.rs index 7f2aa27f..28573196 100644 --- a/model/src/model.rs +++ b/model/src/model.rs @@ -122,7 +122,7 @@ pub struct ExtensionElement; pub struct ExtensionAttribute; /// Enumeration of concrete instances of [BusinessContextElement]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum BusinessContextElementInstance { PerformanceIndicator(PerformanceIndicator), OrganizationUnit(OrganizationUnit), @@ -133,7 +133,7 @@ pub enum BusinessContextElementInstance { #[named_element] #[dmn_element] #[business_context_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct PerformanceIndicator { /// Collection of [Decision] that impact this [PerformanceIndicator]. /// This attribute stores references @@ -151,7 +151,7 @@ impl PerformanceIndicator { #[named_element] #[dmn_element] #[business_context_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct OrganizationUnit { /// Collection of [Decision] that are made by this [OrganizationUnit]. pub(crate) decisions_made: Vec, @@ -173,7 +173,7 @@ impl OrganizationUnit { /// All DMN elements are contained within [Definitions] and that have a graphical /// representation in a DRD. This enumeration specifies the list /// of [DRGElements](DrgElement) contained in [Definitions]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum DrgElement { Decision(Decision), @@ -196,7 +196,7 @@ pub enum Requirement { /// for all contained elements. #[named_element] #[dmn_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Definitions { /// This attribute identifies the expression language used in /// [LiteralExpressions](LiteralExpression) within the scope @@ -494,7 +494,7 @@ impl FeelTypedElement for InformationItem { /// are defined outside the decision model. #[named_element] #[dmn_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct InputData { /// The instance of [InformationItem] that stores the result of this [InputData]. pub(crate) variable: InformationItem, @@ -675,7 +675,7 @@ impl Binding { /// [Decision] #[named_element] #[dmn_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Decision { /// A natural language question that characterizes the [Decision], /// such that the output of the [Decision] is an answer to the question. @@ -736,7 +736,7 @@ impl Decision { /// The class [InformationRequirement] is used to model an information requirement, /// as represented by a plain arrow in a DRD. #[dmn_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct InformationRequirement { /// Reference to [Decision] that this [InformationRequirement] associates /// with its containing [Decision] element. @@ -761,7 +761,7 @@ impl InformationRequirement { /// The class [KnowledgeRequirement] is used to model a knowledge requirement, /// as represented by a dashed arrow in a DRD. #[dmn_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct KnowledgeRequirement { /// Reference to [Invocable] that this [KnowledgeRequirement] associates with /// its containing [Decision] or [BusinessKnowledgeModel] element. @@ -778,7 +778,7 @@ impl KnowledgeRequirement { /// The class [AuthorityRequirement] is used to model an authority requirement, /// as represented by an arrow drawn with a dashed line and a filled circular head in a DRD #[dmn_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct AuthorityRequirement { /// The instance of [KnowledgeSource] that this [AuthorityRequirement] associates /// with its containing [KnowledgeSource], [Decision] or [BusinessKnowledgeModel] element. @@ -810,7 +810,7 @@ impl AuthorityRequirement { /// In a DRD, an instance of [KnowledgeSource] is represented by a `knowledge source` diagram element. #[named_element] #[dmn_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct KnowledgeSource { /// Collection of the instances of [AuthorityRequirement] that compose this [Decision]. pub(crate) authority_requirements: Vec, @@ -830,7 +830,7 @@ impl KnowledgeSource { /// must be a single FEEL boxed function definition. #[named_element] #[dmn_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct BusinessKnowledgeModel { /// Variable that is bound to the function defined by the [FunctionDefinition] for this [BusinessKnowledgeModel]. pub(crate) variable: InformationItem, @@ -868,7 +868,7 @@ impl RequiredVariable for BusinessKnowledgeModel { /// against the decision model contained in an instance of [Definitions]. #[named_element] #[dmn_element] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct DecisionService { /// Variable for this [DecisionService]. pub(crate) variable: InformationItem, @@ -925,7 +925,7 @@ pub enum ItemDefinitionType { #[named_element] #[dmn_element] #[expression] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ItemDefinition { /// This attribute identifies the type language used to specify the base /// type of this [ItemDefinition]. This value overrides the type @@ -986,7 +986,7 @@ impl ItemDefinition { /// [UnaryTests] is used to model a boolean test, where the argument /// to be tested is implicit or denoted with a **?**. /// Test is specified by text in some specified expression language. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct UnaryTests { /// The text of this [UnaryTests]. /// It SHALL be a valid expression in the expressionLanguage. @@ -1011,7 +1011,7 @@ impl UnaryTests { /// [FunctionItem] defines the signature of a function: /// the parameters and the output type of the function. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct FunctionItem { /// Reference to output type of the function. pub(crate) output_type_ref: Option, @@ -1615,7 +1615,7 @@ pub struct AnnotationEntry { /// [Dmndi] is a container for the shared [DmnStyle](DmnStyle)s /// and all [DmnDiagram]s defined in [Definitions]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Dmndi { /// A list of shared [DmnStyle] that can be referenced /// by all [DmnDiagram] and [DmnDiagramElement]. @@ -1625,7 +1625,7 @@ pub struct Dmndi { } /// Defines possible elements of [DmnDiagramElement]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum DmnDiagramElement { DmnShape(DmnShape), DmnEdge(DmnEdge), @@ -1633,7 +1633,7 @@ pub enum DmnDiagramElement { /// [DmnDiagram] is the container of [DmnDiagramElement] ([DmnShape] (s) and [DmnEdge] (s)). /// [DmnDiagram] cannot include other [DmnDiagrams](DmnDiagram). -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct DmnDiagram { /// [DmnDiagram] id. pub id: Option, @@ -1657,7 +1657,7 @@ pub struct DmnDiagram { /// [DmnShape] represents a [Decision], a [BusinessKnowledgeModel], an [InputData] element, /// a [KnowledgeSource], a [DecisionService] or a [TextAnnotation] that is depicted on the diagram. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct DmnShape { /// Unique identifier of this [DmnShape]. pub id: Option, @@ -1688,7 +1688,7 @@ pub struct DmnShape { } /// Struct defines line inside [DecisionService]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct DmnDecisionServiceDividerLine { pub id: Option, /// A list of points relative to the origin of its parent [DmnDiagram] that specifies @@ -1701,7 +1701,7 @@ pub struct DmnDecisionServiceDividerLine { pub local_style: Option, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct DmnEdge { pub id: Option, /// A list of points relative to the origin of its parent [DmnDiagram] that specifies @@ -1735,7 +1735,7 @@ pub struct Association {} pub struct TextAnnotation {} /// [DmnStyle] is used to keep some non-normative visual attributes such as color and font. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct DmnStyle { /// A unique identifier for this style so it can be referenced. /// Only styles defined in the [Dmndi] can be referenced by [DmnDiagramElement] and [DmnDiagram]. @@ -1768,7 +1768,7 @@ pub struct DmnStyle { } /// Struct represents the depiction of some textual information about a DMN element. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct DmnLabel { /// The bounds of the [DmnLabel]. When not specified, the label is positioned /// at its default position as determined in clause 13.5. @@ -1781,7 +1781,7 @@ pub struct DmnLabel { } /// Defines RGB color. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct DcColor { pub red: u8, pub green: u8, @@ -1803,14 +1803,14 @@ impl DcColor { } /// Defines point. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct DcPoint { pub x: f64, pub y: f64, } /// Defines bounds. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct DcBounds { pub x: f64, pub y: f64, @@ -1819,14 +1819,14 @@ pub struct DcBounds { } /// Defines dimensions. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct DcDimension { pub width: f64, pub height: f64, } /// Defines the kind of element alignment. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum DcAlignmentKind { /// Left or top. Start, @@ -1837,7 +1837,7 @@ pub enum DcAlignmentKind { } /// Defines known colors. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum DcKnownColor { Aqua = 0x00FFFF, Black = 0x000000, diff --git a/model/src/xml_parser.rs b/model/src/xml_parser.rs index 973660d1..a704c570 100644 --- a/model/src/xml_parser.rs +++ b/model/src/xml_parser.rs @@ -16,7 +16,7 @@ pub fn from_xml(input: &str) -> Result { // firstly validate the document against the XML Schema let node = validate_schema(&document)?; // initialize the model parser - let mut model_parser = ModelParser::new(); + let mut model_parser = Parser::new(); // parse the model into definitions let definitions = model_parser.parse_definitions(&node)?; // validate the final model against several rules defined in specification @@ -26,15 +26,15 @@ pub fn from_xml(input: &str) -> Result { } } -/// XML parser for DMN model. -pub struct ModelParser { +/// DMN model parser from XML format. +struct Parser { /// Model namespace used in parsed definitions. namespace: String, /// Model name used in parsed definitions. model_name: String, } -impl ModelParser { +impl Parser { /// Creates new model parser. fn new() -> Self { Self { @@ -46,15 +46,15 @@ impl ModelParser { /// Parses model [Definitions]. fn parse_definitions(&mut self, node: &Node) -> Result { self.namespace = required_uri(node, ATTR_NAMESPACE)?; - self.model_name = required_attribute(node, ATTR_NAME)?; + self.model_name = required_name(node)?; let definitions = Definitions { namespace: self.namespace.clone(), model_name: self.model_name.clone(), - name: required_name(node)?, + name: self.model_name.clone(), feel_name: required_feel_name(node)?, id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), expression_language: optional_uri(node, ATTR_EXPRESSION_LANGUAGE)?, @@ -86,8 +86,8 @@ impl ModelParser { let name = required_name(node)?; let feel_name = required_feel_name(node)?; let id = optional_id(node); - let description = optional_child_optional_content(node, NODE_DESCRIPTION); - let label = optional_attribute(node, ATTR_LABEL); + let description = optional_description(node); + let label = optional_label(node); let extension_elements = self.parse_extension_elements(node); let extension_attributes = self.parse_extension_attributes(node); let type_language = optional_attribute(node, ATTR_TYPE_LANGUAGE); @@ -167,8 +167,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(child_node), - description: optional_child_optional_content(child_node, NODE_DESCRIPTION), - label: optional_attribute(child_node, ATTR_LABEL), + description: optional_description(child_node), + label: optional_label(child_node), extension_elements: self.parse_extension_elements(child_node), extension_attributes: self.parse_extension_attributes(child_node), name, @@ -195,12 +195,12 @@ impl ModelParser { name, feel_name, id: optional_id(child_node), - description: optional_child_optional_content(child_node, NODE_DESCRIPTION), - label: optional_attribute(child_node, ATTR_LABEL), + description: optional_description(child_node), + label: optional_label(child_node), extension_elements: self.parse_extension_elements(child_node), extension_attributes: self.parse_extension_attributes(child_node), - question: optional_child_optional_content(child_node, NODE_QUESTION), - allowed_answers: optional_child_optional_content(child_node, NODE_ALLOWED_ANSWERS), + question: optional_child_optional_content(child_node, NODE_QUESTION).map(|value| value.trim().to_string()), + allowed_answers: optional_child_optional_content(child_node, NODE_ALLOWED_ANSWERS).map(|value| value.trim().to_string()), variable, decision_logic: self.parse_optional_child_expression_instance(child_node)?, information_requirements: self.parse_information_requirements(child_node, NODE_INFORMATION_REQUIREMENT)?, @@ -227,8 +227,8 @@ impl ModelParser { name, feel_name, id: optional_id(child_node), - description: optional_child_optional_content(child_node, NODE_DESCRIPTION), - label: optional_attribute(child_node, ATTR_LABEL), + description: optional_description(child_node), + label: optional_label(child_node), extension_elements: self.parse_extension_elements(child_node), extension_attributes: self.parse_extension_attributes(child_node), variable, @@ -256,8 +256,8 @@ impl ModelParser { name, feel_name, id: optional_id(child_node), - description: optional_child_optional_content(child_node, NODE_DESCRIPTION), - label: optional_attribute(child_node, ATTR_LABEL), + description: optional_description(child_node), + label: optional_label(child_node), extension_elements: self.parse_extension_elements(child_node), extension_attributes: self.parse_extension_attributes(child_node), variable, @@ -279,8 +279,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(child_node), - description: optional_child_optional_content(child_node, NODE_DESCRIPTION), - label: optional_attribute(child_node, ATTR_LABEL), + description: optional_description(child_node), + label: optional_label(child_node), extension_elements: self.parse_extension_elements(child_node), extension_attributes: self.parse_extension_attributes(child_node), name: required_name(child_node)?, @@ -323,8 +323,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -355,8 +355,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(child_node), - description: optional_child_optional_content(child_node, NODE_DESCRIPTION), - label: optional_attribute(child_node, ATTR_LABEL), + description: optional_description(child_node), + label: optional_label(child_node), extension_elements: self.parse_extension_elements(child_node), extension_attributes: self.parse_extension_attributes(child_node), name: required_name(child_node)?, @@ -371,8 +371,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(child_node), - description: optional_child_optional_content(child_node, NODE_DESCRIPTION), - label: optional_attribute(child_node, ATTR_LABEL), + description: optional_description(child_node), + label: optional_label(child_node), extension_elements: self.parse_extension_elements(child_node), extension_attributes: self.parse_extension_attributes(child_node), name: required_name(child_node)?, @@ -394,8 +394,8 @@ impl ModelParser { namespace: required_uri(child_node, ATTR_NAMESPACE)?, model_name: self.model_name.clone(), id: optional_id(child_node), - description: optional_child_optional_content(child_node, NODE_DESCRIPTION), - label: optional_attribute(child_node, ATTR_LABEL), + description: optional_description(child_node), + label: optional_label(child_node), extension_elements: self.parse_extension_elements(child_node), extension_attributes: self.parse_extension_attributes(child_node), name: required_name(child_node)?, @@ -441,8 +441,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), name: required_name(node)?, @@ -484,8 +484,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), required_decision: optional_child_required_href(node, NODE_REQUIRED_DECISION)?, @@ -509,8 +509,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), required_knowledge: required_child_required_href(node, NODE_REQUIRED_KNOWLEDGE)?, @@ -533,8 +533,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), required_authority: optional_child_required_href(node, NODE_REQUIRED_AUTHORITY)?, @@ -638,8 +638,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -772,8 +772,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -804,8 +804,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -850,8 +850,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -875,8 +875,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -914,8 +914,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(row_node), - description: optional_child_optional_content(row_node, NODE_DESCRIPTION), - label: optional_attribute(row_node, ATTR_LABEL), + description: optional_description(row_node), + label: optional_label(row_node), extension_elements: self.parse_extension_elements(row_node), extension_attributes: self.parse_extension_attributes(row_node), type_ref: optional_attribute(row_node, ATTR_TYPE_REF), @@ -926,8 +926,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -951,8 +951,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -976,8 +976,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -1000,8 +1000,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -1025,8 +1025,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), @@ -1050,8 +1050,8 @@ impl ModelParser { namespace: self.namespace.clone(), model_name: self.model_name.clone(), id: optional_id(node), - description: optional_child_optional_content(node, NODE_DESCRIPTION), - label: optional_attribute(node, ATTR_LABEL), + description: optional_description(node), + label: optional_label(node), extension_elements: self.parse_extension_elements(node), extension_attributes: self.parse_extension_attributes(node), type_ref: optional_attribute(node, ATTR_TYPE_REF), diff --git a/model/src/xml_utils.rs b/model/src/xml_utils.rs index 92afc9bd..41ee673f 100644 --- a/model/src/xml_utils.rs +++ b/model/src/xml_utils.rs @@ -1,7 +1,7 @@ //! # XML utilities use crate::errors::*; -use dsntk_common::Result; +use dsntk_common::{trim_multiline, Result}; use roxmltree::Node; use std::str::FromStr; @@ -257,3 +257,13 @@ pub fn node_name_pos(node: &Node) -> String { pub fn name_eq(name: &str) -> impl Fn(&Node) -> bool + '_ { move |node: &Node| node.tag_name().name() == name } + +/// Returns the optional description. +pub fn optional_description(node: &Node) -> Option { + optional_child_optional_content(node, NODE_DESCRIPTION).map(trim_multiline) +} + +/// Returns the optional label. +pub fn optional_label(node: &Node) -> Option { + optional_attribute(node, ATTR_LABEL).map(|value| value.trim().to_string()) +} diff --git a/model/src/yaml_parser.rs b/model/src/yaml_parser.rs new file mode 100644 index 00000000..2637e08c --- /dev/null +++ b/model/src/yaml_parser.rs @@ -0,0 +1,289 @@ +//! # YAML parser for DMN model + +use super::errors::*; +use crate::yaml_utils::*; +use crate::{ + Decision, Definitions, DmnId, DrgElement, ExpressionInstance, ExtensionAttribute, ExtensionElement, InformationItem, InformationRequirement, InputData, LiteralExpression, +}; +use dsntk_common::{gen_id, Result}; +use dsntk_feel::{Name, FEEL_TYPE_NAME_ANY}; +use yaml_rust::{Yaml, YamlLoader}; + +/// Parses the YAML input document containing DMN model into [Definitions]. +pub fn from_yaml(input: &str) -> Result { + match YamlLoader::load_from_str(input) { + Ok(docs) => match docs.len() { + 0 => Err(err_yaml_parsing_model_failed("empty YAML model")), + 1 => { + let mut parser = Parser::new(); + parser.parse_definitions(&docs[0]) + } + other => Err(err_yaml_parsing_model_failed(&format!("expected only one document in YAML model, actual is {}", other))), + }, + Err(reason) => Err(err_yaml_parsing_model_failed(&reason.to_string())), + } +} + +/// DMN model parser from YAML format. +struct Parser { + /// Model namespace used in parsed definitions. + namespace: String, + /// Model name used in parsed definitions. + model_name: String, +} + +impl Parser { + /// Creates new model parser. + fn new() -> Self { + Self { + namespace: "".to_string(), + model_name: "".to_string(), + } + } + + /// Parses model [Definitions]. + fn parse_definitions(&mut self, yaml: &Yaml) -> Result { + let root = "(root)"; + self.namespace = required_uri(root, yaml, YAML_NAMESPACE)?; + self.model_name = required_name(root, yaml)?; + let definitions = Definitions { + namespace: self.namespace.clone(), + model_name: self.model_name.clone(), + name: self.model_name.clone(), + feel_name: required_feel_name(root, yaml)?, + id: optional_id(yaml), + description: optional_description(yaml), + label: optional_label(yaml), + extension_elements: self.parse_extension_elements(yaml), + extension_attributes: self.parse_extension_attributes(yaml), + expression_language: optional_uri(yaml, YAML_EXPRESSION_LANGUAGE)?, + type_language: optional_attribute(yaml, YAML_TYPE_LANGUAGE), + exporter: None, + exporter_version: None, + item_definitions: vec![], + drg_elements: self.parse_drg_elements(yaml)?, + business_context_elements: vec![], + imports: vec![], + dmndi: None, + }; + Ok(definitions) + } + + /// Parses DRG elements. + fn parse_drg_elements(&mut self, yaml: &Yaml) -> Result> { + let mut drg_elements = vec![]; + let mut input_data_items = vec![]; + let mut decision_items = vec![]; + if let Some(definitions) = yaml[YAML_DEFINITIONS].as_vec() { + for item in definitions { + if let Some(input_data_yaml) = scalar(item, YAML_INPUT_DATA) { + input_data_items.push(self.parse_input_data(input_data_yaml)?); + } + if let Some(decision_yaml) = scalar(item, YAML_DECISION) { + decision_items.push(self.parse_decision(decision_yaml)?); + } + } + } + drg_elements.append(&mut input_data_items); + drg_elements.append(&mut decision_items); + // drg_elements.append(&mut self.parse_business_knowledge_model_nodes(node)?); + // drg_elements.append(&mut self.parse_decision_services(node)?); + // drg_elements.append(&mut self.parse_knowledge_sources(node)?); + Ok(drg_elements) + } + + /// Parses [InputData]. + fn parse_input_data(&self, yaml: &Yaml) -> Result { + let name = required_name(YAML_INPUT_DATA, yaml)?; + let feel_name = required_feel_name(YAML_INPUT_DATA, yaml)?; + let variable = self + .parse_opt_information_item_child(yaml, YAML_VARIABLE)? + .unwrap_or(self.create_information_item(name.clone(), feel_name.clone())?); + let input_data = InputData { + namespace: self.namespace.clone(), + model_name: self.model_name.clone(), + id: optional_id(yaml), + description: optional_description(yaml), + label: optional_label(yaml), + extension_elements: self.parse_extension_elements(yaml), + extension_attributes: self.parse_extension_attributes(yaml), + name, + feel_name, + variable, + }; + Ok(DrgElement::InputData(input_data)) + } + + /// Parses [Decision]. + fn parse_decision(&mut self, yaml: &Yaml) -> Result { + let name = required_name(YAML_DECISION, yaml)?; + let feel_name = required_feel_name(YAML_DECISION, yaml)?; + let variable = self + .parse_opt_information_item_child(yaml, YAML_VARIABLE)? + .unwrap_or(self.create_information_item(name.clone(), feel_name.clone())?); + let decision = Decision { + namespace: self.namespace.clone(), + model_name: self.model_name.clone(), + name, + feel_name, + id: optional_id(yaml), + description: optional_description(yaml), + label: optional_label(yaml), + extension_elements: self.parse_extension_elements(yaml), + extension_attributes: self.parse_extension_attributes(yaml), + question: optional_attribute(yaml, YAML_QUESTION).map(|value| value.trim().to_string()), + allowed_answers: optional_attribute(yaml, YAML_ALLOWED_ANSWERS).map(|value| value.trim().to_string()), + variable, + decision_logic: self.parse_optional_child_expression_instance(yaml)?, + information_requirements: self.parse_information_requirements(yaml, YAML_INFORMATION_REQUIREMENT)?, + knowledge_requirements: vec![], // self.parse_knowledge_requirements(child_node, NODE_KNOWLEDGE_REQUIREMENT)?, + authority_requirements: vec![], // self.parse_authority_requirements(child_node, NODE_AUTHORITY_REQUIREMENT)?, + }; + Ok(DrgElement::Decision(decision)) + } + + fn parse_optional_child_expression_instance(&self, yaml: &Yaml) -> Result> { + // if let Some(context) = self.parse_optional_context(node)? { + // return Ok(Some(ExpressionInstance::Context(Box::new(context)))); + // } + // if let Some(decision_table) = self.parse_optional_decision_table(node)? { + // return Ok(Some(ExpressionInstance::DecisionTable(Box::new(decision_table)))); + // } + // if let Some(function_definition) = self.parse_optional_function_definition(node)? { + // return Ok(Some(ExpressionInstance::FunctionDefinition(Box::new(function_definition)))); + // } + // if let Some(invocation) = self.parse_optional_invocation(node)? { + // return Ok(Some(ExpressionInstance::Invocation(Box::new(invocation)))); + // } + // if let Some(list) = self.parse_optional_list(node)? { + // return Ok(Some(ExpressionInstance::List(Box::new(list)))); + // } + if let Some(literal_expression) = self.parse_optional_literal_expression(yaml) { + return Ok(Some(ExpressionInstance::LiteralExpression(Box::new(literal_expression)))); + } + // if let Some(relation) = self.parse_optional_relation(node)? { + // return Ok(Some(ExpressionInstance::Relation(Box::new(relation)))); + // } + // if let Some(conditional) = self.parse_optional_conditional(node)? { + // return Ok(Some(ExpressionInstance::Conditional(Box::new(conditional)))); + // } + // if let Some(filter) = self.parse_optional_filter(node)? { + // return Ok(Some(ExpressionInstance::Filter(Box::new(filter)))); + // } + // if let Some(r#for) = self.parse_optional_for(node)? { + // return Ok(Some(ExpressionInstance::For(Box::new(r#for)))); + // } + // if let Some(every) = self.parse_optional_every(node)? { + // return Ok(Some(ExpressionInstance::Every(Box::new(every)))); + // } + // if let Some(some) = self.parse_optional_some(node)? { + // return Ok(Some(ExpressionInstance::Some(Box::new(some)))); + // } + Ok(None) + } + + /// Searches for the literal expression attribute. + /// If found, then parses literal expression and returns it, otherwise returns [None]. + fn parse_optional_literal_expression(&self, yaml: &Yaml) -> Option { + scalar(yaml, YAML_LITERAL_EXPRESSION).map(|literal_expression_yaml| self.parse_literal_expression(literal_expression_yaml)) + } + + /// Parses [LiteralExpression]. + fn parse_literal_expression(&self, yaml: &Yaml) -> LiteralExpression { + LiteralExpression { + namespace: self.namespace.clone(), + model_name: self.model_name.clone(), + id: optional_id(yaml), + description: optional_description(yaml), + label: optional_label(yaml), + extension_elements: self.parse_extension_elements(yaml), + extension_attributes: self.parse_extension_attributes(yaml), + type_ref: optional_attribute(yaml, YAML_TYPE_REF), + text: optional_attribute(yaml, YAML_TEXT), + expression_language: optional_attribute(yaml, YAML_EXPRESSION_LANGUAGE), + imported_values: None, + } + } + + /// Parses an optional [InformationItem]. + fn parse_opt_information_item_child(&self, yaml: &Yaml, attr_name: &str) -> Result> { + Ok(if let Some(information_item_yaml) = scalar(yaml, attr_name) { + Some(self.parse_information_item(information_item_yaml)?) + } else { + None + }) + } + + /// Parses [InformationItem]. + fn parse_information_item(&self, yaml: &Yaml) -> Result { + Ok(InformationItem { + namespace: self.namespace.clone(), + model_name: self.model_name.clone(), + id: optional_id(yaml), + description: optional_description(yaml), + label: optional_label(yaml), + extension_elements: self.parse_extension_elements(yaml), + extension_attributes: self.parse_extension_attributes(yaml), + name: required_name("", yaml)?, + feel_name: required_feel_name("", yaml)?, + type_ref: optional_attribute(yaml, YAML_TYPE_REF).unwrap_or(FEEL_TYPE_NAME_ANY.to_string()), + feel_type: None, + }) + } + + /// Creates a new [InformationItem]. + fn create_information_item(&self, name: String, feel_name: Name) -> Result { + Ok(InformationItem { + namespace: self.namespace.clone(), + model_name: self.model_name.clone(), + id: DmnId::Generated(gen_id()), + description: None, + label: None, + extension_elements: vec![], + extension_attributes: vec![], + name, + feel_name, + type_ref: FEEL_TYPE_NAME_ANY.to_string(), + feel_type: None, + }) + } + + /// Parses a list of [InformationRequirement]. + fn parse_information_requirements(&mut self, node: &Yaml, child_name: &str) -> Result> { + let mut information_requirements = vec![]; + if let Some(items) = node[child_name].as_vec() { + for item in items { + information_requirements.push(self.parse_information_requirement(item)?); + } + } + Ok(information_requirements) + } + + /// Parses [InformationRequirement]. + fn parse_information_requirement(&mut self, yaml: &Yaml) -> Result { + let req = InformationRequirement { + namespace: self.namespace.clone(), + model_name: self.model_name.clone(), + id: optional_id(yaml), + description: optional_description(yaml), + label: optional_label(yaml), + extension_elements: self.parse_extension_elements(yaml), + extension_attributes: self.parse_extension_attributes(yaml), + required_decision: optional_attribute_required_href(yaml, YAML_REQUIRED_DECISION)?, + required_input: optional_attribute_required_href(yaml, YAML_REQUIRED_INPUT)?, + }; + Ok(req) + } + + /// Parses extension elements. + fn parse_extension_elements(&self, _yaml: &Yaml) -> Vec { + // Currently ignored. Ready for future development when needed. + vec![] + } + + /// Parses extension attributes. + fn parse_extension_attributes(&self, _yaml: &Yaml) -> Vec { + // Currently ignored. Ready for future development when needed. + vec![] + } +} diff --git a/model/src/yaml_utils.rs b/model/src/yaml_utils.rs new file mode 100644 index 00000000..a77cbbfb --- /dev/null +++ b/model/src/yaml_utils.rs @@ -0,0 +1,106 @@ +use crate::errors::*; +use crate::DmnId; +use dsntk_common::{gen_id, to_uri, trim_multiline, HRef, Result, Uri}; +use dsntk_feel::Name; +use yaml_rust::Yaml; + +pub const YAML_ALLOWED_ANSWERS: &str = "allowedAnswers"; +pub const YAML_DECISION: &str = "decision"; +pub const YAML_DEFINITIONS: &str = "definitions"; +const YAML_DESCRIPTION: &str = "description"; +pub const YAML_EXPRESSION_LANGUAGE: &str = "expressionLanguage"; +pub const YAML_HREF: &str = "href"; +const YAML_LABEL: &str = "label"; +pub const YAML_ID: &str = "id"; +pub const YAML_INFORMATION_REQUIREMENT: &str = "informationRequirement"; +pub const YAML_INPUT_DATA: &str = "inputData"; +pub const YAML_LITERAL_EXPRESSION: &str = "literalExpression"; +pub const YAML_NAME: &str = "name"; +pub const YAML_NAMESPACE: &str = "namespace"; +pub const YAML_QUESTION: &str = "question"; +pub const YAML_REQUIRED_DECISION: &str = "requiredDecision"; +pub const YAML_REQUIRED_INPUT: &str = "requiredInput"; +pub const YAML_TEXT: &str = "text"; +pub const YAML_TYPE_LANGUAGE: &str = "typeLanguage"; +pub const YAML_TYPE_REF: &str = "typeRef"; +pub const YAML_VARIABLE: &str = "variable"; + +/// Returns the value of the required attribute. +pub fn required_attribute(name: &str, yaml: &Yaml, attr_name: &str) -> Result { + let value = &yaml[attr_name]; + if value.is_badvalue() || value.is_null() || value.is_array() { + return Err(err_yaml_expected_mandatory_attribute(name, attr_name)); + } + let Some(string_value) = value.as_str() else { + return Err(err_yaml_expected_mandatory_attribute(name, attr_name)); + }; + Ok(string_value.to_string()) +} + +/// Returns the value of the optional attribute. +pub fn optional_attribute(yaml: &Yaml, attr_name: &str) -> Option { + let value = &yaml[attr_name]; + if value.is_null() || value.is_badvalue() || value.is_array() { + return None; + } + value.as_str().map(|s| s.trim().to_string()) +} + +/// Returns the required URI attribute. +pub fn required_uri(name: &str, yaml: &Yaml, attr_name: &str) -> Result { + to_uri(required_attribute(name, yaml, attr_name)?.as_str()) +} + +/// Returns an optional URI attribute. +pub fn optional_uri(node: &Yaml, attr_name: &str) -> Result> { + if let Some(value) = optional_attribute(node, attr_name) { + Ok(Some(to_uri(value.as_str())?)) + } else { + Ok(None) + } +} + +/// Returns required name attribute. +pub fn required_name(name: &str, yaml: &Yaml) -> Result { + required_attribute(name, yaml, YAML_NAME) +} + +/// Returns optional identifier if provided in the model, otherwise generates a new one. +pub fn optional_id(yaml: &Yaml) -> DmnId { + optional_attribute(yaml, YAML_ID).map(DmnId::Provided).unwrap_or(DmnId::Generated(gen_id())) +} + +/// Returns the required FEEL name. +pub fn required_feel_name(name: &str, node: &Yaml) -> Result { + let input = required_name(name, node)?; + Ok(dsntk_feel_parser::parse_longest_name(&input).unwrap_or(input.into())) +} + +/// Returns optional description. +pub fn optional_description(yaml: &Yaml) -> Option { + optional_attribute(yaml, YAML_DESCRIPTION).map(trim_multiline) +} + +/// Returns optional label. +pub fn optional_label(yaml: &Yaml) -> Option { + optional_attribute(yaml, YAML_LABEL).map(|value| value.trim().to_string()) +} + +/// Returns the required `href` attribute of the specified optional attribute. +pub fn optional_attribute_required_href(yaml: &Yaml, attr_name: &str) -> Result> { + if let Some(child_yaml) = scalar(yaml, attr_name) { + Ok(Some(HRef::try_from(required_attribute(attr_name, child_yaml, YAML_HREF)?.as_str())?)) + } else { + Ok(None) + } +} + +/// Returns a scalar attribute with specified name. +pub fn scalar<'a>(yaml: &'a Yaml, attr_name: &'a str) -> Option<&'a Yaml> { + let value = &yaml[attr_name]; + if value.is_badvalue() || value.is_null() || value.is_array() { + None + } else { + Some(value) + } +} diff --git a/model/tests/compatibility/level_2/mod.rs b/model/tests/compatibility/level_2/mod.rs new file mode 100644 index 00000000..baf34115 --- /dev/null +++ b/model/tests/compatibility/level_2/mod.rs @@ -0,0 +1 @@ +mod yml_2_0001; diff --git a/model/tests/compatibility/level_2/yml_2_0001.rs b/model/tests/compatibility/level_2/yml_2_0001.rs new file mode 100644 index 00000000..b44d4074 --- /dev/null +++ b/model/tests/compatibility/level_2/yml_2_0001.rs @@ -0,0 +1,9 @@ +use dsntk_examples::*; +use dsntk_model::{from_xml, from_yaml}; + +#[test] +fn _0001() { + let xml = from_xml(DMN_2_0001).unwrap(); + let yaml = from_yaml(YAML_2_0001).unwrap(); + assert_eq!(xml, yaml); +} diff --git a/model/tests/compatibility/mod.rs b/model/tests/compatibility/mod.rs new file mode 100644 index 00000000..96e49562 --- /dev/null +++ b/model/tests/compatibility/mod.rs @@ -0,0 +1 @@ +mod level_2; diff --git a/model/tests/invalid_models/mod.rs b/model/tests/invalid_models/mod.rs new file mode 100644 index 00000000..a651cea0 --- /dev/null +++ b/model/tests/invalid_models/mod.rs @@ -0,0 +1 @@ +mod yaml; diff --git a/model/tests/invalid_models/yaml/basic.rs b/model/tests/invalid_models/yaml/basic.rs new file mode 100644 index 00000000..489cb5af --- /dev/null +++ b/model/tests/invalid_models/yaml/basic.rs @@ -0,0 +1,37 @@ +use dsntk_model::from_yaml; + +#[test] +fn _0001() { + // Parsing an invalid file should fail. + let yaml = r#"key: one\n key: one"#; + assert_eq!( + " parsing model from YAML failed with reason: mapping values are not allowed in this context at line 1 column 15", + from_yaml(yaml).unwrap_err().to_string() + ); +} + +#[test] +fn _0002() { + // Parsing an empty file should fail. + let yaml = r#""#; + assert_eq!( + " parsing model from YAML failed with reason: empty YAML model", + from_yaml(yaml).unwrap_err().to_string() + ); +} + +#[test] +fn _0003() { + // Parsing multiple documents in one file should fail. + let yaml = r#" +key: "1st document" +--- +key: "2nd document" +--- +key: "3rd document" + "#; + assert_eq!( + " parsing model from YAML failed with reason: expected only one document in YAML model, actual is 3", + from_yaml(yaml).unwrap_err().to_string() + ); +} diff --git a/model/tests/invalid_models/yaml/mod.rs b/model/tests/invalid_models/yaml/mod.rs new file mode 100644 index 00000000..1bca5f8c --- /dev/null +++ b/model/tests/invalid_models/yaml/mod.rs @@ -0,0 +1 @@ +mod basic; diff --git a/model/tests/mod.rs b/model/tests/mod.rs new file mode 100644 index 00000000..91b83dcc --- /dev/null +++ b/model/tests/mod.rs @@ -0,0 +1,2 @@ +mod compatibility; +mod invalid_models; From 0fc6f120163849516e8e9e097fbb09f7a20361b5 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 10:19:32 +0100 Subject: [PATCH 04/15] Updates. --- examples/src/compatibility/level_2/2_0001.dmn | 8 +-- examples/src/compatibility/level_2/2_0001.yml | 34 +++++------- examples/src/compatibility/level_2/a.md | 1 - model/src/errors.rs | 4 +- model/src/yaml_parser.rs | 19 ++++--- model/src/yaml_utils.rs | 52 +++++++++---------- 6 files changed, 52 insertions(+), 66 deletions(-) delete mode 100644 examples/src/compatibility/level_2/a.md diff --git a/examples/src/compatibility/level_2/2_0001.dmn b/examples/src/compatibility/level_2/2_0001.dmn index 6db3b37d..f6140a58 100644 --- a/examples/src/compatibility/level_2/2_0001.dmn +++ b/examples/src/compatibility/level_2/2_0001.dmn @@ -17,7 +17,7 @@ This decision prepares a greeting message. - 'Hello' is prepended to the value of the input variable named 'Full Name'. + "Hello" is prepended to the value of the input variable named 'Full Name'. What is the greeting suitable for our customer? @@ -25,11 +25,11 @@ The proper greeting is in the format: Hello {customer's full name} - + - + "Hello " + Full Name @@ -38,7 +38,7 @@ Full name of the customer provided by calling service. - + Full name of the person that will be sent greetings from this decision model. diff --git a/examples/src/compatibility/level_2/2_0001.yml b/examples/src/compatibility/level_2/2_0001.yml index dce719ca..d33d2257 100644 --- a/examples/src/compatibility/level_2/2_0001.yml +++ b/examples/src/compatibility/level_2/2_0001.yml @@ -15,22 +15,22 @@ definitions: id: _75b3add2-4d36-4a19-a76c-268b49b2f436 description: | This decision prepares a greeting message. - 'Hello' is prepended to the value of the input variable named 'Full Name'. + "Hello" is prepended to the value of the input variable named 'Full Name'. question: | What is the greeting suitable for our customer? allowedAnswers: | The proper greeting is in the format: Hello {customer's full name} variable: name: "Greeting Message" + id: _3215b422-b937-4360-9d1c-4c677cae5dfd label: "GREETING MESSAGE" typeRef: string - id: _decision_greeting_message informationRequirement: - id: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b requiredInput: href: "#_cba86e4d-e91c-46a2-9176-e9adf88e15db" literalExpression: - id: _literal_expression + id: _5baa6245-f6fc-4685-8973-fa873817e2c1 text: | "Hello " + Full Name - inputData: @@ -40,21 +40,21 @@ definitions: Full name of the customer provided by calling service. variable: name: "Full Name" + id: _4bc2161f-2f3b-4260-b454-0a01aed0e46b label: "Customer's name" typeRef: string - id: _id_variable_full_name description: | Full name of the person that will be sent greetings from this decision model. diagrams: - diagram: name: Decision Requirement Diagram - resolution: 300 id: _d3a3312e-5924-4f7b-ac0e-232ef9203ff6 + resolution: 300 size: 190.0 240.0 shapes: - - id: _ebf33cfc-0ee3-4708-af8b-91c52237b7d6 - dmnElementRef: _75b3add2-4d36-4a19-a76c-268b49b2f436 + - dmnElementRef: _75b3add2-4d36-4a19-a76c-268b49b2f436 + id: _ebf33cfc-0ee3-4708-af8b-91c52237b7d6 bounds: 20.0 20.0 150.0 60.0 label: text: GREETING MESSAGE @@ -65,26 +65,18 @@ diagrams: bounds: 20.0 160.0 150.0 60.0 sharedStyle: style2 edges: - - id: _e9a73517-0ba2-4b31-b308-82279ae21591 - dmnElementRef: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + - dmnElementRef: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + id: _e9a73517-0ba2-4b31-b308-82279ae21591 waypoints: - - waypoint: 95.0 160.0 - - waypoint: 95.0 80.0 + - point: 95.0 160.0 + - point: 95.0 80.0 styles: - id: style1 - fontFamily: "Arial,Helvetica,sans-serif" - fontSize: 18 - fontBold: true - fontUnderline: true - fontItalic: true - fontStrikeThrough: true + font: bold underline italic strikethrough 18 "Arial, Helvetica,sans-serif" labelVerticalAlignment: start fillColor: 10 255 255 strokeColor: 255 0 0 fontColor: 0 200 0 - id: style2 - fontFamily: "Arial,sans-serif" - fontSize: 12 - fontBold: true - fontUnderline: true + font: bold underline 12 "Arial, sans-serif" strokeColor: 255 0 0 diff --git a/examples/src/compatibility/level_2/a.md b/examples/src/compatibility/level_2/a.md deleted file mode 100644 index ddad7e07..00000000 --- a/examples/src/compatibility/level_2/a.md +++ /dev/null @@ -1 +0,0 @@ -kuku diff --git a/model/src/errors.rs b/model/src/errors.rs index 030f9616..ec414ae6 100644 --- a/model/src/errors.rs +++ b/model/src/errors.rs @@ -98,8 +98,8 @@ pub fn err_yaml_parsing_model_failed(s: &str) -> DsntkError { ModelParserError(format!("parsing model from YAML failed with reason: {s}")).into() } -pub fn err_yaml_expected_mandatory_attribute(node_name: &str, attr_name: &str) -> DsntkError { - ModelParserError(format!("expected value for mandatory attribute '{attr_name}' in node {node_name}")).into() +pub fn err_yaml_expected_mandatory_attribute(attr_name: &str) -> DsntkError { + ModelParserError(format!("expected value for mandatory attribute '{attr_name}'")).into() } /// Errors related with validating the decision model. diff --git a/model/src/yaml_parser.rs b/model/src/yaml_parser.rs index 2637e08c..9c6f22f4 100644 --- a/model/src/yaml_parser.rs +++ b/model/src/yaml_parser.rs @@ -43,14 +43,13 @@ impl Parser { /// Parses model [Definitions]. fn parse_definitions(&mut self, yaml: &Yaml) -> Result { - let root = "(root)"; - self.namespace = required_uri(root, yaml, YAML_NAMESPACE)?; - self.model_name = required_name(root, yaml)?; + self.namespace = required_uri(yaml, YAML_NAMESPACE)?; + self.model_name = required_name(yaml)?; let definitions = Definitions { namespace: self.namespace.clone(), model_name: self.model_name.clone(), name: self.model_name.clone(), - feel_name: required_feel_name(root, yaml)?, + feel_name: required_feel_name(yaml)?, id: optional_id(yaml), description: optional_description(yaml), label: optional_label(yaml), @@ -94,8 +93,8 @@ impl Parser { /// Parses [InputData]. fn parse_input_data(&self, yaml: &Yaml) -> Result { - let name = required_name(YAML_INPUT_DATA, yaml)?; - let feel_name = required_feel_name(YAML_INPUT_DATA, yaml)?; + let name = required_name(yaml)?; + let feel_name = required_feel_name(yaml)?; let variable = self .parse_opt_information_item_child(yaml, YAML_VARIABLE)? .unwrap_or(self.create_information_item(name.clone(), feel_name.clone())?); @@ -116,8 +115,8 @@ impl Parser { /// Parses [Decision]. fn parse_decision(&mut self, yaml: &Yaml) -> Result { - let name = required_name(YAML_DECISION, yaml)?; - let feel_name = required_feel_name(YAML_DECISION, yaml)?; + let name = required_name(yaml)?; + let feel_name = required_feel_name(yaml)?; let variable = self .parse_opt_information_item_child(yaml, YAML_VARIABLE)? .unwrap_or(self.create_information_item(name.clone(), feel_name.clone())?); @@ -224,8 +223,8 @@ impl Parser { label: optional_label(yaml), extension_elements: self.parse_extension_elements(yaml), extension_attributes: self.parse_extension_attributes(yaml), - name: required_name("", yaml)?, - feel_name: required_feel_name("", yaml)?, + name: required_name(yaml)?, + feel_name: required_feel_name(yaml)?, type_ref: optional_attribute(yaml, YAML_TYPE_REF).unwrap_or(FEEL_TYPE_NAME_ANY.to_string()), feel_type: None, }) diff --git a/model/src/yaml_utils.rs b/model/src/yaml_utils.rs index a77cbbfb..f7ec3103 100644 --- a/model/src/yaml_utils.rs +++ b/model/src/yaml_utils.rs @@ -26,43 +26,39 @@ pub const YAML_TYPE_REF: &str = "typeRef"; pub const YAML_VARIABLE: &str = "variable"; /// Returns the value of the required attribute. -pub fn required_attribute(name: &str, yaml: &Yaml, attr_name: &str) -> Result { - let value = &yaml[attr_name]; - if value.is_badvalue() || value.is_null() || value.is_array() { - return Err(err_yaml_expected_mandatory_attribute(name, attr_name)); - } - let Some(string_value) = value.as_str() else { - return Err(err_yaml_expected_mandatory_attribute(name, attr_name)); - }; - Ok(string_value.to_string()) +pub fn required_attribute(yaml: &Yaml, attr_name: &str) -> Result { + Ok( + scalar(yaml, attr_name) + .ok_or(err_yaml_expected_mandatory_attribute(attr_name))? + .as_str() + .ok_or(err_yaml_expected_mandatory_attribute(attr_name))? + .trim() + .to_string(), + ) } /// Returns the value of the optional attribute. pub fn optional_attribute(yaml: &Yaml, attr_name: &str) -> Option { - let value = &yaml[attr_name]; - if value.is_null() || value.is_badvalue() || value.is_array() { - return None; - } - value.as_str().map(|s| s.trim().to_string()) + scalar(yaml, attr_name).map(|value| value.as_str().map(|value| value.trim().to_string())).flatten() } /// Returns the required URI attribute. -pub fn required_uri(name: &str, yaml: &Yaml, attr_name: &str) -> Result { - to_uri(required_attribute(name, yaml, attr_name)?.as_str()) +pub fn required_uri(yaml: &Yaml, attr_name: &str) -> Result { + to_uri(required_attribute(yaml, attr_name)?.as_str()) } /// Returns an optional URI attribute. pub fn optional_uri(node: &Yaml, attr_name: &str) -> Result> { - if let Some(value) = optional_attribute(node, attr_name) { - Ok(Some(to_uri(value.as_str())?)) + Ok(if let Some(value) = optional_attribute(node, attr_name) { + Some(to_uri(value.as_str())?) } else { - Ok(None) - } + None + }) } /// Returns required name attribute. -pub fn required_name(name: &str, yaml: &Yaml) -> Result { - required_attribute(name, yaml, YAML_NAME) +pub fn required_name(yaml: &Yaml) -> Result { + required_attribute(yaml, YAML_NAME) } /// Returns optional identifier if provided in the model, otherwise generates a new one. @@ -71,8 +67,8 @@ pub fn optional_id(yaml: &Yaml) -> DmnId { } /// Returns the required FEEL name. -pub fn required_feel_name(name: &str, node: &Yaml) -> Result { - let input = required_name(name, node)?; +pub fn required_feel_name(node: &Yaml) -> Result { + let input = required_name(node)?; Ok(dsntk_feel_parser::parse_longest_name(&input).unwrap_or(input.into())) } @@ -88,11 +84,11 @@ pub fn optional_label(yaml: &Yaml) -> Option { /// Returns the required `href` attribute of the specified optional attribute. pub fn optional_attribute_required_href(yaml: &Yaml, attr_name: &str) -> Result> { - if let Some(child_yaml) = scalar(yaml, attr_name) { - Ok(Some(HRef::try_from(required_attribute(attr_name, child_yaml, YAML_HREF)?.as_str())?)) + Ok(if let Some(child_yaml) = scalar(yaml, attr_name) { + Some(HRef::try_from(required_attribute(child_yaml, YAML_HREF)?.as_str())?) } else { - Ok(None) - } + None + }) } /// Returns a scalar attribute with specified name. From b7c84ca65d4fa71cae499a78130d72120cf8f3e8 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 10:23:31 +0100 Subject: [PATCH 05/15] Updates. --- examples/src/compatibility/level_2/2_0001.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/compatibility/level_2/2_0001.yml b/examples/src/compatibility/level_2/2_0001.yml index d33d2257..922e92f8 100644 --- a/examples/src/compatibility/level_2/2_0001.yml +++ b/examples/src/compatibility/level_2/2_0001.yml @@ -72,11 +72,11 @@ diagrams: - point: 95.0 80.0 styles: - id: style1 - font: bold underline italic strikethrough 18 "Arial, Helvetica,sans-serif" + font: bold underline italic strikethrough 18 Arial, Helvetica, sans-serif labelVerticalAlignment: start fillColor: 10 255 255 strokeColor: 255 0 0 fontColor: 0 200 0 - id: style2 - font: bold underline 12 "Arial, sans-serif" + font: bold underline 12 Arial, "Fira Sans", sans-serif strokeColor: 255 0 0 From 69d5fe628804382d0c63f0e12c08f84f3a1b5929 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:06:59 +0100 Subject: [PATCH 06/15] Updates. --- examples/src/compatibility/level_2/2_0001.dm | 83 +++++++++++++++++++ examples/src/compatibility/level_2/2_0001.yml | 2 +- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 examples/src/compatibility/level_2/2_0001.dm diff --git a/examples/src/compatibility/level_2/2_0001.dm b/examples/src/compatibility/level_2/2_0001.dm new file mode 100644 index 00000000..b49562b6 --- /dev/null +++ b/examples/src/compatibility/level_2/2_0001.dm @@ -0,0 +1,83 @@ +MODEL + NAMESPACE https://decision-toolkit.org/2_0001/ + NAME 2_0001 + ID _2_0001 + VERSION https://www.omg.org/spec/DMN/20191111/MODEL/ + DESCRIPTION + Compliance level 2: Test 0001 + + The decision named **Greeting Message** has a label defined in diagram definition. + In the diagram this decision is depicted as **GREETING MESSAGE**. + The output variable name remains **Greeting Message**. + +definitions: + - decision: + name: "Greeting Message" + id: _75b3add2-4d36-4a19-a76c-268b49b2f436 + description: | + This decision prepares a greeting message. + "Hello" is prepended to the value of the input variable named 'Full Name'. + question: | + What is the greeting suitable for our customer? + allowedAnswers: | + The proper greeting is in the format: Hello {customer's full name} + variable: + name: "Greeting Message" + id: _3215b422-b937-4360-9d1c-4c677cae5dfd + typeRef: string + label: "GREETING MESSAGE" + informationRequirement: + - id: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + requiredInput: + href: "#_cba86e4d-e91c-46a2-9176-e9adf88e15db" + literalExpression: + id: _5baa6245-f6fc-4685-8973-fa873817e2c1 + text: | + "Hello " + Full Name + - inputData: + name: "Full Name" + id: _cba86e4d-e91c-46a2-9176-e9adf88e15db + description: | + Full name of the customer provided by calling service. + variable: + name: "Full Name" + id: _4bc2161f-2f3b-4260-b454-0a01aed0e46b + label: "Customer's name" + typeRef: string + description: | + Full name of the person that will be sent greetings from this decision model. + +diagrams: + - diagram: + name: Decision Requirement Diagram + id: _d3a3312e-5924-4f7b-ac0e-232ef9203ff6 + resolution: 300 + size: 190.0 240.0 + shapes: + - dmnElementRef: _75b3add2-4d36-4a19-a76c-268b49b2f436 + id: _ebf33cfc-0ee3-4708-af8b-91c52237b7d6 + bounds: 20.0 20.0 150.0 60.0 + label: + text: GREETING MESSAGE + sharedStyle: style1 + sharedStyle: style1 + - id: _48ea7a1d-2575-4cb7-8b63-8baa4cb3b371 + dmnElementRef: _cba86e4d-e91c-46a2-9176-e9adf88e15db + bounds: 20.0 160.0 150.0 60.0 + sharedStyle: style2 + edges: + - dmnElementRef: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + id: _e9a73517-0ba2-4b31-b308-82279ae21591 + waypoints: + - point: 95.0 160.0 + - point: 95.0 80.0 + styles: + - id: style1 + font: bold underline italic strikethrough 18 Arial, Helvetica, sans-serif + labelVerticalAlignment: start + fillColor: 10 255 255 + strokeColor: 255 0 0 + fontColor: 0 200 0 + - id: style2 + font: bold underline 12 Arial, "Fira Sans", sans-serif + strokeColor: 255 0 0 diff --git a/examples/src/compatibility/level_2/2_0001.yml b/examples/src/compatibility/level_2/2_0001.yml index 922e92f8..23743ac5 100644 --- a/examples/src/compatibility/level_2/2_0001.yml +++ b/examples/src/compatibility/level_2/2_0001.yml @@ -23,8 +23,8 @@ definitions: variable: name: "Greeting Message" id: _3215b422-b937-4360-9d1c-4c677cae5dfd - label: "GREETING MESSAGE" typeRef: string + label: "GREETING MESSAGE" informationRequirement: - id: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b requiredInput: From b0aac2ac8a68b099e51b1a101b9d3dc843641c5f Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:22:42 +0100 Subject: [PATCH 07/15] Updates. --- examples/src/compatibility/level_2/2_0001.dm | 128 +++++++++---------- 1 file changed, 62 insertions(+), 66 deletions(-) diff --git a/examples/src/compatibility/level_2/2_0001.dm b/examples/src/compatibility/level_2/2_0001.dm index b49562b6..efb40659 100644 --- a/examples/src/compatibility/level_2/2_0001.dm +++ b/examples/src/compatibility/level_2/2_0001.dm @@ -10,74 +10,70 @@ MODEL In the diagram this decision is depicted as **GREETING MESSAGE**. The output variable name remains **Greeting Message**. -definitions: - - decision: - name: "Greeting Message" - id: _75b3add2-4d36-4a19-a76c-268b49b2f436 - description: | +DECISION + NAME Greeting Message + ID _75b3add2-4d36-4a19-a76c-268b49b2f436 + DESCRIPTION This decision prepares a greeting message. "Hello" is prepended to the value of the input variable named 'Full Name'. - question: | + QUESTION What is the greeting suitable for our customer? - allowedAnswers: | + ANSWERS The proper greeting is in the format: Hello {customer's full name} - variable: - name: "Greeting Message" - id: _3215b422-b937-4360-9d1c-4c677cae5dfd - typeRef: string - label: "GREETING MESSAGE" - informationRequirement: - - id: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b - requiredInput: - href: "#_cba86e4d-e91c-46a2-9176-e9adf88e15db" - literalExpression: - id: _5baa6245-f6fc-4685-8973-fa873817e2c1 - text: | - "Hello " + Full Name - - inputData: - name: "Full Name" - id: _cba86e4d-e91c-46a2-9176-e9adf88e15db - description: | - Full name of the customer provided by calling service. - variable: - name: "Full Name" - id: _4bc2161f-2f3b-4260-b454-0a01aed0e46b - label: "Customer's name" - typeRef: string - description: | - Full name of the person that will be sent greetings from this decision model. + VARIABLE + NAME Greeting Message + ID _3215b422-b937-4360-9d1c-4c677cae5dfd + TYPE-REF string + LABEL `GREETING MESSAGE` + INFORMATION-REQUIREMENT + ID _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + INPUT #_cba86e4d-e91c-46a2-9176-e9adf88e15db + LITERAL-EXPRESSION + ID _5baa6245-f6fc-4685-8973-fa873817e2c1 + TEXT "Hello " + Full Name + +INPUT-DATA + NAME Full Name + ID _cba86e4d-e91c-46a2-9176-e9adf88e15db + DESCRIPTION Full name of the customer provided by calling service. + VARIABLE + NAME "Full Name" + ID _4bc2161f-2f3b-4260-b454-0a01aed0e46b + LABEL Customer's name + TYPE-REF string + DESCRIPTION + Full name of the person that will be sent greetings from this decision model. -diagrams: - - diagram: - name: Decision Requirement Diagram - id: _d3a3312e-5924-4f7b-ac0e-232ef9203ff6 - resolution: 300 - size: 190.0 240.0 - shapes: - - dmnElementRef: _75b3add2-4d36-4a19-a76c-268b49b2f436 - id: _ebf33cfc-0ee3-4708-af8b-91c52237b7d6 - bounds: 20.0 20.0 150.0 60.0 - label: - text: GREETING MESSAGE - sharedStyle: style1 - sharedStyle: style1 - - id: _48ea7a1d-2575-4cb7-8b63-8baa4cb3b371 - dmnElementRef: _cba86e4d-e91c-46a2-9176-e9adf88e15db - bounds: 20.0 160.0 150.0 60.0 - sharedStyle: style2 - edges: - - dmnElementRef: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b - id: _e9a73517-0ba2-4b31-b308-82279ae21591 - waypoints: - - point: 95.0 160.0 - - point: 95.0 80.0 - styles: - - id: style1 - font: bold underline italic strikethrough 18 Arial, Helvetica, sans-serif - labelVerticalAlignment: start - fillColor: 10 255 255 - strokeColor: 255 0 0 - fontColor: 0 200 0 - - id: style2 - font: bold underline 12 Arial, "Fira Sans", sans-serif - strokeColor: 255 0 0 +DIAGRAM + NAME Decision Requirement Diagram + ID _d3a3312e-5924-4f7b-ac0e-232ef9203ff6 + RESOLUTION 300 + SIZE 190.0 240.0 + SHAPE + DMN-ELEMENT-REF _75b3add2-4d36-4a19-a76c-268b49b2f436 + ID _ebf33cfc-0ee3-4708-af8b-91c52237b7d6 + BOUNDS 20.0 20.0 150.0 60.0 + LABEL + TEXT `GREETING MESSAGE` + SHARED-STYLE style1 + SHARED-STYLE style1 + SHAPE + DMN-ELEMENT-REF _cba86e4d-e91c-46a2-9176-e9adf88e15db + ID _48ea7a1d-2575-4cb7-8b63-8baa4cb3b371 + BOUNDS 20.0 160.0 150.0 60.0 + SHARED-STYLE style2 + EDGE + ID _e9a73517-0ba2-4b31-b308-82279ae21591 + DMN-ELEMENT-REF: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + WAYPOINTS 95.0 160.0, 95.0 80.0 + STYLE + ID style1 + FONT bold underline italic strikethrough 18 Arial, Helvetica, sans-serif + LABEL-VERTICAL-ALIGN start + FILL-COLOR 10 255 255 + STROKE-COLOR 255 0 0 + FONT-COLOR 0 200 0 + STYLE + ID style2 + FONT bold underline 12 Arial, "Fira Sans", sans-serif + STROKE-COLOR 255 0 0 From 496309f08efcf287070e8270e0bd58c52571d40d Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:23:29 +0100 Subject: [PATCH 08/15] Updates. --- examples/src/compatibility/level_2/2_0001.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/compatibility/level_2/2_0001.dm b/examples/src/compatibility/level_2/2_0001.dm index efb40659..deec3bd7 100644 --- a/examples/src/compatibility/level_2/2_0001.dm +++ b/examples/src/compatibility/level_2/2_0001.dm @@ -28,7 +28,7 @@ DECISION INFORMATION-REQUIREMENT ID _70c3f69a-63f3-4197-96ce-b206c8bd2a6b INPUT #_cba86e4d-e91c-46a2-9176-e9adf88e15db - LITERAL-EXPRESSION + LITERAL_EXPRESSION ID _5baa6245-f6fc-4685-8973-fa873817e2c1 TEXT "Hello " + Full Name From ee7e19751d63a1ac509a8a3fb3c701903e5146e3 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:27:35 +0100 Subject: [PATCH 09/15] Updates. --- examples/src/compatibility/level_2/2_0001.dm | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/src/compatibility/level_2/2_0001.dm b/examples/src/compatibility/level_2/2_0001.dm index deec3bd7..803a246e 100644 --- a/examples/src/compatibility/level_2/2_0001.dm +++ b/examples/src/compatibility/level_2/2_0001.dm @@ -23,16 +23,16 @@ DECISION VARIABLE NAME Greeting Message ID _3215b422-b937-4360-9d1c-4c677cae5dfd - TYPE-REF string + TYPE REF string LABEL `GREETING MESSAGE` - INFORMATION-REQUIREMENT + INFORMATION_REQUIREMENT ID _70c3f69a-63f3-4197-96ce-b206c8bd2a6b INPUT #_cba86e4d-e91c-46a2-9176-e9adf88e15db LITERAL_EXPRESSION ID _5baa6245-f6fc-4685-8973-fa873817e2c1 TEXT "Hello " + Full Name -INPUT-DATA +INPUT_DATA NAME Full Name ID _cba86e4d-e91c-46a2-9176-e9adf88e15db DESCRIPTION Full name of the customer provided by calling service. @@ -40,7 +40,7 @@ INPUT-DATA NAME "Full Name" ID _4bc2161f-2f3b-4260-b454-0a01aed0e46b LABEL Customer's name - TYPE-REF string + TYPE_REF string DESCRIPTION Full name of the person that will be sent greetings from this decision model. @@ -50,30 +50,30 @@ DIAGRAM RESOLUTION 300 SIZE 190.0 240.0 SHAPE - DMN-ELEMENT-REF _75b3add2-4d36-4a19-a76c-268b49b2f436 + DMN_ELEMENT_REF _75b3add2-4d36-4a19-a76c-268b49b2f436 ID _ebf33cfc-0ee3-4708-af8b-91c52237b7d6 BOUNDS 20.0 20.0 150.0 60.0 LABEL TEXT `GREETING MESSAGE` SHARED-STYLE style1 - SHARED-STYLE style1 + SHARED_STYLE style1 SHAPE - DMN-ELEMENT-REF _cba86e4d-e91c-46a2-9176-e9adf88e15db + DMN_ELEMENT_REF _cba86e4d-e91c-46a2-9176-e9adf88e15db ID _48ea7a1d-2575-4cb7-8b63-8baa4cb3b371 BOUNDS 20.0 160.0 150.0 60.0 - SHARED-STYLE style2 + SHARED_STYLE style2 EDGE ID _e9a73517-0ba2-4b31-b308-82279ae21591 - DMN-ELEMENT-REF: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b + DMN_ELEMENT_REF: _70c3f69a-63f3-4197-96ce-b206c8bd2a6b WAYPOINTS 95.0 160.0, 95.0 80.0 STYLE ID style1 FONT bold underline italic strikethrough 18 Arial, Helvetica, sans-serif - LABEL-VERTICAL-ALIGN start - FILL-COLOR 10 255 255 - STROKE-COLOR 255 0 0 - FONT-COLOR 0 200 0 + LABEL_VERTICAL_ALIGN start + FILL_COLOR 10 255 255 + STROKE_COLOR 255 0 0 + FONT_COLOR 0 200 0 STYLE ID style2 FONT bold underline 12 Arial, "Fira Sans", sans-serif - STROKE-COLOR 255 0 0 + STROKE_COLOR 255 0 0 From 29cfffc6298a262c99e96dc0f7ecdc4452759957 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:28:23 +0100 Subject: [PATCH 10/15] Updates. --- examples/src/compatibility/level_2/2_0001.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/compatibility/level_2/2_0001.dm b/examples/src/compatibility/level_2/2_0001.dm index 803a246e..ecb9b13c 100644 --- a/examples/src/compatibility/level_2/2_0001.dm +++ b/examples/src/compatibility/level_2/2_0001.dm @@ -23,8 +23,8 @@ DECISION VARIABLE NAME Greeting Message ID _3215b422-b937-4360-9d1c-4c677cae5dfd - TYPE REF string - LABEL `GREETING MESSAGE` + TYPE_REF string + LABEL "GREETING MESSAGE" INFORMATION_REQUIREMENT ID _70c3f69a-63f3-4197-96ce-b206c8bd2a6b INPUT #_cba86e4d-e91c-46a2-9176-e9adf88e15db From dec4483fd87f937a1d8c79eaf714a8548c6062f8 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:50:12 +0100 Subject: [PATCH 11/15] Updates. --- examples/src/compatibility/level_2/{2_0001.dm => 2_0001.dml} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename examples/src/compatibility/level_2/{2_0001.dm => 2_0001.dml} (96%) diff --git a/examples/src/compatibility/level_2/2_0001.dm b/examples/src/compatibility/level_2/2_0001.dml similarity index 96% rename from examples/src/compatibility/level_2/2_0001.dm rename to examples/src/compatibility/level_2/2_0001.dml index ecb9b13c..a759833f 100644 --- a/examples/src/compatibility/level_2/2_0001.dm +++ b/examples/src/compatibility/level_2/2_0001.dml @@ -18,8 +18,9 @@ DECISION "Hello" is prepended to the value of the input variable named 'Full Name'. QUESTION What is the greeting suitable for our customer? - ANSWERS - The proper greeting is in the format: Hello {customer's full name} + ALLOWED_ANSWERS + The proper greeting is in the format: + Hello {customer's full name} VARIABLE NAME Greeting Message ID _3215b422-b937-4360-9d1c-4c677cae5dfd From 2e7961899200309aa9ab7c1be1693adab68821a1 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:03:19 +0100 Subject: [PATCH 12/15] Updates. --- examples/src/compatibility/level_2/{2_0001.dml => 2_0001.dm} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/src/compatibility/level_2/{2_0001.dml => 2_0001.dm} (100%) diff --git a/examples/src/compatibility/level_2/2_0001.dml b/examples/src/compatibility/level_2/2_0001.dm similarity index 100% rename from examples/src/compatibility/level_2/2_0001.dml rename to examples/src/compatibility/level_2/2_0001.dm From 8e1199ba3f8103bac278786be19f2330aab176f1 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:07:13 +0100 Subject: [PATCH 13/15] Updates. --- examples/src/compatibility/level_2/{2_0001.dm => 2_0001.dml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/src/compatibility/level_2/{2_0001.dm => 2_0001.dml} (100%) diff --git a/examples/src/compatibility/level_2/2_0001.dm b/examples/src/compatibility/level_2/2_0001.dml similarity index 100% rename from examples/src/compatibility/level_2/2_0001.dm rename to examples/src/compatibility/level_2/2_0001.dml From 7706ee8c1393a32a21e64f5858c3b2aae73cc44d Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:08:34 +0100 Subject: [PATCH 14/15] Updates. --- examples/src/compatibility/level_2/{2_0001.dml => 2_0001.dmm} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/src/compatibility/level_2/{2_0001.dml => 2_0001.dmm} (100%) diff --git a/examples/src/compatibility/level_2/2_0001.dml b/examples/src/compatibility/level_2/2_0001.dmm similarity index 100% rename from examples/src/compatibility/level_2/2_0001.dml rename to examples/src/compatibility/level_2/2_0001.dmm From f3092993ace7387809481cf3b619bbb1f543ce6f Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Thu, 20 Mar 2025 13:53:32 +0100 Subject: [PATCH 15/15] Updates. --- model-evaluator/src/tests/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/model-evaluator/src/tests/mod.rs b/model-evaluator/src/tests/mod.rs index caad0e39..d789ddc2 100644 --- a/model-evaluator/src/tests/mod.rs +++ b/model-evaluator/src/tests/mod.rs @@ -5,8 +5,7 @@ use dsntk_feel::FeelScope; use dsntk_model::DmnElement; use std::collections::{BTreeMap, BTreeSet}; use std::fs; -use std::sync::Arc; -use std::sync::LazyLock; +use std::sync::{Arc, LazyLock}; use walkdir::WalkDir; mod compatibility;