LCOV - code coverage report
Current view: top level - eventdispatcher - message_definition.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 79.8 % 84 67
Test Date: 2025-05-30 15:24:13 Functions: 100.0 % 4 4
Legend: Lines: hit not hit

            Line data    Source code
       1              : // Copyright (c) 2012-2024  Made to Order Software Corp.  All Rights Reserved
       2              : //
       3              : // https://snapwebsites.org/project/eventdispatcher
       4              : // contact@m2osw.com
       5              : //
       6              : // This program is free software; you can redistribute it and/or modify
       7              : // it under the terms of the GNU General Public License as published by
       8              : // the Free Software Foundation; either version 2 of the License, or
       9              : // (at your option) any later version.
      10              : //
      11              : // This program is distributed in the hope that it will be useful,
      12              : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13              : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14              : // GNU General Public License for more details.
      15              : //
      16              : // You should have received a copy of the GNU General Public License
      17              : // along with this program; if not, write to the Free Software
      18              : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      19              : 
      20              : /** \file
      21              :  * \brief Handle message definitions.
      22              :  *
      23              :  * The library supports loading message definitions from files. Those
      24              :  * definitions are useful to make sure a message includes all the
      25              :  * required parameters, does not include certain forbidden parameters,
      26              :  * and that parameters have a value that matches its type.
      27              :  *
      28              :  * This implements the get_message_definition() and with time probably
      29              :  * many other functions. For now, the check is done in the message.cpp
      30              :  * which is probably not the right place...
      31              :  */
      32              : 
      33              : 
      34              : // self
      35              : //
      36              : #include    "eventdispatcher/message_definition.h"
      37              : 
      38              : #include    "eventdispatcher/exception.h"
      39              : #include    "eventdispatcher/message.h"
      40              : 
      41              : 
      42              : // advgetopt
      43              : //
      44              : #include    <advgetopt/conf_file.h>
      45              : 
      46              : 
      47              : // cppthread
      48              : //
      49              : #include    <cppthread/guard.h>
      50              : #include    <cppthread/mutex.h>
      51              : 
      52              : 
      53              : // snapdev
      54              : //
      55              : #include    <snapdev/pathinfo.h>
      56              : #include    <snapdev/tokenize_string.h>
      57              : 
      58              : 
      59              : // last include
      60              : //
      61              : #include    <snapdev/poison.h>
      62              : 
      63              : 
      64              : 
      65              : namespace ed
      66              : {
      67              : 
      68              : 
      69              : 
      70              : namespace
      71              : {
      72              : 
      73              : 
      74              : 
      75              : /** \brief The extension used to check for message definitions.
      76              :  *
      77              :  * This prefix is appened to the filename before we try to read
      78              :  * message definitions for a given message.
      79              :  */
      80              : char const * const g_message_definition_suffix = ".conf";
      81              : 
      82              : 
      83              : /** \brief The paths to the list of message definitions.
      84              :  *
      85              :  * This variable is dynamically set when you call the
      86              :  * process_message_definition_options() function.
      87              :  *
      88              :  * The variable supports any number of paths separated by
      89              :  * a colon (:). In most cases, you don't need more than one
      90              :  * path, but tests usually require at least two: the folder
      91              :  * where the message definitions get installed
      92              :  * (`BUILD/Debug/dist/eventdispatcher/messages`) and the location
      93              :  * of the messages this very project defines.
      94              :  */
      95              : std::string g_message_definition_paths = std::string();
      96              : 
      97              : 
      98              : /** \brief The list of loaded message definitions.
      99              :  *
     100              :  * We load message definitions the first time we receive a message.
     101              :  * The definitions are then stored in this map until the application
     102              :  * is done.
     103              :  */
     104              : message_definition::map_t   g_message_definitions;
     105              : 
     106              : 
     107              : /** \brief Options to handle the message definition.
     108              :  *
     109              :  * At the moment, this gives the user the ability to define the
     110              :  * path to the definitions. This is really useful for the programmers
     111              :  * since the definitions are under the BUILD folder rather than
     112              :  * `/usr/share/...`
     113              :  */
     114              : advgetopt::option const g_options[] =
     115              : {
     116              :     // MESSAGE DEFINITION OPTIONS
     117              :     //
     118              :     advgetopt::define_option(
     119              :           advgetopt::Name("path-to-message-definitions")
     120              :         , advgetopt::Flags(advgetopt::all_flags<
     121              :               advgetopt::GETOPT_FLAG_GROUP_OPTIONS
     122              :             , advgetopt::GETOPT_FLAG_COMMAND_LINE
     123              :             , advgetopt::GETOPT_FLAG_ENVIRONMENT_VARIABLE
     124              :             , advgetopt::GETOPT_FLAG_CONFIGURATION_FILE
     125              :             , advgetopt::GETOPT_FLAG_REQUIRED>())
     126              :         , advgetopt::Help("the path to the message definitions used to verify message validity before dispatching them.")
     127              :         , advgetopt::DefaultValue("/usr/share/eventdispatcher/messages")
     128              :     ),
     129              : 
     130              :     // END
     131              :     //
     132              :     advgetopt::end_options()
     133              : };
     134              : 
     135              : 
     136              : 
     137              : } // no name namespace
     138              : 
     139              : 
     140              : 
     141              : /** \brief Add message definition specific command line options.
     142              :  *
     143              :  * This function adds the message definition command line options
     144              :  * to your \p opts variable.
     145              :  *
     146              :  * This needs to be called before you parse the argv array of command
     147              :  * line options.
     148              :  *
     149              :  * \param[in,out] opts  The option array to dynamically update with the
     150              :  * message definition command line options.
     151              :  */
     152            1 : void add_message_definition_options(advgetopt::getopt & opts)
     153              : {
     154            1 :     opts.parse_options_info(g_options, true);
     155            1 : }
     156              : 
     157              : 
     158              : /** \brief Retrieve the command line parameters.
     159              :  *
     160              :  * This function needs to be called before the get_message_definition()
     161              :  * function gets called in order to properly setup the path to
     162              :  * the list of message definitions.
     163              :  *
     164              :  * \param[in] opts  The advgetopt options.
     165              :  */
     166            1 : void process_message_definition_options(advgetopt::getopt const & opts)
     167              : {
     168            3 :     g_message_definition_paths = opts.get_string("path-to-message-definitions");
     169            1 : }
     170              : 
     171              : 
     172              : /** \brief Set the list of paths to message definitions.
     173              :  *
     174              :  * In most cases, you want to use the add_message_definition_options()
     175              :  * and the process_message_definition_options() functions. Those handle
     176              :  * command line options so the user can use the --path-to-message-definitions
     177              :  * option on their command line.
     178              :  *
     179              :  * When working on tests, however, it happens that this is not a good
     180              :  * option and having direct access to the variable is much easier.
     181              :  * To make the test more robuts, it is better to call this function
     182              :  * again once done in order to clear the paths:
     183              :  *
     184              :  * \code
     185              :  *     ed::set_message_definition_paths("my:paths:here");
     186              :  *     ... run test ...
     187              :  *     ed::set_message_definition_paths(std::string());
     188              :  * \endcode
     189              :  *
     190              :  * Or use the RAII clas proposed:
     191              :  *
     192              :  * \code
     193              :  *     ed::manage_message_definition_paths mdp("my:paths:here");
     194              :  *     ... run test ...
     195              :  * \endcode
     196              :  */
     197            1 : void set_message_definition_paths(std::string const & paths)
     198              : {
     199            1 :     g_message_definition_paths = paths;
     200            1 : }
     201              : 
     202              : 
     203              : /** \brief Read definitions of a message.
     204              :  *
     205              :  * This function searches for a file named \p command plus the .conf
     206              :  * suffix in the message definition directory
     207              :  * (`/usr/share/eventdispatcher/messages`).
     208              :  *
     209              :  * \note
     210              :  * If a package gets installed later with a command definition which we
     211              :  * missed loading here, then that command will continue to not be tested
     212              :  * (as in the newly instaleld definition will be ignored). These definitions
     213              :  * will be taken in account on a restart of any services making use of them.
     214              :  *
     215              :  * \warning
     216              :  * This function is expected to be called with the real message command
     217              :  * and not the f_match.f_expr which could be a regular expression.
     218              :  *
     219              :  * \todo
     220              :  * Add support for includes.
     221              :  *
     222              :  * \param[in] command  Name of the message definition to load.
     223              :  *
     224              :  * \return A pointer to the loaded message definition.
     225              :  */
     226           20 : message_definition::pointer_t get_message_definition(std::string const & command)
     227              : {
     228           20 :     if(g_message_definition_paths.empty())
     229              :     {
     230              :         throw invalid_parameter(
     231              :               "message definition:"
     232            0 :             + command
     233            0 :             + ": no paths defined to message definitions. (i.e. did you call the add_message_definition_options() and process_message_definition_options() functions?)");
     234              :     }
     235              : 
     236           20 :     verify_message_name(command);
     237              : 
     238           20 :     cppthread::guard lock(*cppthread::g_system_mutex);
     239              : 
     240           20 :     auto it(g_message_definitions.find(command));
     241           20 :     if(it != g_message_definitions.end())
     242              :     {
     243            3 :         return it->second;
     244              :     }
     245              : 
     246           17 :     message_definition::pointer_t def(std::make_shared<message_definition>());
     247           17 :     def->f_command = command;
     248           17 :     g_message_definitions[command] = def;
     249              : 
     250           17 :     std::list<std::string> paths;
     251           51 :     snapdev::tokenize_string(
     252              :           paths
     253              :         , g_message_definition_paths
     254              :         , ":"
     255              :         , true);
     256              : 
     257           17 :     bool found(false);
     258           30 :     for(auto const & p : paths)
     259              :     {
     260           30 :         std::string filename(p);
     261           30 :         filename += '/';
     262           30 :         filename += command;
     263           30 :         filename += g_message_definition_suffix;
     264              : 
     265           30 :         if(!snapdev::pathinfo::file_exists(filename))
     266              :         {
     267              :             // no such file, just return the default message definition
     268              :             //
     269           13 :             continue;
     270              :         }
     271           17 :         found = true;
     272              : 
     273           17 :         advgetopt::conf_file_setup setup(filename);
     274           17 :         advgetopt::conf_file::pointer_t config(advgetopt::conf_file::get_conf_file(setup));
     275           17 :         advgetopt::conf_file::sections_t sections(config->get_sections());
     276           41 :         for(auto const & s : sections)
     277              :         {
     278              :             // section names use dashes between words
     279              :             // our messages use underscores
     280              :             //
     281           24 :             message_parameter param = {
     282              :                 .f_name = advgetopt::option_with_underscores(s),
     283           24 :             };
     284              : 
     285           24 :             std::string param_name(s + "::type");
     286           24 :             if(config->has_parameter(param_name))
     287              :             {
     288            5 :                 std::string const & type(config->get_parameter(param_name));
     289            5 :                 if(type == "default"
     290            5 :                 || type == "string")
     291              :                 {
     292            0 :                     param.f_type = parameter_type_t::PARAMETER_TYPE_STRING;
     293              :                 }
     294            5 :                 else if(type == "integer")
     295              :                 {
     296            0 :                     param.f_type = parameter_type_t::PARAMETER_TYPE_INTEGER;
     297              :                 }
     298            5 :                 else if(type == "address")
     299              :                 {
     300            1 :                     param.f_type = parameter_type_t::PARAMETER_TYPE_ADDRESS;
     301              :                 }
     302            4 :                 else if(type == "timespec")
     303              :                 {
     304            4 :                     param.f_type = parameter_type_t::PARAMETER_TYPE_TIMESPEC;
     305              :                 }
     306              :                 else
     307              :                 {
     308              :                     throw invalid_parameter(
     309              :                           "message definition:"
     310            0 :                         + command
     311            0 :                         + ": parameter type \""
     312            0 :                         + type
     313            0 :                         + "\" is not a supported type.");
     314              :                 }
     315            5 :             }
     316              : 
     317           24 :             param_name = s + "::flags";
     318           24 :             if(config->has_parameter(param_name))
     319              :             {
     320              :                 // start from a clean slate
     321              :                 //
     322           22 :                 param.f_flags = 0;
     323              : 
     324           22 :                 std::string const & flags(config->get_parameter(param_name));
     325           22 :                 std::list<std::string> flag_names;
     326           66 :                 snapdev::tokenize_string(flag_names, flags, ",", true);
     327           44 :                 for(auto const & f : flag_names)
     328              :                 {
     329           22 :                     if(f == "required")
     330              :                     {
     331            4 :                         param.f_flags |= PARAMETER_FLAG_REQUIRED;
     332              :                     }
     333           18 :                     else if(f == "empty")
     334              :                     {
     335            0 :                         param.f_flags |= PARAMETER_FLAG_EMPTY;
     336              :                     }
     337           18 :                     else if(f == "forbidden")
     338              :                     {
     339            0 :                         param.f_flags |= PARAMETER_FLAG_FORBIDDEN;
     340              :                     }
     341           18 :                     else if(f != "optional"
     342            0 :                          && f != "defined"
     343           18 :                          && f != "allowed")
     344              :                     {
     345              :                         throw invalid_parameter(
     346              :                               "message definition:"
     347            0 :                             + command
     348            0 :                             + ": parameter flag \""
     349            0 :                             + f
     350            0 :                             + "\" not supported.");
     351              :                     }
     352              :                 }
     353           22 :             }
     354              : 
     355           24 :             def->f_parameters.push_back(param);
     356           24 :         }
     357              : 
     358              :         // we only read one config file; other copies in the
     359              :         // other folders are ignored
     360              :         //
     361              :         break;
     362           47 :     }
     363              : 
     364              : #ifdef _DEBUG
     365           17 :     if(!found)
     366              :     {
     367              :         throw invalid_parameter(
     368              :               "message definition for \""
     369            0 :             + command
     370            0 :             + "\" not found.");
     371              :     }
     372              : #endif
     373              : 
     374           17 :     return def;
     375           20 : }
     376              : 
     377              : 
     378              : 
     379              : } // namespace ed
     380              : // vim: ts=4 sw=4 et
        

Generated by: LCOV version 2.0-1

Snap C++ | List of projects | List of versions