//! Правила валидации манифеста поверх схемы (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 { 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, 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()); } }