chore: breaking rename

This commit is contained in:
MistEO
2022-12-02 00:22:47 +08:00
parent 8ff2c5e1d0
commit d9dcbb6289
345 changed files with 374 additions and 372 deletions

View File

@@ -0,0 +1,670 @@
#include "TaskData.h"
#include <algorithm>
#include <meojson/json.hpp>
#include "Common/AsstTypes.h"
#include "GeneralConfig.h"
#include "TemplResource.h"
#include "Utils/Logger.hpp"
#include "Utils/Ranges.hpp"
#include "Utils/StringMisc.hpp"
const std::unordered_set<std::string>& asst::TaskData::get_templ_required() const noexcept
{
return m_templ_required;
}
std::shared_ptr<asst::TaskInfo> asst::TaskData::get_raw(std::string_view name) const
{
// 普通 task 或已经生成过的 `@` 型 task
if (auto it = m_raw_all_tasks_info.find(name); it != m_raw_all_tasks_info.cend()) [[likely]] {
return it->second;
}
size_t at_pos = name.find('@');
if (at_pos == std::string_view::npos) [[unlikely]] {
return nullptr;
}
// `@` 前面的字符长度
size_t name_len = at_pos;
auto base_task_iter = get_raw(name.substr(name_len + 1));
if (base_task_iter == nullptr) [[unlikely]] {
return nullptr;
}
std::string_view derived_task_name = name.substr(0, name_len);
return _generate_task_info(base_task_iter, derived_task_name);
}
std::shared_ptr<asst::TaskInfo> asst::TaskData::get(std::string_view name)
{
// 普通 task 或已经生成过的 `@` 型 task
if (auto it = m_all_tasks_info.find(name); it != m_all_tasks_info.cend()) [[likely]] {
return it->second;
}
return expend_sharp_task(name, get_raw(name)).value_or(nullptr);
}
bool asst::TaskData::parse(const json::value& json)
{
LogTraceFunction;
const auto& json_obj = json.as_object();
{
enum TaskStatus
{
NotToBeGenerate = 0, // 已经显式生成 或 不是待显式生成 的资源
ToBeGenerate, // 待生成 的资源
Generating, // 正在生成 的资源
NotExists, // 不存在的资源
};
std::unordered_map<std::string_view, TaskStatus> task_status;
for (const std::string& name : json_obj | views::keys) {
task_status[task_name_view(name)] = ToBeGenerate;
}
auto generate_task_and_its_base = [&](const std::string& name) -> bool {
auto generate_task = [&](const std::string& name, std::string_view prefix, taskptr_t base_ptr,
const json::value& task_json) {
auto task_info_ptr = generate_task_info(name, task_json, base_ptr, prefix);
if (task_info_ptr == nullptr) {
return false;
}
task_status[task_name_view(name)] = NotToBeGenerate;
insert_or_assign_raw_task(name, task_info_ptr);
return true;
};
std::function<bool(const std::string&, bool)> generate_fun;
generate_fun = [&](const std::string& name, bool must_true) -> bool {
switch (task_status[task_name_view(name)]) {
case NotToBeGenerate:
// 已经显式生成 或 曾经显式生成(外服隐式引用国服资源)
if (m_raw_all_tasks_info.contains(name)) {
return true;
}
// 隐式生成的资源
if (size_t p = name.find('@'); p != std::string::npos) {
return generate_fun(name.substr(p + 1), must_true);
}
task_status[name] = NotExists;
[[fallthrough]];
case NotExists:
if (must_true) {
// 必须有名字为 name 的资源
Log.error("Unknown task:", name);
}
// 不一定必须有名字为 name 的资源,例如 Roguelike@Abandon 不必有 Abandon.
return false;
case ToBeGenerate: {
task_status[name] = Generating;
const json::value& task_json = json_obj.at(name);
if (auto opt = task_json.find<std::string>("baseTask")) {
// BaseTask
std::string base = opt.value();
if (!base.empty()) {
return generate_fun(base, must_true) && generate_task(name, "", get_raw(base), task_json);
}
// `"baseTask": ""` 表示不使用已生成的同名任务
}
else if (m_raw_all_tasks_info.contains(name)) {
// 已生成(外服覆写国服资源)
return generate_task(name, "", get_raw(name), task_json);
}
// TemplateTask
if (size_t p = name.find('@'); p != std::string::npos) {
if (std::string base = name.substr(p + 1); generate_fun(base, false)) {
return generate_task(name, name.substr(0, p), get_raw(base), task_json);
}
}
return generate_task(name, "", nullptr, task_json);
}
[[unlikely]] case Generating:
Log.error("Task", name, "is generated cyclically");
return false;
[[unlikely]] default:
Log.error("Task", name, "has unknown status");
return false;
}
};
return generate_fun(name, true);
};
for (const std::string& name : json_obj | views::keys) {
generate_task_and_its_base(name);
}
// 生成 # 型任务
for (const auto& [name, old_task] : m_raw_all_tasks_info) {
expend_sharp_task(name, old_task);
}
}
#ifdef ASST_DEBUG
{
bool validity = true;
// 语法检查
for (const auto& [name, task_json] : json_obj) [[likely]] {
validity &= syntax_check(name, task_json);
}
for (const auto& [name, task] : m_all_tasks_info) {
auto check_tasklist = [&](const tasklist_t& task_list, std::string_view list_type,
bool enable_justreturn_check = false) {
std::unordered_set<std::string_view> tasks_set {};
std::string justreturn_task_name = "";
for (const std::string& task_name : task_list) {
if (tasks_set.contains(task_name)) [[unlikely]] {
continue;
}
// 检查是否有 JustReturn 任务不是最后一个任务
if (enable_justreturn_check && !justreturn_task_name.empty()) [[unlikely]] {
Log.error((std::string(name) += "->") += list_type,
"has a not-final JustReturn task:", justreturn_task_name);
validity = false;
}
if (auto ptr = get_raw(task_name); ptr == nullptr) [[unlikely]] {
Log.error(task_name, "in", (std::string(name) += "->") += list_type, "is null");
validity = false;
}
else if (ptr->algorithm == AlgorithmType::JustReturn) {
justreturn_task_name = ptr->name;
}
tasks_set.emplace(task_name_view(task_name));
}
return true;
};
check_tasklist(task->next, "next", true);
check_tasklist(task->sub, "sub");
check_tasklist(task->exceeded_next, "exceeded_next", true);
check_tasklist(task->on_error_next, "on_error_next", true);
check_tasklist(task->reduce_other_times, "reduce_other_times");
}
if (!validity) return false;
}
#endif
return true;
}
std::optional<asst::TaskData::taskptr_t> asst::TaskData::expend_sharp_task(std::string_view name, taskptr_t old_task)
{
if (old_task == nullptr) [[unlikely]] {
return std::nullopt;
}
bool task_changed = false;
auto task_info = _generate_task_info(old_task);
auto expend_sharp_task_list = [&](tasklist_t& new_task_list, const tasklist_t& task_list,
std::string_view list_type) -> bool {
new_task_list.clear();
std::function<bool(const tasklist_t&)> generate_tasks;
std::unordered_set<std::string_view> tasks_set {};
generate_tasks = [&](const tasklist_t& raw_tasks) {
for (std::string_view task : raw_tasks) {
if (task.empty()) {
Log.error("Task", name, "has a empty", list_type);
return false;
}
if (tasks_set.contains(task)) [[unlikely]] {
task_changed = true;
continue;
}
tasks_set.emplace(task_name_view(task));
size_t pos = task.rfind('#');
if (pos == std::string_view::npos) [[likely]] {
new_task_list.emplace_back(task);
continue;
}
#ifdef ASST_DEBUG
if (pos == 0) [[unlikely]] {
Log.trace("Task", name, "has a virtual", list_type, ":", (std::string("`") += task) += '`');
}
#endif // ASST_DEBUG
task_changed = true;
std::string_view type = task.substr(pos + 1);
if (type == "self") {
new_task_list.emplace_back(name);
continue;
}
taskptr_t other_task_info_ptr = pos ? get_raw(task.substr(0, pos)) : default_task_info_ptr;
#define ASST_TASKDATA_GENERATE_TASKS(t) \
else if (type == #t) \
{ \
if (!generate_tasks(other_task_info_ptr->t)) { \
return false; \
} \
}
if (other_task_info_ptr == nullptr) [[unlikely]] {
Log.error("Task", task, "not found");
return false;
}
else if (type == "back") {
// "A#back" === "A", "B@A#back" === "B@A", "#back" === null
if (pos) {
new_task_list.emplace_back(task.substr(0, pos));
}
}
ASST_TASKDATA_GENERATE_TASKS(next)
ASST_TASKDATA_GENERATE_TASKS(sub)
ASST_TASKDATA_GENERATE_TASKS(on_error_next)
ASST_TASKDATA_GENERATE_TASKS(exceeded_next)
ASST_TASKDATA_GENERATE_TASKS(reduce_other_times)
else [[unlikely]]
{
Log.error("Unknown type", type, "in", task);
return false;
}
#undef ASST_TASKDATA_GENERATE_TASKS
}
return true;
};
if (!generate_tasks(task_list)) [[unlikely]] {
Log.error("Generate task_list", (std::string(name) += "->") += list_type, "failed.");
return false;
}
return true;
};
#define ASST_TASKDATA_GENERATE_SHARP_TASK(type) \
if (!expend_sharp_task_list(task_info->type, old_task->type, #type)) { \
return std::nullopt; \
}
ASST_TASKDATA_GENERATE_SHARP_TASK(next);
ASST_TASKDATA_GENERATE_SHARP_TASK(sub);
ASST_TASKDATA_GENERATE_SHARP_TASK(exceeded_next);
ASST_TASKDATA_GENERATE_SHARP_TASK(on_error_next);
ASST_TASKDATA_GENERATE_SHARP_TASK(reduce_other_times);
#undef ASST_TASKDATA_GENERATE_SHARP_TASK
// tasks 个数超过上限时不再 emplace返回临时值
constexpr size_t MAX_TASKS_SIZE = 65535;
if (m_all_tasks_info.size() < MAX_TASKS_SIZE) [[likely]] {
return insert_or_assign_task(name, task_changed ? task_info : old_task).first->second;
}
else {
#ifdef ASST_DEBUG
Log.debug("Task count has exceeded the upper limit:", MAX_TASKS_SIZE, "current task:", name);
#endif // ASST_DEBUG
return task_changed ? task_info : old_task;
}
}
asst::TaskData::taskptr_t asst::TaskData::generate_task_info(const std::string& name, const json::value& task_json,
taskptr_t default_ptr, std::string_view task_prefix)
{
if (default_ptr == nullptr) {
default_ptr = default_task_info_ptr;
task_prefix = "";
}
// 获取 algorithm 并按照 algorithm 生成 TaskInfo
auto algorithm = default_ptr->algorithm;
taskptr_t default_derived_ptr = default_ptr;
if (auto opt = task_json.find<std::string>("algorithm")) {
std::string algorithm_str = opt.value();
algorithm = get_algorithm_type(algorithm_str);
if (default_ptr->algorithm != algorithm) {
// 相同 algorithm 时才继承派生类成员
default_derived_ptr = nullptr;
}
}
taskptr_t task_info_ptr = nullptr;
switch (algorithm) {
case AlgorithmType::MatchTemplate:
task_info_ptr =
generate_match_task_info(name, task_json, std::dynamic_pointer_cast<MatchTaskInfo>(default_derived_ptr));
break;
case AlgorithmType::OcrDetect:
task_info_ptr =
generate_ocr_task_info(name, task_json, std::dynamic_pointer_cast<OcrTaskInfo>(default_derived_ptr));
break;
case AlgorithmType::Hash:
task_info_ptr =
generate_hash_task_info(name, task_json, std::dynamic_pointer_cast<HashTaskInfo>(default_derived_ptr));
break;
case AlgorithmType::JustReturn:
task_info_ptr = std::make_shared<TaskInfo>();
break;
default:
Log.error("Unknown algorithm in task", name);
return nullptr;
}
// 不管什么algorithm都有基础成员next, roi, 等等)
if (!append_base_task_info(task_info_ptr, name, task_json, default_ptr, task_prefix)) {
return nullptr;
}
task_info_ptr->algorithm = algorithm;
task_info_ptr->name = name;
return task_info_ptr;
}
asst::TaskData::taskptr_t asst::TaskData::generate_match_task_info(const std::string& name,
const json::value& task_json,
std::shared_ptr<MatchTaskInfo> default_ptr)
{
if (default_ptr == nullptr) {
default_ptr = default_match_task_info_ptr;
}
auto match_task_info_ptr = std::make_shared<MatchTaskInfo>();
// template 留空时不从模板任务继承
match_task_info_ptr->templ_name = task_json.get("template", name + ".png");
m_templ_required.emplace(match_task_info_ptr->templ_name);
// 其余若留空则继承模板任务
match_task_info_ptr->templ_threshold = task_json.get("templThreshold", default_ptr->templ_threshold);
if (auto opt = task_json.find<json::array>("maskRange")) {
auto& mask_range = *opt;
match_task_info_ptr->mask_range =
std::make_pair(static_cast<int>(mask_range[0]), static_cast<int>(mask_range[1]));
}
else {
match_task_info_ptr->mask_range = default_ptr->mask_range;
}
return match_task_info_ptr;
}
asst::TaskData::taskptr_t asst::TaskData::generate_ocr_task_info([[maybe_unused]] const std::string& name,
const json::value& task_json,
std::shared_ptr<OcrTaskInfo> default_ptr)
{
if (default_ptr == nullptr) {
default_ptr = default_ocr_task_info_ptr;
}
auto ocr_task_info_ptr = std::make_shared<OcrTaskInfo>();
auto array_opt = task_json.find<json::array>("text");
ocr_task_info_ptr->text = array_opt ? to_string_list(array_opt.value()) : default_ptr->text;
#ifdef ASST_DEBUG
if (!array_opt && default_ptr->text.empty()) {
Log.warn("Ocr task", name, "has implicit empty text.");
}
#endif
ocr_task_info_ptr->full_match = task_json.get("fullMatch", default_ptr->full_match);
ocr_task_info_ptr->is_ascii = task_json.get("isAscii", default_ptr->is_ascii);
ocr_task_info_ptr->without_det = task_json.get("withoutDet", default_ptr->without_det);
if (auto opt = task_json.find<json::array>("ocrReplace")) {
for (const json::value& rep : opt.value()) {
ocr_task_info_ptr->replace_map.emplace(rep[0].as_string(), rep[1].as_string());
}
}
else {
ocr_task_info_ptr->replace_map = default_ptr->replace_map;
}
return ocr_task_info_ptr;
}
asst::TaskData::taskptr_t asst::TaskData::generate_hash_task_info([[maybe_unused]] const std::string& name,
const json::value& task_json,
std::shared_ptr<HashTaskInfo> default_ptr)
{
if (default_ptr == nullptr) {
default_ptr = default_hash_task_info_ptr;
}
auto hash_task_info_ptr = std::make_shared<HashTaskInfo>();
auto array_opt = task_json.find<json::array>("hash");
hash_task_info_ptr->hashes = array_opt ? to_string_list(array_opt.value()) : default_ptr->hashes;
#ifdef ASST_DEBUG
if (!array_opt && default_ptr->hashes.empty()) {
Log.warn("Hash task", name, "has implicit empty hashes.");
}
#endif
hash_task_info_ptr->dist_threshold = task_json.get("threshold", default_ptr->dist_threshold);
if (auto opt = task_json.find<json::array>("maskRange")) {
auto& mask_range = *opt;
hash_task_info_ptr->mask_range =
std::make_pair(static_cast<int>(mask_range[0]), static_cast<int>(mask_range[1]));
}
else {
hash_task_info_ptr->mask_range = default_ptr->mask_range;
}
hash_task_info_ptr->bound = task_json.get("bound", default_ptr->bound);
return hash_task_info_ptr;
}
bool asst::TaskData::append_base_task_info(taskptr_t task_info_ptr, const std::string& name,
const json::value& task_json, taskptr_t default_ptr,
std::string_view task_prefix)
{
if (default_ptr == nullptr) {
default_ptr = default_task_info_ptr;
}
if (auto opt = task_json.find<std::string>("action")) {
std::string action = opt.value();
task_info_ptr->action = get_action_type(action);
if (task_info_ptr->action == ProcessTaskAction::Invalid) [[unlikely]] {
Log.error("Unknown action:", action, ", Task:", name);
return false;
}
}
else {
task_info_ptr->action = default_ptr->action;
}
task_info_ptr->cache = task_json.get("cache", default_ptr->cache);
task_info_ptr->max_times = task_json.get("maxTimes", default_ptr->max_times);
auto array_opt = task_json.find<json::array>("exceededNext");
task_info_ptr->exceeded_next =
array_opt ? to_string_list(array_opt.value()) : append_prefix(default_ptr->exceeded_next, task_prefix);
array_opt = task_json.find<json::array>("onErrorNext");
task_info_ptr->on_error_next =
array_opt ? to_string_list(array_opt.value()) : append_prefix(default_ptr->on_error_next, task_prefix);
task_info_ptr->pre_delay = task_json.get("preDelay", default_ptr->pre_delay);
task_info_ptr->post_delay = task_json.get("postDelay", default_ptr->post_delay);
array_opt = task_json.find<json::array>("reduceOtherTimes");
task_info_ptr->reduce_other_times =
array_opt ? to_string_list(array_opt.value()) : append_prefix(default_ptr->reduce_other_times, task_prefix);
if (auto opt = task_json.find<json::array>("roi")) {
auto& roi_arr = *opt;
int x = static_cast<int>(roi_arr[0]);
int y = static_cast<int>(roi_arr[1]);
int width = static_cast<int>(roi_arr[2]);
int height = static_cast<int>(roi_arr[3]);
#ifdef ASST_DEBUG
if (x + width > WindowWidthDefault || y + height > WindowHeightDefault) {
Log.error(name, "roi is out of bounds");
return false;
}
#endif
task_info_ptr->roi = Rect(x, y, width, height);
}
else {
task_info_ptr->roi = default_ptr->roi;
}
array_opt = task_json.find<json::array>("sub");
task_info_ptr->sub = array_opt ? to_string_list(array_opt.value()) : append_prefix(default_ptr->sub, task_prefix);
task_info_ptr->sub_error_ignored = task_json.get("subErrorIgnored", default_ptr->sub_error_ignored);
array_opt = task_json.find<json::array>("next");
task_info_ptr->next = array_opt ? to_string_list(array_opt.value()) : append_prefix(default_ptr->next, task_prefix);
if (auto opt = task_json.find<json::array>("rectMove")) {
auto& move_arr = opt.value();
task_info_ptr->rect_move = Rect(move_arr[0].as_integer(), move_arr[1].as_integer(), move_arr[2].as_integer(),
move_arr[3].as_integer());
}
else {
task_info_ptr->rect_move = default_ptr->rect_move;
}
if (auto opt = task_json.find<json::array>("specificRect")) {
auto& rect_arr = opt.value();
task_info_ptr->specific_rect = Rect(rect_arr[0].as_integer(), rect_arr[1].as_integer(),
rect_arr[2].as_integer(), rect_arr[3].as_integer());
}
else {
task_info_ptr->specific_rect = default_ptr->specific_rect;
}
if (auto opt = task_json.find<json::array>("specialParams")) {
auto& special_params = opt.value();
for (auto& param : special_params) {
task_info_ptr->special_params.emplace_back(param.as_integer());
}
}
else {
task_info_ptr->special_params = default_ptr->special_params;
}
return true;
}
std::shared_ptr<asst::MatchTaskInfo> asst::TaskData::_default_match_task_info()
{
auto match_task_info_ptr = std::make_shared<MatchTaskInfo>();
match_task_info_ptr->templ_name = "__INVALID__";
match_task_info_ptr->templ_threshold = TemplThresholdDefault;
return match_task_info_ptr;
}
std::shared_ptr<asst::OcrTaskInfo> asst::TaskData::_default_ocr_task_info()
{
auto ocr_task_info_ptr = std::make_shared<OcrTaskInfo>();
ocr_task_info_ptr->full_match = false;
ocr_task_info_ptr->is_ascii = false;
ocr_task_info_ptr->without_det = false;
return ocr_task_info_ptr;
}
std::shared_ptr<asst::HashTaskInfo> asst::TaskData::_default_hash_task_info()
{
auto hash_task_info_ptr = std::make_shared<HashTaskInfo>();
hash_task_info_ptr->dist_threshold = 0;
hash_task_info_ptr->bound = true;
return hash_task_info_ptr;
}
asst::TaskData::taskptr_t asst::TaskData::_default_task_info()
{
auto task_info_ptr = std::make_shared<TaskInfo>();
task_info_ptr->algorithm = AlgorithmType::MatchTemplate;
task_info_ptr->action = ProcessTaskAction::DoNothing;
task_info_ptr->cache = true;
task_info_ptr->max_times = INT_MAX;
task_info_ptr->pre_delay = 0;
task_info_ptr->post_delay = 0;
task_info_ptr->roi = Rect();
task_info_ptr->sub_error_ignored = false;
task_info_ptr->rect_move = Rect();
task_info_ptr->specific_rect = Rect();
return task_info_ptr;
}
#ifdef ASST_DEBUG
// 为了解决类似 beddc7c828126c678391e0b4da288db6d2c2d58a 导致的问题,加载的时候做一个语法检查
// 主要是处理是否包含未知键值的问题
bool asst::TaskData::syntax_check(const std::string& task_name, const json::value& task_json)
{
// clang-format off
// 以下按字典序排序
static const std::unordered_map<AlgorithmType, std::unordered_set<std::string>> allowed_key_under_algorithm = {
{ AlgorithmType::Invalid,
{
"action", "algorithm", "baseTask", "cache", "exceededNext", "fullMatch",
"hash", "isAscii", "maskRange", "maxTimes", "next", "ocrReplace",
"onErrorNext", "postDelay", "preDelay", "rectMove", "reduceOtherTimes", "roi",
"specialParams", "sub", "subErrorIgnored", "templThreshold", "template", "text",
"threshold", "withoutDet",
} },
{ AlgorithmType::MatchTemplate,
{
"action", "algorithm", "baseTask", "cache", "exceededNext", "maskRange",
"maxTimes", "next", "onErrorNext", "postDelay", "preDelay", "rectMove",
"reduceOtherTimes", "roi", "sub", "subErrorIgnored", "templThreshold", "template",
} },
{ AlgorithmType::OcrDetect,
{
"action", "algorithm", "baseTask", "cache", "exceededNext",
"fullMatch", "isAscii", "maxTimes", "next", "ocrReplace",
"onErrorNext", "postDelay", "preDelay", "rectMove", "reduceOtherTimes",
"roi", "sub", "subErrorIgnored", "text", "withoutDet",
} },
{ AlgorithmType::JustReturn,
{
"action", "algorithm", "baseTask", "exceededNext", "maxTimes", "next",
"onErrorNext", "postDelay", "preDelay", "reduceOtherTimes", "specialParams", "sub",
"subErrorIgnored",
} },
{ AlgorithmType::Hash,
{
"action", "algorithm", "baseTask", "cache", "exceededNext", "hash",
"maskRange", "maxTimes", "next", "onErrorNext", "postDelay", "preDelay",
"rectMove", "reduceOtherTimes", "roi", "specialParams", "sub", "subErrorIgnored",
"threshold",
} },
};
// clang-format on
static const std::unordered_map<ProcessTaskAction, std::unordered_set<std::string>> allowed_key_under_action = {
{ ProcessTaskAction::ClickRect,
{
"specificRect",
} },
{ ProcessTaskAction::Swipe, { "specificRect", "rectMove" } },
};
auto is_doc = [&](std::string_view key) {
return key.find("Doc") != std::string_view::npos || key.find("doc") != std::string_view::npos;
};
// 兜底策略,如果某个 key ("xxx") 不符合规范(可能是代码中使用到的参数,而不是任务流程)
// 需要加一个注释 ("xxx_Doc") 就能过 syntax_check.
auto has_doc = [&](const std::string& key) -> bool {
return task_json.find(key + "_Doc") || task_json.find(key + "_doc");
};
bool validity = true;
if (!m_all_tasks_info.contains(task_name)) {
Log.error("TaskData::syntax_check | Task", task_name, "has not been generated.");
return false;
}
// 获取 algorithm
auto algorithm = m_all_tasks_info[task_name]->algorithm;
if (algorithm == AlgorithmType::Invalid) [[unlikely]] {
Log.error(task_name, "has unknown algorithm.");
validity = false;
}
// 获取 action
auto action = m_all_tasks_info[task_name]->action;
if (action == ProcessTaskAction::Invalid) [[unlikely]] {
Log.error(task_name, "has unknown action.");
validity = false;
}
std::unordered_set<std::string> allowed_key {};
if (allowed_key_under_algorithm.contains(algorithm)) {
decltype(allowed_key) tmp = allowed_key_under_algorithm.at(algorithm);
allowed_key.merge(tmp);
}
if (allowed_key_under_action.contains(action)) {
decltype(allowed_key) tmp = allowed_key_under_action.at(action);
allowed_key.merge(tmp);
}
for (const auto& name : task_json.as_object() | views::keys) {
if (!allowed_key.contains(name) && !is_doc(name) && !has_doc(name)) {
Log.error(task_name, "has unknown key:", name);
validity = false;
}
}
return validity;
}
#endif