понедельник, 14 октября 2019 г.

[prog.c++] Упоролся шаблонами по полной программе или результат третьего подхода к удобному парсеру HTTP-полей в RESTinio

История неудачных попыток создать удобный парсер HTTP-полей для RESTinio продолжается. Результат третьей попытки под катом. Рассказ о первой попытке здесь, а о второй -- здесь.

Анализируя результат второй попытки появилась, как тогда казалось, стройная идея по разделению парсера на такие понятия, как value_producer, value_transformer и value_consumer. По первым прикидкам казалось, что это позволит делать декларативное описание структуры значения HTTP-поля на основании грамматики этого поля из RFC.

Оно-то как-то так и оказалось. Но уж слишком многословно и мудрено.

В качестве примера под катом находится класс для разбора значения HTTP-поля Cache-Control. Содержимое этого поля в RFC определяется как:

Cache-Control   = 1#cache-directive

cache-directive = token [ "=" ( token / quoted-string ) ]

При этом конструкция вида 1#element определяется вот так:

1#element => *( "," OWS ) element *( OWS "," [ OWS element ] )

Т.е. правило для Cache-Control можно развернуть так:

Cache-Control   = *( "," OWS ) cache-directive *( OWS "," [ OWS cache-directive ] )

cache-directive = token [ "=" ( token / quoted-string ) ]

Т.е. этому правилу может удовлетворять, например, такая последовательность:

, ,   , max-age=5, , , no-transform,  ,,, ,

Итак, вот что получилось:

struct cache_control_value_t
{
   using directive_container_t = std::map<
         std::string, restinio::optional_t<std::string> >;

   directive_container_t m_directives;

   struct details
   {
      template<typename T>
      struct container_adaptor {
         using container_type = directive_container_t;
         using value_type = restinio::optional_t<
               std::pair< std::string, restinio::optional_t<std::string> > >;

         static void
         store( container_type & dest, value_type && what )
         {
            if( what )
               dest.emplace( std::move(*what) );
         }
      };
   };

   static auto
   make_parser()
   {
      using namespace restinio::http_field_parser;

      using directive_t = std::pair<
            std::string,
            restinio::optional_t<std::string> >;

      return produce< cache_control_value_t >(
               repeat< nothing_t >( 0, N,
                  symbol(',') >> skip(),
                  rfc::ows() >> skip() ) >> skip(),

               produce< directive_t >(
                  rfc::token() >> to_lower() >> &directive_t::first,
                  optional< std::string >(
                     symbol('=') >> skip(),
                     alternatives< std::string >(
                        rfc::token() >> to_lower(),
                        rfc::quoted_string() ) >> as_result()
                  ) >> &directive_t::second
               ) >> custom_consumer( [](auto & dest, directive_t && v) {
                     dest.m_directives.emplace( std::move(v) );
                  } ),

               repeat< directive_container_t, details::container_adaptor >(
                  0, N,
                  produce< restinio::optional_t<directive_t> >(
                     rfc::ows() >> skip(),
                     symbol(',') >> skip(),
                     optional< directive_t >(
                        rfc::ows() >> skip(),
                        rfc::token() >> to_lower() >> &directive_t::first,
                        optional< std::string >(
                           symbol('=') >> skip(),
                           alternatives< std::string >(
                              rfc::token() >> to_lower(),
                              rfc::quoted_string() ) >> as_result()
                        ) >> &directive_t::second
                     ) >> as_result()
                  ) >> as_result()
               ) >> custom_consumer( [](auto & dest, directive_container_t && v) {
                     // There is no a good way except the copy from
                     // 'v' to 'dest' one-by-one.
                     forauto & item : v )
                        dest.m_directives[ item.first ] = item.second;
                  } )

            ) >> as_result();
   }

   static std::pair< bool, cache_control_value_t >
   try_parse( string_view_t what )
   {
      return restinio::http_field_parser::try_parse_field_value< cache_control_value_t >(
            what,
            make_parser() );
   }
};

Кроме осознания того, что мне предстоит четвертая итерация, есть еще два важных вывода из содеянного:

  • возможность упороться шаблонами еще не означает, что ими нужно упарываться. Не нужно так делать :(
  • очередное подтверждение того, что простота не предшествует сложности, а следует за ней. И в данном конкретном случае меня ждет еще очень длинный путь.

На вопрос "А нафига это все вообще?" пока ответ простой: "Хочется сделать один раз нормально, чтобы потом иметь возможность декларативно описывать содержимое HTTP-поля". Или, другими словами, "лучше день потерять, затем за 5 минут долететь". Но с каждым подходом уверенность в том, что это правильный ответ, снижается. Но пока не до критического уровня ;)

Upd. Вот до чего удалось сократить после некоторого анализа получившегося варианта:

struct cache_control_value_t
{
   using directive_t = std::pair<
         std::string,
         restinio::optional_t<std::string> >;

   using directive_container_t = std::map<
         std::string, restinio::optional_t<std::string> >;

   directive_container_t m_directives;

   static auto
   make_parser()
   {
      using namespace restinio::http_field_parser;

      return produce< cache_control_value_t >(
         one_or_more_of< directive_container_t >(
            produce< directive_t >(
               rfc::token() >> to_lower() >> &directive_t::first,
               optional< std::string >(
                  symbol('=') >> skip(),
                  alternatives< std::string >(
                     rfc::token() >> to_lower(),
                     rfc::quoted_string() ) >> as_result()
               ) >> &directive_t::second
            )
         ) >> &cache_control_value_t::m_directives
      ) >> as_result();
   }

   static std::pair< bool, cache_control_value_t >
   try_parse( string_view_t what )
   {
      using namespace restinio::http_field_parser;

      return try_parse_field_value< cache_control_value_t >(
            what,
            make_parser() );
   }
};

Комментариев нет: