| /** |
| @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 "txn_box/Modifier.h" |
| #include "txn_box/Context.h" |
| #include "txn_box/Config.h" |
| #include "txn_box/Comparison.h" |
| #include "txn_box/yaml_util.h" |
| #include "tsutil/LocalBuffer.h" |
| |
| using swoc::Errata; |
| using swoc::Rv; |
| using swoc::TextView; |
| using namespace swoc::literals; |
| |
| static constexpr size_t FILTER_LIST_LOCAL_BUFFER_SIZE = 32; |
| |
| Errata |
| Modifier::define(swoc::TextView name, Modifier::Worker const &f) |
| { |
| if (auto spot = _factory.find(name); spot == _factory.end()) { |
| _factory.insert(spot, {name, f}); |
| return {}; |
| } |
| return Errata(S_ERROR, R"(Modifier "{}" is already defined.)", name); |
| } |
| |
| Rv<Modifier::Handle> |
| Modifier::load(Config &cfg, YAML::Node const &node, ActiveType ex_type) |
| { |
| if (!node.IsMap()) { |
| return Errata(S_ERROR, R"(Modifier at {} is not an object as required.)", 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); |
| } |
| // See if @a key is in the factory. |
| if (auto spot{_factory.find(key)}; spot != _factory.end()) { |
| auto &&[handle, errata]{spot->second(cfg, node, key, arg, value_node)}; |
| |
| if (!errata.is_ok()) { |
| return std::move(errata); |
| } |
| if (!handle->is_valid_for(ex_type)) { |
| return Errata(S_ERROR, R"(Modifier "{}" at {} cannot accept a feature of type "{}".)", key, node.Mark(), ex_type); |
| } |
| |
| return std::move(handle); |
| } |
| } |
| return Errata(S_ERROR, R"(No valid modifier key in object at {}.)", node.Mark()); |
| } |
| |
| swoc::Rv<Feature> |
| Modifier::operator()(Context &, feature_type_for<NIL>) |
| { |
| return NIL_FEATURE; |
| } |
| |
| swoc::Rv<Feature> |
| Modifier::operator()(Context &, feature_type_for<IP_ADDR>) |
| { |
| return NIL_FEATURE; |
| } |
| |
| swoc::Rv<Feature> |
| Modifier::operator()(Context &, feature_type_for<STRING>) |
| { |
| return NIL_FEATURE; |
| } |
| |
| // --- |
| |
| class Mod_hash : public Modifier |
| { |
| using self_type = Mod_hash; |
| using super_type = Modifier; |
| |
| public: |
| static const std::string KEY; ///< Identifier name. |
| using Modifier::operator(); // declare hidden member function |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify. |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, feature_type_for<STRING> feature) override; |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ex_type Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| protected: |
| unsigned _n = 0; ///< Number of hash buckets. |
| |
| /// Constructor for @c load. |
| Mod_hash(unsigned n); |
| }; |
| |
| const std::string Mod_hash::KEY{"hash"}; |
| |
| Mod_hash::Mod_hash(unsigned n) : _n(n) {} |
| |
| bool |
| Mod_hash::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(STRING); |
| } |
| |
| ActiveType |
| Mod_hash::result_type(ActiveType const &) const |
| { |
| return {NIL, INTEGER}; |
| } |
| |
| Rv<Feature> |
| Mod_hash::operator()(Context &, feature_type_for<STRING> feature) |
| { |
| feature_type_for<INTEGER> value = std::hash<std::string_view>{}(feature); |
| return Feature{feature_type_for<INTEGER>{value % _n}}; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_hash::load(Config &, YAML::Node node, TextView, TextView, YAML::Node key_value) |
| { |
| if (!key_value.IsScalar()) { |
| return Errata(S_ERROR, R"(Value for "{}" at {} in modifier at {} is not a number as required.)", KEY, key_value.Mark(), |
| node.Mark()); |
| } |
| TextView src{key_value.Scalar()}, parsed; |
| src.trim_if(&isspace); |
| auto n = swoc::svtou(src, &parsed); |
| if (src.size() != parsed.size()) { |
| return Errata(S_ERROR, R"(Value "{}" for "{}" at {} in modifier at {} is not a number as required.)", src, KEY, |
| key_value.Mark(), node.Mark()); |
| } |
| if (n < 2) { |
| return Errata(S_ERROR, R"(Value "{}" for "{}" at {} in modifier at {} must be at least 2.)", src, KEY, key_value.Mark(), |
| node.Mark()); |
| } |
| |
| return {Handle{new self_type(n)}, {}}; |
| } |
| |
| // --- |
| |
| /// Do replacement based on regular expression matching. |
| class Mod_rxp_replace : public Modifier |
| { |
| using self_type = Mod_rxp_replace; |
| using super_type = Modifier; |
| |
| public: |
| static inline const std::string KEY{"rxp-replace"}; ///< Identifier name. |
| using Modifier::operator(); // declare hidden member function |
| |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| ActiveType result_type(ActiveType const &) const override; |
| |
| Rv<Feature> operator()(Context &ctx, feature_type_for<STRING> src) override; |
| |
| static constexpr TextView ARG_GLOBAL = "g"; ///< Global replace. |
| static constexpr TextView ARG_NOCASE = "nc"; ///< Case insensitive match. |
| protected: |
| RxpOp _op; ///< Regular expression operator. |
| Expr _replacement; ///< Replacement text. |
| bool _global_p = false; |
| |
| Mod_rxp_replace(RxpOp &&op, Expr &&replacement) : _op(std::move(op)), _replacement(std::move(replacement)) {} |
| }; |
| |
| bool |
| Mod_rxp_replace::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(STRING); |
| } |
| |
| ActiveType |
| Mod_rxp_replace::result_type(ActiveType const &) const |
| { |
| return {NIL, STRING}; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_rxp_replace::load(Config &cfg, YAML::Node node, TextView, TextView args, YAML::Node key_value) |
| { |
| Rxp::Options opt; |
| bool global_p = false; |
| |
| if (!key_value.IsSequence() || key_value.size() != 2) { |
| return Errata(S_ERROR, R"(Value for modifier "{}" at {} is not list of size 2 - [ pattern, replacement ] - as required.)", KEY, |
| node.Mark()); |
| } |
| |
| while (args) { |
| auto token = args.take_prefix_at(','); |
| if (ARG_GLOBAL == token) { |
| global_p = true; |
| } else if (ARG_NOCASE == token) { |
| opt.f.nc = true; |
| } else { |
| return Errata(S_ERROR, R"(Invalid option "{}" for modifier "{}" at {}.)", token, KEY, key_value.Mark()); |
| } |
| } |
| |
| YAML::Node rxp_src_node = key_value[0]; |
| auto &&[pattern, pattern_errata]{cfg.parse_expr(key_value[0])}; |
| if (!pattern_errata.is_ok()) { |
| pattern_errata.note(R"(While parsing expression for "{}" modifier at {}.)", KEY, key_value.Mark()); |
| return std::move(pattern_errata); |
| } |
| |
| auto &&[op, op_errata]{RxpOp::load(cfg, std::move(pattern), opt)}; |
| if (!op_errata.is_ok()) { |
| op_errata.note(R"(While parsing pattern for modifier "{}".)", KEY); |
| return std::move(op_errata); |
| } |
| cfg.require_rxp_group_count(op.capture_count()); |
| |
| auto &&[rep, rep_errata]{cfg.parse_expr(key_value[1])}; |
| if (!rep_errata.is_ok()) { |
| rep_errata.note(R"(While parsing replacement for modifier "{}".)", KEY); |
| return std::move(rep_errata); |
| } |
| |
| auto self = new self_type(std::move(op), std::move(rep)); |
| self->_global_p = global_p; |
| return {Handle(self)}; |
| } |
| |
| Rv<Feature> |
| Mod_rxp_replace::operator()(Context &ctx, feature_type_for<STRING> src) |
| { |
| /* Some unfortunate issues make this more complex than one might assume. |
| * The root issue is the non-recursive nature of the transient buffer. To generate the |
| * replacement text the feature for it must be extracted every time, because it is likely to |
| * depend on extracting capture groups which can vary with every replacement. This will trash |
| * the overall result if it also stored in the transient buffer. Therefore it is necessary to |
| * construct pieces of the final string as the replacements are done, and then assemble them |
| * later. At some point it could be desirable to make this sort of piece wise string a general |
| * type that can be passed around, without having to immediately render it as a contiguous string. |
| * For now, this is just here. |
| */ |
| struct piece { |
| TextView _text; |
| piece *_next = nullptr; |
| }; |
| |
| piece anchor; // to avoid checking for nullptr / start of list. |
| piece *last = &anchor; // last piece added. |
| [[maybe_unused]] int result; |
| |
| while (src && (result = _op(ctx, src)) > 0) { |
| auto match = ctx.active_group(0); |
| auto p = ctx.make<piece>(); |
| p->_text.assign(src.data(), match.data()); |
| last->_next = p; |
| last = p; |
| // write the replacement text. |
| auto r = ctx.extract(_replacement); |
| if (r.index() == IndexFor(STRING)) { |
| ctx.commit(r); |
| auto rp = ctx.make<piece>(); |
| rp->_text = std::get<IndexFor(STRING)>(r); |
| last->_next = rp; |
| last = rp; |
| } |
| // Clip the match. |
| src.assign(match.data_end(), src.data_end()); |
| if (!_global_p) { |
| break; |
| } |
| } |
| |
| // How big is the result? |
| size_t n = src.size(); |
| for (piece *spot = anchor._next; spot != nullptr; spot = spot->_next) { |
| n += spot->_text.size(); |
| } |
| |
| // Pieces assemble! |
| auto span = ctx.transient_buffer(n).rebind<char>(); |
| swoc::FixedBufferWriter w(span); |
| for (piece *spot = anchor._next; spot != nullptr; spot = spot->_next) { |
| w.write(spot->_text); |
| } |
| w.write(src); |
| ctx.transient_finalize(w.size()); |
| |
| return {FeatureView(w.view())}; |
| } |
| |
| // --- |
| /// Filter a list. |
| class Mod_filter : public FilterMod |
| { |
| using self_type = Mod_filter; |
| using super_type = Modifier; |
| |
| public: |
| static inline const std::string KEY = "filter"; ///< Identifier name. |
| using super_type::operator(); // declare hidden member function |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify [in,out] |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, Feature &feature) override; |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modify that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &ex_type) const override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| /// A filter comparison case. |
| struct Case { |
| Action _action = PASS; ///< Action on match. |
| Expr _expr; ///< Replacement expression, if any. |
| Comparison::Handle _cmp; ///< Comparison. |
| |
| /// Assign the comparison for this case. |
| void assign(Comparison::Handle &&handle); |
| |
| bool operator()(Context &ctx, Feature const &feature); |
| |
| /** YAML load hook. |
| * |
| * @param cfg Configuration. |
| * @param node Node containing comparison. |
| * @return Errors, if any. |
| * |
| * This is called during case loading, before the generic loading is done. It is required to |
| * check any non-generic keys and remove them - anything not expected by the generic load will |
| * be flagged as an error. |
| */ |
| Errata pre_load(Config &cfg, YAML::Node node); |
| }; |
| |
| /// Container for cases with comparisons. |
| ComparisonGroup<Case> _cases; |
| |
| /** Perform the comparisons for the filter. |
| * |
| * @param ctx Txn context |
| * @param feature Feature to compare |
| * @return The matched @c Case or @c nullptr if no case matched. |
| */ |
| Case const *compare(Context &ctx, Feature const &feature) const; |
| }; |
| |
| bool |
| Mod_filter::is_valid_for(ActiveType const &) const |
| { |
| return true; |
| } |
| |
| ActiveType |
| Mod_filter::result_type(ActiveType const &ex_type) const |
| { |
| return ex_type; |
| } |
| |
| auto |
| Mod_filter::compare(Context &ctx, Feature const &feature) const -> Case const * |
| { |
| for (auto const &c : _cases) { |
| if (!c._cmp || (*c._cmp)(ctx, feature)) { |
| return &c; |
| } |
| } |
| return nullptr; |
| } |
| |
| Rv<Feature> |
| Mod_filter::operator()(Context &ctx, Feature &feature) |
| { |
| Feature zret{}; |
| if (feature.is_list()) { |
| auto src = std::get<IndexFor(TUPLE)>(feature); |
| ts::LocalBuffer<Feature, FILTER_LIST_LOCAL_BUFFER_SIZE> farray(src.count()); |
| feature_type_for<TUPLE> dst = {farray.data(), src.count()}; |
| unsigned dst_idx = 0; |
| for (Feature f = feature; !is_nil(f); f = cdr(f)) { |
| Feature item = car(f); |
| auto c = _cases(ctx, item); |
| Action action = ((c != _cases.end()) ? c->_action : DROP); |
| switch (action) { |
| case DROP: |
| break; |
| case PASS: |
| dst[dst_idx++] = item; |
| break; |
| case REPLACE: |
| dst[dst_idx++] = ctx.extract(c->_expr); |
| break; |
| } |
| } |
| auto span = ctx.alloc_span<Feature>(dst_idx); |
| for (unsigned idx = 0; idx < dst_idx; ++idx) { |
| span[idx] = dst[idx]; |
| } |
| zret = span; |
| } else { |
| auto c = this->compare(ctx, feature); |
| Action action = c ? c->_action : DROP; |
| switch (action) { |
| case DROP: |
| zret = NIL_FEATURE; |
| break; |
| case PASS: |
| zret = feature; |
| break; |
| case REPLACE: |
| zret = ctx.extract(c->_expr); |
| break; |
| } |
| } |
| return zret; |
| } |
| |
| void |
| Mod_filter::Case::assign(Comparison::Handle &&handle) |
| { |
| _cmp = std::move(handle); |
| } |
| |
| Errata |
| Mod_filter::Case::pre_load(Config &cfg, YAML::Node cmp_node) |
| { |
| if (!cmp_node.IsMap()) { |
| return Errata(S_ERROR, "List element at {} for {} modifier is not a comparison object.", cmp_node.Mark(), KEY); |
| } |
| |
| Expr replace_expr; |
| unsigned action_count = 0; |
| |
| if (auto do_node = cmp_node[Global::DO_KEY]; do_node) { |
| return Errata(S_ERROR, R"("{}" at line {} is not allowed in a modifier comparison.)", Global::DO_KEY, do_node.Mark()); |
| } |
| |
| YAML::Node drop_node = cmp_node[ACTION_DROP]; |
| if (drop_node) { |
| _action = DROP; |
| cmp_node.remove(ACTION_DROP); |
| ++action_count; |
| } |
| |
| YAML::Node pass_node = cmp_node[ACTION_PASS]; |
| if (pass_node) { |
| _action = PASS; |
| cmp_node.remove(ACTION_PASS); |
| ++action_count; |
| } |
| |
| YAML::Node replace_node = cmp_node[ACTION_REPLACE]; |
| if (replace_node) { |
| auto &&[expr, errata] = cfg.parse_expr(replace_node); |
| if (!errata.is_ok()) { |
| errata.note("While parsing expression at {} for {} key in comparison at {}.", replace_node.Mark(), ACTION_REPLACE, |
| cmp_node.Mark()); |
| return std::move(errata); |
| } |
| _expr = std::move(expr); |
| _action = REPLACE; |
| cmp_node.remove(ACTION_REPLACE); |
| ++action_count; |
| } |
| |
| if (action_count > 1) { |
| return Errata(S_ERROR, "Only one of {}, {}, {} is allowed in the {} comparison at {}.", ACTION_REPLACE, ACTION_DROP, |
| ACTION_PASS, KEY, cmp_node.Mark()); |
| } |
| |
| return {}; |
| } |
| |
| bool |
| Mod_filter::Case::operator()(Context &ctx, Feature const &feature) |
| { |
| return !_cmp || (*_cmp)(ctx, feature); |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_filter::load(Config &cfg, YAML::Node node, TextView, TextView, YAML::Node key_value) |
| { |
| auto self = new self_type; |
| Handle handle(self); |
| auto active_type = cfg.active_type(); |
| auto scope{cfg.feature_scope(active_type.can_satisfy(TUPLE) ? active_type.tuple_types() : active_type)}; |
| |
| if (auto errata = self->_cases.load(cfg, key_value); !errata.is_ok()) { |
| errata.note(R"(While parsing modifier "{}" at line {}.)", KEY, node.Mark()); |
| return errata; |
| } |
| |
| return handle; |
| } |
| |
| // --- |
| /// Replace the feature with another feature if the input is nil or empty. |
| class Mod_else : public Modifier |
| { |
| using self_type = Mod_else; |
| using super_type = Modifier; |
| |
| public: |
| static constexpr TextView KEY{"else"}; ///< Identifier name. |
| using super_type::operator(); // declare hidden member function |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify [in,out] |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, Feature &feature) override; |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| protected: |
| Expr _value; |
| |
| explicit Mod_else(Expr &&fmt) : _value(std::move(fmt)) {} |
| }; |
| |
| bool |
| Mod_else::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(MaskFor(NIL, STRING)); |
| } |
| |
| ActiveType |
| Mod_else::result_type(ActiveType const &) const |
| { |
| return _value.result_type(); |
| } |
| |
| Rv<Feature> |
| Mod_else::operator()(Context &ctx, Feature &feature) |
| { |
| return is_empty(feature) ? ctx.extract(_value) : feature; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_else::load(Config &cfg, YAML::Node, TextView, TextView, YAML::Node key_value) |
| { |
| auto &&[fmt, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| errata.note(R"(While parsing "{}" modifier at {}.)", KEY, key_value.Mark()); |
| return std::move(errata); |
| } |
| return Handle(new self_type{std::move(fmt)}); |
| }; |
| |
| // --- |
| /// Concatenate a tuple into a string. |
| class Mod_join : public Modifier |
| { |
| using self_type = Mod_join; |
| using super_type = Modifier; |
| |
| public: |
| static constexpr TextView KEY{"join"}; ///< Identifier name. |
| using super_type::operator(); // declare hidden member function |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify [in,out] |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, Feature &feature) override; |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| protected: |
| Expr _separator; |
| |
| explicit Mod_join(Expr &&fmt) : _separator(std::move(fmt)) {} |
| }; |
| |
| bool |
| Mod_join::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(MaskFor(NIL, STRING, TUPLE)); |
| } |
| |
| ActiveType |
| Mod_join::result_type(ActiveType const &) const |
| { |
| return STRING; |
| } |
| |
| Rv<Feature> |
| Mod_join::operator()(Context &ctx, Feature &feature) |
| { |
| // Get the separator - if that doesn't work, leave it empty. |
| TextView sep; |
| auto value = ctx.extract(_separator); |
| if (auto ptr = std::get_if<IndexFor(STRING)>(&value); ptr != nullptr) { |
| sep = *ptr; |
| } |
| |
| return feature.join(ctx, sep); |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_join::load(Config &cfg, YAML::Node, TextView, TextView, YAML::Node key_value) |
| { |
| auto &&[expr, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| errata.note(R"(While parsing "{}" modifier at {}.)", KEY, key_value.Mark()); |
| return std::move(errata); |
| } |
| |
| if (!expr.result_type().can_satisfy(STRING)) { |
| errata.note(R"("{}" modifier at {} requires a string argument.)", KEY, key_value.Mark()); |
| return std::move(errata); |
| } |
| return Handle(new self_type{std::move(expr)}); |
| }; |
| |
| // --- |
| /// Concatenate a string to the active feature. |
| class Mod_concat : public Modifier |
| { |
| using self_type = Mod_concat; |
| using super_type = Modifier; |
| |
| public: |
| static constexpr TextView KEY{"concat"}; ///< Identifier name. |
| using super_type::operator(); // declare hidden member function |
| |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify [in,out] |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, Feature &feature) override; |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| protected: |
| Expr _expr; |
| |
| explicit Mod_concat(Expr &&expr) : _expr(std::move(expr)) {} |
| |
| struct Visitor { |
| Context &_ctx; |
| /// Active feature to modify. |
| /// This will always be a @c STRING - if not, the visitor isn't called at all. |
| Feature &_target; |
| Visitor(Context &ctx, Feature &f) : _ctx(ctx), _target(f) {} |
| |
| Rv<Feature> operator()(feature_type_for<STRING> const &s); |
| Rv<Feature> operator()(feature_type_for<TUPLE> const &t); |
| |
| /// @return An empty string if it is not a handled type. |
| template <typename F> |
| auto |
| operator()(F const &) -> EnableForFeatureTypes<F, Rv<Feature>> |
| { |
| return _target; |
| } |
| }; |
| }; |
| |
| bool |
| Mod_concat::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy({NIL, STRING}); |
| } |
| |
| ActiveType |
| Mod_concat::result_type(ActiveType const &) const |
| { |
| return STRING; |
| } |
| |
| Rv<Feature> |
| Mod_concat::Visitor::operator()(const feature_type_for<STRING> &s) |
| { |
| if (s.empty()) { |
| return _target; |
| } |
| |
| TextView src{std::get<IndexFor(STRING)>(_target)}; |
| _ctx.transient_require(src.size() + s.size()); |
| auto view = _ctx.render_transient([&](swoc::BufferWriter &w) { w.write(src).write(s); }); |
| return {_ctx.commit(view)}; |
| } |
| |
| Rv<Feature> |
| Mod_concat::Visitor::operator()(const feature_type_for<TUPLE> &t) |
| { |
| TextView src{std::get<IndexFor(STRING)>(_target)}; |
| if (t[0].index() != IndexFor(STRING) || t[1].index() != IndexFor(STRING)) { |
| return _target; |
| } |
| TextView text{std::get<IndexFor(STRING)>(t[1])}; |
| if (text.empty()) { |
| return _target; |
| } |
| TextView sep{std::get<IndexFor(STRING)>(t[0])}; |
| |
| _ctx.transient_require(src.size() + sep.size() + text.size()); |
| auto view = _ctx.render_transient([=](swoc::BufferWriter &w) { |
| w.write(src); |
| if (src.size() && !src.ends_with(sep)) { |
| w.write(sep); |
| } |
| w.write(text); |
| }); |
| return {_ctx.commit(view)}; |
| } |
| |
| Rv<Feature> |
| Mod_concat::operator()(Context &ctx, Feature &feature) |
| { |
| switch (feature.index()) { |
| case IndexFor(STRING): |
| break; |
| case IndexFor(NIL): // treat NIL as the empty string. |
| feature = FeatureView::Literal(""); |
| break; |
| default: |
| return feature; |
| } |
| |
| Feature f{ctx.extract(_expr)}; |
| return std::visit(Visitor(ctx, feature), f); |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_concat::load(Config &cfg, YAML::Node, TextView, TextView, YAML::Node key_value) |
| { |
| auto &&[expr, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| errata.note(R"(While parsing "{}" modifier at {}.)", KEY, key_value.Mark()); |
| return std::move(errata); |
| } |
| if (!expr.result_type().can_satisfy(MaskFor(STRING, TUPLE))) { |
| errata.note(R"("{}" modifier at {} requires a string or a list of two strings.)", KEY, key_value.Mark()); |
| return std::move(errata); |
| } |
| return Handle(new self_type{std::move(expr)}); |
| } |
| |
| // --- |
| |
| /// Convert the feature to boolean. |
| class Mod_as_bool : public Modifier |
| { |
| using self_type = Mod_as_bool; |
| using super_type = Modifier; |
| |
| public: |
| inline static const std::string KEY = "as-bool"; ///< Identifier name. |
| using super_type::operator(); // declare hidden member function |
| |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify [in,out] |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, Feature &feature) override; |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| protected: |
| inline static const auto VALUE_TYPES = MaskFor({STRING, INTEGER, FLOAT, BOOLEAN, TUPLE, IP_ADDR, NIL}); |
| Expr _value; ///< Default value. |
| |
| explicit Mod_as_bool(Expr &&expr) : _value(std::move(expr)) {} |
| }; |
| |
| bool |
| Mod_as_bool::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(VALUE_TYPES); |
| } |
| |
| ActiveType |
| Mod_as_bool::result_type(ActiveType const &) const |
| { |
| return {BOOLEAN}; |
| } |
| |
| Rv<Feature> |
| Mod_as_bool::operator()(Context &, Feature &feature) |
| { |
| return {feature.as_bool()}; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_as_bool::load(Config &cfg, YAML::Node, TextView, TextView, YAML::Node key_value) |
| { |
| auto &&[expr, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| errata.note(R"(While parsing "{}" modifier at {}.)", KEY, key_value.Mark()); |
| return std::move(errata); |
| } |
| if (!(expr.is_null() || expr.result_type().can_satisfy(VALUE_TYPES))) { |
| return Errata(S_ERROR, "Value of {} modifier is not of type {}.", KEY, VALUE_TYPES); |
| } |
| return Handle(new self_type{std::move(expr)}); |
| } |
| |
| // --- // |
| /// Convert the feature to an Integer. |
| class Mod_as_integer : public Modifier |
| { |
| using self_type = Mod_as_integer; |
| using super_type = Modifier; |
| |
| public: |
| static constexpr TextView KEY{"as-integer"}; ///< Identifier name. |
| using super_type::operator(); // declare hidden member function |
| |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify [in,out] |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, Feature &feature) override; |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| protected: |
| Expr _value; ///< Default value. |
| |
| explicit Mod_as_integer(Expr &&expr) : _value(std::move(expr)) {} |
| }; |
| |
| bool |
| Mod_as_integer::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(MaskFor({STRING, INTEGER, FLOAT, BOOLEAN, NIL})); |
| } |
| |
| ActiveType |
| Mod_as_integer::result_type(ActiveType const &) const |
| { |
| return {MaskFor({NIL, INTEGER})}; |
| } |
| |
| Rv<Feature> |
| Mod_as_integer::operator()(Context &ctx, Feature &feature) |
| { |
| auto &&[value, errata]{feature.as_integer()}; |
| if (errata.is_ok()) { |
| return Feature{value}; |
| } |
| |
| if (errata.is_ok()) { |
| return Feature{ctx.extract(_value)}; |
| } |
| return feature; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_as_integer::load(Config &cfg, YAML::Node, TextView, TextView, YAML::Node key_value) |
| { |
| auto &&[expr, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| errata.note(R"(While parsing "{}" modifier at {}.)", KEY, key_value.Mark()); |
| return std::move(errata); |
| } |
| if (!(expr.is_null() || expr.result_type().can_satisfy(MaskFor(INTEGER)))) { |
| return Errata(S_ERROR, "Value of {} modifier is not of type {}.", KEY, INTEGER); |
| } |
| return Handle(new self_type{std::move(expr)}); |
| } |
| |
| // --- // |
| |
| /// Convert the feature to an IP address. |
| class Mod_as_ip_addr : public Modifier |
| { |
| using self_type = Mod_as_ip_addr; |
| using super_type = Modifier; |
| |
| public: |
| static const std::string KEY; ///< Identifier name. |
| using super_type::operator(); // declare hidden member function |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify [in,out] |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, Feature &feature) override; |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| protected: |
| explicit Mod_as_ip_addr() = default; |
| |
| /// Identity conversion. |
| Feature convert(Context &ctx, feature_type_for<IP_ADDR> n); |
| /// Convert from string |
| Feature convert(Context &ctx, feature_type_for<STRING> s); |
| |
| /// Generic failure case. |
| template <typename T> |
| auto |
| convert(Context &, T &) -> EnableForFeatureTypes<T, Feature> |
| { |
| return NIL_FEATURE; |
| } |
| }; |
| |
| const std::string Mod_as_ip_addr::KEY{"as-ip-addr"}; |
| |
| bool |
| Mod_as_ip_addr::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(MaskFor({IP_ADDR, STRING})); |
| } |
| |
| ActiveType |
| Mod_as_ip_addr::result_type(ActiveType const &) const |
| { |
| return {MaskFor({NIL, IP_ADDR})}; |
| } |
| |
| Rv<Feature> |
| Mod_as_ip_addr::operator()(Context &ctx, Feature &feature) |
| { |
| auto visitor = [&](auto &t) { return this->convert(ctx, t); }; |
| return std::visit(visitor, feature); |
| } |
| |
| auto |
| Mod_as_ip_addr::load(Config &, YAML::Node, TextView, TextView, YAML::Node) -> Rv<Handle> |
| { |
| return Handle(new self_type); |
| } |
| |
| Feature |
| Mod_as_ip_addr::convert(Context &, feature_type_for<IP_ADDR> n) |
| { |
| return n; |
| } |
| |
| Feature |
| Mod_as_ip_addr::convert(Context &, feature_type_for<STRING> s) |
| { |
| swoc::IPAddr addr{s}; |
| return addr.is_valid() ? Feature{addr} : NIL_FEATURE; |
| }; |
| |
| // --- |
| |
| /// Convert the feature to a Duration. |
| class Mod_As_Duration : public Modifier |
| { |
| using self_type = Mod_As_Duration; |
| using super_type = Modifier; |
| |
| public: |
| static const std::string KEY; ///< Identifier name. |
| using super_type::operator(); // declare hidden member function |
| |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify [in,out] |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, Feature &feature) override; |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<Handle> load(Config &cfg, YAML::Node node, TextView key, TextView arg, YAML::Node key_value); |
| |
| protected: |
| Expr _value; ///< Default value. |
| |
| explicit Mod_As_Duration(Expr &&expr) : _value(std::move(expr)) {} |
| }; |
| |
| const std::string Mod_As_Duration::KEY{"as-duration"}; |
| |
| bool |
| Mod_As_Duration::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(MaskFor({STRING, DURATION, TUPLE, NIL})); |
| } |
| |
| ActiveType |
| Mod_As_Duration::result_type(ActiveType const &) const |
| { |
| return {MaskFor({NIL, DURATION})}; |
| } |
| |
| Rv<Feature> |
| Mod_As_Duration::operator()(Context &, Feature &feature) |
| { |
| auto &&[duration, errata]{feature.as_duration()}; |
| return {Feature(duration), std::move(errata)}; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_As_Duration::load(Config &cfg, YAML::Node, TextView, TextView, YAML::Node key_value) |
| { |
| auto &&[expr, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| errata.note(R"(While parsing "{}" modifier at {}.)", KEY, key_value.Mark()); |
| return std::move(errata); |
| } |
| return Handle(new self_type{std::move(expr)}); |
| } |
| |
| // --- |
| /// url-encode modifier. |
| class Mod_url_encode : public Modifier |
| { |
| using self_type = Mod_url_encode; |
| using super_type = Modifier; |
| |
| // Our own map. TSStringPercentEncode will not escape all we need. |
| static constexpr unsigned char escape_codes[32] = { |
| 0xFF, 0xFF, 0xFF, |
| 0xFF, // control |
| 0xBE, // space ” # % $ & |
| 0x19, // + , / |
| 0x00, // |
| 0x3F, // < > : ; = ? |
| 0x80, // @ |
| 0x00, 0x00, // |
| 0x1E, 0x80, // [ \ ] ^ ` |
| 0x00, 0x00, // |
| 0x1F, // { | } ~ DEL |
| 0x00, 0x00, 0x00, |
| 0x00, // all non-ascii characters unmodified |
| 0x00, 0x00, 0x00, |
| 0x00, // |
| 0x00, 0x00, 0x00, |
| 0x00, // |
| 0x00, 0x00, 0x00, |
| 0x00 // |
| }; |
| |
| public: |
| inline static const std::string KEY = "url-encode"; |
| using super_type::operator(); // declare hidden member function |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify |
| * @return Errors, if any. |
| */ |
| Rv<Feature> operator()(Context &ctx, feature_type_for<STRING> feature) override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<super_type::Handle> load(Config &, YAML::Node, TextView, TextView, YAML::Node); |
| }; |
| |
| bool |
| Mod_url_encode::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(MaskFor(NIL, STRING)); |
| } |
| |
| ActiveType |
| Mod_url_encode::result_type(ActiveType const &) const |
| { |
| return {MaskFor({NIL, STRING})}; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_url_encode::load(Config &, YAML::Node, TextView, TextView, YAML::Node) |
| { |
| return Modifier::Handle(new self_type); |
| } |
| |
| Rv<Feature> |
| Mod_url_encode::operator()(Context &ctx, feature_type_for<STRING> feature) |
| { |
| const size_t size = feature.size() * 3; // *3 should suffice. |
| size_t length; |
| auto buff = ctx.transient_buffer(size); |
| if (TS_SUCCESS == TSStringPercentEncode(feature.data(), feature.size(), buff.data(), size, &length, escape_codes)) { |
| ctx.transient_finalize(length).commit_transient(); // adjust the transient buffer length and commit it. |
| return {FeatureView::Literal({buff.data(), length})}; // literal because it's committed. |
| } |
| return NIL_FEATURE; |
| } |
| |
| // --- |
| /// url-decode modifier |
| class Mod_url_decode : public Modifier |
| { |
| using self_type = Mod_url_decode; |
| using super_type = Modifier; |
| |
| public: |
| inline static const std::string KEY = "url-decode"; |
| using super_type::operator(); // declare hidden member function |
| |
| /** Check if @a ftype is a valid type to be modified. |
| * |
| * @param ftype Type of feature to modify. |
| * @return @c true if this modifier can modity that feature type, @c false if not. |
| */ |
| bool is_valid_for(ActiveType const &ex_type) const override; |
| |
| /// Resulting type of feature after modifying. |
| ActiveType result_type(ActiveType const &) const override; |
| |
| /** Modify the feature. |
| * |
| * @param ctx Run time context. |
| * @param feature Feature to modify |
| * @return Errors, if any. |
| * |
| */ |
| Rv<Feature> operator()(Context &ctx, feature_type_for<STRING> feature) override; |
| |
| /** Create an instance from YAML config. |
| * |
| * @param cfg Configuration state object. |
| * @param mod_node Node with modifier. |
| * @param key_node Node in @a mod_node that identifies the modifier. |
| * @return A constructed instance or errors. |
| */ |
| static Rv<super_type::Handle> load(Config &, YAML::Node, TextView, TextView, YAML::Node); |
| }; |
| |
| bool |
| Mod_url_decode::is_valid_for(ActiveType const &ex_type) const |
| { |
| return ex_type.can_satisfy(MaskFor(NIL, STRING)); |
| } |
| |
| ActiveType |
| Mod_url_decode::result_type(ActiveType const &) const |
| { |
| return {MaskFor({NIL, STRING})}; |
| } |
| |
| Rv<Modifier::Handle> |
| Mod_url_decode::load(Config &, YAML::Node, TextView, TextView, YAML::Node) |
| { |
| return Modifier::Handle(new self_type); |
| } |
| |
| Rv<Feature> |
| Mod_url_decode::operator()(Context &ctx, feature_type_for<STRING> feature) |
| { |
| const size_t size = feature.size(); |
| size_t length; |
| auto buff = ctx.transient_buffer(size); |
| if (TS_SUCCESS == TSStringPercentDecode(feature.data(), size, buff.data(), size, &length)) { |
| ctx.transient_finalize(length).commit_transient(); // adjust the transient buffer length and commit it. |
| return {FeatureView::Literal({buff.data(), length})}; // literal because it's committed. |
| } |
| return NIL_FEATURE; |
| } |
| // --- // |
| |
| namespace |
| { |
| [[maybe_unused]] bool INITIALIZED = []() -> bool { |
| Modifier::define(Mod_hash::KEY, &Mod_hash::load); |
| Modifier::define(Mod_else::KEY, &Mod_else::load); |
| Modifier::define(Mod_join::KEY, &Mod_join::load); |
| Modifier::define(Mod_concat::KEY, &Mod_concat::load); |
| Modifier::define(Mod_as_bool::KEY, &Mod_as_bool::load); |
| Modifier::define(Mod_as_integer::KEY, &Mod_as_integer::load); |
| Modifier::define(Mod_As_Duration::KEY, &Mod_As_Duration::load); |
| Modifier::define(Mod_filter::KEY, &Mod_filter::load); |
| Modifier::define(Mod_as_ip_addr::KEY, &Mod_as_ip_addr::load); |
| Modifier::define(Mod_rxp_replace::KEY, &Mod_rxp_replace::load); |
| Modifier::define(Mod_url_encode::KEY, &Mod_url_encode::load); |
| Modifier::define(Mod_url_decode::KEY, &Mod_url_decode::load); |
| return true; |
| }(); |
| } // namespace |