| /** | |
| @section license License | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you under the Apache License, Version 2.0 (the | |
| "License"); you may not use this file except in compliance | |
| with the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| Copyright 2019, Oath Inc. | |
| */ | |
| #include <string> | |
| #include <utility> | |
| #include <algorithm> | |
| #include <swoc/bwf_base.h> | |
| #include "txn_box/common.h" | |
| #include "txn_box/Rxp.h" | |
| #include "txn_box/Comparison.h" | |
| #include "txn_box/Directive.h" | |
| #include "txn_box/Config.h" | |
| #include "txn_box/Context.h" | |
| using swoc::TextView; | |
| using namespace swoc::literals; | |
| using swoc::Errata; | |
| using swoc::Rv; | |
| Comparison::Factory Comparison::_factory; | |
| unsigned | |
| Comparison::rxp_group_count() const | |
| { | |
| return 0; | |
| } | |
| Errata | |
| Comparison::define(swoc::TextView name, ActiveType const &types, Comparison::Loader &&worker) | |
| { | |
| _factory[name] = std::make_tuple(std::move(worker), types); | |
| return {}; | |
| } | |
| bool | |
| Comparison::operator()(Context &ctx, Generic const *g) const | |
| { | |
| Feature f{g->extract()}; | |
| return IndexFor(GENERIC) == f.index() ? false : (*this)(ctx, f); | |
| } | |
| Rv<Comparison::Handle> | |
| Comparison::load(Config &cfg, YAML::Node node) | |
| { | |
| if (!node.IsMap()) { | |
| return Errata(S_ERROR, "Comparison at {} is not an object.", node.Mark()); | |
| } | |
| for (auto const &[key_node, value_node] : node) { | |
| TextView key{key_node.Scalar()}; | |
| auto &&[arg, arg_errata]{parse_arg(key)}; | |
| if (!arg_errata.is_ok()) { | |
| return std::move(arg_errata); | |
| } | |
| if (key == Global::DO_KEY) { | |
| continue; | |
| } | |
| // See if this is in the factory. It's not an error if it's not, to enable adding extra | |
| // keys to comparison. First key that is in the factory determines the comparison type. | |
| if (auto spot{_factory.find(key)}; spot != _factory.end()) { | |
| auto &&[loader, types] = spot->second; | |
| if (!cfg.active_type().can_satisfy(types)) { | |
| return Errata(S_ERROR, R"(Comparison "{}" at {} is not valid for active feature.)", key, node.Mark()); | |
| } | |
| auto &&[handle, errata]{loader(cfg, node, key, arg, value_node)}; | |
| if (!errata.is_ok()) { | |
| return std::move(errata); | |
| } | |
| return std::move(handle); | |
| } | |
| } | |
| return Errata(S_ERROR, R"(No valid comparison key in object at {}.)", node.Mark()); | |
| } | |
| void | |
| Comparison::can_accelerate(Accelerator::Counters &) const | |
| { | |
| } | |
| void | |
| Comparison::accelerate(StringAccelerator *) const | |
| { | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| class Cmp_otherwise : public Comparison | |
| { | |
| using self_type = Cmp_otherwise; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static const std::string KEY; ///< Comparison name. | |
| static const ValueMask TYPES; ///< Supported types. | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| /// Construct an instance from YAML configuration. | |
| static Rv<Handle> load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node); | |
| protected: | |
| Cmp_otherwise() = default; | |
| }; | |
| const std::string Cmp_otherwise::KEY{"otherwise"}; | |
| const ValueMask Cmp_otherwise::TYPES{ValueMask{}.set()}; | |
| bool | |
| Cmp_otherwise::operator()(Context &, Feature const &) const | |
| { | |
| return true; | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_otherwise::load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node) | |
| { | |
| return {Handle{new self_type}, {}}; | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| /** Utility base class for comparisons that are based on literal string matching. | |
| * This is @b not intended to be used as a comparison itself. | |
| */ | |
| class Cmp_String : public Comparison | |
| { | |
| using self_type = Cmp_String; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static constexpr TextView NO_CASE_OPT{"nc"}; | |
| protected: | |
| union Options { | |
| unsigned int all; | |
| struct { | |
| unsigned int nc : 1; | |
| } f; | |
| Options() { all = 0; } // Force zero initialization. | |
| }; | |
| static Rv<Options> parse_options(TextView options); | |
| }; | |
| Rv<Cmp_String::Options> | |
| Cmp_String::parse_options(TextView options) | |
| { | |
| Options zret; | |
| while (options) { | |
| auto token = options.take_prefix_at(','); | |
| if (0 == strcasecmp(NO_CASE_OPT, token)) { | |
| zret.f.nc = true; | |
| } else { | |
| return Errata(S_ERROR, R"("{}" is not a valid option for a string comparison.)", token); | |
| } | |
| } | |
| return zret; | |
| } | |
| // --- | |
| // Specialized subclasses for the various options. | |
| /// Base case for literal string comparisons. | |
| class Cmp_LiteralString : public Cmp_String | |
| { | |
| public: | |
| static constexpr TextView MATCH_KEY{"match"}; | |
| static constexpr TextView CONTAIN_KEY{"contains"}; | |
| static constexpr TextView PREFIX_KEY{"prefix"}; | |
| static constexpr TextView SUFFIX_KEY{"suffix"}; | |
| static constexpr TextView TLD_KEY{"tld"}; | |
| static constexpr TextView PATH_KEY{"path"}; | |
| /// Mark for @c STRING support only. | |
| static const ActiveType TYPES; | |
| /** Compare @a text for a match. | |
| * | |
| * @param ctx The transaction context. | |
| * @param text The feature to compare. | |
| * @return @c true if @a text matches, @c false otherwise. | |
| * | |
| * External API - required as a @c Comparison. | |
| */ | |
| bool operator()(Context &ctx, feature_type_for<STRING> const &text) const override; | |
| /** Instantiate an instance from YAML configuration. | |
| * | |
| * @param cfg Global configuration object. | |
| * @param cmp_node The node containing the comparison. | |
| * @param key Key for comparison. | |
| * @param arg Argument for @a key, if any (stripped from @a key). | |
| * @param value_node Value node for for @a key. | |
| * @return An instance or errors on failure. | |
| */ | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| Expr _expr; ///< To be compared to active feature. | |
| Cmp_LiteralString(Expr &&expr); | |
| using Comparison::operator(); // declare hidden member function | |
| /** Specialized comparison. | |
| * | |
| * @param ctx Runtime context. | |
| * @param text Configured value to check against. | |
| * @param active Runtime value to check. | |
| * @return @c true on match @c false otherwise. | |
| * | |
| * This class will handle extracting the stored expression and pass it piecewise (if needed) | |
| * to the specialized subclass. @a text is the extracted text, @a active is the value passed | |
| * in at run time to check. | |
| */ | |
| virtual bool operator()(Context &ctx, TextView const &text, TextView active) const = 0; | |
| struct expr_validator { | |
| bool | |
| operator()(std::monostate const &) | |
| { | |
| return false; | |
| } | |
| bool | |
| operator()(Feature const &f) | |
| { | |
| return f.value_type() == STRING; | |
| } | |
| bool | |
| operator()(Expr::Direct const &d) | |
| { | |
| return d._result_type.can_satisfy(STRING); | |
| } | |
| bool | |
| operator()(Expr::Composite const &) | |
| { | |
| return true; | |
| } | |
| bool | |
| operator()(Expr::List const &list) | |
| { | |
| return list._types.can_satisfy(STRING); | |
| } | |
| }; | |
| }; | |
| const ActiveType Cmp_LiteralString::TYPES{STRING, ActiveType::TupleOf(STRING)}; | |
| Cmp_LiteralString::Cmp_LiteralString(Expr &&expr) : _expr(std::move(expr)) {} | |
| bool | |
| Cmp_LiteralString::operator()(Context &ctx, feature_type_for<STRING> const &feature) const | |
| { | |
| Feature f{ctx.extract(_expr)}; | |
| if (auto view = std::get_if<IndexFor(STRING)>(&f); nullptr != view) { | |
| return (*this)(ctx, *view, feature); | |
| } else if (auto t = std::get_if<IndexFor(TUPLE)>(&f); nullptr != t) { | |
| return std::any_of(t->begin(), t->end(), [&](Feature &f) -> bool { | |
| auto view = std::get_if<IndexFor(STRING)>(&f); | |
| return view && (*this)(ctx, *view, feature); | |
| }); | |
| } | |
| return false; | |
| } | |
| /// Match entire string. | |
| class Cmp_MatchStd : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_MatchStd; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_MatchStd::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (text == active) { | |
| ctx.set_literal_capture(active); | |
| ctx._remainder.clear(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Match entire string, ignoring case | |
| class Cmp_MatchNC : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_MatchNC; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_MatchNC::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (0 == strcasecmp(text, active)) { | |
| ctx.set_literal_capture(active); | |
| ctx._remainder.clear(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Compare the active feature to a string suffix. | |
| class Cmp_Suffix : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_Suffix; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_Suffix::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (active.ends_with(text)) { | |
| ctx.set_literal_capture(active.suffix(text.size())); | |
| ctx._remainder = active.remove_suffix(text.size()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Compare without case the active feature to a string suffix. | |
| class Cmp_SuffixNC : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_SuffixNC; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_SuffixNC::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (active.ends_with_nocase(text)) { | |
| ctx.set_literal_capture(active.suffix(text.size())); | |
| ctx._remainder = active.remove_suffix(text.size()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| class Cmp_Prefix : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_Prefix; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_Prefix::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (active.starts_with(text)) { | |
| ctx.set_literal_capture(active.prefix(text.size())); | |
| ctx._remainder = active.remove_prefix(text.size()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| class Cmp_PrefixNC : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_PrefixNC; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_PrefixNC::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (active.starts_with_nocase(text)) { | |
| ctx.set_literal_capture(active.prefix(text.size())); | |
| ctx._remainder = active.remove_prefix(text.size()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| class Cmp_Contains : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_Contains; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_Contains::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (auto idx = active.find(text); idx != TextView::npos) { | |
| #if 0 | |
| if (ctx._update_remainder_p) { | |
| auto n = active.size() - text.size(); | |
| auto span = ctx._arena->alloc(n).rebind<char>(); | |
| memcpy(span, active.prefix(idx)); | |
| memcpy(span.data() + idx, active.suffix(idx).data(), active.size() - (idx + text.size())); | |
| ctx.set_literal_capture(span.view()); | |
| } | |
| #else | |
| ctx._remainder.clear(); | |
| #endif | |
| return true; | |
| } | |
| return false; | |
| } | |
| class Cmp_ContainsNC : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_ContainsNC; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_ContainsNC::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (text.size() <= active.size()) { | |
| auto spot = std::search(active.begin(), active.end(), text.begin(), text.end(), | |
| [](char lhs, char rhs) { return tolower(lhs) == tolower(rhs); }); | |
| if (spot != active.end()) { | |
| #if 0 | |
| if (ctx._update_remainder_p) { | |
| size_t idx = spot - active.begin(); | |
| auto n = active.size() - text.size(); | |
| auto span = ctx._arena->alloc(n).rebind<char>(); | |
| memcpy(span, active.prefix(idx)); | |
| memcpy(span.data() + idx, active.suffix(idx).data(), active.size() - (idx + text.size())); | |
| ctx.set_literal_capture(span.view()); | |
| } | |
| #else | |
| ctx._remainder.clear(); | |
| #endif | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| class Cmp_TLD : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_TLD; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_TLD::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (active.ends_with(text) && (text.size() == active.size() || active[active.size() - text.size() - 1] == '.')) { | |
| auto capture = active.suffix(text.size() + 1); | |
| ctx.set_literal_capture(capture); | |
| ctx._remainder = active.prefix(active.size() - capture.size()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| class Cmp_TLDNC : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_TLDNC; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_TLDNC::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (active.ends_with_nocase(text) && (text.size() == active.size() || active[active.size() - text.size() - 1] == '.')) { | |
| auto capture = active.suffix(text.size() + 1); | |
| ctx.set_literal_capture(capture); | |
| ctx._remainder = active.prefix(active.size() - capture.size()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| // --- | |
| class Cmp_Path : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_Path; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_Path::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| auto target = text; | |
| target.rtrim('/'); // Not reliable to do at load, since not always a config time constant. | |
| if (active.starts_with(target)) { | |
| auto rest = active.substr(target.size()); | |
| if (rest.empty() || rest == "/"_tv) { | |
| auto n = target.size() + rest.size(); | |
| ctx.set_literal_capture(active.prefix(n)); | |
| ctx._remainder = active.substr(n); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| class Cmp_PathNC : public Cmp_LiteralString | |
| { | |
| protected: | |
| using self_type = Cmp_PathNC; | |
| using super_type = Cmp_LiteralString; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, TextView const &text, TextView active) const override; | |
| friend super_type; | |
| }; | |
| bool | |
| Cmp_PathNC::operator()(Context &ctx, TextView const &text, TextView active) const | |
| { | |
| if (active.starts_with_nocase(text)) { | |
| auto rest = active.substr(text.size()); | |
| if (rest.empty() || rest == "/"_tv) { | |
| auto n = text.size() + rest.size(); | |
| ctx.set_literal_capture(active.prefix(n)); | |
| ctx._remainder = active.substr(n); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // --- | |
| Rv<Comparison::Handle> | |
| Cmp_LiteralString::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node) | |
| { | |
| auto &&[expr, errata]{cfg.parse_expr(value_node)}; | |
| if (!errata.is_ok()) { | |
| errata.note(R"(While parsing comparison "{}" at {}.)", key, cmp_node.Mark()); | |
| return std::move(errata); | |
| } | |
| auto &&[options, opt_errata]{parse_options(arg)}; | |
| if (!opt_errata.is_ok()) { | |
| opt_errata.note(R"(While parsing argument "{}" for comparison "{}".)", arg, key); | |
| return std::move(opt_errata); | |
| } | |
| auto expr_type = expr.result_type(); | |
| if (!expr_type.can_satisfy(TYPES)) { | |
| return Errata(S_ERROR, R"(Value type "{}" for comparison "{}" at {} is not supported.)", expr_type, key, cmp_node.Mark()); | |
| } | |
| if (MATCH_KEY == key) { | |
| return options.f.nc ? Handle{new Cmp_MatchNC(std::move(expr))} : Handle{new Cmp_MatchStd(std::move(expr))}; | |
| } else if (PREFIX_KEY == key) { | |
| return options.f.nc ? Handle{new Cmp_PrefixNC(std::move(expr))} : Handle{new Cmp_Prefix(std::move(expr))}; | |
| } else if (SUFFIX_KEY == key) { | |
| return options.f.nc ? Handle{new Cmp_SuffixNC(std::move(expr))} : Handle{new Cmp_Suffix(std::move(expr))}; | |
| } else if (CONTAIN_KEY == key) { | |
| return options.f.nc ? Handle(new Cmp_Contains(std::move(expr))) : Handle(new Cmp_ContainsNC(std::move(expr))); | |
| } else if (TLD_KEY == key) { | |
| return options.f.nc ? Handle(new Cmp_TLDNC(std::move(expr))) : Handle(new Cmp_TLD(std::move(expr))); | |
| } else if (PATH_KEY == key) { | |
| return options.f.nc ? Handle(new Cmp_PathNC(std::move(expr))) : Handle(new Cmp_Path(std::move(expr))); | |
| } | |
| return Errata(S_ERROR, R"(Internal error, unrecognized key "{}".)", key); | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| class Cmp_Rxp : public Cmp_String | |
| { | |
| using self_type = Cmp_Rxp; ///< Self reference type. | |
| using super_type = Cmp_String; ///< Super type. | |
| public: | |
| static constexpr TextView KEY{"rxp"}; ///< YAML key. | |
| static const ActiveType TYPES; ///< Valid comparison types. | |
| static Rv<Comparison::Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, | |
| YAML::Node value_node); | |
| protected: | |
| /// Static value - a literal or a dynamic regular expression. | |
| /// @see rxp_visitor | |
| using Item = std::variant<Rxp, Expr>; | |
| /// Process the comparison based on the expression type. | |
| /// This is used during configuration load. | |
| struct expr_visitor { | |
| /** Constructor. | |
| * | |
| * @param cfg Configuration being loaded. | |
| * @param opt Options from directive arguments. | |
| */ | |
| expr_visitor(Config &cfg, Rxp::Options opt) : _cfg(cfg), _rxp_opt(opt) {} | |
| Rv<Handle> operator()(std::monostate); | |
| Rv<Handle> operator()(Feature &f); | |
| Rv<Handle> operator()(Expr::List &l); | |
| Rv<Handle> operator()(Expr::Direct &d); | |
| Rv<Handle> operator()(Expr::Composite &comp); | |
| Config &_cfg; ///< Configuration being loaded. | |
| Rxp::Options _rxp_opt; ///< Any options from directive arguments. | |
| }; | |
| /** Handlers for @c Item. | |
| * | |
| * This enables dynamic regular expressions at a reasonable run time cost. If the configuration | |
| * is a literal it is compiled during configuration load and stored as an @c Rxp instance. | |
| * Otherwise the @c Expr is stored and evaluated on invocation. | |
| */ | |
| struct rxp_visitor { | |
| /** Invoke the @a rxp against the active feature. | |
| * | |
| * @param rxp Compiled regular expression. | |
| * @return @c true on success, @c false otherwise. | |
| */ | |
| bool operator()(Rxp const &rxp); | |
| /** Compile the @a expr into a regular expression. | |
| * | |
| * @param expr Feature expression. | |
| * @return @c true on successful match, @c false otherwise. | |
| * | |
| * @internal This compiles the feature from @a expr and then invokes the @c Rxp overload to do | |
| * the match. | |
| */ | |
| bool operator()(Expr const &expr); | |
| Context &_ctx; ///< Configuration context. | |
| Rxp::Options _rxp_opt; ///< Options for the regex. | |
| TextView _src; ///< regex text. | |
| }; | |
| }; | |
| class Cmp_RxpSingle : public Cmp_Rxp | |
| { | |
| using self_type = Cmp_RxpSingle; | |
| using super_type = Cmp_Rxp; | |
| public: | |
| using Comparison::operator(); // declare hidden member function | |
| Cmp_RxpSingle(Expr &&expr, Rxp::Options); | |
| Cmp_RxpSingle(Rxp &&rxp); | |
| protected: | |
| bool operator()(Context &ctx, feature_type_for<STRING> const &active) const override; | |
| Item _rxp; | |
| Rxp::Options _opt; | |
| }; | |
| class Cmp_RxpList : public Cmp_Rxp | |
| { | |
| using self_type = Cmp_RxpList; | |
| using super_type = Cmp_Rxp; | |
| friend super_type; | |
| public: | |
| Cmp_RxpList(Rxp::Options opt) : _opt(opt) {} | |
| protected: | |
| struct expr_visitor { | |
| Errata operator()(Feature &f); | |
| Errata | |
| operator()(Expr::List &) | |
| { | |
| return Errata(S_ERROR, "Invalid type"); | |
| } | |
| Errata | |
| operator()(Expr::Direct &d) | |
| { | |
| _rxp.emplace_back(Expr{std::move(d)}); | |
| return {}; | |
| } | |
| Errata | |
| operator()(Expr::Composite &comp) | |
| { | |
| _rxp.emplace_back(Expr{std::move(comp)}); | |
| return {}; | |
| } | |
| Errata | |
| operator()(std::monostate) | |
| { | |
| return Errata(S_ERROR, "Invalid type"); | |
| } | |
| Rxp::Options _rxp_opt; | |
| std::vector<Item> &_rxp; | |
| }; | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, feature_type_for<STRING> const &active) const override; | |
| std::vector<Item> _rxp; | |
| Rxp::Options _opt; | |
| }; | |
| Errata | |
| Cmp_RxpList::expr_visitor::operator()(Feature &f) | |
| { | |
| if (IndexFor(STRING) != f.index()) { | |
| return Errata(S_ERROR, R"("{}" literal must be a string.)", KEY); | |
| } | |
| auto &&[rxp, rxp_errata]{Rxp::parse(std::get<IndexFor(STRING)>(f), _rxp_opt)}; | |
| if (!rxp_errata.is_ok()) { | |
| rxp_errata.note(R"(While parsing feature expression for "{}" comparison.)", KEY); | |
| return std::move(rxp_errata); | |
| } | |
| _rxp.emplace_back(std::move(rxp)); | |
| return {}; | |
| } | |
| const ActiveType Cmp_Rxp::TYPES{STRING, ActiveType::TupleOf(STRING)}; | |
| bool | |
| Cmp_Rxp::rxp_visitor::operator()(const Rxp &rxp) | |
| { | |
| auto result = rxp(_src, _ctx.rxp_working_match_data()); | |
| if (result > 0) { | |
| _ctx.rxp_commit_match(_src); | |
| _ctx._remainder.clear(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool | |
| Cmp_Rxp::rxp_visitor::operator()(Expr const &expr) | |
| { | |
| auto f = _ctx.extract(expr); | |
| if (auto text = std::get_if<IndexFor(STRING)>(&f); text != nullptr) { | |
| auto &&[rxp, rxp_errata]{Rxp::parse(*text, _rxp_opt)}; | |
| if (rxp_errata.is_ok()) { | |
| _ctx.rxp_match_require(rxp.capture_count()); | |
| return (*this)(rxp); // forward to Rxp overload. | |
| } | |
| } | |
| return false; | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_Rxp::expr_visitor::operator()(Feature &f) | |
| { | |
| if (IndexFor(STRING) != f.index()) { | |
| return Errata(S_ERROR, R"("{}" literal must be a string.)", KEY); | |
| } | |
| auto &&[rxp, rxp_errata]{Rxp::parse(std::get<IndexFor(STRING)>(f), _rxp_opt)}; | |
| if (!rxp_errata.is_ok()) { | |
| rxp_errata.note(R"(While parsing feature expression for "{}" comparison.)", KEY); | |
| return std::move(rxp_errata); | |
| } | |
| _cfg.require_rxp_group_count(rxp.capture_count()); | |
| return Handle(new Cmp_RxpSingle(std::move(rxp))); | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_Rxp::expr_visitor::operator()(std::monostate) | |
| { | |
| return Errata(S_ERROR, R"(Literal must be a string)"); | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_Rxp::expr_visitor::operator()(Expr::Direct &d) | |
| { | |
| // coverity[use_after_move] - intentional: visitor consumes the variant alternative | |
| return Handle(new Cmp_RxpSingle(Expr{std::move(d)}, _rxp_opt)); | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_Rxp::expr_visitor::operator()(Expr::Composite &comp) | |
| { | |
| return Handle(new Cmp_RxpSingle(Expr{std::move(comp)}, _rxp_opt)); | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_Rxp::expr_visitor::operator()(Expr::List &l) | |
| { | |
| auto rxm = new Cmp_RxpList{_rxp_opt}; | |
| Cmp_RxpList::expr_visitor ev{_rxp_opt, rxm->_rxp}; | |
| for (Expr &elt : l._exprs) { | |
| if (!elt.result_type().can_satisfy(STRING)) { | |
| delete rxm; | |
| return Errata(S_ERROR, R"("{}" literal must be a string.)", KEY); | |
| } | |
| std::visit(ev, elt._raw); | |
| } | |
| return Handle{rxm}; | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_Rxp::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node) | |
| { | |
| auto &&[expr, errata]{cfg.parse_expr(value_node)}; | |
| if (!errata.is_ok()) { | |
| errata.note(R"(While parsing comparison "{}" at {}.)", key, cmp_node.Mark()); | |
| return std::move(errata); | |
| } | |
| auto &&[options, opt_errata]{self_type::parse_options(arg)}; | |
| if (!opt_errata.is_ok()) { | |
| opt_errata.note(R"(While parsing argument "{}" for comparison "{}".)", arg, key); | |
| return std::move(opt_errata); | |
| } | |
| Rxp::Options rxp_opt; | |
| rxp_opt.f.nc = options.f.nc; | |
| return std::visit(expr_visitor{cfg, rxp_opt}, expr._raw); | |
| } | |
| Cmp_RxpSingle::Cmp_RxpSingle(Expr &&expr, Rxp::Options opt) : _rxp(std::move(expr)), _opt(opt) {} | |
| Cmp_RxpSingle::Cmp_RxpSingle(Rxp &&rxp) : _rxp(std::move(rxp)) {} | |
| bool | |
| Cmp_RxpSingle::operator()(Context &ctx, feature_type_for<STRING> const &active) const | |
| { | |
| return std::visit(rxp_visitor{ctx, _opt, active}, _rxp); | |
| } | |
| bool | |
| Cmp_RxpList::operator()(Context &ctx, feature_type_for<STRING> const &) const | |
| { | |
| return std::any_of(_rxp.begin(), _rxp.end(), [&](Item const &item) { return std::visit(rxp_visitor{ctx, _opt, {}}, item); }); | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| /** Compare a boolean value. | |
| * Check if a value is true. | |
| */ | |
| class Cmp_is_true : public Comparison | |
| { | |
| using self_type = Cmp_is_true; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static inline const std::string KEY{"is-true"}; ///< Comparison name. | |
| static inline const ValueMask TYPES{MaskFor(NIL, STRING, BOOLEAN, INTEGER)}; ///< Supported types. | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| /// Construct an instance from YAML configuration. | |
| static Rv<Handle> load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node); | |
| protected: | |
| Cmp_is_true() = default; | |
| }; | |
| bool | |
| Cmp_is_true::operator()(Context &, Feature const &feature) const | |
| { | |
| return feature.as_bool(); | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_is_true::load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node) | |
| { | |
| return {Handle{new self_type}, {}}; | |
| } | |
| /** Compare a boolean value. | |
| * Check if a value is false. | |
| */ | |
| class Cmp_is_false : public Comparison | |
| { | |
| using self_type = Cmp_is_false; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static const std::string KEY; ///< Comparison name. | |
| static const ValueMask TYPES; ///< Supported types. | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| /// Construct an instance from YAML configuration. | |
| static Rv<Handle> load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node); | |
| protected: | |
| Cmp_is_false() = default; | |
| }; | |
| const std::string Cmp_is_false::KEY{"is-false"}; | |
| const ValueMask Cmp_is_false::TYPES{MaskFor({STRING, BOOLEAN, INTEGER})}; | |
| bool | |
| Cmp_is_false::operator()(Context &, Feature const &feature) const | |
| { | |
| return !feature.as_bool(); | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_is_false::load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node) | |
| { | |
| return {Handle{new self_type}, {}}; | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| /** Check for NULL value. | |
| */ | |
| class Cmp_is_null : public Comparison | |
| { | |
| using self_type = Cmp_is_null; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static const std::string KEY; ///< Comparison name. | |
| static const ValueMask TYPES; ///< Supported types. | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, feature_type_for<NIL>) const override; | |
| /// Construct an instance from YAML configuration. | |
| static Rv<Handle> load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node); | |
| protected: | |
| Cmp_is_null() = default; | |
| }; | |
| const std::string Cmp_is_null::KEY{"is-null"}; | |
| const ValueMask Cmp_is_null::TYPES{MaskFor(NIL)}; | |
| bool | |
| Cmp_is_null::operator()(Context &, feature_type_for<NIL>) const | |
| { | |
| return true; | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_is_null::load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node) | |
| { | |
| return {Handle{new self_type}, {}}; | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| /** Check for empty (NULL or empty string) | |
| */ | |
| class Cmp_is_empty : public Comparison | |
| { | |
| using self_type = Cmp_is_empty; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static const std::string KEY; ///< Comparison name. | |
| static const ValueMask TYPES; ///< Supported types. | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, feature_type_for<NIL>) const override; | |
| bool operator()(Context &ctx, feature_type_for<STRING> const &s) const override; | |
| bool operator()(Context &ctx, feature_type_for<TUPLE> const &s) const override; | |
| /// Construct an instance from YAML configuration. | |
| static Rv<Handle> load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node); | |
| protected: | |
| Cmp_is_empty() = default; | |
| }; | |
| const std::string Cmp_is_empty::KEY{"is-empty"}; | |
| const ValueMask Cmp_is_empty::TYPES{MaskFor({NIL, STRING, TUPLE})}; | |
| bool | |
| Cmp_is_empty::operator()(Context &, feature_type_for<NIL>) const | |
| { | |
| return true; | |
| } | |
| bool | |
| Cmp_is_empty::operator()(Context &, feature_type_for<STRING> const &s) const | |
| { | |
| return s.empty(); | |
| } | |
| bool | |
| Cmp_is_empty::operator()(Context &, feature_type_for<TUPLE> const &t) const | |
| { | |
| return t.count() == 0; | |
| } | |
| Rv<Comparison::Handle> | |
| Cmp_is_empty::load(Config &, YAML::Node const &, TextView const &, TextView const &, YAML::Node) | |
| { | |
| return {Handle{new self_type}, {}}; | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| /// Common elements for all binary integer comparisons. | |
| /// @internal Verify the base comparison operators for @c Feature support these types. | |
| struct Base_Binary_Cmp : public Comparison { | |
| using self_type = Base_Binary_Cmp; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static inline const ActiveType TYPES{INTEGER, BOOLEAN, IP_ADDR, DURATION}; ///< Mask for supported types. | |
| /** Instantiate an instance from YAML configuration. | |
| * | |
| * @tparam Concrete type to instantiate. | |
| * @param cfg Global configuration object. | |
| * @param cmp_node The node containing the comparison. | |
| * @param key Key for comparison. | |
| * @param arg Argument for @a key, if any (stripped from @a key). | |
| * @param value_node Value node for for @a key. | |
| * @return An instance or errors on failure. | |
| */ | |
| template <typename T> | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| Expr _expr; | |
| Base_Binary_Cmp(Expr &&expr) : _expr(std::move(expr)) {} | |
| }; | |
| template <typename T> | |
| Rv<Comparison::Handle> | |
| Base_Binary_Cmp::load(Config &cfg, YAML::Node const &, TextView const &key, TextView const &, YAML::Node value_node) | |
| { | |
| auto &&[expr, errata] = cfg.parse_expr(value_node); | |
| if (!errata.is_ok()) { | |
| return std::move(errata.note(R"(While parsing comparison "{}" value at {}.)", key, value_node.Mark())); | |
| } | |
| auto expr_type = expr.result_type(); | |
| if (!expr_type.can_satisfy(TYPES)) { | |
| return Errata(S_ERROR, R"(The value is of type "{}" for "{}" at {} which is not "{}" as required.)", expr_type, key, | |
| value_node.Mark(), TYPES); | |
| } | |
| return Handle(new T(std::move(expr))); | |
| } | |
| // --- The concrete comparisons. | |
| struct Cmp_eq : public Base_Binary_Cmp { | |
| using self_type = Cmp_eq; ///< Self reference type. | |
| using super_type = Base_Binary_Cmp; ///< Parent type. | |
| public: | |
| static inline const std::string KEY = "eq"; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool | |
| operator()(Context &ctx, Feature const &f) const override | |
| { | |
| return f == ctx.extract(_expr); | |
| } | |
| static Rv<Handle> | |
| load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node) | |
| { | |
| return super_type::load<self_type>(cfg, cmp_node, key, arg, value_node); | |
| } | |
| }; | |
| struct Cmp_ne : public Base_Binary_Cmp { | |
| using self_type = Cmp_ne; ///< Self reference type. | |
| using super_type = Base_Binary_Cmp; ///< Parent type. | |
| public: | |
| static inline const std::string KEY = "ne"; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool | |
| operator()(Context &ctx, Feature const &f) const override | |
| { | |
| return f != ctx.extract(_expr); | |
| } | |
| static Rv<Handle> | |
| load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node) | |
| { | |
| return super_type::load<self_type>(cfg, cmp_node, key, arg, value_node); | |
| } | |
| }; | |
| struct Cmp_lt : public Base_Binary_Cmp { | |
| using self_type = Cmp_lt; ///< Self reference type. | |
| using super_type = Base_Binary_Cmp; ///< Parent type. | |
| public: | |
| static inline const std::string KEY = "lt"; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool | |
| operator()(Context &ctx, Feature const &f) const override | |
| { | |
| return f < ctx.extract(_expr); | |
| } | |
| static Rv<Handle> | |
| load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node) | |
| { | |
| return super_type::load<self_type>(cfg, cmp_node, key, arg, value_node); | |
| } | |
| }; | |
| struct Cmp_le : public Base_Binary_Cmp { | |
| using self_type = Cmp_le; ///< Self reference type. | |
| using super_type = Base_Binary_Cmp; ///< Parent type. | |
| public: | |
| static inline const std::string KEY = "le"; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool | |
| operator()(Context &ctx, Feature const &f) const override | |
| { | |
| return f <= ctx.extract(_expr); | |
| } | |
| static Rv<Handle> | |
| load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node) | |
| { | |
| return super_type::load<self_type>(cfg, cmp_node, key, arg, value_node); | |
| } | |
| }; | |
| struct Cmp_gt : public Base_Binary_Cmp { | |
| using self_type = Cmp_gt; ///< Self reference type. | |
| using super_type = Base_Binary_Cmp; ///< Parent type. | |
| public: | |
| static inline const std::string KEY = "gt"; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool | |
| operator()(Context &ctx, Feature const &f) const override | |
| { | |
| return ctx.extract(_expr) < f; | |
| } | |
| static Rv<Handle> | |
| load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node) | |
| { | |
| return super_type::load<self_type>(cfg, cmp_node, key, arg, value_node); | |
| } | |
| }; | |
| struct Cmp_ge : public Base_Binary_Cmp { | |
| using self_type = Cmp_ge; ///< Self reference type. | |
| using super_type = Base_Binary_Cmp; ///< Parent type. | |
| public: | |
| static inline const std::string KEY = "ge"; | |
| using super_type::super_type; | |
| using Comparison::operator(); // declare hidden member function | |
| bool | |
| operator()(Context &ctx, Feature const &f) const override | |
| { | |
| return ctx.extract(_expr) <= f; | |
| } | |
| static Rv<Handle> | |
| load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node) | |
| { | |
| return super_type::load<self_type>(cfg, cmp_node, key, arg, value_node); | |
| } | |
| }; | |
| // --- // | |
| /// Compare against a range. | |
| class Cmp_in : public Comparison | |
| { | |
| using self_type = Cmp_in; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static const std::string KEY; ///< Comparison name. | |
| static const ActiveType TYPES; ///< Supported types. | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, feature_type_for<INTEGER> n) const override; | |
| bool operator()(Context &ctx, feature_type_for<IP_ADDR> const &addr) const override; | |
| /// Construct an instance from YAML configuration. | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| Expr _min; | |
| Expr _max; | |
| }; | |
| const std::string Cmp_in::KEY{"in"}; | |
| const ActiveType Cmp_in::TYPES{INTEGER, IP_ADDR}; | |
| bool | |
| Cmp_in::operator()(Context &ctx, feature_type_for<IP_ADDR> const &addr) const | |
| { | |
| auto lhs = ctx.extract(_min); | |
| auto rhs = ctx.extract(_max); | |
| return (lhs.index() == rhs.index()) && (lhs.index() == IndexFor(IP_ADDR)) && (std::get<IndexFor(IP_ADDR)>(lhs) <= addr) && | |
| (addr <= std::get<IndexFor(IP_ADDR)>(rhs)); | |
| } | |
| bool | |
| Cmp_in::operator()(Context &ctx, feature_type_for<INTEGER> n) const | |
| { | |
| auto lhs = ctx.extract(_min); | |
| auto rhs = ctx.extract(_max); | |
| return (lhs.index() == rhs.index()) && (lhs.index() == IndexFor(INTEGER)) && (std::get<IndexFor(INTEGER)>(lhs) <= n) && | |
| (n <= std::get<IndexFor(INTEGER)>(rhs)); | |
| } | |
| auto | |
| Cmp_in::load(Config &cfg, YAML::Node const &cmp_node, TextView const &, TextView const &, YAML::Node value_node) -> Rv<Handle> | |
| { | |
| auto self = new self_type; | |
| Handle handle{self}; | |
| if (value_node.IsScalar()) { | |
| // Check if it's a valid IP range - all done. | |
| swoc::IPRange ip_range; | |
| if (ip_range.load(value_node.Scalar())) { | |
| if (!cfg.active_type().can_satisfy(IP_ADDR)) { | |
| return Errata(S_ERROR, R"("{}" at line {} cannot check values of type {} against a feature of type {}.)", KEY, | |
| cmp_node.Mark(), IP_ADDR, cfg.active_type()); | |
| } | |
| self->_min = Feature{ip_range.min()}; | |
| self->_max = Feature{ip_range.max()}; | |
| return handle; | |
| } | |
| // Need to parse and verify it's a range of integers. | |
| TextView max_text = value_node.Scalar(); | |
| auto min_text = max_text.take_prefix_at('-'); | |
| TextView parsed; | |
| if (max_text.empty()) { | |
| return Errata( | |
| S_ERROR, | |
| R"(Value for "{}" at line {} must be two integers separated by a '-', or IP address range or network. [separate '-' not found])", | |
| KEY, cmp_node.Mark()); | |
| } | |
| auto n_min = svtoi(min_text.trim_if(&isspace), &parsed); | |
| if (parsed.size() != min_text.size()) { | |
| return Errata( | |
| S_ERROR, | |
| R"(Value for "{}" at line {} must be two integers separated by a '-', or IP address range or network. [minimum value "{}" is not an integer])", | |
| KEY, cmp_node.Mark(), min_text); | |
| } | |
| auto n_max = svtoi(max_text.trim_if(&isspace), &parsed); | |
| if (parsed.size() != max_text.size()) { | |
| return Errata( | |
| S_ERROR, | |
| R"(Value for "{}" at line {} must be two integers separated by a '-', or IP address range or network. [maximum value "{}" is not an integer])", | |
| KEY, cmp_node.Mark(), max_text); | |
| } | |
| if (!cfg.active_type().can_satisfy(INTEGER)) { | |
| return Errata(S_ERROR, R"("{}" at line {} cannot check values of type {} against a feature of type {}.)", KEY, | |
| cmp_node.Mark(), INTEGER, cfg.active_type()); | |
| } | |
| self->_min = Feature(n_min); | |
| self->_max = Feature(n_max); | |
| return handle; | |
| } else if (value_node.IsSequence()) { | |
| if (value_node.size() == 2) { | |
| auto &&[lhs, lhs_errata] = cfg.parse_expr(value_node[0]); | |
| if (!lhs_errata.is_ok()) { | |
| return std::move(lhs_errata); | |
| } | |
| auto lhs_type = lhs.result_type(); | |
| auto &&[rhs, rhs_errata] = cfg.parse_expr(value_node[1]); | |
| if (!rhs_errata.is_ok()) { | |
| return std::move(rhs_errata); | |
| } | |
| auto rhs_type = rhs.result_type(); | |
| if (lhs_type != rhs_type) { | |
| return Errata(S_ERROR, R"("{}" at line {} cannot compare a range of mixed types [{}, {}].)", KEY, cmp_node.Mark(), lhs_type, | |
| rhs_type); | |
| } | |
| if (!lhs_type.can_satisfy(MaskFor({INTEGER, IP_ADDR}))) { | |
| return Errata(S_ERROR, R"("{}" at line {} requires values of type {} or {}, not {}.)", KEY, cmp_node.Mark(), INTEGER, | |
| IP_ADDR, lhs_type); | |
| } | |
| if (!cfg.active_type().can_satisfy(lhs_type)) { | |
| return Errata(S_ERROR, R"("{}" at line {} cannot check values of type {} against a feature of type {}.)", KEY, | |
| cmp_node.Mark(), lhs_type, cfg.active_type()); | |
| } | |
| self->_min = std::move(lhs); | |
| self->_max = std::move(rhs); | |
| return handle; | |
| } else { | |
| return Errata(S_ERROR, R"(The list for "{}" at line {} is not exactly 2 elements are required.)", KEY, cmp_node.Mark()); | |
| } | |
| } | |
| return Errata( | |
| S_ERROR, | |
| R"(Value for "{}" at line {} must be a string representing an integer range, an IP address range or netowork, or list of two integers or IP addresses.)", | |
| KEY, cmp_node.Mark()); | |
| } | |
| /* ------------------------------------------------------------------------------------ */ | |
| class ComboComparison : public Comparison | |
| { | |
| using self_type = ComboComparison; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static const ActiveType TYPES; ///< Supported types. | |
| virtual TextView const &key() const = 0; | |
| /// Construct an instance from YAML configuration. | |
| static Rv<std::vector<Handle>> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, | |
| YAML::Node value_node); | |
| protected: | |
| /// List of sub-comparisons. | |
| std::vector<Handle> _cmps; | |
| ComboComparison(std::vector<Handle> &&cmps) : _cmps(std::move(cmps)) {} | |
| static Errata load_case(Config &cfg, std::vector<Handle> &cmps, YAML::Node node); | |
| }; | |
| const ActiveType ComboComparison::TYPES{ActiveType::any_type()}; | |
| auto | |
| ComboComparison::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &, | |
| YAML::Node value_node) -> Rv<std::vector<Handle>> | |
| { | |
| std::vector<Handle> cmps; | |
| if (value_node.IsMap()) { | |
| auto errata = self_type::load_case(cfg, cmps, value_node); | |
| if (!errata.is_ok()) { | |
| errata.note("While parsing {} comparison at {}.", key, cmp_node.Mark()); | |
| } | |
| } else if (value_node.IsSequence()) { | |
| cmps.reserve(cmp_node.size()); | |
| for (auto child : value_node) { | |
| auto errata = self_type::load_case(cfg, cmps, std::move(child)); | |
| if (!errata.is_ok()) { | |
| errata.note("While parsing {} comparison at {}.", key, cmp_node.Mark()); | |
| return errata; | |
| } | |
| } | |
| } | |
| return cmps; | |
| } | |
| Errata | |
| ComboComparison::load_case(Config &cfg, std::vector<Handle> &cmps, YAML::Node node) | |
| { | |
| auto &&[cmp_handle, cmp_errata]{Comparison::load(cfg, node)}; | |
| if (!cmp_errata.is_ok()) { | |
| return std::move(cmp_errata); | |
| } | |
| cmps.emplace_back(std::move(cmp_handle)); | |
| return {}; | |
| } | |
| // --- | |
| class Cmp_any_of : public ComboComparison | |
| { | |
| using self_type = Cmp_any_of; ///< Self reference type. | |
| using super_type = ComboComparison; ///< Parent type. | |
| public: | |
| static constexpr TextView KEY = "any-of"; ///< Comparison name. | |
| TextView const & | |
| key() const override | |
| { | |
| return KEY; | |
| } | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| Cmp_any_of(std::vector<Handle> &&cmps) : super_type(std::move(cmps)) {} | |
| }; | |
| bool | |
| Cmp_any_of::operator()(Context &ctx, Feature const &feature) const | |
| { | |
| return std::any_of(_cmps.begin(), _cmps.end(), [&](Handle const &cmp) -> bool { return (*cmp)(ctx, feature); }); | |
| } | |
| auto | |
| Cmp_any_of::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, | |
| YAML::Node value_node) -> Rv<Handle> | |
| { | |
| auto &&[cmps, errata] = super_type::load(cfg, cmp_node, key, arg, value_node); | |
| if (!errata.is_ok()) { | |
| return std::move(errata); | |
| } | |
| return Handle{new self_type{std::move(cmps)}}; | |
| } | |
| // --- | |
| class Cmp_all_of : public ComboComparison | |
| { | |
| using self_type = Cmp_all_of; ///< Self reference type. | |
| using super_type = ComboComparison; ///< Parent type. | |
| public: | |
| static constexpr TextView KEY = "all-of"; ///< Comparison name. | |
| TextView const & | |
| key() const override | |
| { | |
| return KEY; | |
| } | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| Cmp_all_of(std::vector<Handle> &&cmps) : super_type(std::move(cmps)) {} | |
| }; | |
| bool | |
| Cmp_all_of::operator()(Context &ctx, Feature const &feature) const | |
| { | |
| return std::all_of(_cmps.begin(), _cmps.end(), [&](Handle const &cmp) -> bool { return (*cmp)(ctx, feature); }); | |
| } | |
| auto | |
| Cmp_all_of::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, | |
| YAML::Node value_node) -> Rv<Handle> | |
| { | |
| auto &&[cmps, errata] = super_type::load(cfg, cmp_node, key, arg, value_node); | |
| if (!errata.is_ok()) { | |
| return std::move(errata); | |
| } | |
| return Handle{new self_type{std::move(cmps)}}; | |
| } | |
| // --- | |
| class Cmp_none_of : public ComboComparison | |
| { | |
| using self_type = Cmp_none_of; ///< Self reference type. | |
| using super_type = ComboComparison; ///< Parent type. | |
| public: | |
| static constexpr TextView KEY = "none-of"; ///< Comparison name. | |
| TextView const & | |
| key() const override | |
| { | |
| return KEY; | |
| } | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| Cmp_none_of(std::vector<Handle> &&cmps) : super_type(std::move(cmps)) {} | |
| }; | |
| bool | |
| Cmp_none_of::operator()(Context &ctx, Feature const &feature) const | |
| { | |
| return std::none_of(_cmps.begin(), _cmps.end(), [&](Handle const &cmp) -> bool { return (*cmp)(ctx, feature); }); | |
| } | |
| auto | |
| Cmp_none_of::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, | |
| YAML::Node value_node) -> Rv<Handle> | |
| { | |
| auto &&[cmps, errata] = super_type::load(cfg, cmp_node, key, arg, value_node); | |
| if (!errata.is_ok()) { | |
| return std::move(errata); | |
| } | |
| return Handle{new self_type{std::move(cmps)}}; | |
| } | |
| // --- | |
| class Cmp_for_all : public Comparison | |
| { | |
| using self_type = Cmp_for_all; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static constexpr TextView KEY = "for-all"; ///< Comparison name. | |
| static const ActiveType TYPES; ///< Supported types. | |
| TextView const & | |
| key() const | |
| { | |
| return KEY; | |
| } | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| Comparison::Handle _cmp; | |
| explicit Cmp_for_all(Handle &&cmp) : _cmp(std::move(cmp)) {} | |
| }; | |
| const ActiveType Cmp_for_all::TYPES{ActiveType::any_type()}; | |
| bool | |
| Cmp_for_all::operator()(Context &ctx, Feature const &feature) const | |
| { | |
| if (TUPLE != feature.value_type()) { | |
| return (*_cmp)(ctx, feature); | |
| } | |
| auto &t{std::get<TUPLE>(feature)}; | |
| return std::all_of(t.begin(), t.end(), [&](Feature const &f) { return (*_cmp)(ctx, f); }); | |
| } | |
| auto | |
| Cmp_for_all::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &, | |
| YAML::Node value_node) -> Rv<Handle> | |
| { | |
| if (!value_node.IsMap()) { | |
| return Errata(S_ERROR, "{} comparison value at {} must be a single comparison.", key, value_node.Mark()); | |
| } | |
| auto scope{cfg.feature_scope(cfg.active_type().tuple_types())}; | |
| auto &&[cmp, errata] = Comparison::load(cfg, value_node); | |
| if (!errata.is_ok()) { | |
| errata.note("While parsing nested comparison of {} at {}.", key, cmp_node.Mark()); | |
| return std::move(errata); | |
| } | |
| return Handle(new self_type{std::move(cmp)}); | |
| } | |
| // --- | |
| class Cmp_for_any : public Comparison | |
| { | |
| using self_type = Cmp_for_any; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static constexpr TextView KEY = "for-any"; ///< Comparison name. | |
| static const ActiveType TYPES; ///< Supported types. | |
| TextView const & | |
| key() const | |
| { | |
| return KEY; | |
| } | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| Comparison::Handle _cmp; | |
| explicit Cmp_for_any(Handle &&cmp) : _cmp(std::move(cmp)) {} | |
| }; | |
| const ActiveType Cmp_for_any::TYPES{ActiveType::any_type()}; | |
| bool | |
| Cmp_for_any::operator()(Context &ctx, Feature const &feature) const | |
| { | |
| if (TUPLE != feature.value_type()) { | |
| return (*_cmp)(ctx, feature); | |
| } | |
| auto &t{std::get<TUPLE>(feature)}; | |
| return std::any_of(t.begin(), t.end(), [&](Feature const &f) { return (*_cmp)(ctx, f); }); | |
| } | |
| auto | |
| Cmp_for_any::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &, | |
| YAML::Node value_node) -> Rv<Handle> | |
| { | |
| if (!value_node.IsMap()) { | |
| return Errata(S_ERROR, "{} comparison value at {} must be a single comparison.", key, value_node.Mark()); | |
| } | |
| auto scope{cfg.feature_scope(cfg.active_type().tuple_types())}; | |
| auto &&[cmp, errata] = Comparison::load(cfg, value_node); | |
| if (!errata.is_ok()) { | |
| errata.note("While parsing nested comparison of {} at {}.", key, cmp_node.Mark()); | |
| return std::move(errata); | |
| } | |
| return Handle(new self_type{std::move(cmp)}); | |
| } | |
| // --- | |
| class Cmp_for_none : public Comparison | |
| { | |
| using self_type = Cmp_for_none; ///< Self reference type. | |
| using super_type = Comparison; ///< Parent type. | |
| public: | |
| static constexpr TextView KEY = "for-none"; ///< Comparison name. | |
| static const ActiveType TYPES; ///< Supported types. | |
| TextView const & | |
| key() const | |
| { | |
| return KEY; | |
| } | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| Comparison::Handle _cmp; | |
| explicit Cmp_for_none(Handle &&cmp) : _cmp(std::move(cmp)) {} | |
| }; | |
| const ActiveType Cmp_for_none::TYPES{ActiveType::any_type()}; | |
| bool | |
| Cmp_for_none::operator()(Context &ctx, Feature const &feature) const | |
| { | |
| if (TUPLE != feature.value_type()) { | |
| return (*_cmp)(ctx, feature); | |
| } | |
| auto &t{std::get<TUPLE>(feature)}; | |
| return std::all_of(t.begin(), t.end(), [&](Feature const &f) { return (*_cmp)(ctx, f); }); | |
| } | |
| auto | |
| Cmp_for_none::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &, | |
| YAML::Node value_node) -> Rv<Handle> | |
| { | |
| if (!value_node.IsMap()) { | |
| return Errata(S_ERROR, "{} comparison value at {} must be a single comparison.", key, value_node.Mark()); | |
| } | |
| auto scope{cfg.feature_scope(cfg.active_type().tuple_types())}; | |
| auto &&[cmp, errata] = Comparison::load(cfg, value_node); | |
| if (!errata.is_ok()) { | |
| errata.note("While parsing nested comparison of {} at {}.", key, cmp_node.Mark()); | |
| return std::move(errata); | |
| } | |
| return Handle(new self_type{std::move(cmp)}); | |
| } | |
| // --- | |
| /// Compare the active feature as a tuple to a list of comparisons. | |
| class Cmp_as_tuple : public ComboComparison | |
| { | |
| using self_type = Cmp_as_tuple; ///< Self reference type. | |
| using super_type = ComboComparison; ///< Parent type. | |
| public: | |
| static constexpr TextView KEY = "as-tuple"; ///< Comparison name. | |
| TextView const & | |
| key() const override | |
| { | |
| return KEY; | |
| } | |
| using Comparison::operator(); // declare hidden member function | |
| bool operator()(Context &ctx, Feature const &feature) const override; | |
| static Rv<Handle> load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, YAML::Node value_node); | |
| protected: | |
| explicit Cmp_as_tuple(std::vector<Handle> &&cmps) : super_type(std::move(cmps)) {} | |
| }; | |
| bool | |
| Cmp_as_tuple::operator()(Context &ctx, Feature const &feature) const | |
| { | |
| if (_cmps.empty()) { | |
| return true; | |
| } | |
| auto vtype = feature.value_type(); | |
| if (TUPLE != vtype) { | |
| return (*_cmps[0])(ctx, feature); | |
| } | |
| auto t = std::get<TUPLE>(feature); | |
| auto t_size = t.size(); | |
| auto c_size = _cmps.size(); | |
| auto limit = std::min(t_size, c_size); | |
| size_t idx = 0; | |
| while (idx < limit) { | |
| if (!(*_cmps[idx])(ctx, t[idx])) { | |
| return false; | |
| } | |
| ++idx; | |
| } | |
| return true; | |
| } | |
| auto | |
| Cmp_as_tuple::load(Config &cfg, YAML::Node const &cmp_node, TextView const &key, TextView const &arg, | |
| YAML::Node value_node) -> Rv<Handle> | |
| { | |
| auto ttype = cfg.active_type().tuple_types(); | |
| ActiveType atype{ttype}; | |
| auto scope = cfg.feature_scope(ttype); // drop tuple, use nested type. | |
| auto &&[cmps, errata] = super_type::load(cfg, cmp_node, key, arg, value_node); | |
| if (!errata.is_ok()) { | |
| return std::move(errata); | |
| } | |
| return Handle{new self_type{std::move(cmps)}}; | |
| } | |
| // --- ComparisonGroup --- // | |
| Errata | |
| ComparisonGroupBase::load(Config &cfg, YAML::Node node) | |
| { | |
| if (node.IsMap()) { | |
| auto errata{this->load_case(cfg, node)}; | |
| if (!errata.is_ok()) { | |
| return errata; | |
| } | |
| } else if (node.IsSequence()) { | |
| for (auto child : node) { | |
| if (auto errata{this->load_case(cfg, child)}; !errata.is_ok()) { | |
| return errata; | |
| } | |
| } | |
| } else { | |
| return Errata(S_ERROR, "The node at {} was not comparison nor a list of comparisons as required.", node.Mark()); | |
| } | |
| return {}; | |
| } | |
| Rv<Comparison::Handle> | |
| ComparisonGroupBase::load_cmp(Config &cfg, YAML::Node node) | |
| { | |
| auto &&[handle, errata]{Comparison::load(cfg, node)}; | |
| if (!errata.is_ok()) { | |
| return std::move(errata); | |
| } | |
| return std::move(handle); | |
| } | |
| // --- Initialization --- // | |
| namespace | |
| { | |
| [[maybe_unused]] bool INITIALIZED = []() -> bool { | |
| Comparison::define(Cmp_otherwise::KEY, Cmp_otherwise::TYPES, Cmp_otherwise::load); | |
| Comparison::define(Cmp_is_true::KEY, Cmp_is_true::TYPES, Cmp_is_true::load); | |
| Comparison::define(Cmp_is_false::KEY, Cmp_is_false::TYPES, Cmp_is_false::load); | |
| Comparison::define(Cmp_is_null::KEY, Cmp_is_null::TYPES, Cmp_is_null::load); | |
| Comparison::define(Cmp_is_empty::KEY, Cmp_is_empty::TYPES, Cmp_is_empty::load); | |
| Comparison::define(Cmp_LiteralString::MATCH_KEY, Cmp_LiteralString::TYPES, Cmp_LiteralString::load); | |
| Comparison::define(Cmp_LiteralString::PREFIX_KEY, Cmp_LiteralString::TYPES, Cmp_LiteralString::load); | |
| Comparison::define(Cmp_LiteralString::SUFFIX_KEY, Cmp_LiteralString::TYPES, Cmp_LiteralString::load); | |
| Comparison::define(Cmp_LiteralString::CONTAIN_KEY, Cmp_LiteralString::TYPES, Cmp_LiteralString::load); | |
| Comparison::define(Cmp_LiteralString::TLD_KEY, Cmp_LiteralString::TYPES, Cmp_LiteralString::load); | |
| Comparison::define(Cmp_LiteralString::PATH_KEY, Cmp_LiteralString::TYPES, Cmp_LiteralString::load); | |
| Comparison::define(Cmp_Rxp::KEY, Cmp_Rxp::TYPES, Cmp_Rxp::load); | |
| Comparison::define(Cmp_eq::KEY, Cmp_eq::TYPES, Cmp_eq::load); | |
| Comparison::define(Cmp_ne::KEY, Cmp_ne::TYPES, Cmp_ne::load); | |
| Comparison::define(Cmp_lt::KEY, Cmp_le::TYPES, Cmp_lt::load); | |
| Comparison::define(Cmp_le::KEY, Cmp_lt::TYPES, Cmp_le::load); | |
| Comparison::define(Cmp_gt::KEY, Cmp_gt::TYPES, Cmp_gt::load); | |
| Comparison::define(Cmp_ge::KEY, Cmp_ge::TYPES, Cmp_ge::load); | |
| Comparison::define(Cmp_in::KEY, Cmp_in::TYPES, Cmp_in::load); | |
| Comparison::define(Cmp_none_of::KEY, Cmp_none_of::TYPES, Cmp_none_of::load); | |
| Comparison::define(Cmp_all_of::KEY, Cmp_all_of::TYPES, Cmp_all_of::load); | |
| Comparison::define(Cmp_any_of::KEY, Cmp_any_of::TYPES, Cmp_any_of::load); | |
| Comparison::define(Cmp_as_tuple::KEY, Cmp_as_tuple::TYPES, Cmp_as_tuple::load); | |
| Comparison::define(Cmp_for_all::KEY, Cmp_for_all::TYPES, Cmp_for_all::load); | |
| Comparison::define(Cmp_for_any::KEY, Cmp_for_any::TYPES, Cmp_for_any::load); | |
| Comparison::define(Cmp_for_none::KEY, Cmp_for_none::TYPES, Cmp_for_none::load); | |
| return true; | |
| }(); | |
| } // namespace |