Program Listing for File validate.hpp

Return to documentation for file (include/eat/process/validate.hpp)

#pragma once
#include <adm/document.hpp>
#include <iosfwd>
#include <memory>
#include <optional>
#include <variant>
#include <vector>

#include "adm_bw64.hpp"
#include "eat/process/language_codes.hpp"
#include "eat/process/profiles.hpp"
#include "eat/process/validate_detail.hpp"
#include "eat/utilities/element_visitor.hpp"

namespace eat::process::validation {

template <typename T>
struct Range {
  std::optional<T> lower_limit;
  std::optional<T> upper_limit;

  bool operator()(T n) const { return (!lower_limit || n >= *lower_limit) && (!upper_limit || n <= *upper_limit); }

  std::string format() const {
    if (lower_limit && !upper_limit)
      return "at least " + std::to_string(*lower_limit);
    else if (!lower_limit && upper_limit)
      return "up to " + std::to_string(*upper_limit);
    else if (lower_limit && upper_limit && (*lower_limit == *upper_limit))
      return std::to_string(*lower_limit);
    else if (lower_limit && upper_limit)
      return "between " + std::to_string(*lower_limit) + " and " + std::to_string(*upper_limit);
    else
      throw std::runtime_error("formatting an open range");
  }

  static Range up_to(T upper_limit) { return Range{{}, upper_limit}; }

  static Range at_least(T lower_limit) { return Range{lower_limit, {}}; }

  static Range between(T lower_limit, T upper_limit) { return Range{lower_limit, upper_limit}; }

  static Range exactly(T limit) { return Range{limit, limit}; }
};

using CountRange = Range<size_t>;

struct NumElementsMessage {
  static std::string name() { return "NumElementsMessage"; }

  std::vector<std::string> path;
  std::string element;
  size_t n;
  std::string relationship = "elements";

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("element", element);
    f("n", n);
    f("relationship", relationship);
  }
};

struct NumElements {
  static std::string name() { return "NumElements"; }

  std::vector<std::string> path;
  std::string element;
  CountRange range;
  std::string relationship = "elements";

  using Message = NumElementsMessage;

  std::vector<Message> run(const ADMData &adm) const;

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("element", element);
    f("range", range);
    f("relationship", relationship);
  }
};

struct StringLengthMessage {
  static std::string name() { return "StringLengthMessage"; }

  std::vector<std::string> path;
  size_t n;

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("n", n);
  }
};

struct StringLength {
  static std::string name() { return "StringLength"; }

  std::vector<std::string> path;
  CountRange range;

  using Message = StringLengthMessage;

  std::vector<Message> run(const ADMData &adm) const;

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("range", range);
  }
};

struct ValidLanguageMessage {
  static std::string name() { return "ValidLanguageMessage"; }

  std::vector<std::string> path;
  std::string value;

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("value", value);
  }
};

struct ValidLanguage {
  static std::string name() { return "ValidLanguage"; }

  std::vector<std::string> path;
  LanguageCodeType type;

  using Message = ValidLanguageMessage;

  std::vector<Message> run(const ADMData &adm) const;

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("type", type);
  }
};

struct ElementPresentMessage {
  static std::string name() { return "ElementPresentMessage"; }

  std::vector<std::string> path;
  std::string element;
  bool present;

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("element", element);
    f("present", present);
  }
};

struct ElementPresent {
  static std::string name() { return "ElementPresent"; }

  std::vector<std::string> path;
  std::string element;
  bool present;

  using Message = ElementPresentMessage;

  std::vector<Message> run(const ADMData &adm) const;

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("element", element);
    f("present", present);
  }
};

template <typename T>
struct UniqueElementsMessage {
  static std::string name() { return "UniqueElementsMessage<" + detail::type_name<T> + ">"; }

  std::vector<std::string> path1;
  T value;
  std::vector<std::string> path2a;
  std::vector<std::string> path2b;

  template <typename F>
  void visit(F f) {
    f("path1", path1);
    f("value", value);
    f("path2a", path2a);
    f("path2b", path2b);
  }
};

template <typename T>
struct UniqueElements {
  static std::string name() { return "UniqueElements<" + detail::type_name<T> + ">"; }

  using Message = UniqueElementsMessage<T>;

  std::vector<std::string> path1;
  std::vector<std::string> path2;

