From 0164cea7e7b3b4f0a636caaabaa502cfd08edc41 Mon Sep 17 00:00:00 2001 From: Kyle Shores Date: Mon, 16 Oct 2023 11:22:47 -0500 Subject: [PATCH] adding a schema validator --- include/micm/configure/solver_config.hpp | 59 +++++++++++-- .../configure/process/test_surface_config.cpp | 9 ++ test/unit/unit_configs/MZ326/reactions.json | 87 ------------------- test/unit/unit_configs/MZ326/species.json | 40 --------- .../contains_nonstandard_key/reactions.json | 20 +++++ .../contains_nonstandard_key/species.json | 24 +++++ .../contains_nonstandard_key}/tolerance.json | 0 .../process/surface/valid/reactions.json | 1 + 8 files changed, 108 insertions(+), 132 deletions(-) delete mode 100644 test/unit/unit_configs/MZ326/reactions.json delete mode 100644 test/unit/unit_configs/MZ326/species.json create mode 100644 test/unit/unit_configs/process/surface/contains_nonstandard_key/reactions.json create mode 100644 test/unit/unit_configs/process/surface/contains_nonstandard_key/species.json rename test/unit/unit_configs/{MZ326 => process/surface/contains_nonstandard_key}/tolerance.json (100%) diff --git a/include/micm/configure/solver_config.hpp b/include/micm/configure/solver_config.hpp index ea004b40e..4d35b8537 100644 --- a/include/micm/configure/solver_config.hpp +++ b/include/micm/configure/solver_config.hpp @@ -39,7 +39,8 @@ namespace micm CAMPDataSectionNotFound, InvalidMechanism, ObjectTypeNotFound, - RequiredKeyNotFound + RequiredKeyNotFound, + ContainsNonStandardKey }; inline std::string configParseStatusToString(const ConfigParseStatus& status) @@ -59,6 +60,7 @@ namespace micm case ConfigParseStatus::InvalidMechanism: return "InvalidMechanism"; case ConfigParseStatus::ObjectTypeNotFound: return "ObjectTypeNotFound"; case ConfigParseStatus::RequiredKeyNotFound: return "RequiredKeyNotFound"; + case ConfigParseStatus::ContainsNonStandardKey: return "ContainsNonStandardKey"; default: return "Unknown"; } } @@ -744,10 +746,13 @@ namespace micm const std::string PRODUCTS = "gas-phase products"; const std::string MUSICA_NAME = "MUSICA name"; const std::string PROBABILITY = "reaction probability"; - for (const auto& key : { REACTANTS, PRODUCTS, MUSICA_NAME }) - { - if (!ValidateJsonWithKey(object, key)) - return ConfigParseStatus::RequiredKeyNotFound; + + std::vector object_keys; + for (auto& [key, value] : object.items()) + object_keys.push_back(key); + auto status = ValidateSchema(object_keys, { "type", REACTANTS, PRODUCTS, MUSICA_NAME }, {PROBABILITY}); + if (status != ConfigParseStatus::Success) { + return status; } std::string species_name = object[REACTANTS].get(); @@ -780,6 +785,50 @@ namespace micm return ConfigParseStatus::Success; } + + + /// @brief Search for nonstandard keys. Only nonstandard keys starting with __ are allowed. Others are considered typos + /// @param object_keys the keys of the object + /// @param required_keys The required keys + /// @param optional_keys The optional keys + /// @return true if only standard keys are found + ConfigParseStatus ValidateSchema(const std::vector& object_keys, const std::vector& required_keys, const std::vector& optional_keys) { + // standard keys are: + // those in required keys + // those in optional keys + // starting with __ + // anything else is reported as an error so that typos are caught, specifically for optional keys + + auto sorted_object_keys = object_keys; + auto sorted_required_keys = required_keys; + auto sorted_optional_keys = optional_keys; + std::sort(sorted_object_keys.begin(), sorted_object_keys.end()); + std::sort(sorted_required_keys.begin(), sorted_required_keys.end()); + std::sort(sorted_optional_keys.begin(), sorted_optional_keys.end()); + + + // get the difference between the object keys and those required + // what's left should be the optional keys and valid comments + std::vector difference; + std::set_difference(sorted_object_keys.begin(), sorted_object_keys.end(), sorted_required_keys.begin(), sorted_required_keys.end(), std::back_inserter(difference)); + + // check that the number of keys remaining is exactly equal to the expected number of required keys + if (difference.size() != (object_keys.size() - required_keys.size())) { + return ConfigParseStatus::RequiredKeyNotFound; + } + + std::vector remaining; + std::set_difference(difference.begin(), difference.end(), optional_keys.begin(), optional_keys.end(), std::back_inserter(remaining)); + + // now, anything left must be standard comment starting with __ + for(auto& key : remaining) + { + if (!key.starts_with("__")) { + return ConfigParseStatus::ContainsNonStandardKey; + } + } + return ConfigParseStatus::Success; + } }; /// @brief Public interface to read and parse config diff --git a/test/unit/configure/process/test_surface_config.cpp b/test/unit/configure/process/test_surface_config.cpp index 2c14cc1db..b996e9793 100644 --- a/test/unit/configure/process/test_surface_config.cpp +++ b/test/unit/configure/process/test_surface_config.cpp @@ -64,3 +64,12 @@ TEST(SurfaceConfig, ParseConfig) EXPECT_EQ(surface_rate_constant->mean_free_speed_factor_, 8.0 * GAS_CONSTANT / (M_PI * 0.321)); } } + + +TEST(SurfaceConfig, DetectsNonstandardKeys) +{ + micm::SolverConfig solver_config; + + micm::ConfigParseStatus status = solver_config.ReadAndParse("./unit_configs/process/surface/contains_nonstandard_key"); + EXPECT_EQ(micm::ConfigParseStatus::ContainsNonStandardKey, status); +} diff --git a/test/unit/unit_configs/MZ326/reactions.json b/test/unit/unit_configs/MZ326/reactions.json deleted file mode 100644 index 1903b5e94..000000000 --- a/test/unit/unit_configs/MZ326/reactions.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "comments": [ "This mechanism may contain refactored reactions based on custom", - "...." - ], - "camp-data": [ - { - "name": "MZ326_TS1.3_20230106", - "type": "MECHANISM", - "reactions": [ - { - "type": "TROE", - "k0_A": 5.5e-30, - "kinf_A": 8.3e-13, - "N": -2, - "reactants": { - "C2H2": { }, - "OH": { } - }, - "products": { - "GLYOXAL": { "yield": 0.65 }, - "OH": { "yield": 0.65 }, - "HCOOH": { "yield": 0.35 }, - "HO2": { "yield": 0.35 }, - "CO": { "yield": 0.35 } - } - }, - { - "type": "ARRHENIUS", - "A": 2.0e-12, - "Ea": -6.90325e-21, - "reactants": { - "CH3O2": { }, - "TERPO2": { } - }, - "products": { - "TERPROD1": { }, - "CH2O": { "yield": 0.95 }, - "CH3OH": { "yield": 0.25 }, - "HO2": { }, - "CH3COCH3": { "yield": 0.025 } - } - }, - { - "type": "ARRHENIUS", - "A": 3.8e-12, - "Ea": -2.7613e-21, - "reactants": { - "C3H7OOH": { }, - "OH": { } - }, - "products": { - "H2O": { }, - "C3H7O2": { } - } - }, - { - "type": "PHOTOLYSIS", - "MUSICA name": "jterpnit", - "reactants": { - "TERPNIT": { } - }, - "products": { - "TERPROD1": { }, - "NO2": { }, - "HO2": { } - } - }, - { - "type": "TROE", - "k0_A": 1.07767, - "k0_B": -5.6, - "k0_C": -14000, - "kinf_A": 1.03323e+17, - "kinf_C": -14000, - "N": 1.5, - "reactants": { - "PAN": { } - }, - "products": { - "CH3CO3": { }, - "NO2": { } - } - } - ] - }] -} - diff --git a/test/unit/unit_configs/MZ326/species.json b/test/unit/unit_configs/MZ326/species.json deleted file mode 100644 index 42572e3db..000000000 --- a/test/unit/unit_configs/MZ326/species.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "camp-data": [ - { - "name": "ALKNIT", - "type": "CHEM_SPEC", - "description": "standard alkyl nitrate from BIGALK+OH chemistry", - "molecular weight [kg mol-1]": 0.133141 - }, - { - "name": "BZOOH", - "type": "CHEM_SPEC", - "description": "hydroperoxide from toluene oxidation", - "molecular weight [kg mol-1]": 0.124135 - }, - { - "name": "C6H5OOH", - "type": "CHEM_SPEC", - "description": "phenyl hydroperoxide", - "molecular weight [kg mol-1]": 0.110109 - }, - { - "name": "COF2", - "type": "CHEM_SPEC", - "description": "", - "molecular weight [kg mol-1]": 0.0 - }, - { - "name": "O2", - "type": "CHEM_SPEC", - "description": "", - "molecular weight [kg mol-1]": 0.0 - }, - { - "name": "FUR2O2", - "type": "CHEM_SPEC", - "description": "peroxy radical from furanone", - "molecular weight [kg mol-1]": 0.0 - } - ] -} \ No newline at end of file diff --git a/test/unit/unit_configs/process/surface/contains_nonstandard_key/reactions.json b/test/unit/unit_configs/process/surface/contains_nonstandard_key/reactions.json new file mode 100644 index 000000000..746737687 --- /dev/null +++ b/test/unit/unit_configs/process/surface/contains_nonstandard_key/reactions.json @@ -0,0 +1,20 @@ +{ + "camp-data": [ + { + "name": "Valid surface reactions", + "type": "MECHANISM", + "reactions": [ + { + "type": "SURFACE", + "gas-phase reactant": "foo", + "gas-phase products": { + "bar": { "yield": 1.0 }, + "baz": { "yield": 3.2 } + }, + "MUSICA name": "kfoo", + "mUSICA name": "kfoo" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/process/surface/contains_nonstandard_key/species.json b/test/unit/unit_configs/process/surface/contains_nonstandard_key/species.json new file mode 100644 index 000000000..50a902e10 --- /dev/null +++ b/test/unit/unit_configs/process/surface/contains_nonstandard_key/species.json @@ -0,0 +1,24 @@ +{ + "camp-data": [ + { + "name": "foo", + "type": "CHEM_SPEC", + "molecular weight [kg mol-1]" : 0.123, + "diffusion coefficient [m2 s-1]" : 2.3e-4 + }, + { + "name": "bar", + "type": "CHEM_SPEC", + "molecular weight [kg mol-1]" : 0.321, + "diffusion coefficient [m2 s-1]" : 0.4e-5 + }, + { + "name": "baz", + "type": "CHEM_SPEC" + }, + { + "name": "quz", + "type": "CHEM_SPEC" + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/MZ326/tolerance.json b/test/unit/unit_configs/process/surface/contains_nonstandard_key/tolerance.json similarity index 100% rename from test/unit/unit_configs/MZ326/tolerance.json rename to test/unit/unit_configs/process/surface/contains_nonstandard_key/tolerance.json diff --git a/test/unit/unit_configs/process/surface/valid/reactions.json b/test/unit/unit_configs/process/surface/valid/reactions.json index a86b68473..bb0348806 100644 --- a/test/unit/unit_configs/process/surface/valid/reactions.json +++ b/test/unit/unit_configs/process/surface/valid/reactions.json @@ -5,6 +5,7 @@ "type": "MECHANISM", "reactions": [ { + "__my comment": "asdf", "type": "SURFACE", "gas-phase reactant": "foo", "gas-phase products": {