Files
msime-windows/engine/contracts/webview/validator.h
T
fanlusky a7779d2b05 refactor(engine): 将 engine 从 submodule 内嵌为仓内一等组件
`vendor/MetasequoiaImeEngine` submodule 改为 `engine/` 目录,导入的是
product-lock.json 原本钉住的提交 c810d201,逐文件核对过 blob,因此这是一次
搬迁而不是引擎升级。引擎自己的嵌套 submodule(googlepinyinime-rev、utfcpp、
miniaudio)按各自 gitlink 一并展开,来源与提交见 engine/UPSTREAM.md。

Windows 专门化裁掉了不属于本产品的部分:whisper.cpp 与本地推理 provider、
macOS 示例宿主、整个 dictionary/ 建库流水线、上游 CI 与 release-please 配置。
词库仍按 product-lock.json 在构建时取发布产物。

引擎不再进 product-lock.json:没有 gitlink 就没有可比对的东西,锁里写一个上游
commit 等于宣称这棵树仍等于上游,而第一个 Windows 专门化改动落地时这句话就假了。
verify-contracts / verify-checkout 随之移除,verify-published 保留。

CI 与工具链:引擎源码并入根 scripts/format.sh(删掉引擎自己的那份,避免两套排除
列表分叉);引擎 voice.yml 里的 silero_vad.onnx 摘要校验移到本仓 lock job;
engine-bump-triage 工作流与其测试删除;gitsubmodule 的 dependabot 频率由 daily
改为 monthly。

README、README.en、AGENTS.md、CONTRIBUTING、docs/product-release.md 及各子目录
文档同步说明新的位置与边界。
2026-09-12 03:40:48 +08:00

144 lines
5.1 KiB
C++

#pragma once
#include "schema.h"
#include <boost/json.hpp>
#include <cmath>
#include <string_view>
namespace metasequoia::webview
{
namespace detail
{
inline bool Matches(const boost::json::value &value, const boost::json::object &rule)
{
if (const auto *branches = rule.if_contains("anyOf"))
{
for (const auto &branch : branches->as_array())
if (Matches(value, branch.as_object()))
return true;
return false;
}
if (const auto *type = rule.if_contains("type"))
{
const auto &name = type->as_string();
if (name == "array")
{
if (!value.is_array())
return false;
for (const auto &item : value.as_array())
if (!Matches(item, rule.at("items").as_object()))
return false;
}
else if (name == "object")
{
if (!value.is_object())
return false;
const auto &object = value.as_object();
const auto &properties = rule.at("properties").as_object();
for (const auto &key : rule.at("required").as_array())
if (!object.contains(key.as_string()))
return false;
for (const auto &item : object)
{
if (const auto *property = properties.if_contains(item.key()))
{
if (!Matches(item.value(), property->as_object()))
return false;
}
else if (!rule.at("additionalProperties").as_bool())
return false;
}
}
else if (name == "string" && !value.is_string())
return false;
else if (name == "boolean" && !value.is_bool())
return false;
else if (name == "number" || name == "integer")
{
if (!value.is_number())
return false;
const double number = value.to_number<double>();
if (!std::isfinite(number))
return false;
if (name == "integer" && (std::floor(number) != number || std::abs(number) > 9007199254740991.0))
return false;
}
}
if (const auto *values = rule.if_contains("enum"))
{
bool found = false;
for (const auto &allowed : values->as_array())
found = found || value == allowed;
if (!found)
return false;
}
if (const auto *minimum = rule.if_contains("minimum"))
if (value.to_number<double>() < minimum->to_number<double>())
return false;
if (const auto *maximum = rule.if_contains("maximum"))
if (value.to_number<double>() > maximum->to_number<double>())
return false;
return true;
}
} // namespace detail
// Missing version is the legacy v1 skin envelope; an explicit unknown version is never dispatched.
inline bool Validate(const boost::json::value &message, std::string_view direction, std::string_view surface = {})
{
static const auto schema = boost::json::parse(SchemaJson);
if (!message.is_object())
return false;
const auto &object = message.as_object();
const auto *type = object.if_contains("type");
if (!type || !type->is_string())
return false;
if (const auto *version = object.if_contains("protocolVersion"))
if (!version->is_number() || version->to_number<double>() != Version)
return false;
const auto *rules = schema.as_object().if_contains(direction);
if (!rules)
return false;
const auto *entry = rules->as_object().if_contains(type->as_string());
if (!entry)
return false;
const auto &spec = entry->as_object();
if (const auto *surfaces = spec.if_contains("surfaces"))
{
bool found = false;
for (const auto &allowed : surfaces->as_array())
found = found || allowed.as_string() == surface;
if (!found)
return false;
}
const auto *dataRule = spec.if_contains("data");
const auto *data = object.if_contains("data");
if (dataRule && (!data || !detail::Matches(*data, dataRule->as_object())))
return false;
if (!dataRule && data)
return false;
for (const auto &item : object)
{
if (item.key() == "type" || item.key() == "protocolVersion" || item.key() == "data")
continue;
const auto *properties = spec.if_contains("properties");
const auto *property = properties ? properties->as_object().if_contains(item.key()) : nullptr;
if (property)
{
if (!detail::Matches(item.value(), property->as_object()))
return false;
}
else
{
const auto *additional = spec.if_contains("additionalProperties");
if (!additional || !additional->as_bool())
return false;
}
}
if (const auto *required = spec.if_contains("required"))
for (const auto &key : required->as_array())
if (!object.contains(key.as_string()))
return false;
return true;
}
} // namespace metasequoia::webview