/**
  @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 <swoc/TextView.h>
#include <swoc/Errata.h>
#include <swoc/BufferWriter.h>
#include <swoc/bwf_base.h>

#include "txn_box/common.h"
#include "txn_box/Config.h"
#include "txn_box/Context.h"
#include "txn_box/Directive.h"
#include "txn_box/Comparison.h"

#include "txn_box/yaml_util.h"
#include "txn_box/ts_util.h"

using swoc::BufferWriter;
using swoc::Errata;
using swoc::Rv;
using swoc::TextView;
namespace bwf = swoc::bwf;
using namespace swoc::literals;

/* ------------------------------------------------------------------------------------ */
Feature
Generic::extract() const
{
  return NIL_FEATURE;
}
/* ------------------------------------------------------------------------------------ */
class Do_ua_req_url_host : public Directive
{
  using super_type = Directive;
  using self_type  = Do_ua_req_url_host;

public:
  static const std::string KEY;
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_ua_req_url_host(Expr &&expr);

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Feature expression.
};

const std::string Do_ua_req_url_host::KEY{"ua-req-url-host"};
const HookMask    Do_ua_req_url_host::HOOKS = MaskFor(Hook::PREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP);

Do_ua_req_url_host::Do_ua_req_url_host(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_url_host::invoke(Context &ctx)
{
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    if (auto url{hdr.url()}; url.is_valid()) {
      auto value = ctx.extract(_expr);
      if (auto host = std::get_if<IndexFor(STRING)>(&value); nullptr != host) {
        url.host_set(*host);
      }
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_url_host::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                         YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING);
  }
  return Handle(new self_type{std::move(expr)});
}

// ---

class Do_proxy_req_url_host : public Directive
{
  using super_type = Directive;
  using self_type  = Do_proxy_req_url_host;

public:
  static const std::string KEY;
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_proxy_req_url_host(Expr &&expr);

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Host feature expression.
};

const std::string Do_proxy_req_url_host::KEY{"proxy-req-url-host"};
const HookMask    Do_proxy_req_url_host::HOOKS{MaskFor({Hook::PREQ})};

Do_proxy_req_url_host::Do_proxy_req_url_host(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_proxy_req_url_host::invoke(Context &ctx)
{
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    if (auto url{hdr.url()}; url.is_valid()) {
      auto value = ctx.extract(_expr);
      if (auto host = std::get_if<IndexFor(STRING)>(&value); nullptr != host) {
        url.host_set(*host);
      }
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_url_host::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &,
                            swoc::TextView const &, YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING);
  }
  return Handle(new self_type{std::move(expr)});
}

/* ------------------------------------------------------------------------------------ */

class Do_ua_req_url_port : public Directive
{
  using super_type = Directive;
  using self_type  = Do_ua_req_url_port;

public:
  static const std::string KEY;
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_ua_req_url_port(Expr &&expr);

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Feature expression.
};

const std::string Do_ua_req_url_port::KEY{"ua-req-url-port"};
const HookMask    Do_ua_req_url_port::HOOKS{MaskFor({Hook::CREQ, Hook::PREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Do_ua_req_url_port::Do_ua_req_url_port(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_url_port::invoke(Context &ctx)
{
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    if (auto url{hdr.url()}; url.is_valid()) {
      auto port = ctx.extract(_expr).as_integer(-1);
      if (0 < port && port < std::numeric_limits<in_port_t>::max()) {
        url.port_set(port);
      }
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_url_port::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                         YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(INTEGER)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), INTEGER);
  }
  return Handle(new self_type{std::move(expr)});
}

// ---

class Do_proxy_req_url_port : public Directive
{
  using super_type = Directive;
  using self_type  = Do_proxy_req_url_port;

public:
  static const std::string KEY;
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_proxy_req_url_port(Expr &&expr);

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Feature expression.
};

const std::string Do_proxy_req_url_port::KEY{"proxy-req-url-port"};
const HookMask    Do_proxy_req_url_port::HOOKS{MaskFor(Hook::PREQ)};

Do_proxy_req_url_port::Do_proxy_req_url_port(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_proxy_req_url_port::invoke(Context &ctx)
{
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    if (auto url{hdr.url()}; url.is_valid()) {
      auto port = ctx.extract(_expr).as_integer(-1);
      if (0 < port && port < std::numeric_limits<in_port_t>::max()) {
        url.port_set(port);
      }
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_url_port::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &,
                            swoc::TextView const &, YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(INTEGER)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), INTEGER);
  }
  return Handle(new self_type{std::move(expr)});
}

/* ------------------------------------------------------------------------------------ */
namespace
{
// @return @c true if @a loc is syntactically a valid location and break out the pieces.
bool
Loc_String_Parse(TextView const &loc, TextView &host_token, int &port)
{
  TextView port_token, rest;
  if (swoc::IPEndpoint::tokenize(loc, &host_token, &port_token, &rest) && rest.size() == 0) {
    if (port_token.empty()) {
      port = 0;
      return true;
    }

    if (auto n = svtou(port_token, &rest);
        rest.size() == port_token.size() && 0 < n && n <= std::numeric_limits<in_port_t>::max()) {
      port = n;
      return true;
    }
  }
  return false;
}

/// Set the location in a URL, accepting either a string or a tuple of <host, port>.
void
URL_Loc_Set(Context &ctx, Expr &expr, ts::URL &url)
{
  auto     value = ctx.extract(expr);
  TextView host_token;
  int      port = -1; // if still -1 after parsing, the parsing failed.
  if (auto loc = std::get_if<IndexFor(STRING)>(&value); nullptr != loc) {
    // split the string to get the pieces.
    Loc_String_Parse(*loc, host_token, port);
  } else if (auto t = std::get_if<IndexFor(TUPLE)>(&value); nullptr != t) {
    // Must be host name, then port.
    if (t->size() > 0) {
      if (auto &f0 = (*t)[0]; f0.index() == IndexFor(STRING)) {
        host_token = std::get<IndexFor(STRING)>(f0);
        if (t->size() > 1) {
          auto &f1 = (*t)[1];
          if (is_empty(f1)) {
            port = 0; // clear port.
          } else {
            port = f1.as_integer(-1); // try as integer, fail if not convertible.
          }
        }
      }
    }
  }
  if (0 <= port && port < std::numeric_limits<in_port_t>::max()) {
    url.host_set(host_token);
    url.port_set(port); // if @a port is 0 then it will be removed from the URL.
  }
}

/// Set the location in the Host field and URL (if needed).
void
Req_Loc_Set(Context &ctx, Expr &expr, ts::HttpRequest &req)
{
  auto     value = ctx.extract(expr);
  TextView host_token;
  int      port = -1; // if still -1 after parsing, the parsing failed.
  if (auto loc = std::get_if<IndexFor(STRING)>(&value); nullptr != loc && Loc_String_Parse(*loc, host_token, port)) {
    req.field_obtain(ts::HTTP_FIELD_HOST).assign(*loc);
  } else if (auto t = std::get_if<IndexFor(TUPLE)>(&value); nullptr != t) {
    if (t->size() > 0) { // host name, then port.
      if (auto &f0 = (*t)[0]; f0.index() == IndexFor(STRING)) {
        host_token = std::get<IndexFor(STRING)>(f0);
        if (t->size() > 1) {
          auto &f1 = (*t)[1];
          if (is_empty(f1)) {
            port = 0; // clear port.
          } else {
            port = f1.as_integer(-1); // try as integer, fail if not convertible.
          }
        } else {
          port = 0; // no port element, clear port.
        }
        auto                    buffer = ctx.transient_buffer(host_token.size() + 1 + std::numeric_limits<in_port_t>::digits10);
        swoc::FixedBufferWriter w{buffer};
        w.write(host_token);
        if (port > 0) {
          w.write(':');
          bwformat(w, swoc::bwf::Spec::DEFAULT, port);
        }
        req.field_obtain(ts::HTTP_FIELD_HOST).assign(w.view());
        ctx.transient_discard();
      }
    }
  }

  // If the field was set, set the URL to match if it has a host.
  if (port >= 0) {
    if (auto url{req.url()}; url.is_valid() && !url.host().empty()) {
      url.host_set(host_token);
      url.port_set(port);
    }
  }
}

} // namespace

class Do_ua_req_url_loc : public Directive
{
  using super_type = Directive;
  using self_type  = Do_ua_req_url_loc;

public:
  static inline const std::string KEY = "ua-req-url-loc";
  static const HookMask           HOOKS; ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_ua_req_url_loc(Expr &&expr);

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Feature expression.
};

const HookMask Do_ua_req_url_loc::HOOKS = MaskFor(Hook::PREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP);

Do_ua_req_url_loc::Do_ua_req_url_loc(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_url_loc::invoke(Context &ctx)
{
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    if (auto url{hdr.url()}; url.is_valid()) {
      URL_Loc_Set(ctx, _expr, url);
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_url_loc::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                        YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy({STRING, TUPLE})) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {} or a {} of 2 elements.)", KEY, drtv_node.Mark(), STRING,
                  TUPLE);
  }
  return Handle(new self_type{std::move(expr)});
}

// ---

class Do_proxy_req_url_loc : public Directive
{
  using super_type = Directive;
  using self_type  = Do_proxy_req_url_loc;

public:
  static inline const std::string KEY = "proxy-req-url-loc";
  static const HookMask           HOOKS; ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_proxy_req_url_loc(Expr &&expr);

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Feature expression.
};

const HookMask Do_proxy_req_url_loc::HOOKS = MaskFor(Hook::PREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP);

Do_proxy_req_url_loc::Do_proxy_req_url_loc(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_proxy_req_url_loc::invoke(Context &ctx)
{
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    if (auto url{hdr.url()}; url.is_valid()) {
      URL_Loc_Set(ctx, _expr, url);
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_url_loc::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                           YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy({STRING, TUPLE})) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {} or a {} of 2 elements.)", KEY, drtv_node.Mark(), STRING,
                  TUPLE);
  }
  return Handle(new self_type{std::move(expr)});
}

/* ------------------------------------------------------------------------------------ */
/** Set the host for the request.
 * This updates both the URL and the "Host" field, if appropriate.
 */
class Do_ua_req_host : public Directive
{
  using super_type = Directive;      ///< Parent type.
  using self_type  = Do_ua_req_host; ///< Self reference type.
public:
  static inline const std::string KEY{"ua-req-host"}; ///< Directive name.
  static const HookMask           HOOKS;              ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_ua_req_host(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Host feature.
};

const HookMask Do_ua_req_host::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Do_ua_req_host::Do_ua_req_host(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_host::invoke(Context &ctx)
{
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    auto value = ctx.extract(_expr);
    if (auto host = std::get_if<IndexFor(STRING)>(&value); nullptr != host) {
      hdr.host_set(*host);
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_host::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                     YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING);
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the port for the user agent request.
 * This updates both the URL and the "Host" field, if appropriate.
 */
class Do_ua_req_port : public Directive
{
  using super_type = Directive;      ///< Parent type.
  using self_type  = Do_ua_req_port; ///< Self reference type.
public:
  static inline const std::string KEY{"ua-req-port"}; ///< Directive name.
  static const HookMask           HOOKS;              ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_ua_req_port(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Host feature.
};

const HookMask Do_ua_req_port::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Do_ua_req_port::Do_ua_req_port(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_port::invoke(Context &ctx)
{
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    auto value = ctx.extract(_expr);
    if (auto port = value.as_integer(-1); port >= 0) {
      hdr.port_set(port);
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_port::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                     YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(INTEGER)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), INTEGER);
  }
  return Handle(new self_type(std::move(expr)));
}

// ---

/** Set the port for the proxy request.
 * This updates both the URL and the "Host" field, if appropriate.
 */
class Do_proxy_req_port : public Directive
{
  using super_type = Directive;         ///< Parent type.
  using self_type  = Do_proxy_req_port; ///< Self reference type.
public:
  static inline const std::string KEY{"proxy-req-port"}; ///< Directive name.
  static const HookMask           HOOKS;                 ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_proxy_req_port(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Host feature.
};

const HookMask Do_proxy_req_port::HOOKS{MaskFor(Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP)};

Do_proxy_req_port::Do_proxy_req_port(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_proxy_req_port::invoke(Context &ctx)
{
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    auto value = ctx.extract(_expr);
    if (auto port = value.as_integer(-1); port >= 0) {
      hdr.port_set(port);
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_port::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                        YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(INTEGER)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), INTEGER);
  }
  return Handle(new self_type(std::move(expr)));
}

/* ------------------------------------------------------------------------------------ */
/** Set the host for the request.
 * This updates both the URL and the "Host" field, if appropriate.
 */
class Do_proxy_req_host : public Directive
{
  using super_type = Directive;         ///< Parent type.
  using self_type  = Do_proxy_req_host; ///< Self reference type.
public:
  static inline const std::string KEY{"proxy-req-host"};      ///< Directive name.
  static inline const HookMask    HOOKS{MaskFor(Hook::PREQ)}; ///< Valid hooks for directive.

  /** Construct with feature extractor @a fmt.
   *
   * @param fmt Feature for host.
   */
  explicit Do_proxy_req_host(Expr &&fmt);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _fmt; ///< Host feature.
};

Do_proxy_req_host::Do_proxy_req_host(Expr &&fmt) : _fmt(std::move(fmt)) {}

Errata
Do_proxy_req_host::invoke(Context &ctx)
{
  TextView host{std::get<IndexFor(STRING)>(ctx.extract(_fmt))};
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    hdr.host_set(host);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_host::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                        YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a string.)", KEY, drtv_node.Mark());
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the location for the user agent request.
 * This updates both the URL and the "Host" field, if appropriate.
 */
class Do_ua_req_loc : public Directive
{
  using super_type = Directive;     ///< Parent type.
  using self_type  = Do_ua_req_loc; ///< Self reference type.
public:
  static inline const std::string KEY{"ua-req-loc"}; ///< Directive name.
  static const HookMask           HOOKS;             ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_ua_req_loc(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Host feature.
};

const HookMask Do_ua_req_loc::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Do_ua_req_loc::Do_ua_req_loc(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_loc::invoke(Context &ctx)
{
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    Req_Loc_Set(ctx, _expr, hdr);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_loc::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                    YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy({STRING, TUPLE})) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {} or a {}.)", KEY, drtv_node.Mark(), STRING, TUPLE);
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the location for the proxy request.
 * This updates both the URL and the "Host" field, if appropriate.
 */
class Do_proxy_req_loc : public Directive
{
  using super_type = Directive;        ///< Parent type.
  using self_type  = Do_proxy_req_loc; ///< Self reference type.
public:
  static inline const std::string KEY{"proxy-req-loc"}; ///< Directive name.
  static const HookMask           HOOKS;                ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression.
   */
  explicit Do_proxy_req_loc(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Host feature.
};

const HookMask Do_proxy_req_loc::HOOKS{MaskFor({Hook::PREQ})};

Do_proxy_req_loc::Do_proxy_req_loc(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_proxy_req_loc::invoke(Context &ctx)
{
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    Req_Loc_Set(ctx, _expr, hdr);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_loc::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                       YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy({STRING, TUPLE})) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {} or a {}.)", KEY, drtv_node.Mark(), STRING, TUPLE);
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the scheme for the inbound request.
 */
class Do_ua_req_scheme : public Directive
{
  using super_type = Directive;        ///< Parent type.
  using self_type  = Do_ua_req_scheme; ///< Self reference type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature extractor @a fmt.
   *
   * @param fmt Feature for host.
   */
  explicit Do_ua_req_scheme(Expr &&fmt);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Scheme expression.
};

const std::string Do_ua_req_scheme::KEY{"ua-req-scheme"};
const HookMask    Do_ua_req_scheme::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Do_ua_req_scheme::Do_ua_req_scheme(Expr &&fmt) : _expr(std::move(fmt)) {}

Errata
Do_ua_req_scheme::invoke(Context &ctx)
{
  TextView text{std::get<IndexFor(STRING)>(ctx.extract(_expr))};
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    hdr.url().scheme_set(text);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_scheme::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                       YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING);
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the URL for the inbound request.
 */
class Do_ua_req_url : public Directive
{
  using super_type = Directive;     ///< Parent type.
  using self_type  = Do_ua_req_url; ///< Self reference type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature extractor @a fmt.
   *
   * @param fmt Feature for host.
   */
  explicit Do_ua_req_url(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< URL expression.
};

const std::string Do_ua_req_url::KEY{"ua-req-url"};
const HookMask    Do_ua_req_url::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Do_ua_req_url::Do_ua_req_url(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_url::invoke(Context &ctx)
{
  TextView text{std::get<IndexFor(STRING)>(ctx.extract(_expr))};
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    hdr.url_set(text);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_url::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                    YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING);
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the scheme for the outbound request.
 */
class Do_proxy_req_scheme : public Directive
{
  using super_type = Directive;           ///< Parent type.
  using self_type  = Do_proxy_req_scheme; ///< Self reference type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature extractor @a fmt.
   *
   * @param fmt Feature for host.
   */
  explicit Do_proxy_req_scheme(Expr &&fmt);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _fmt; ///< Host feature.
};

const std::string Do_proxy_req_scheme::KEY{"proxy-req-scheme"};
const HookMask    Do_proxy_req_scheme::HOOKS{MaskFor({Hook::PREQ})};

Do_proxy_req_scheme::Do_proxy_req_scheme(Expr &&fmt) : _fmt(std::move(fmt)) {}

Errata
Do_proxy_req_scheme::invoke(Context &ctx)
{
  TextView host{std::get<IndexFor(STRING)>(ctx.extract(_fmt))};
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    hdr.url().scheme_set(host);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_scheme::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                          YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING);
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the URL for the outbound request.
 */
class Do_proxy_req_url : public Directive
{
  using super_type = Directive;        ///< Parent type.
  using self_type  = Do_proxy_req_url; ///< Self reference type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression for the URL.
   */
  explicit Do_proxy_req_url(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< URL expression.
};

const std::string Do_proxy_req_url::KEY{"proxy-req-url"};
const HookMask    Do_proxy_req_url::HOOKS{MaskFor({Hook::PREQ})};

Do_proxy_req_url::Do_proxy_req_url(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_proxy_req_url::invoke(Context &ctx)
{
  TextView text{std::get<IndexFor(STRING)>(ctx.extract(_expr))};
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    hdr.url_set(text);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_url::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                       YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING);
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
class Do_did_remap : public Directive
{
  using self_type = Do_did_remap;

public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature expression..
   *
   * @param expr Feature expression for the URL.
   */
  explicit Do_did_remap(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Boolean to set whether remap was done.
};

const std::string Do_did_remap::KEY{"did-remap"};
const HookMask    Do_did_remap::HOOKS{MaskFor(Hook::REMAP)};

Do_did_remap::Do_did_remap(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_did_remap::invoke(Context &ctx)
{
  auto f            = ctx.extract(_expr);
  ctx._remap_status = f.as_bool() ? TSREMAP_DID_REMAP : TSREMAP_NO_REMAP;
  return {};
}

Rv<Directive::Handle>
Do_did_remap::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                   YAML::Node key_value)
{
  // Default, with no value, is @c true.
  if (key_value.IsNull()) {
    return Handle{new self_type(Expr(true))};
  }
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing value of "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(BOOLEAN)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be convertible to a {}.)", KEY, drtv_node.Mark(), BOOLEAN);
  }
  return Handle{new self_type{std::move(expr)}};
}

/* ------------------------------------------------------------------------------------ */
/** Do the remap.
 */
class Do_apply_remap_rule : public Directive
{
  using super_type = Directive;           ///< Parent type.
  using self_type  = Do_apply_remap_rule; ///< Self reference type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);
};

const std::string Do_apply_remap_rule::KEY{"apply-remap-rule"};
const HookMask    Do_apply_remap_rule::HOOKS{MaskFor(Hook::REMAP)};

Errata
Do_apply_remap_rule::invoke(Context &ctx)
{
  ctx._remap_status = TSREMAP_DID_REMAP;
  // This is complex because the internal logic is as well. A bit fragile, but this is
  // really only useful as a backwards compatibility fix for pre ATS 9 and should eventually
  // be removed.
  // Copy over the host and port.
  ts::URL replacement_url{ctx._remap_info->requestBufp, ctx._remap_info->mapToUrl};
  ts::URL target_url{ctx._remap_info->requestBufp, ctx._remap_info->mapFromUrl};
  ts::URL request_url{ctx._remap_info->requestBufp, ctx._remap_info->requestUrl};

  in_port_t port = replacement_url.port();
  // decanonicalize the port - may need to dig in and see if it was explicitly set.
  if ((port == 80 && replacement_url.scheme() == ts::URL_SCHEME_HTTP) ||
      (port == 443 && replacement_url.scheme() == ts::URL_SCHEME_HTTPS)) {
    port = 0;
  }
  request_url.port_set(port);
  request_url.host_set(replacement_url.host());
  if (ts::HttpRequest{ctx._remap_info->requestBufp, ctx._remap_info->requestHdrp}.method() != "CONNECT"_tv) {
    request_url.scheme_set(replacement_url.scheme());
    // Update the path as needed.
    auto replacement_path{replacement_url.path()};
    auto target_path{target_url.path()};
    auto request_path{request_url.path()};

    // Need to do better - see if Context can provide an ArenaWriter?
    swoc::LocalBufferWriter<(1 << 16) - 1> url_w;
    url_w.write(replacement_path);
    if (request_path.size() > target_path.size()) {
      // Always slash separate the replacement from the remnant of the incoming request path.
      if (url_w.size() && url_w.view()[url_w.size() - 1] != '/') {
        url_w.write('/');
      }
      // Already have the separating slash, trim it from the target path.
      url_w.write(request_path.substr(target_path.size()).ltrim('/'));
    }
    request_url.path_set(TextView{url_w.view()}.ltrim('/'));
  };

  return {};
}

swoc::Rv<Directive::Handle>
Do_apply_remap_rule::load(Config &, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &, YAML::Node)
{
  return Handle(new self_type);
}
/* ------------------------------------------------------------------------------------ */
/** Set the path for the request.
 */
class Do_ua_req_path : public Directive
{
  using super_type = Directive;      ///< Parent type.
  using self_type  = Do_ua_req_path; ///< Self reference type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature extractor @a expr.
   *
   * @param expr Feature for host.
   */
  explicit Do_ua_req_path(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Host feature.
};

const std::string Do_ua_req_path::KEY{"ua-req-path"};
const HookMask    Do_ua_req_path::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Do_ua_req_path::Do_ua_req_path(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_path::invoke(Context &ctx)
{
  auto value{ctx.extract(_expr)};
  if (auto text = std::get_if<IndexFor(STRING)>(&value); text) {
    if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
      hdr.url().path_set(*text);
    }
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_path::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                     YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a string.)", KEY, drtv_node.Mark());
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the fragment for the request.
 */
class Do_ua_req_fragment : public Directive
{
  using super_type = Directive;          ///< Parent type.
  using self_type  = Do_ua_req_fragment; ///< Self reference type.
public:
  static inline const std::string KEY{"ua-req-fragment"}; ///< Directive name.
  static const HookMask           HOOKS;                  ///< Valid hooks for directive.

  /** Construct with feature extractor @a fmt.
   *
   * @param expr Feature for host.
   */
  explicit Do_ua_req_fragment(Expr &&expr);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Host feature.
};

const HookMask Do_ua_req_fragment::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Do_ua_req_fragment::Do_ua_req_fragment(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_ua_req_fragment::invoke(Context &ctx)
{
  TextView text{std::get<IndexFor(STRING)>(ctx.extract(_expr))};
  if (auto hdr{ctx.ua_req_hdr()}; hdr.is_valid()) {
    hdr.url().fragment_set(text);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_ua_req_fragment::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                         YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (expr.is_null()) {
    expr = Feature{FeatureView::Literal(""_tv)};
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a string.)", KEY, drtv_node.Mark());
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the path for the request.
 */
class Do_proxy_req_path : public Directive
{
  using super_type = Directive;         ///< Parent type.
  using self_type  = Do_proxy_req_path; ///< Self reference type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  /** Construct with feature extractor @a fmt.
   *
   * @param fmt Feature for host.
   */
  explicit Do_proxy_req_path(Expr &&fmt);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _fmt; ///< Host feature.
};

const std::string Do_proxy_req_path::KEY{"proxy-req-path"};
const HookMask    Do_proxy_req_path::HOOKS{MaskFor({Hook::PREQ})};

Do_proxy_req_path::Do_proxy_req_path(Expr &&fmt) : _fmt(std::move(fmt)) {}

Errata
Do_proxy_req_path::invoke(Context &ctx)
{
  TextView host{std::get<IndexFor(STRING)>(ctx.extract(_fmt))};
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    hdr.url().path_set(host);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_path::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                        YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a string.)", KEY, drtv_node.Mark());
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/** Set the fragment for the request.
 */
class Do_proxy_req_fragment : public Directive
{
  using super_type = Directive;             ///< Parent type.
  using self_type  = Do_proxy_req_fragment; ///< Self reference type.
public:
  static inline const std::string KEY{"proxy-req-fragment"}; ///< Directive name.
  static const HookMask           HOOKS;                     ///< Valid hooks for directive.

  /** Construct with feature extractor @a fmt.
   *
   * @param fmt Feature for host.
   */
  explicit Do_proxy_req_fragment(Expr &&fmt);

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _fmt; ///< Host feature.
};

const HookMask Do_proxy_req_fragment::HOOKS{MaskFor({Hook::PREQ})};

Do_proxy_req_fragment::Do_proxy_req_fragment(Expr &&fmt) : _fmt(std::move(fmt)) {}

Errata
Do_proxy_req_fragment::invoke(Context &ctx)
{
  TextView text{std::get<IndexFor(STRING)>(ctx.extract(_fmt))};
  if (auto hdr{ctx.proxy_req_hdr()}; hdr.is_valid()) {
    hdr.url().fragment_set(text);
  }
  return {};
}

swoc::Rv<Directive::Handle>
Do_proxy_req_fragment::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &,
                            swoc::TextView const &, YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark());
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(Value for "{}" directive at {} must be a string.)", KEY, drtv_node.Mark());
  }
  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
class FieldDirective : public Directive
{
  using self_type  = FieldDirective; ///< Self reference type.
  using super_type = Directive;      ///< Parent type.
protected:
  TextView _name; ///< Field name.
  Expr     _expr; ///< Value for field.

  /** Base constructor.
   *
   * @param name Name of the field.
   * @param expr Value to assign to the field.
   */
  FieldDirective(TextView const &name, Expr &&expr);

  /** Load from configuration.
   *
   * @param cfg Configuration.
   * @param maker Subclass maker.
   * @param key Name of the key identifying the directive.
   * @param name Field name (directive argumnet).
   * @param key_value  Value of the node for @a key.
   * @return An instance of the directive for @a key, or errors.
   */
  static Rv<Handle> load(Config &cfg, std::function<Handle(TextView const &name, Expr &&fmt)> const &maker, TextView const &key,
                         TextView const &name, YAML::Node key_value);

  /** Invoke the directive on an HTTP header.
   *
   * @param ctx Runtime context.
   * @param hdr HTTP header.
   * @return Errors, if any.
   */
  Errata invoke_on_hdr(Context &ctx, ts::HttpHeader &&hdr);

  /** Get the directive name (key).
   *
   * @return The directive key.
   *
   * Used by subclasses to provide the key for diagnostics.
   */
  virtual swoc::TextView key() const = 0;

  template <typename T>
  void
  apply(Context &, ts::HttpHeader &&, T const &)
  {
  }

  /// Visitor to perform the assignment.
  struct Apply {
    Context        &_ctx;   ///< Runtime context.
    ts::HttpHeader &_hdr;   ///< HTTP Header to modify.
    ts::HttpField   _field; ///< Working field.
    TextView const &_name;  ///< Field name.

    Apply(Context &ctx, ts::HttpHeader &hdr, TextView const &name) : _ctx(ctx), _hdr(hdr), _field(hdr.field(name)), _name(name) {}

    /// Clear all duplicate fields past @a _field.
    void
    clear_dups()
    {
      if (_field.is_valid()) {
        for (auto nf = _field.next_dup(); nf.is_valid();) {
          nf.destroy();
        }
      }
    }

    /// Assigned @a text to a single field, creating as needed.
    /// @return @c true if a field value was changed, @c false if the current value is @a text.
    void
    assign(TextView const &text)
    {
      if (_field.is_valid()) {
        if (_field.value() != text) {
          _field.assign(text);
        }
      } else {
        _hdr.field_create(_name).assign(text);
      }
    }

    /// Nil / NULL means destroy the field.
    void
    operator()(feature_type_for<NIL>)
    {
      if (_field.is_valid()) {
        this->clear_dups();
        _field.destroy();
      }
    }

    /// Assign the string, clear out any dups.
    void
    operator()(feature_type_for<STRING> &text)
    {
      this->assign(text);
      this->clear_dups();
    }

    /// Assign the tuple elements to duplicate fields.
    void
    operator()(feature_type_for<TUPLE> &t)
    {
      for (auto const &tf : t) {
        auto text{std::get<STRING>(tf.join(_ctx, ", "_tv))};
        // skip to next equal field, destroying mismatched fields.
        // once @a _field becomes invalid, it remains in that state.
        while (_field.is_valid() && _field.value() != text) {
          auto tmp = _field.next_dup();
          _field.destroy();
          _field = std::move(tmp);
        }
        this->assign(text);
        _field = _field.next_dup(); // does nothing if @a _field is invalid.
      }
      if (_field.is_valid()) {
        this->clear_dups(); // Any remaining fields need to be cleaned up.
        _field.destroy();
      }
    }

    // Other types, convert to string
    template <typename T>
    auto
    operator()(T &&t) -> EnableForFeatureTypes<T, void>
    {
      this->assign(_ctx.render_transient([&t](BufferWriter &w) { bwformat(w, bwf::Spec::DEFAULT, t); }));
      this->clear_dups();
    }
  };
};

FieldDirective::FieldDirective(TextView const &name, Expr &&expr) : _name(name), _expr(std::move(expr)) {}

Errata
FieldDirective::invoke_on_hdr(Context &ctx, ts::HttpHeader &&hdr)
{
  if (hdr.is_valid()) {
    auto value{ctx.extract(this->_expr)};
    std::visit(Apply(ctx, hdr, _name), value);
    return {};
  }
  return Errata(S_ERROR, R"(Failed to assign field value due to invalid HTTP header.)");
}

auto
FieldDirective::load(Config &cfg, std::function<Handle(TextView const &, Expr &&)> const &maker, TextView const &key,
                     TextView const &arg, YAML::Node key_value) -> Rv<Handle>
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing value for "{}".)", key);
    return std::move(errata);
  }

  auto expr_type = expr.result_type();
  if (!expr_type.has_value()) {
    return Errata(S_ERROR, R"(Directive "{}" must have a value.)", key);
  }
  return maker(cfg.localize(arg), std::move(expr));
}

// -- Implementations --

/* ------------------------------------------------------------------------------------ */
/// Set transaction level debuggging for this transaction.
class Do_txn_debug : public Directive
{
  using self_type  = Do_txn_debug; ///< Self reference type.
  using super_type = Directive;    ///< Parent type.
public:
  static inline const std::string KEY{"txn-debug"}; ///< Directive name.

  /// Valid hooks for directive.
  static inline const HookMask HOOKS{
    MaskFor({Hook::TXN_START, Hook::CREQ, Hook::PREQ, Hook::URSP, Hook::PRSP, Hook::PRE_REMAP, Hook::POST_REMAP, Hook::REMAP})};

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML configuration.
   *
   * @param cfg Configuration data.
   * @param drtv_node Node containing the directive.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Whether transaction debug is enabled or disabled.

  Do_txn_debug(Expr &&msg);
};

Do_txn_debug::Do_txn_debug(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_txn_debug::invoke(Context &ctx)
{
  auto f = ctx.extract(_expr);
  ctx._txn.enable_debug(f.as_bool());
  return {};
}

Rv<Directive::Handle>
Do_txn_debug::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                   YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    errata.note(R"(While parsing message at {} for "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
    return std::move(errata);
  }
  return {Handle{new self_type{std::move(expr)}}};
}

// --
class Do_ua_req_field : public FieldDirective
{
  using self_type  = Do_ua_req_field;
  using super_type = FieldDirective;

public:
  static const std::string KEY;   ///< Directive key.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  using super_type::invoke;
  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  using super_type::super_type; // Inherit super_type constructors.
  TextView
  key() const override
  {
    return KEY;
  }
};

const std::string Do_ua_req_field::KEY{"ua-req-field"};
const HookMask    Do_ua_req_field::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Errata
Do_ua_req_field::invoke(Context &ctx)
{
  return this->invoke_on_hdr(ctx, ctx.ua_req_hdr());
}

Rv<Directive::Handle>
Do_ua_req_field::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg,
                      YAML::Node key_value)
{
  return super_type::load(
    cfg, [](TextView const &name, Expr &&fmt) -> Handle { return Handle(new self_type(name, std::move(fmt))); }, KEY, arg,
    key_value);
}

// --
class Do_proxy_req_field : public FieldDirective
{
  using self_type  = Do_proxy_req_field;
  using super_type = FieldDirective;

public:
  static const std::string KEY;   ///< Directive key.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  using super_type::super_type; // Inherit super_type constructors.
  TextView
  key() const override
  {
    return KEY;
  }
};

const std::string Do_proxy_req_field::KEY{"proxy-req-field"};
const HookMask    Do_proxy_req_field::HOOKS{MaskFor({Hook::PREQ})};

Errata
Do_proxy_req_field::invoke(Context &ctx)
{
  return this->invoke_on_hdr(ctx, ctx.proxy_req_hdr());
}

Rv<Directive::Handle>
Do_proxy_req_field::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg,
                         YAML::Node key_value)
{
  return super_type::load(
    cfg, [](TextView const &name, Expr &&fmt) -> Handle { return Handle(new self_type(name, std::move(fmt))); }, KEY, arg,
    key_value);
}

// --
class Do_proxy_rsp_field : public FieldDirective
{
  using self_type  = Do_proxy_rsp_field;
  using super_type = FieldDirective;

public:
  static const std::string KEY;   ///< Directive key.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  using super_type::super_type; // Inherit super_type constructors.
  TextView
  key() const override
  {
    return KEY;
  }
};

const std::string Do_proxy_rsp_field::KEY{"proxy-rsp-field"};
const HookMask    Do_proxy_rsp_field::HOOKS{MaskFor(Hook::PRSP)};

Errata
Do_proxy_rsp_field::invoke(Context &ctx)
{
  return this->invoke_on_hdr(ctx, ctx.proxy_rsp_hdr());
}

Rv<Directive::Handle>
Do_proxy_rsp_field::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg,
                         YAML::Node key_value)
{
  return super_type::load(
    cfg, [](TextView const &name, Expr &&fmt) -> Handle { return Handle(new self_type(name, std::move(fmt))); }, KEY, arg,
    key_value);
}

// --
class Do_upstream_rsp_field : public FieldDirective
{
  using self_type  = Do_upstream_rsp_field;
  using super_type = FieldDirective;

public:
  static const std::string KEY;   ///< Directive key.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  using super_type::super_type; // Inherit super_type constructors.
  TextView
  key() const override
  {
    return KEY;
  }
};

const std::string Do_upstream_rsp_field::KEY{"upstream-rsp-field"};
const HookMask    Do_upstream_rsp_field::HOOKS{MaskFor(Hook::URSP)};

Errata
Do_upstream_rsp_field::invoke(Context &ctx)
{
  return this->invoke_on_hdr(ctx, ctx.upstream_rsp_hdr());
}

Rv<Directive::Handle>
Do_upstream_rsp_field::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg,
                            YAML::Node key_value)
{
  return super_type::load(
    cfg, [](TextView const &name, Expr &&fmt) -> Handle { return Handle(new self_type(name, std::move(fmt))); }, KEY, arg,
    key_value);
}
/* ------------------------------------------------------------------------------------ */
/// Set upstream response status code.
class Do_upstream_rsp_status : public Directive
{
  using self_type  = Do_upstream_rsp_status; ///< Self reference type.
  using super_type = Directive;              ///< Parent type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Return status.

  Do_upstream_rsp_status() = default;
};

const std::string Do_upstream_rsp_status::KEY{"upstream-rsp-status"};
const HookMask    Do_upstream_rsp_status::HOOKS{MaskFor({Hook::URSP})};

Errata
Do_upstream_rsp_status::invoke(Context &ctx)
{
  int     status = TS_HTTP_STATUS_NONE;
  Feature value  = ctx.extract(_expr);
  auto    vtype  = value.value_type();
  if (INTEGER == vtype) {
    status = std::get<IndexFor(INTEGER)>(value);
  } else if (TUPLE == vtype) {
    auto t = std::get<IndexFor(TUPLE)>(value);
    if (0 < t.count() && t.count() <= 2) {
      if (t[0].value_type() != INTEGER) {
        return Errata(S_ERROR, R"(Tuple for "{}" must be an integer and a string.)", KEY);
      }
      status = std::get<IndexFor(INTEGER)>(t[0]);
      if (t.count() == 2) {
        if (t[1].value_type() != STRING) {
          return Errata(S_ERROR, R"(Tuple for "{}" must be an integer and a string.)", KEY);
        }
        ctx._txn.ursp_hdr().reason_set(std::get<IndexFor(STRING)>(t[1]));
      }
    } else {
      return Errata(S_ERROR, R"(Tuple for "{}" has {} elements, instead of there required 1 or 2.)", KEY, t.size());
    }
  }
  if (100 <= status && status <= 599) {
    ctx._txn.ursp_hdr().status_set(static_cast<TSHttpStatus>(status));
  } else {
    return Errata(S_ERROR, R"(Status value {} out of range 100..599 for "{}".)", status, KEY);
  }
  return {};
}

Rv<Directive::Handle>
Do_upstream_rsp_status::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &,
                             swoc::TextView const &, YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }
  auto   self = new self_type;
  Handle handle(self);

  auto expr_type = expr.result_type();
  if (!expr_type.can_satisfy({INTEGER, TUPLE})) {
    return Errata(S_ERROR, R"(Value for "{}" at {} is not an integer or tuple as required.)", KEY, drtv_node.Mark());
  }
  self->_expr = std::move(expr);
  return handle;
}
/* ------------------------------------------------------------------------------------ */
/// Set upstream response reason phrase.
class Do_upstream_reason : public Directive
{
  using self_type  = Do_upstream_reason; ///< Self reference type.
  using super_type = Directive;          ///< Parent type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML configuration.
   *
   * @param cfg Configuration data.
   * @param drtv_node Node containing the directive.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  TSHttpStatus _status = TS_HTTP_STATUS_NONE; ///< Return status is literal, 0 => extract at runtime.
  Expr         _fmt;                          ///< Reason phrase.

  Do_upstream_reason() = default;
};

const std::string Do_upstream_reason::KEY{"upstream-reason"};
const HookMask    Do_upstream_reason::HOOKS{MaskFor({Hook::URSP})};

Errata
Do_upstream_reason::invoke(Context &ctx)
{
  auto value = ctx.extract(_fmt);
  if (STRING != value.value_type()) {
    return Errata(S_ERROR, R"(Value for "{}" is not a string.)", KEY);
  }
  ctx._txn.ursp_hdr().reason_set(std::get<IndexFor(STRING)>(value));
  return {};
}

Rv<Directive::Handle>
Do_upstream_reason::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                         YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(The value for "{}" must be a string.)", KEY, drtv_node.Mark());
  }
  auto   self = new self_type;
  Handle handle(self);

  self->_fmt = std::move(expr);

  return handle;
}
/* ------------------------------------------------------------------------------------ */
/// Set proxy response status code.
class Do_proxy_rsp_status : public Directive
{
  using self_type  = Do_proxy_rsp_status; ///< Self reference type.
  using super_type = Directive;           ///< Parent type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML configuration.
   *
   * @param cfg Configuration data.
   * @param drtv_node Node containing the directive.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Return status.

  Do_proxy_rsp_status() = default;
};

const std::string Do_proxy_rsp_status::KEY{"proxy-rsp-status"};
const HookMask    Do_proxy_rsp_status::HOOKS{MaskFor({Hook::PRSP})};

Errata
Do_proxy_rsp_status::invoke(Context &ctx)
{
  int     status = TS_HTTP_STATUS_NONE;
  Feature value  = ctx.extract(_expr);
  auto    vtype  = value.value_type();
  if (INTEGER == vtype) {
    status = std::get<IndexFor(INTEGER)>(value);
  } else if (TUPLE == vtype) {
    auto t = std::get<IndexFor(TUPLE)>(value);
    if (0 < t.count() && t.count() <= 2) {
      if (t[0].value_type() != INTEGER) {
        return Errata(S_ERROR, R"(Tuple for "{}" must be an integer and a string.)", KEY);
      }
      status = std::get<IndexFor(INTEGER)>(t[0]);
      if (t.count() == 2) {
        if (t[1].value_type() != STRING) {
          return Errata(S_ERROR, R"(Tuple for "{}" must be an integer and a string.)", KEY);
        }
        ctx._txn.prsp_hdr().reason_set(std::get<IndexFor(STRING)>(t[1]));
      }
    } else {
      return Errata(S_ERROR, R"(Tuple for "{}" has {} elements, instead of there required 1 or 2.)", KEY, t.size());
    }
  }
  if (100 <= status && status <= 599) {
    ctx._txn.prsp_hdr().status_set(static_cast<TSHttpStatus>(status));
  } else {
    return Errata(S_ERROR, R"(Status value {} out of range 100..599 for "{}".)", status, KEY);
  }
  return {};
}

