69beaad596
- deny_unknown_fields: опечатка в ключе capability (граница доверия F §3) не проглатывается. - VEHICLE_SIGNALS → shturman_sdk::vehicle (single source; валидатор берёт оттуда). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Alexander <akotenev2003@gmail.com>
97 lines
3.3 KiB
Rust
97 lines
3.3 KiB
Rust
//! Правила валидации манифеста поверх схемы (plugin-sdk §2/§3, F §3). Схема — `shturman_sdk::Manifest`.
|
|
|
|
use shturman_sdk::Manifest;
|
|
|
|
/// Поддерживаемые мажоры API (plugin-sdk §7).
|
|
const SUPPORTED_API: &[&str] = &["1"];
|
|
|
|
/// Проверить распарсенный манифест. Пустой вектор — валиден.
|
|
pub fn validate(m: &Manifest) -> Vec<String> {
|
|
let mut errs = Vec::new();
|
|
|
|
// id `ru.shturman.*` закрыт за first-party (F §3)
|
|
if m.plugin.id.starts_with("ru.shturman.") {
|
|
errs.push(format!(
|
|
"id '{}' использует зарезервированный префикс ru.shturman.* (закрыт за first-party)",
|
|
m.plugin.id
|
|
));
|
|
}
|
|
|
|
// shturman_api поддержан
|
|
if !SUPPORTED_API.contains(&m.plugin.shturman_api.as_str()) {
|
|
errs.push(format!(
|
|
"shturman_api '{}' не поддержан (поддерживаются: {SUPPORTED_API:?})",
|
|
m.plugin.shturman_api
|
|
));
|
|
}
|
|
|
|
// квота тайлов: len(tiles) ≤ ui_tiles (plugin-sdk §2)
|
|
let tiles = m.extension_points.tiles.len() as u32;
|
|
if tiles > m.capabilities.ui_tiles {
|
|
errs.push(format!(
|
|
"тайлов {tiles} больше квоты ui_tiles {}",
|
|
m.capabilities.ui_tiles
|
|
));
|
|
}
|
|
|
|
// vehicle_read только из каталога data-model (single source — shturman_sdk::vehicle)
|
|
for sig in &m.capabilities.vehicle_read {
|
|
if !shturman_sdk::vehicle::is_known(sig) {
|
|
errs.push(format!("vehicle_read '{sig}' нет в каталоге data-model"));
|
|
}
|
|
}
|
|
|
|
errs
|
|
}
|
|
|
|
/// Распарсить YAML (схема) и проверить правила. `Err` — ошибка парсинга; `Ok(errs)` — нарушения правил.
|
|
pub fn validate_yaml(yaml: &str) -> Result<Vec<String>, String> {
|
|
let m = Manifest::from_yaml(yaml).map_err(|e| format!("парсинг манифеста: {e}"))?;
|
|
Ok(validate(&m))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn fixture(name: &str) -> String {
|
|
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../fixtures/manifests/");
|
|
std::fs::read_to_string(format!("{dir}{name}"))
|
|
.unwrap_or_else(|e| panic!("фикстура {name}: {e}"))
|
|
}
|
|
|
|
#[test]
|
|
fn good_passes() {
|
|
assert!(validate_yaml(&fixture("good.yaml")).unwrap().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn reserved_id_rejected() {
|
|
let errs = validate_yaml(&fixture("bad-id-reserved.yaml")).unwrap();
|
|
assert!(errs.iter().any(|e| e.contains("ru.shturman")));
|
|
}
|
|
|
|
#[test]
|
|
fn unsupported_api_rejected() {
|
|
let errs = validate_yaml(&fixture("bad-api.yaml")).unwrap();
|
|
assert!(errs.iter().any(|e| e.contains("shturman_api")));
|
|
}
|
|
|
|
#[test]
|
|
fn tiles_over_quota_rejected() {
|
|
let errs = validate_yaml(&fixture("bad-tiles-quota.yaml")).unwrap();
|
|
assert!(errs.iter().any(|e| e.contains("квоты")));
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_vehicle_read_rejected() {
|
|
let errs = validate_yaml(&fixture("bad-vehicle-read.yaml")).unwrap();
|
|
assert!(errs.iter().any(|e| e.contains("vehicle_read")));
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_yaml_errors() {
|
|
assert!(validate_yaml("::: not yaml :::").is_err());
|
|
}
|
|
}
|