  std::vector<Message> run(const ADMData &adm) const {
    std::vector<Message> messages;

    namespace ev = eat::utilities::element_visitor;

    // paths include the starting element, and this needs to be removed from the second path part
    auto remove_first = [](ev::Path path) {
      path.erase(path.begin());
      return path;
    };

    ev::visit(adm.document.read(), path1, [&](const ev::Path &path1_refs) {
      std::map<T, ev::Path> seen;
      ev::visit(path1_refs.back(), path2, [&](const ev::Path &path2_refs) {
        T value = path2_refs.back()->as_t<T>();
        if (auto it = seen.find(value); it != seen.end())
          messages.push_back({ev::path_to_strings(path1_refs), value, ev::path_to_strings(it->second),
                              ev::path_to_strings(remove_first(path2_refs))});
        else
          seen.emplace(value, remove_first(path2_refs));
      });
    });

    return messages;
  }

  template <typename F>
  void visit(F f) {
    f("path1", path1);
    f("path2", path2);
  }
};

template <typename T>
struct ElementInRangeMessage {
  static std::string name() { return "ElementInRangeMessage<" + detail::type_name<T> + ">"; }

  std::vector<std::string> path;
  T value;

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("value", value);
  }
};

template <typename T>
struct ElementInRange {
  static std::string name() { return "ElementInRange<" + detail::type_name<T> + ">"; }

  using Message = ElementInRangeMessage<T>;

  std::vector<std::string> path;
  Range<T> range;

  std::vector<Message> run(const ADMData &adm) const {
    std::vector<Message> messages;

    namespace ev = eat::utilities::element_visitor;
    ev::visit(adm.document.read(), path, [&](const ev::Path &path_refs) {
      T value = path_refs.back()->as_t<T>();
      if (!range(value)) messages.push_back({ev::path_to_strings(path_refs), value});
    });

    return messages;
  }

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("range", range);
  }
};

template <typename T>
struct ElementInListMessage {
  static std::string name() { return "ElementInListMessage<" + detail::type_name<T> + ">"; }

  std::vector<std::string> path;
  T value;

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("value", value);
  }
};

template <typename T>
struct ElementInList {
  static std::string name() { return "ElementInList<" + detail::type_name<T> + ">"; }

  using Message = ElementInListMessage<T>;

  std::vector<std::string> path;
  std::vector<T> options;

  std::vector<Message> run(const ADMData &adm) const {
    std::vector<Message> messages;

    namespace ev = eat::utilities::element_visitor;

    ev::visit(adm.document.read(), path, [&](const ev::Path &path_refs) {
      T value = path_refs.back()->as_t<T>();

      bool found = false;
      for (auto &option : options)
        if (value == option) found = true;

      if (!found) messages.push_back({ev::path_to_strings(path_refs), value});
    });

    return messages;
  }

  template <typename F>
  void visit(F f) {
    f("path", path);
    f("options", options);
  }
};

struct ObjectContentOrNestedMessage {
  static std::string name() { return "ObjectContentOrNestedMessage"; }

  adm::AudioObjectId object_id;
  bool both;

  template <typename F>
  void visit(F f) {
    f("object_id", object_id);
    f("both", both);
  }
};

struct ObjectContentOrNested {
  static std::string name() { return "ObjectContentOrNested"; }

  using Message = ObjectContentOrNestedMessage;

  std::vector<Message> run(const ADMData &adm) const;

  template <typename F>
  void visit(F) {}
};

using Check = std::variant<ElementInList<std::string>, ElementInRange<float>, ElementPresent, NumElements,
                           ObjectContentOrNested, StringLength, UniqueElements<std::string>, ValidLanguage>;

using Message = detail::ToMessages<Check>;

struct ValidationResult {
  Check check;
  std::vector<Message> messages;
};

using ValidationResults = std::vector<ValidationResult>;

class ProfileValidator {
 public:
  ProfileValidator(std::vector<Check> checks_) : checks(std::move(checks_)) {}

  ValidationResults run(const ADMData &adm) const;

 private:
  std::vector<Check> checks;
};

bool any_messages(const ValidationResults &results);

std::string format_check(const Check &check);
std::string format_message(const Message &message);
void format_results(std::ostream &s, const ValidationResults &results, bool show_checks_without_messages = false);

ProfileValidator make_emission_profile_validator(int level);
ProfileValidator make_profile_validator(const profiles::Profile &);

}  // namespace eat::process::validation