Rv<Directive::Handle>
Do_proxy_rsp_status::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                          YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }
  auto   self = new self_type;
  Handle handle(self);

  auto expr_type = expr.result_type();
  if (!expr_type.can_satisfy(MaskFor({INTEGER, TUPLE}))) {
    return Errata(S_ERROR, R"(Value for "{}" at {} is not an integer or tuple as required.)", KEY, drtv_node.Mark());
  }
  self->_expr = std::move(expr);
  return handle;
}
/* ------------------------------------------------------------------------------------ */
/// Set proxy response reason phrase.
class Do_proxy_rsp_reason : public Directive
{
  using self_type  = Do_proxy_rsp_reason; ///< Self reference type.
  using super_type = Directive;           ///< Parent type.
public:
  static inline const std::string KEY{"proxy-rsp-reason"};      ///< Directive name.
  static inline const HookMask    HOOKS{MaskFor({Hook::PRSP})}; ///< Valid hooks for directive.

  /** Invoke directive.
   *
   * @param ctx Transaction context.
   * @return Errors, if any.
   */
  Errata invoke(Context &ctx) override;

  /** Load from YAML configuration.
   *
   * @param cfg Configuration data.
   * @param drtv_node Node containing the directive.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  TSHttpStatus _status = TS_HTTP_STATUS_NONE; ///< Return status is literal, 0 => extract at runtime.
  Expr         _expr;                         ///< Reason phrase.

  Do_proxy_rsp_reason() = default;
};

Errata
Do_proxy_rsp_reason::invoke(Context &ctx)
{
  auto value = ctx.extract(_expr);
  if (STRING != value.value_type()) {
    return Errata(S_ERROR, R"(Value for "{}" is not a string.)", KEY);
  }
  ctx._txn.prsp_hdr().reason_set(std::get<IndexFor(STRING)>(value));
  return {};
}

Rv<Directive::Handle>
Do_proxy_rsp_reason::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                          YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(The value for "{}" must be a string.)", KEY, drtv_node.Mark());
  }
  auto   self = new self_type;
  Handle handle(self);

  self->_expr = std::move(expr);

  return handle;
}
/* ------------------------------------------------------------------------------------ */
/// Set proxy response (error) body.
class Do_proxy_rsp_body : public Directive
{
  using self_type  = Do_proxy_rsp_body; ///< Self reference type.
  using super_type = Directive;         ///< Parent type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML configuration.
   *
   * @param cfg Configuration data.
   * @param drtv_node Node containing the directive.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Body content.

  Do_proxy_rsp_body() = default;
};

const std::string Do_proxy_rsp_body::KEY{"proxy-rsp-body"};
const HookMask    Do_proxy_rsp_body::HOOKS{MaskFor({Hook::PRSP})};

Errata
Do_proxy_rsp_body::invoke(Context &ctx)
{
  TextView body, mime{"text/html"};
  auto     value = ctx.extract(_expr);
  if (STRING == value.value_type()) {
    body = std::get<IndexFor(STRING)>(value);
  } else if (auto tp = std::get_if<IndexFor(TUPLE)>(&value); tp) {
    if (tp->count() == 2) {
      if (auto ptr = std::get_if<IndexFor(STRING)>(&(*tp)[0]); ptr) {
        body = *ptr;
      }
      if (auto ptr = std::get_if<IndexFor(STRING)>(&(*tp)[1]); ptr) {
        mime = *ptr;
      }
    } else {
      return Errata(S_ERROR, R"(Value for "{}" is not a list of length 2.)", KEY);
    }
  } else {
    return Errata(S_ERROR, R"(Value for "{}" is not a string nor a list.)", KEY);
  }
  ctx._txn.error_body_set(body, mime);
  return {};
}

Rv<Directive::Handle>
Do_proxy_rsp_body::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                        YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy({STRING, ActiveType::TupleOf(STRING)})) {
    return Errata(S_ERROR, R"(The value for "{}" must be a string or a list of two strings.)", KEY, drtv_node.Mark());
  }
  auto   self = new self_type;
  Handle handle(self);

  self->_expr = std::move(expr);

  return handle;
}
/* ------------------------------------------------------------------------------------ */
/// Replace the upstream response body with a feature.
class Do_upstream_rsp_body : public Directive
{
  using self_type  = Do_upstream_rsp_body; ///< Self reference type.
  using super_type = Directive;            ///< Parent type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML configuration.
   *
   * @param cfg Configuration data.
   * @param drtv_node Node containing the directive.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr; ///< Body content.

  Do_upstream_rsp_body(Expr &&expr) : _expr(std::move(expr)) {}
};

const std::string Do_upstream_rsp_body::KEY{"upstream-rsp-body"};
const HookMask    Do_upstream_rsp_body::HOOKS{MaskFor({Hook::URSP})};

Errata
Do_upstream_rsp_body::invoke(Context &ctx)
{
  /// State data for the transform continuation.
  /// @internal Due to ugliness in the plugin API where the final event for the @c Continuation
  /// can arrive after the transaction is destroyed, the @c IOBuffer needs to get cleaned up at
  /// transaction termination, not the final transform event. Therefore the destructor here does
  /// the cleanup, so that it can be marked for cleanup in the @c Context.
  struct State {
    TextView   _view;                ///< Source view for body.
    TSIOBuffer _tsio_buff = nullptr; ///< Buffer used to write body.
    TSCont     _cont      = nullptr; ///< Transform continuation with which State is associated.
    /// Clean up the @c IOBuffer.
    ~State()
    {
      if (_cont != nullptr) {
        TSContDataSet(_cont, nullptr);
      }
      if (_tsio_buff) {
        TSIOBufferDestroy(_tsio_buff);
      }
    }
  };

  auto static transform = [](TSCont contp, TSEvent ev_code, void *) -> int {
    if (TSVConnClosedGet(contp)) {
      // IOBuffer is cleaned up at transaction close, not here.
      if (auto state = static_cast<State *>(TSContDataGet(contp)); state) {
        state->_cont = nullptr;
        TSContDataSet(contp, nullptr);
      }
      TSContDestroy(contp);
      return 0;
    }

    auto in_vio = TSVConnWriteVIOGet(contp);
    switch (ev_code) {
    case TS_EVENT_ERROR:
      TSContCall(TSVIOContGet(in_vio), TS_EVENT_ERROR, in_vio);
      break;
    case TS_EVENT_VCONN_WRITE_COMPLETE:
      TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
      break;
    default:
      // Consume all input data.
      auto in_todo   = TSVIONTodoGet(in_vio);
      auto in_reader = TSVIOReaderGet(in_vio);
      if (in_reader && in_todo) {
        auto avail = TSIOBufferReaderAvail(in_reader);
        in_todo    = std::min(in_todo, avail);
        if (in_todo > 0) {
          TSIOBufferReaderConsume(in_reader, in_todo);
          TSVIONDoneSet(in_vio, TSVIONDoneGet(in_vio) + in_todo);
          TSContCall(TSVIOContGet(in_vio),
                     (TSVIONTodoGet(in_vio) <= 0) ? TS_EVENT_VCONN_WRITE_COMPLETE : TS_EVENT_VCONN_WRITE_READY, in_vio);
        }
        // If the buffer isn't already there, create it and write out the view.
        if (auto state = static_cast<State *>(TSContDataGet(contp)); state && !state->_tsio_buff) {
          auto out_vconn    = TSTransformOutputVConnGet(contp);
          state->_tsio_buff = TSIOBufferCreate();
          TSIOBufferWrite(state->_tsio_buff, state->_view.data(), state->_view.size());
          auto out_vio = TSVConnWrite(out_vconn, contp, TSIOBufferReaderAlloc(state->_tsio_buff), state->_view.size());
          TSVIOReenable(out_vio);
        }
      }
      break;
    }

    return 0;
  };

  auto      value        = ctx.extract(_expr);
  auto      vtype        = value.value_type();
  TextView *content      = nullptr;
  TextView  content_type = "text/html";
  if (STRING == vtype) {
    content = &std::get<IndexFor(STRING)>(value);
  } else if (TUPLE == vtype) {
    auto t = std::get<IndexFor(TUPLE)>(value);
    if (t.size() > 0) {
      if (STRING == t[0].value_type()) {
        content = &std::get<IndexFor(STRING)>(t[0]);
        if (t.size() > 1 && STRING == t[1].value_type()) {
          content_type = std::get<IndexFor(STRING)>(t[1]);
        }
      }
    }
  }

  if (content) {
    // The view contents are in the transaction data, but the view in the feature is not.
    // Put a copy in the transform @a state.
    auto state = ctx.make<State>();
    ctx.mark_for_cleanup(state);
    auto cont    = TSTransformCreate(transform, ctx._txn);
    state->_cont = cont;
    state->_view = *content;
    TSContDataSet(cont, state);
    TSHttpTxnHookAdd(ctx._txn, TS_HTTP_RESPONSE_TRANSFORM_HOOK, cont);
    ctx._txn.ursp_hdr().field_obtain("Content-Type"_tv).assign(content_type);
  }

  return {};
}

Rv<Directive::Handle>
Do_upstream_rsp_body::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                           YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }
  if (!expr.result_type().can_satisfy(STRING)) {
    return Errata(S_ERROR, R"(The value for "{}" must be a string.)", KEY, drtv_node.Mark());
  }

  return Handle(new self_type(std::move(expr)));
}
// ---
/// Immediate proxy reply.
class Do_proxy_reply : public Directive
{
  using self_type  = Do_proxy_reply; ///< Self reference type.
  using super_type = Directive;      ///< Parent type.

  /// Per context information.
  /// This is what is stored in the span @c CfgInfo::_ctx_span
  struct CtxInfo {
    TextView _reason; ///< Status reason string.
  };

public:
  inline static const std::string KEY        = "proxy-reply"; ///< Directive name.
  inline static const std::string STATUS_KEY = "status";      ///< Key for status value.
  inline static const std::string REASON_KEY = "reason";      ///< Key for reason value.
  inline static const std::string BODY_KEY   = "body";        ///< Key for body.

  static inline const HookMask HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP})}; ///< Valid hooks for directive.

  /// Need to do fixups on a later hook.
  static constexpr Hook FIXUP_HOOK = Hook::PRSP;

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

  static Errata cfg_init(Config &cfg, CfgStaticData const *);

protected:
  using index_type = FeatureGroup::index_type;

  FeatureGroup _fg; ///< Support cross references among the keys.

  int        _status = 0; ///< Return status is literal, 0 => extract at runtime.
  index_type _status_idx; ///< Return status.
  index_type _reason_idx; ///< Status reason text.
  index_type _body_idx;   ///< Body content of respons.
  /// Bounce from fixup hook directive back to @a this.
  Directive::Handle _fixup{new LambdaDirective([this](Context &ctx) -> Errata { return this->fixup(ctx); })};

  Errata load_status();

  /// Do post invocation fixup.
  Errata fixup(Context &ctx);
};

Errata
Do_proxy_reply::cfg_init(Config &cfg, CfgStaticData const *)
{
  cfg.reserve_slot(FIXUP_HOOK); // needed to fix up "Location" field in proxy response.
  return {};
}

Errata
Do_proxy_reply::invoke(Context &ctx)
{
  auto ctx_info = ctx.obtain_named_object<CtxInfo>(KEY);

  // Is a fix up hook required to set the reason correctly?
  bool need_hook_p = false;

  // Finalize the reason and stash it in context storage.
  if (_reason_idx != FeatureGroup::INVALID_IDX) {
    Feature reason = _fg.extract(ctx, _reason_idx);
    if (reason.index() == IndexFor(STRING)) {
      ctx.commit(reason);
      need_hook_p       = ctx_info->_reason.empty(); // hook needed if this is first to set reason.
      ctx_info->_reason = std::get<IndexFor(STRING)>(reason);
    }
  }

  // Set the status to prevent the upstream request.
  if (_status) {
    ctx._txn.status_set(static_cast<TSHttpStatus>(_status));
  } else {
    auto &&[status, errata]{_fg.extract(ctx, _status_idx).as_integer(-1)};
    if (100 <= status && status <= 599) {
      ctx._txn.status_set(static_cast<TSHttpStatus>(status));
    }
  }

  // Set the body.
  if (_body_idx != FeatureGroup::INVALID_IDX) {
    auto body{_fg.extract(ctx, _body_idx)};
    ctx._txn.error_body_set(std::get<IndexFor(STRING)>(body), "text/html");
  }

  // Arrange for fixup to get invoked.
  if (need_hook_p) {
    ctx.on_hook_do(FIXUP_HOOK, _fixup.get());
  }
  return {};
}

Errata
Do_proxy_reply::fixup(Context &ctx)
{
  if (auto ctx_info = ctx.named_object<CtxInfo>(KEY); ctx_info) {
    if (!ctx_info->_reason.empty()) {
      auto hdr{ctx.proxy_rsp_hdr()};
      hdr.reason_set(ctx_info->_reason);
    }
  }
  return {};
}

Errata
Do_proxy_reply::load_status()
{
  _status_idx = _fg.index_of(STATUS_KEY);

  FeatureGroup::ExprInfo &info = _fg[_status_idx];

  if (info._expr.is_literal()) {
    auto &&[status, errata]{std::get<Feature>(info._expr._raw).as_integer(-1)};
    if (!errata.is_ok()) {
      errata.note("While load key '{}' for directive '{}'", STATUS_KEY, KEY);
      return std::move(errata);
    }
    if (!(100 <= status && status <= 599)) {
      return Errata(S_ERROR, R"(Value for '{}' key in {} directive is not a positive integer 100..599 as required.)", STATUS_KEY,
                    KEY);
    }
    _status = status;
  } else if (!info._expr.result_type().can_satisfy(MaskFor(STRING, INTEGER))) {
    return Errata(S_ERROR, R"({} is not an integer nor string as required.)", STATUS_KEY);
  }
  return {};
}

Rv<Directive::Handle>
Do_proxy_reply::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                     YAML::Node key_value)
{
  Handle handle{new self_type};
  Errata errata;
  auto   self = static_cast<self_type *>(handle.get());
  if (key_value.IsScalar()) {
    errata = self->_fg.load_as_scalar(cfg, key_value, STATUS_KEY);
  } else if (key_value.IsSequence()) {
    errata = self->_fg.load_as_tuple(cfg, key_value,
                                     {
                                       {STATUS_KEY, FeatureGroup::REQUIRED},
                                       {REASON_KEY}
    });
  } else if (key_value.IsMap()) {
    errata = self->_fg.load(cfg, key_value,
                            {
                              {STATUS_KEY, FeatureGroup::REQUIRED},
                              {REASON_KEY},
                              {BODY_KEY}
    });
  } else {
    return Errata(S_ERROR, R"(Value for "{}" key at {} is must be a scalar, a list, or a map and is not.)", KEY, key_value.Mark());
  }
  if (!errata.is_ok()) {
    errata.note(R"(While parsing value at {} in "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
    return {{}, std::move(errata)};
  }

  self->_reason_idx = self->_fg.index_of(REASON_KEY);
  self->_body_idx   = self->_fg.index_of(BODY_KEY);
  errata.note(self->load_status());

  if (!errata.is_ok()) {
    errata.note(R"(While parsing value at {} in "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
    return {{}, std::move(errata)};
  }

  return {std::move(handle), {}};
}
// ---
/* ------------------------------------------------------------------------------------ */
class Do_remap_redirect : public Directive
{
  using self_type  = Do_remap_redirect; ///< Self reference type.
  using super_type = Directive;         ///< Parent type.
public:
  static inline const HookMask    HOOKS{MaskFor(Hook::REMAP)}; ///< Valid hooks for directive.
  static inline const std::string KEY = "remap-redirect";      ///< Directive name.

  Errata            invoke(Context &ctx) override; ///< Runtime activation.
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);
};

Rv<Directive::Handle>
Do_remap_redirect::load(Config &, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &, YAML::Node)
{
  return Handle{new self_type};
}

Errata
Do_remap_redirect::invoke(Context &ctx)
{
  if (ctx._remap_info) {
    ctx._remap_info->redirect = 1;
    ctx._remap_status         = TSREMAP_DID_REMAP;
  }
  return {};
}
/* ------------------------------------------------------------------------------------ */
/// Redirect.
/// Although this could technically be done "by hand", it's common enough to justify
/// a specific directive.
class Do_redirect : public Directive
{
  using self_type  = Do_redirect; ///< Self reference type.
  using super_type = Directive;   ///< Parent type.

  /// Per context information, used for fix up on proxy response hook.
  /// -- doc Do_redirect::CtxInfo
  struct CtxInfo {
    TextView _location; ///< Redirect target location.
    TextView _reason;   ///< Status text.
  };

public:
  inline static const std::string KEY          = "redirect"; ///< Directive name.
  inline static const std::string STATUS_KEY   = "status";   ///< Key for status value.
  inline static const std::string REASON_KEY   = "reason";   ///< Key for reason value.
  inline static const std::string LOCATION_KEY = "location"; ///< Key for location value.
  inline static const std::string BODY_KEY     = "body";     ///< Key for body.

  static const HookMask HOOKS; ///< Valid hooks for directive.

  /// Need to do fixups on a later hook.
  static constexpr Hook FIXUP_HOOK = Hook::PRSP;
  /// Status code to use if not specified.
  static const int DEFAULT_STATUS = TS_HTTP_STATUS_MOVED_PERMANENTLY;

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

  /// Configuration level initialization.
  static Errata cfg_init(Config &cfg, CfgStaticData const *rtti);

protected:
  using index_type = FeatureGroup::index_type;

  FeatureGroup _fg; ///< Support cross references among the keys.

  int        _status = 0;   ///< Return status is literal, 0 => extract at runtime.
  index_type _status_idx;   ///< Return status.
  index_type _reason_idx;   ///< Status reason text.
  index_type _location_idx; ///< Location field value.
  index_type _body_idx;     ///< Body content of respons.
  /// Bounce from fixup hook directive back to @a this.
  Directive::Handle _set_location{new LambdaDirective([this](Context &ctx) -> Errata { return this->fixup(ctx); })};

  Errata load_status();

  /// Do post invocation fixup.
  Errata fixup(Context &ctx);
};

const HookMask Do_redirect::HOOKS{MaskFor(Hook::PRE_REMAP, Hook::REMAP)};

Errata
Do_redirect::cfg_init(Config &cfg, CfgStaticData const *)
{
  cfg.reserve_slot(FIXUP_HOOK); // needed to fix up "Location" field in proxy response.
  return {};
}
// -- doc Do_redirect::cfg_init

Errata
Do_redirect::invoke(Context &ctx)
{
  auto ctx_info = ctx.obtain_named_object<CtxInfo>(KEY);

  // If the Location view is empty, it hasn't been set and therefore the clean up hook
  // hasn't been set either, so need to do that.
  bool need_hook_p = ctx_info->_location.empty();

  // Finalize the location and stash it in context storage.
  Feature location = _fg.extract(ctx, _location_idx);
  if (location.index() == IndexFor(STRING)) {
    ctx.commit(location);
    ctx_info->_location = std::get<IndexFor(STRING)>(location);
  } else {
    return Errata(S_ERROR, "{} directive - '{}' was not a string as required.", KEY, LOCATION_KEY);
  }

  // Set the status to prevent the upstream request.
  if (_status) {
    ctx._txn.status_set(static_cast<TSHttpStatus>(_status));
  } else {
    Feature value = _fg.extract(ctx, _status_idx);
    auto &&[status, errata]{value.as_integer(DEFAULT_STATUS)};
    if (!(100 <= status && status <= 599)) {
      status = DEFAULT_STATUS;
    }
    ctx._txn.status_set(static_cast<TSHttpStatus>(status));
  }
  // Set the reason.
  if (_reason_idx != FeatureGroup::INVALID_IDX) {
    auto reason{_fg.extract(ctx, _reason_idx)};
    if (reason.index() == IndexFor(STRING)) {
      ctx.commit(reason);
      ctx_info->_reason = std::get<IndexFor(STRING)>(reason);
    }
  }
  // Set the body.
  if (_body_idx != FeatureGroup::INVALID_IDX) {
    auto body{_fg.extract(ctx, _body_idx)};
    ctx._txn.error_body_set(std::get<IndexFor(STRING)>(body), "text/html");
  }
  // Arrange for fixup to get invoked.
  if (need_hook_p) {
    ctx.on_hook_do(FIXUP_HOOK, _set_location.get());
  }
  return {};
}

Errata
Do_redirect::fixup(Context &ctx)
{
  if (auto ctx_info = ctx.named_object<CtxInfo>(KEY); ctx_info) {
    auto hdr{ctx.proxy_rsp_hdr()};

    auto field{hdr.field_obtain(ts::HTTP_FIELD_LOCATION)};
    field.assign(ctx_info->_location);

    if (!ctx_info->_reason.empty()) {
      hdr.reason_set(ctx_info->_reason);
    }
  }
  return {};
}

Errata
Do_redirect::load_status()
{
  _status_idx = _fg.index_of(STATUS_KEY);

  if (_status_idx == FeatureGroup::INVALID_IDX) { // not present, use default value.
    _status = DEFAULT_STATUS;
    return {};
  }

  FeatureGroup::ExprInfo &info = _fg[_status_idx];

  if (info._expr.is_literal()) {
    auto &&[status, errata]{std::get<Feature>(info._expr._raw).as_integer(DEFAULT_STATUS)};
    _status = status;
    if (!errata.is_ok()) {
      errata.note("While load key '{}' for directive '{}'", STATUS_KEY, KEY);
      return std::move(errata);
    }
    if (!(100 <= status && status <= 599)) {
      return Errata(S_ERROR, R"(Value for '{}' key in {} directive is not a positive integer 100..599 as required.)", STATUS_KEY,
                    KEY);
    }
  } else {
    auto rtype = info._expr.result_type();
    if (rtype != STRING && rtype != INTEGER) {
      return Errata(S_ERROR, R"({} is not an integer nor string as required.)", STATUS_KEY);
    }
  }
  return {};
}

Rv<Directive::Handle>
Do_redirect::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                  YAML::Node key_value)
{
  Handle handle{new self_type};
  Errata errata;
  auto   self = static_cast<self_type *>(handle.get());
  if (key_value.IsScalar()) {
    errata = self->_fg.load_as_scalar(cfg, key_value, LOCATION_KEY);
  } else if (key_value.IsSequence()) {
    errata = self->_fg.load_as_tuple(cfg, key_value,
                                     {
                                       {STATUS_KEY,   FeatureGroup::REQUIRED},
                                       {LOCATION_KEY, FeatureGroup::REQUIRED}
    });
  } else if (key_value.IsMap()) {
    errata = self->_fg.load(cfg, key_value,
                            {
                              {LOCATION_KEY, FeatureGroup::REQUIRED},
                              {STATUS_KEY},
                              {REASON_KEY},
                              {BODY_KEY}
    });
  } else {
    return Errata(S_ERROR, R"(Value for "{}" key at {} is must be a scalar, a list, or a map and is not.)", KEY, key_value.Mark());
  }
  if (!errata.is_ok()) {
    errata.note(R"(While parsing value at {} in "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
    return {{}, std::move(errata)};
  }

  self->_reason_idx   = self->_fg.index_of(REASON_KEY);
  self->_body_idx     = self->_fg.index_of(BODY_KEY);
  self->_location_idx = self->_fg.index_of(LOCATION_KEY);
  errata.note(self->load_status());

  if (!errata.is_ok()) {
    errata.note(R"(While parsing value at {} in "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
    return {{}, std::move(errata)};
  }

  return {std::move(handle), {}};
}
/* ------------------------------------------------------------------------------------ */
/// Send a debug message.
class Do_debug : public Directive
{
  using self_type  = Do_debug;
  using super_type = Directive;

public:
  static const std::string KEY;
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata            invoke(Context &ctx) override;
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _tag;
  Expr _msg;

  Do_debug(Expr &&tag, Expr &&msg);
};

const std::string Do_debug::KEY{"debug"};
const HookMask    Do_debug::HOOKS{MaskFor({Hook::POST_LOAD, Hook::TXN_START, Hook::CREQ, Hook::PREQ, Hook::URSP, Hook::PRSP,
                                           Hook::PRE_REMAP, Hook::POST_REMAP, Hook::REMAP})};

Do_debug::Do_debug(Expr &&tag, Expr &&msg) : _tag(std::move(tag)), _msg(std::move(msg)) {}

Errata
Do_debug::invoke(Context &ctx)
{
  [[maybe_unused]] TextView tag = ctx.extract_view(_tag, {Context::EX_COMMIT, Context::EX_C_STR});
  TextView                  msg = ctx.extract_view(_msg);
  TS_DBG("%.*s", static_cast<int>(msg.size()), msg.data());
  return {};
}

Rv<Directive::Handle>
Do_debug::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
               YAML::Node key_value)
{
  if (key_value.IsScalar()) {
    auto &&[msg_fmt, msg_errata] = cfg.parse_expr(key_value);
    if (!msg_errata.is_ok()) {
      msg_errata.note(R"(While parsing message at {} for "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
      return {{}, std::move(msg_errata)};
    }
    return {Handle{new self_type{Expr{Config::PLUGIN_TAG}, std::move(msg_fmt)}}, {}};
  } else if (key_value.IsSequence()) {
    if (key_value.size() > 2) {
      return Errata(S_ERROR, R"(Value for "{}" key at {} is not a list of two strings as required.)", KEY, key_value.Mark());
    } else if (key_value.size() < 1) {
      return Errata(S_ERROR, R"(The list value for "{}" key at {} does not have at least one string as required.)", KEY,
                    key_value.Mark());
    }
    auto &&[tag_expr, tag_errata] = cfg.parse_expr(key_value[0]);
    if (!tag_errata.is_ok()) {
      tag_errata.note(R"(While parsing tag at {} for "{}" directive at {}.)", key_value[0].Mark(), KEY, drtv_node.Mark());
      return std::move(tag_errata);
    }
    auto &&[msg_expr, msg_errata] = cfg.parse_expr(key_value[1]);
    if (!tag_errata.is_ok()) {
      tag_errata.note(R"(While parsing message at {} for "{}" directive at {}.)", key_value[1].Mark(), KEY, drtv_node.Mark());
      return std::move(tag_errata);
    }
    return Handle(new self_type(std::move(tag_expr), std::move(msg_expr)));
  }
  return Errata(S_ERROR, R"(Value for "{}" key at {} is not a string or a list of strings as required.)", KEY, key_value.Mark());
}

/* ------------------------------------------------------------------------------------ */
/// Log an Error message.
class Do_error : public Directive
{
  using self_type  = Do_error;
  using super_type = Directive;

public:
  static inline const std::string KEY{"error"};
  static const HookMask           HOOKS; ///< Valid hooks for directive.

  Errata            invoke(Context &ctx) override;
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _msg;

  Do_error(Expr &&msg);
};

const HookMask Do_error::HOOKS{MaskFor({Hook::POST_LOAD, Hook::TXN_START, Hook::CREQ, Hook::PREQ, Hook::URSP, Hook::PRSP,
                                        Hook::PRE_REMAP, Hook::POST_REMAP, Hook::REMAP})};

Do_error::Do_error(Expr &&msg) : _msg(std::move(msg)) {}

Errata
Do_error::invoke(Context &ctx)
{
  TextView msg = ctx.extract_view(_msg);
  ts::Log_Error(msg);
  return {};
}

Rv<Directive::Handle>
Do_error::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
               YAML::Node key_value)
{
  auto &&[msg_fmt, msg_errata] = cfg.parse_expr(key_value);
  if (!msg_errata.is_ok()) {
    msg_errata.note(R"(While parsing message at {} for "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
    return {{}, std::move(msg_errata)};
  }
  return {Handle{new self_type{std::move(msg_fmt)}}};
}

/// Log an notify message.
class Do_note : public Directive
{
  using self_type  = Do_note;   ///< Self reference type.
  using super_type = Directive; ///< Super type.
public:
  static inline const std::string KEY{"note"}; ///< Name of directive.
  /// Valid hooks for this directive.
  static inline const HookMask HOOKS{MaskFor(Hook::POST_LOAD, Hook::TXN_START, Hook::CREQ, Hook::PREQ, Hook::URSP, Hook::PRSP,
                                             Hook::PRE_REMAP, Hook::POST_REMAP, Hook::REMAP)};

  /// Runtime invocation.
  Errata invoke(Context &ctx) override;

  /// Load from configuration.
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _msg; ///< Message to log.

  /** Constructor.
   *
   * @param msg Parsed feature expression for message.
   */
  Do_note(Expr &&msg);
};
// -- doc end Do_note

Do_note::Do_note(Expr &&msg) : _msg(std::move(msg)) {}

// -- doc note::invoke
Errata
Do_note::invoke(Context &ctx)
{
  TextView msg = ctx.extract_view(_msg);
  ts::Log_Note(msg);
  return {};
}
// -- doc note::invoke

// -- doc note::load
Rv<Directive::Handle>
Do_note::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
              YAML::Node key_value)
{
  auto &&[msg_fmt, msg_errata] = cfg.parse_expr(key_value);
  if (!msg_errata.is_ok()) {
    msg_errata.note(R"(While parsing message at {} for "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
    return std::move(msg_errata);
  }
  return Handle{new self_type{std::move(msg_fmt)}};
}
// -- doc note::load

/// Log an warning message.
class Do_warning : public Directive
{
  using self_type  = Do_warning;
  using super_type = Directive;

public:
  static inline const std::string KEY{"warning"};
  static const HookMask           HOOKS; ///< Valid hooks for directive.

  Errata            invoke(Context &ctx) override;
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _msg;

  Do_warning(Expr &&msg);
};

const HookMask Do_warning::HOOKS{MaskFor({Hook::POST_LOAD, Hook::TXN_START, Hook::CREQ, Hook::PREQ, Hook::URSP, Hook::PRSP,
                                          Hook::PRE_REMAP, Hook::POST_REMAP, Hook::REMAP})};

Do_warning::Do_warning(Expr &&msg) : _msg(std::move(msg)) {}

Errata
Do_warning::invoke(Context &ctx)
{
  TextView msg = ctx.extract_view(_msg);
  ts::Log_Warning(msg);
  return {};
}

Rv<Directive::Handle>
Do_warning::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                 YAML::Node key_value)
{
  auto &&[msg_fmt, msg_errata] = cfg.parse_expr(key_value);
  if (!msg_errata.is_ok()) {
    msg_errata.note(R"(While parsing message at {} for "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
    return {{}, std::move(msg_errata)};
  }
  return {Handle{new self_type{std::move(msg_fmt)}}};
}

/* ------------------------------------------------------------------------------------ */
/// Set the cache key.
class Do_cache_key : public Directive
{
  using self_type  = Do_cache_key; ///< Self reference type.
  using super_type = Directive;    ///< Parent type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _fmt; ///< Cache key.

  Do_cache_key(Expr &&fmt) : _fmt(std::move(fmt)) {}
};

const std::string Do_cache_key::KEY{"cache-key"};
const HookMask    Do_cache_key::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

Errata
Do_cache_key::invoke(Context &ctx)
{
  auto value = ctx.extract(_fmt);
  ctx._txn.cache_key_assign(std::get<IndexFor(STRING)>(value));
  return {};
}

Rv<Directive::Handle>
Do_cache_key::load(Config    &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &,
                   YAML::Node key_value)
{
  auto &&[fmt, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }

  return Handle(new self_type(std::move(fmt)));
}
/* ------------------------------------------------------------------------------------ */
/// Set a transaction configuration variable override.
class Do_txn_conf : public Directive
{
  using self_type  = Do_txn_conf; ///< Self reference type.
  using super_type = Directive;   ///< Parent type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr              _expr; ///< Value for override.
  ts::TxnConfigVar *_var = nullptr;

  Do_txn_conf(Expr &&fmt, ts::TxnConfigVar *var) : _expr(std::move(fmt)), _var(var) {}
};

const std::string Do_txn_conf::KEY{"txn-conf"};
const HookMask    Do_txn_conf::HOOKS{
  MaskFor({Hook::TXN_START, Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP, Hook::PREQ})};

Errata
Do_txn_conf::invoke(Context &ctx)
{
  auto value = ctx.extract(_expr);
  if (value.index() == IndexFor(INTEGER)) {
    ctx._txn.override_assign(*_var, std::get<IndexFor(INTEGER)>(value));
  } else if (value.index() == IndexFor(BOOLEAN)) {
    ctx._txn.override_assign(*_var, std::get<IndexFor(BOOLEAN)>(value) ? 1L : 0L);
  } else if (value.index() == IndexFor(STRING)) {
    // Unfortunately although the interface doesn't appear to require C strings, in practice some of
    // the string overridables do (such as client cert file path).
    auto str = ctx.localize_as_c_str(std::get<IndexFor(STRING)>(value));
    ctx._txn.override_assign(*_var, str);
  } else if (value.index() == IndexFor(FLOAT)) {
    ctx._txn.override_assign(*_var, std::get<IndexFor(FLOAT)>(value));
  }
  return {};
}

Rv<Directive::Handle>
Do_txn_conf::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg,
                  YAML::Node key_value)
{
  auto txn_var = ts::HttpTxn::find_override(arg);
  if (!txn_var) {
    return Errata(S_ERROR, R"("{}" is not recognized as an overridable transaction configuration variable.)", arg);
  }
  if (txn_var->type() != TS_RECORDDATATYPE_INT && txn_var->type() != TS_RECORDDATATYPE_STRING &&
      txn_var->type() != TS_RECORDDATATYPE_FLOAT) {
    return Errata(S_ERROR, R"("{}" is of type "{}" which is not currently supported.)", arg,
                  ts::TSRecordDataTypeNames[txn_var->type()]);
  }
  auto &&[fmt, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }

  return Handle(new self_type(std::move(fmt), txn_var));
}

/* ------------------------------------------------------------------------------------ */
/// Set the address for the upstream.
class Do_upstream_addr : public Directive
{
  using self_type  = Do_upstream_addr; ///< Self reference type.
  using super_type = Directive;        ///< Parent type.
public:
  static inline const std::string KEY{"upstream-addr"}; ///< Directive name.
  static const HookMask           HOOKS;                ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr              _expr; ///< Address.
  ts::TxnConfigVar *_var = nullptr;

  Do_upstream_addr(Expr &&expr) : _expr(std::move(expr)) {}
};

const HookMask Do_upstream_addr::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP, Hook::PREQ})};

Errata
Do_upstream_addr::invoke(Context &ctx)
{
  auto value = ctx.extract(_expr);
  if (value.index() == IndexFor(IP_ADDR)) {
    ctx._txn.set_upstream_addr(std::get<IndexFor(IP_ADDR)>(value));
  }
  return {};
}

Rv<Directive::Handle>
Do_upstream_addr::load(Config    &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &,
                       YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }

  if (!expr.result_type().can_satisfy(IP_ADDR)) {
    return Errata(S_ERROR, R"(Value for "{}" must be an IP address.)");
  }

  return Handle(new self_type(std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/// Set a transaction local variable.
class Do_var : public Directive
{
  using self_type  = Do_var;    ///< Self reference type.
  using super_type = Directive; ///< Parent type.
public:
  static const std::string KEY;   ///< Directive name.
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override; ///< Runtime activation.

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  TextView _name;  ///< Variable name.
  Expr     _value; ///< Value for variable.

  Do_var(TextView const &arg, Expr &&value) : _name(arg), _value(std::move(value)) {}
};

const std::string Do_var::KEY{"var"};
const HookMask    Do_var::HOOKS{
  MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP, Hook::PREQ, Hook::URSP, Hook::PRSP})};

Errata
Do_var::invoke(Context &ctx)
{
  ctx.store_txn_var(_name, ctx.extract(_value));
  return {};
}

Rv<Directive::Handle>
Do_var::load(Config &cfg, CfgStaticData const *, YAML::Node, swoc::TextView const &, swoc::TextView const &arg,
             YAML::Node key_value)
{
  auto &&[expr, errata]{cfg.parse_expr(key_value)};
  if (!errata.is_ok()) {
    return std::move(errata);
  }

  return Handle(new self_type(cfg.localize(arg), std::move(expr)));
}
/* ------------------------------------------------------------------------------------ */
/// Internal transaction error control
class Do_txn_error : public Directive
{
  using self_type  = Do_txn_error;
  using super_type = Directive;

public:
  static inline const std::string KEY{"txn-error"};
  /// Valid hooks for directive.
  static inline const HookMask HOOKS{
    MaskFor({Hook::TXN_START, Hook::CREQ, Hook::PREQ, Hook::URSP, Hook::PRSP, Hook::PRE_REMAP, Hook::POST_REMAP, Hook::REMAP})};

  Errata            invoke(Context &ctx) override;
  static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr _expr;

  Do_txn_error(Expr &&msg);
};

Do_txn_error::Do_txn_error(Expr &&expr) : _expr(std::move(expr)) {}

Errata
Do_txn_error::invoke(Context &ctx)
{
  ctx._global_status = ctx.extract(_expr).as_bool() ? TS_EVENT_HTTP_ERROR : TS_EVENT_HTTP_CONTINUE;
  return {};
}

Rv<Directive::Handle>
Do_txn_error::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
                   YAML::Node key_value)
{
  auto &&[expr, errata] = cfg.parse_expr(key_value);
  if (!errata.is_ok()) {
    errata.note(R"(While parsing message at {} for "{}" directive at {}.)", key_value.Mark(), KEY, drtv_node.Mark());
    return {std::move(errata)};
  }
  return {Handle{new self_type{std::move(expr)}}};
}

/* ------------------------------------------------------------------------------------ */
/** @c with directive.
 *
 * This is a core directive that has lots of special properties.
 */
