LCOV - code coverage report
Current view: top level - apps/stateRuleEngine - indiCompRuleConfig.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 82.6 % 247 204
Test Date: 2026-04-15 19:34:29 Functions: 100.0 % 5 5

            Line data    Source code
       1              : /** \file indiCompRuleConfig.hpp
       2              :  * \brief Configuration of rules for the MagAO-X stateRuleEngine
       3              :  *
       4              :  * \ingroup stateRuleEngine_files
       5              :  */
       6              : 
       7              : #ifndef stateRuleEngine_indiCompRuleConfig_hpp
       8              : #define stateRuleEngine_indiCompRuleConfig_hpp
       9              : 
      10              : #include <map>
      11              : 
      12              : #include "indiCompRules.hpp"
      13              : 
      14              : /// Structure to provide management of the rule and property maps
      15              : /** This owns all pointers in the rule engine, and `delete`s them on destruction.
      16              :  */
      17              : struct indiRuleMaps
      18              : {
      19              :     typedef std::map<std::string, indiCompRule *>      ruleMapT;
      20              :     typedef std::map<std::string, pcf::IndiProperty *> propMapT;
      21              : 
      22              :     ruleMapT rules;
      23              :     propMapT props;
      24              : 
      25           20 :     ~indiRuleMaps()
      26              :     {
      27           20 :         auto rit = rules.begin();
      28           47 :         while( rit != rules.end() )
      29              :         {
      30           27 :             delete rit->second;
      31           27 :             ++rit;
      32              :         }
      33              : 
      34           20 :         auto pit = props.begin();
      35           43 :         while( pit != props.end() )
      36              :         {
      37           23 :             delete pit->second;
      38           23 :             ++pit;
      39              :         }
      40           20 :     }
      41              : };
      42              : 
      43              : /* Structure used to hold ruleVal rule keys aside for final processing
      44              :    ruleVal rules can be created before the rules they link exist, so
      45              :    we hold the keys aside and set the pointers after all rules are created.
      46              : */
      47              : struct ruleRuleKeys
      48              : {
      49              :     std::string rule1;
      50              :     std::string rule2;
      51              : };
      52              : 
      53              : /// Extract a property from a rule configuration
      54              : /** Reads the property and element, adding the property to the property map if necessary.
      55              :  *
      56              :  * \throws mx::err::invalidconfig if the property is already in the map but of a different type
      57              :  */
      58           24 : void extractRuleProp(
      59              :     pcf::IndiProperty **prop,    ///< [out] pointer to the property, newly created or existing, which is in the map.
      60              :     std::string        &element, ///< [out] the element name from the configuration
      61              :     indiRuleMaps       &maps,    ///< [in] contains the property map to which the property is added
      62              :     const std::string  &section, ///< [in] name of the section for this rule
      63              :     const std::string  &propkey, ///< [in] the key for the property name
      64              :     const std::string  &elkey,   ///< [in] the key for the element name
      65              :     const pcf::IndiProperty::Type &type,  ///< [in] the type of the property
      66              :     mx::app::appConfigurator      &config ///< [in] the application configuration structure
      67              : )
      68              : {
      69           24 :     std::string property;
      70           24 :     config.configUnused( property, mx::app::iniFile::makeKey( section, propkey ) );
      71              : 
      72           24 :     if( maps.props.count( property ) > 0 )
      73              :     {
      74              :         // If the property already exists we just check if it's the right type
      75            1 :         if( maps.props[property]->getType() != type )
      76              :         {
      77              :             throw mx::exception( mx::error_t::invalidconfig,
      78            0 :                                  "property " + property + " exists but is not correct type" );
      79              :         }
      80              : 
      81            1 :         *prop = maps.props[property];
      82              :     }
      83              :     else
      84              :     {
      85              :         // Otherwise we create it
      86           23 :         *prop = new pcf::IndiProperty( type );
      87           23 :         maps.props.insert( std::pair<std::string, pcf::IndiProperty *>( { property, *prop } ) );
      88              : 
      89              :         ///\todo have to split device and propertyName
      90              :     }
      91              : 
      92           24 :     config.configUnused( element, mx::app::iniFile::makeKey( section, elkey ) );
      93           24 : }
      94              : 
      95              : /// \cond
      96              : // strip leading and trailing whitespace and then opening and closing "".  leaves spaces between "".
      97           28 : inline void stripQuotesWS( std::string &str )
      98              : {
      99           28 :     if( str.size() == 0 )
     100              :     {
     101           28 :         return;
     102              :     }
     103              : 
     104            0 :     if( str[0] != '\"' && str[0] != ' ' && str.back() != ' ' ) // get out fast if we can
     105              :     {
     106            0 :         return;
     107              :     }
     108              : 
     109              :     // strip white space at front
     110            0 :     size_t ns = str.find_first_not_of( " \t\r\n" );
     111            0 :     if( ns != std::string::npos && ns != 0 )
     112              :     {
     113            0 :         str.erase( 0, ns );
     114              : 
     115            0 :         if( str.size() == 0 )
     116              :         {
     117            0 :             return;
     118              :         }
     119              :     }
     120            0 :     else if( ns == std::string::npos ) // the rare all spaces
     121              :     {
     122            0 :         str = "";
     123            0 :         return;
     124              :     }
     125              : 
     126              :     // strip white space at back
     127            0 :     ns = str.find_last_not_of( " \t\r\n" );
     128            0 :     if( ns != std::string::npos && ns != str.size() - 1 )
     129              :     {
     130            0 :         str.erase( ns + 1 );
     131              : 
     132            0 :         if( str.size() == 0 )
     133              :         {
     134            0 :             return;
     135              :         }
     136              :     }
     137              : 
     138            0 :     if( str[0] == '\"' && str.back() == '\"' )
     139              :     {
     140            0 :         if( str.size() == 1 || str.size() == 2 )
     141              :         {
     142            0 :             str = "";
     143            0 :             return;
     144              :         }
     145            0 :         str.erase( str.size() - 1, 1 );
     146            0 :         str.erase( 0, 1 );
     147              :     }
     148            0 :     else if( str[0] == '\"' )
     149              :     {
     150            0 :         if( str.size() == 1 )
     151              :         {
     152            0 :             str = "";
     153            0 :             return;
     154              :         }
     155            0 :         str.erase( 0, 1 );
     156              :     }
     157            0 :     else if( str.back() == '\"' )
     158              :     {
     159            0 :         if( str.size() == 1 || str.size() == 2 )
     160              :         {
     161            0 :             str = "";
     162            0 :             return;
     163              :         }
     164            0 :         str.erase( str.size() - 1, 1 );
     165              :     }
     166              : }
     167              : /// \endcond
     168              : 
     169              : /// Load the rule and properties maps for a rule engine from a configuration file
     170              : /** ///\todo check for insertion failure
     171              :  * ///\todo add a constructor that has priority, message, and comparison, to reduce duplication
     172              :  */
     173           19 : void loadRuleConfig( indiRuleMaps &maps,                          /**< [out] contains the rule and property maps in
     174              :                                                                              which to place the items found in config */
     175              :                      std::map<std::string, ruleRuleKeys> &rrkMap, /**< [out] Holds the ruleVal rule keys aside for
     176              :                                                                              later post-processing*/
     177              :                      mx::app::appConfigurator &config             /**< [in] the application configuration structure */
     178              : )
     179              : {
     180           19 :     std::vector<std::string> sections;
     181              : 
     182           19 :     config.unusedSections( sections );
     183              : 
     184           19 :     if( sections.size() == 0 )
     185              :     {
     186            3 :         throw mx::exception( mx::error_t::invalidconfig, "no rules found in config" );
     187              :     }
     188              : 
     189           43 :     for( size_t i = 0; i < sections.size(); ++i )
     190              :     {
     191           28 :         bool ruleTypeSet = config.isSetUnused( mx::app::iniFile::makeKey( sections[i], "ruleType" ) );
     192              : 
     193              :         // If there is no ruleType then this isn't a rule
     194           28 :         if( !ruleTypeSet )
     195              :         {
     196            0 :             continue;
     197              :         }
     198              : 
     199              :         // If the rule already exists this is an error
     200           28 :         if( maps.rules.count( sections[i] ) != 0 )
     201              :         {
     202            0 :             throw mx::exception( mx::error_t::invalidconfig, "duplicate rule: " + sections[i] );
     203              :         }
     204              : 
     205           28 :         std::string ruleType;
     206           56 :         config.configUnused( ruleType, mx::app::iniFile::makeKey( sections[i], "ruleType" ) );
     207              : 
     208           56 :         std::string priostr = "none";
     209           28 :         config.configUnused( priostr, mx::app::iniFile::makeKey( sections[i], "priority" ) );
     210           28 :         rulePriority priority = string2priority( priostr );
     211              : 
     212           28 :         std::string message;
     213           28 :         config.configUnused( message, mx::app::iniFile::makeKey( sections[i], "message" ) );
     214           28 :         stripQuotesWS( message ); // strips "" and any leading/trailing whitespace
     215              : 
     216           56 :         std::string compstr = "Eq";
     217           28 :         config.configUnused( compstr, mx::app::iniFile::makeKey( sections[i], "comp" ) );
     218           28 :         ruleComparison comparison = string2comp( compstr );
     219              : 
     220           28 :         if( ruleType == numValRule::name )
     221              :         {
     222            2 :             numValRule *nvr = new numValRule;
     223            2 :             maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], nvr } ) );
     224              : 
     225            2 :             nvr->priority( priority );
     226            2 :             nvr->message( message );
     227            2 :             nvr->comparison( comparison );
     228              : 
     229            2 :             pcf::IndiProperty *prop = nullptr;
     230            2 :             std::string        element;
     231              : 
     232            8 :             extractRuleProp(
     233            4 :                 &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Number, config );
     234            2 :             nvr->property( prop );
     235            2 :             nvr->element( element );
     236              : 
     237            2 :             double target = nvr->target();
     238            2 :             config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
     239            2 :             nvr->target( target );
     240              : 
     241            2 :             double tol = nvr->tol();
     242            2 :             config.configUnused( tol, mx::app::iniFile::makeKey( sections[i], "tol" ) );
     243            2 :             nvr->tol( tol );
     244            2 :         }
     245           26 :         else if( ruleType == txtValRule::name )
     246              :         {
     247            9 :             txtValRule *tvr = new txtValRule;
     248            9 :             maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], tvr } ) );
     249              : 
     250            9 :             tvr->priority( priority );
     251            9 :             tvr->message( message );
     252            9 :             tvr->comparison( comparison );
     253              : 
     254            9 :             pcf::IndiProperty *prop = nullptr;
     255            9 :             std::string        element;
     256              : 
     257           36 :             extractRuleProp(
     258           18 :                 &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Text, config );
     259            9 :             tvr->property( prop );
     260            9 :             tvr->element( element );
     261              : 
     262            9 :             std::string target = tvr->target();
     263            9 :             config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
     264            9 :             tvr->target( target );
     265            9 :         }
     266           17 :         else if( ruleType == swValRule::name )
     267              :         {
     268            3 :             swValRule *svr = new swValRule;
     269            3 :             maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], svr } ) );
     270              : 
     271            3 :             svr->priority( priority );
     272            3 :             svr->message( message );
     273            3 :             svr->comparison( comparison );
     274              : 
     275            3 :             pcf::IndiProperty *prop = nullptr;
     276            3 :             std::string        element;
     277              : 
     278           12 :             extractRuleProp(
     279            6 :                 &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Switch, config );
     280            3 :             svr->property( prop );
     281            3 :             svr->element( element );
     282              : 
     283            6 :             std::string target = "On";
     284            3 :             config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
     285            3 :             svr->target( target );
     286            3 :         }
     287           14 :         else if( ruleType == timeDiffRule::name )
     288              :         {
     289            2 :             timeDiffRule *nvr = new timeDiffRule;
     290            2 :             maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], nvr } ) );
     291              : 
     292            2 :             nvr->priority( priority );
     293            2 :             nvr->message( message );
     294            2 :             nvr->comparison( comparison );
     295              : 
     296            2 :             pcf::IndiProperty *prop = nullptr;
     297            2 :             std::string        element;
     298              : 
     299            8 :             extractRuleProp(
     300            4 :                 &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Number, config );
     301            2 :             nvr->property( prop );
     302            2 :             nvr->element( element );
     303              : 
     304            2 :             double target = nvr->target();
     305            2 :             config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
     306            2 :             nvr->target( target );
     307              : 
     308            2 :             double tol = nvr->tol();
     309            2 :             config.configUnused( tol, mx::app::iniFile::makeKey( sections[i], "tol" ) );
     310            2 :             nvr->tol( tol );
     311            2 :         }
     312           12 :         else if( ruleType == elCompNumRule::name )
     313              :         {
     314            1 :             elCompNumRule *nvr = new elCompNumRule;
     315            1 :             maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], nvr } ) );
     316              : 
     317            1 :             nvr->priority( priority );
     318            1 :             nvr->message( message );
     319            1 :             nvr->comparison( comparison );
     320              : 
     321              :             pcf::IndiProperty *prop1;
     322            1 :             std::string        element1;
     323              : 
     324            4 :             extractRuleProp(
     325            2 :                 &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Number, config );
     326            1 :             nvr->property1( prop1 );
     327            1 :             nvr->element1( element1 );
     328              : 
     329              :             pcf::IndiProperty *prop2;
     330            1 :             std::string        element2;
     331              : 
     332            4 :             extractRuleProp(
     333            2 :                 &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Number, config );
     334            1 :             nvr->property2( prop2 );
     335            1 :             nvr->element2( element2 );
     336            1 :         }
     337           11 :         else if( ruleType == elCompTxtRule::name )
     338              :         {
     339            1 :             elCompTxtRule *tvr = new elCompTxtRule;
     340            1 :             maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], tvr } ) );
     341              : 
     342            1 :             tvr->priority( priority );
     343            1 :             tvr->message( message );
     344            1 :             tvr->comparison( comparison );
     345              : 
     346              :             pcf::IndiProperty *prop1;
     347            1 :             std::string        element1;
     348              : 
     349            4 :             extractRuleProp(
     350            2 :                 &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Text, config );
     351            1 :             tvr->property1( prop1 );
     352            1 :             tvr->element1( element1 );
     353              : 
     354              :             pcf::IndiProperty *prop2;
     355            1 :             std::string        element2;
     356              : 
     357            4 :             extractRuleProp(
     358            2 :                 &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Text, config );
     359            1 :             tvr->property2( prop2 );
     360            1 :             tvr->element2( element2 );
     361            1 :         }
     362           10 :         else if( ruleType == elCompSwRule::name )
     363              :         {
     364            2 :             elCompSwRule *svr = new elCompSwRule;
     365            2 :             maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], svr } ) );
     366              : 
     367            2 :             svr->priority( priority );
     368            2 :             svr->message( message );
     369            2 :             svr->comparison( comparison );
     370              : 
     371              :             pcf::IndiProperty *prop1;
     372            2 :             std::string        element1;
     373              : 
     374            8 :             extractRuleProp(
     375            4 :                 &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Switch, config );
     376            2 :             svr->property1( prop1 );
     377            2 :             svr->element1( element1 );
     378              : 
     379              :             pcf::IndiProperty *prop2;
     380            2 :             std::string        element2;
     381              : 
     382            8 :             extractRuleProp(
     383            4 :                 &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Switch, config );
     384            2 :             svr->property2( prop2 );
     385            2 :             svr->element2( element2 );
     386            2 :         }
     387            8 :         else if( ruleType == ruleCompRule::name )
     388              :         {
     389              :             // Here we have to hold the ruleVal keys separately for later processing after all the rules are created.
     390              : 
     391            7 :             if( rrkMap.count( sections[i] ) > 0 )
     392              :             {
     393              :                 // This probably should be impossible, since we already checked maps.rules above...
     394            0 :                 throw mx::exception( mx::error_t::invalidconfig, "duplicate ruleRule: " + sections[i] );
     395              :             }
     396              : 
     397            7 :             ruleCompRule *rcr = new ruleCompRule;
     398            7 :             maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], rcr } ) );
     399              : 
     400            7 :             rcr->priority( priority );
     401            7 :             rcr->message( message );
     402            7 :             rcr->comparison( comparison );
     403              : 
     404            7 :             ruleRuleKeys rrk;
     405              : 
     406            7 :             config.configUnused( rrk.rule1, mx::app::iniFile::makeKey( sections[i], "rule1" ) );
     407            7 :             if( rrk.rule1 == "" )
     408              :             {
     409              :                 throw mx::exception( mx::error_t::invalidconfig,
     410            0 :                                      "rule1 for ruleVal rule " + sections[i] + " not found" );
     411              :             }
     412            7 :             if( rrk.rule1 == sections[i] )
     413              :             {
     414              :                 throw mx::exception( mx::error_t::invalidconfig,
     415            1 :                                      "rule1 for ruleVal rule " + sections[i] + " can't equal rule name" );
     416              :             }
     417              : 
     418            6 :             config.configUnused( rrk.rule2, mx::app::iniFile::makeKey( sections[i], "rule2" ) );
     419            6 :             if( rrk.rule2 == "" )
     420              :             {
     421              :                 throw mx::exception( mx::error_t::invalidconfig,
     422            0 :                                      "rule2 for ruleVal rule " + sections[i] + " not found" );
     423              :             }
     424            6 :             if( rrk.rule2 == sections[i] )
     425              :             {
     426              :                 throw mx::exception( mx::error_t::invalidconfig,
     427            1 :                                      "rule2 for ruleVal rule " + sections[i] + " can't equal rule name" );
     428              :             }
     429              : 
     430            5 :             rrkMap.insert( std::pair<std::string, ruleRuleKeys>( sections[i], rrk ) );
     431            7 :         }
     432              :         else
     433              :         {
     434              :             throw mx::exception( mx::error_t::notimpl,
     435            1 :                                  std::format( "unknown rule type {} in {}", ruleType, sections[i] ) );
     436              :         }
     437           37 :     }
     438           19 : }
     439              : 
     440              : /// Finalize ruleVal rules
     441              : /** ///\todo check for insertion failure
     442              :  * ///\todo add a constructor that has priority, message, and comparison, to reduce duplication
     443              :  */
     444            4 : void finalizeRuleValRules(
     445              :     indiRuleMaps &maps, /**< [in/out] contains the rule and property maps with rules ot finalize */
     446              :     std::map<std::string, ruleRuleKeys> &rrkMap ///< [out] Holds the ruleVal rule keys aside for later post-processing
     447              : )
     448              : {
     449              :     // Now set the rule pointers for any ruleVal rules
     450            4 :     auto it = rrkMap.begin();
     451            7 :     while( it != rrkMap.end() )
     452              :     {
     453            5 :         if( maps.rules.count( it->first ) == 0 )
     454              :         {
     455            0 :             throw mx::exception( mx::error_t::invalidconfig, std::format( "rule parsing error for {}", it->first ) );
     456              :         }
     457              : 
     458            5 :         if( maps.rules.count( it->second.rule1 ) == 0 )
     459              :         {
     460              :             throw mx::exception( mx::error_t::invalidconfig,
     461            1 :                                  std::format( "rule1 {} not found "
     462              :                                               "for ruleVal rule {}",
     463            1 :                                               it->second.rule1,
     464            3 :                                               it->first ) );
     465              :         }
     466              : 
     467            4 :         if( maps.rules.count( it->second.rule2 ) == 0 )
     468              :         {
     469              :             throw mx::exception( mx::error_t::invalidconfig,
     470            1 :                                  std::format( "rule2 {} not found "
     471              :                                               "for ruleVal rule {}",
     472            1 :                                               it->second.rule2,
     473            3 :                                               it->first ) );
     474              :         }
     475              : 
     476            3 :         ruleCompRule *rcr = nullptr;
     477              : 
     478              :         try
     479              :         {
     480            3 :             rcr = dynamic_cast<ruleCompRule *>( maps.rules[it->first] );
     481              :         }
     482            0 :         catch( const std::exception &e )
     483              :         {
     484            0 :             std::throw_with_nested(
     485            0 :                 mx::exception( mx::error_t::invalidconfig, std::format( "error casting {}", it->first ) ) );
     486            0 :         }
     487              : 
     488            3 :         if( rcr == nullptr )
     489              :         {
     490              :             throw mx::exception( mx::error_t::invalidconfig,
     491            0 :                                  std::format( "{} is not a ruleVal rule but has rules", it->first ) );
     492              :         }
     493              : 
     494            3 :         rcr->rule1( maps.rules[it->second.rule1] );
     495            3 :         rcr->rule2( maps.rules[it->second.rule2] );
     496              : 
     497            3 :         ++it;
     498              :     }
     499            2 : }
     500              : 
     501              : #endif // stateRuleEngine_indiCompRuleConfig_hpp
        

Generated by: LCOV version 2.0-1