class Do_with : public Directive
{
  using super_type = Directive;
  using self_type  = Do_with;

public:
  static const std::string KEY;
  static const std::string SELECT_KEY;
  static const std::string FOR_EACH_KEY;
  static const std::string CONTINUE_KEY;
  static const HookMask    HOOKS; ///< Valid hooks for directive.

  Errata invoke(Context &ctx) override;

  /** Load from YAML node.
   *
   * @param cfg Configuration data.
   * @param rtti Configuration level static data for this directive.
   * @param drtv_node Node containing the directive.
   * @param name Name from key node tag.
   * @param arg Arg from key node tag.
   * @param key_value Value for directive @a KEY
   * @return A directive, or errors on failure.
   */
  static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name,
                         swoc::TextView const &arg, YAML::Node key_value);

protected:
  Expr              _expr; ///< Feature expression
  Directive::Handle _do;   ///< Explicit actions.

  union {
    uint32_t all = 0;
    struct {
      unsigned for_each_p : 1; ///< Direct action is per tuple element.
      unsigned continue_p : 1; ///< Continue with directives after this - want default to be 0.
    } f;
  } _opt;

  /// A single case in the select.
  struct Case {
    Comparison::Handle _cmp; ///< Comparison to perform.
    Directive::Handle  _do;  ///< Directives to execute.
  };
  using CaseGroup = std::vector<Case>;
  CaseGroup _cases; ///< List of cases for the select.

  Do_with() = default;

  Errata load_case(Config &cfg, YAML::Node node);
};

const std::string Do_with::KEY{"with"};
const std::string Do_with::SELECT_KEY{"select"};
const std::string Do_with::FOR_EACH_KEY{"for-each"};
const std::string Do_with::CONTINUE_KEY{"continue"};

const HookMask Do_with::HOOKS{MaskFor({Hook::POST_LOAD, Hook::TXN_START, Hook::CREQ, Hook::PREQ, Hook::URSP, Hook::PRSP,
                                       Hook::PRE_REMAP, Hook::POST_REMAP, Hook::REMAP})};

Errata
Do_with::invoke(Context &ctx)
{
  Feature feature{ctx.extract(_expr)};
  ctx.commit(feature);
  Feature save{ctx._active};
  ctx._active = feature;

  if (_do) {
    if (_opt.f.for_each_p) {
      ctx._active_ext = feature;
      while (!is_nil(feature)) {
        ctx._active = car(feature);
        ctx.mark_terminal(false);
        _do->invoke(ctx);
        cdr(feature);
      }
      clear(feature);
      ctx._active_ext = NIL_FEATURE;
      // Iteration can potentially modify the extracted feature value, so if there are comparisons
      // reset the feature.
      if (!_cases.empty()) {
        ctx._active = feature = ctx.extract(_expr);
      }
    } else {
      ctx.mark_terminal(false);
      _do->invoke(ctx);
    }
  }

  ctx.mark_terminal(false); // default is continue on.
  for (auto const &c : _cases) {
    if (!c._cmp || (*c._cmp)(ctx, feature)) {
      if (c._do) {
        c._do->invoke(ctx);
      }
      ctx.mark_terminal(!_opt.f.continue_p); // successful compare, mark terminal.
      break;
    }
  }
  // Need to restore to previous state if nothing matched.
  clear(ctx._active);
  ctx._active = save;
  return {};
}

swoc::Rv<Directive::Handle>
Do_with::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
              YAML::Node key_value)
{
  // Need to parse this first, so the feature type can be determined.
  auto &&[expr, errata] = cfg.parse_expr(key_value);

  if (!errata.is_ok()) {
    return std::move(errata);
  }

  auto  *self = new self_type;
  Handle handle(self); // for return, and cleanup in case of error.
  self->_expr  = std::move(expr);
  auto f_scope = cfg.feature_scope(self->_expr.result_type());

  YAML::Node select_node{drtv_node[SELECT_KEY]};
  if (select_node) {
    if (select_node.IsMap()) {
      errata = self->load_case(cfg, select_node);
      if (!errata.is_ok()) {
        return std::move(errata);
      }
    } else if (select_node.IsSequence()) {
      for (YAML::Node child : select_node) {
        errata = (self->load_case(cfg, child));
        if (!errata.is_ok()) {
          errata.note(R"(While loading "{}" directive at {} in "{}" at {}.)", KEY, drtv_node.Mark(), SELECT_KEY,
                      select_node.Mark());
          return std::move(errata);
        }
      }
    } else {
      return Errata(S_ERROR, R"(The value for "{}" at {} in "{}" directive at {} is not a list or object.")", SELECT_KEY,
                    select_node.Mark(), KEY, drtv_node.Mark());
    }
  }

  YAML::Node continue_node{drtv_node[CONTINUE_KEY]};
  if (continue_node) {
    self->_opt.f.continue_p = true;
  }

  YAML::Node do_node{drtv_node[DO_KEY]};
  YAML::Node for_each_node{drtv_node[FOR_EACH_KEY]};
  if (do_node && for_each_node) {
    return Errata(S_ERROR, R"("{}" directive cannot have both "{}" and "{}" as keys - {}.)", DO_KEY, FOR_EACH_KEY,
                  drtv_node.Mark());
  } else if (do_node) {
    auto &&[do_handle, errata]{cfg.parse_directive(do_node)};
    if (errata.is_ok()) {
      self->_do = std::move(do_handle);
    } else {
      errata.note(R"(While parsing "{}" key at {} in selection case at {}.)", DO_KEY, do_node.Mark(), drtv_node.Mark());
      return std::move(errata);
    }
  } else if (for_each_node) {
    auto &&[fe_handle, errata]{cfg.parse_directive(for_each_node)};
    if (errata.is_ok()) {
      self->_do               = std::move(fe_handle);
      self->_opt.f.for_each_p = true;
    } else {
      errata.note(R"(While parsing "{}" key at {} in selection case at {}.)", FOR_EACH_KEY, for_each_node.Mark(), drtv_node.Mark());
      return std::move(errata);
    }
  }
  return handle;
}

Errata
Do_with::load_case(Config &cfg, YAML::Node node)
{
  if (node.IsMap()) {
    Case       c;
    YAML::Node do_node{node[DO_KEY]};
    // It's allowed to have no comparison, which is either an empty map or only a DO key.
    // In that case the comparison always matches.
    if (node.size() > 1 || (node.size() == 1 && !do_node)) {
      auto f_scope = cfg.feature_scope(_expr.result_type());
      auto &&[cmp_handle, cmp_errata]{Comparison::load(cfg, node)};
      if (cmp_errata.is_ok()) {
        c._cmp = std::move(cmp_handle);
      } else {
        return std::move(cmp_errata);
      }
    }

    if (do_node) {
      auto c_scope = cfg.capture_scope((c._cmp ? c._cmp->rxp_group_count() : 0), node.Mark().line);
      auto &&[handle, errata]{cfg.parse_directive(do_node)};
      if (errata.is_ok()) {
        c._do = std::move(handle);
      } else {
        errata.note(R"(While parsing "{}" key at {} in selection case at {}.)", DO_KEY, do_node.Mark(), node.Mark());
        return std::move(errata);
      }
    } else {
      c._do.reset(new NilDirective);
    }
    // Everything is fine, update the case load and return.
    _cases.emplace_back(std::move(c));
    return {};
  }
  return Errata(S_ERROR, R"(The value at {} for "{}" is not an object as required.")", node.Mark(), SELECT_KEY);
}

/* ------------------------------------------------------------------------------------ */
const std::string When::KEY{"when"};
const HookMask    When::HOOKS{
  MaskFor({Hook::CREQ, Hook::PREQ, Hook::URSP, Hook::PRSP, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP})};

When::When(Hook hook_idx, Directive::Handle &&directive) : _hook(hook_idx), _directive(std::move(directive)) {}

// Put the internal directive in the directive array for the specified hook.
Errata
When::invoke(Context &ctx)
{
  return ctx.on_hook_do(_hook, _directive.get());
}

swoc::Rv<Directive::Handle>
When::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &,
           YAML::Node key_value)
{
  Errata zret;
  if (Hook hook{HookName[key_value.Scalar()]}; hook != Hook::INVALID) {
    if (YAML::Node do_node{drtv_node[DO_KEY]}; do_node) {
      auto save = cfg._hook;
      cfg._hook = hook;
      auto &&[do_handle, do_errata]{cfg.parse_directive(do_node)};
      cfg._hook = save;
      if (do_errata.is_ok()) {
        cfg.reserve_slot(hook);
        return {Handle{new self_type{hook, std::move(do_handle)}}, {}};
      } else {
        zret.note(do_errata);
        zret.note(R"(Failed to load directive in "{}" at {} in "{}" directive at {}.)", DO_KEY, do_node.Mark(), KEY,
                  key_value.Mark());
      }
    } else {
      zret.note(R"(The required "{}" key was not found in the "{}" directive at {}.")", DO_KEY, KEY, drtv_node.Mark());
    }
  } else {
    zret.note(R"(Invalid hook name "{}" in "{}" directive at {}.)", key_value.Scalar(), When::KEY, key_value.Mark());
  }
  return {{}, std::move(zret)};
}

/* ------------------------------------------------------------------------------------ */

namespace
{
[[maybe_unused]] bool INITIALIZED = []() -> bool {
  Config::define<When>();
  Config::define<Do_with>();

  Config::define<Do_ua_req_field>();
  Config::define<Do_ua_req_url>();
  Config::define<Do_ua_req_url>("ua-url-host"_tv); // alias
  Config::define<Do_ua_req_url_host>();
  Config::define<Do_ua_req_url_port>();
  Config::define<Do_ua_req_url_loc>();
  Config::define<Do_ua_req_scheme>();
  Config::define<Do_ua_req_host>();
  Config::define<Do_ua_req_port>();
  Config::define<Do_ua_req_loc>();
  Config::define<Do_ua_req_path>();
  Config::define<Do_ua_req_fragment>();

  Config::define<Do_proxy_req_field>();
  Config::define<Do_proxy_req_url>();
  Config::define<Do_proxy_req_url_host>();
  Config::define<Do_proxy_req_url_port>();
  Config::define<Do_proxy_req_url_loc>();
  Config::define<Do_proxy_req_host>();
  Config::define<Do_proxy_req_port>();
  Config::define<Do_proxy_req_loc>();
  Config::define<Do_proxy_req_scheme>();
  Config::define<Do_proxy_req_path>();
  Config::define<Do_proxy_req_fragment>();

  Config::define<Do_upstream_rsp_field>();
  Config::define<Do_upstream_rsp_status>();
  Config::define<Do_upstream_reason>();

  Config::define<Do_upstream_addr>();

  Config::define<Do_proxy_rsp_field>();
  Config::define<Do_proxy_rsp_status>();
  Config::define<Do_proxy_rsp_reason>();
  Config::define<Do_proxy_rsp_body>();

  Config::define<Do_upstream_rsp_body>();

  Config::define<Do_cache_key>();
  Config::define<Do_txn_conf>();
  Config::define<Do_redirect>();
  Config::define<Do_remap_redirect>();
  Config::define<Do_proxy_reply>();
  Config::define<Do_debug>();
  Config::define<Do_note>();
  Config::define<Do_warning>();
  Config::define<Do_error>();
  Config::define<Do_txn_error>();
  Config::define<Do_txn_debug>();
  Config::define<Do_var>();

  Config::define<Do_apply_remap_rule>();
  Config::define<Do_did_remap>();

  return true;
}();
} // namespace
