diff --git a/crates/apischema/src/lib.rs b/crates/apischema/src/lib.rs index 5db24230e1..cff749e84f 100644 --- a/crates/apischema/src/lib.rs +++ b/crates/apischema/src/lib.rs @@ -250,7 +250,8 @@ impl ParamSchema { /// What is wrong with the schema itself, or `None`: a parameter name that is empty, repeated /// or holds `.`, `[` or `]`; a required parameter with a default; a default that isn't JSON or /// doesn't pass its parameter's type; an enum with no names or a repeated name; a range with - /// an end that isn't finite or that no value can be in; nesting past [`MAX_DEPTH`]. + /// an end that isn't finite or that no value can be in; nesting past [`MAX_DEPTH`], or a + /// [`Type::Ids`] or [`Type::Point`], whose values are all lists, at the nesting limit. pub fn definition_problem(&self) -> Option { object_problem("", self.params, 0) } @@ -319,7 +320,9 @@ pub enum Problem { Missing, /// The value is not of the parameter's type: `expected` says what it must be. WrongType { expected: String, got: String }, - /// The schema nests deeper than [`MAX_DEPTH`] here ([`ParamSchema::definition_problem`]). + /// The value nests deeper than [`MAX_DEPTH`] here: a list given for a [`Type::Color`] at the + /// nesting limit, or any value under a schema that nests too deeply itself + /// ([`ParamSchema::definition_problem`]). TooDeep, } @@ -779,6 +782,8 @@ fn type_problem(at: &str, ty: &Type, depth: usize) -> Option { Type::Object(params) => object_problem(at, params, depth + 1), Type::Array(_) if depth >= MAX_DEPTH => Some(format!("`{at}` nests deeper than {MAX_DEPTH} levels")), Type::Array(item) => type_problem(&format!("{at}[]"), item, depth + 1), + // Every value of these is a list, and a list at the nesting limit is refused. + Type::Ids | Type::Point if depth >= MAX_DEPTH => Some(format!("`{at}` nests deeper than {MAX_DEPTH} levels")), _ => None, } } diff --git a/crates/apischema/src/tests.rs b/crates/apischema/src/tests.rs index 30b939879a..4b8d51d8b0 100644 --- a/crates/apischema/src/tests.rs +++ b/crates/apischema/src/tests.rs @@ -156,6 +156,36 @@ fn id_lists_points_and_color_lists_count_toward_the_nesting_limit() { assert!(check(MAX_DEPTH, &Type::Color, &json!("#ff0000")).is_ok()); } +/// `ty` inside `levels` lists. +fn in_lists(levels: usize, ty: Type) -> Type { + (0..levels).fold(ty, |inner, _| Type::Array(Box::leak(Box::new(inner)))) +} + +/// `v` inside `levels` lists. +fn wrapped(levels: usize, v: Value) -> Value { + (0..levels).fold(v, |inner, _| Value::Array(vec![inner])) +} + +/// The schema check and the value check agree on the fixed-shape lists at the nesting limit: an id +/// list or a point there could take no value, so the schema is wrong in itself, and a color there +/// takes a string, while a color list is refused as nested too deeply, at its path. +#[test] +fn fixed_shape_lists_at_the_nesting_limit_follow_the_schema_check() { + for ty in [Type::Ids, Type::Point, Type::Color] { + assert_eq!(one(in_lists(MAX_DEPTH - 1, ty)).definition_problem(), None, "{ty:?}"); + } + for ty in [Type::Ids, Type::Point] { + let problem = one(in_lists(MAX_DEPTH, ty)).definition_problem(); + assert!(problem.as_deref().is_some_and(|p| p.contains("nests deeper")), "{ty:?}: {problem:?}"); + } + let color = one(in_lists(MAX_DEPTH, Type::Color)); + assert_eq!(color.definition_problem(), None); + assert_eq!(color.validate(&json!({"x": wrapped(MAX_DEPTH, json!("#ff0000"))})).unwrap(), None); + let e = color.validate(&json!({"x": wrapped(MAX_DEPTH, json!([1, 0, 0]))})).unwrap_err(); + assert_eq!(e.problem, Problem::TooDeep); + assert_eq!(e.path, format!("x{}", "[0]".repeat(MAX_DEPTH))); +} + #[test] fn a_saved_retired_key_past_the_nesting_limit_is_refused() { let params = holding("older", nested_lists(500_000)); diff --git a/crates/engine/src/schemas.rs b/crates/engine/src/schemas.rs index fb08bb649c..08bc0f719d 100644 --- a/crates/engine/src/schemas.rs +++ b/crates/engine/src/schemas.rs @@ -69,7 +69,7 @@ fn param_error(id: &str, e: &ParamError) -> EngineError { Problem::Unknown { keys } => bad(id, format!("unknown parameter `{path}`; it takes: {keys}", keys = keys.join(", "))), Problem::Missing => bad(id, format!("missing `{path}`")), Problem::WrongType { expected, got } => bad(id, format!("`{path}` must be {expected}, not {got}")), - Problem::TooDeep => EngineError::Internal { cmd: id.into(), msg: e.to_string() }, + Problem::TooDeep => bad(id, format!("`{path}` is nested too deeply")), } } @@ -246,6 +246,30 @@ mod tests { assert!(s.doc().unwrap().history.undo.is_empty()); } + /// A value nested past the limit of a sound schema is a parameter error that names it, not an + /// internal error: a color list at the nesting limit, where a color string still passes. + #[test] + fn a_value_nested_too_deeply_is_a_parameter_error() { + let levels = vectorcraft_apischema::MAX_DEPTH; + let ty = (0..levels).fold(Type::Color, |inner, _| Type::Array(Box::leak(Box::new(inner)))); + let params: &'static [Param] = Box::leak(Box::new([Param::new("fills", ty)])); + let schema = ParamSchema::new(params, ""); + assert_eq!(schema.definition_problem(), None); + let wrapped = |v: Value| (0..levels).fold(v, |inner, _| Value::Array(vec![inner])); + assert!(schema.validate(&json!({"fills": wrapped(json!("#ff0000"))})).is_ok()); + let e = schema.validate(&json!({"fills": wrapped(json!([1, 0, 0]))})).unwrap_err(); + let path = format!("fills{}", "[0]".repeat(levels)); + match param_error("view.synthetic", &e) { + EngineError::BadParams { cmd, msg } => { + assert_eq!(cmd, "view.synthetic"); + assert_eq!(msg, format!("`{path}` is nested too deeply")); + } + other => panic!("not a parameter error: {other}"), + } + let shown = param_error("view.synthetic", &e).to_string(); + assert!(shown.starts_with("invalid parameters for `view.synthetic`") && !shown.contains("report this bug"), "{shown}"); + } + /// Each command's JSON schema follows the 2020-12 rules, and a validator of those rules, which /// shares no code with the writer, gives the same answer as the schema check. None of these /// schemas has a lenient parameter. diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 95b2615ec3..a10fe06229 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -6103,6 +6103,7 @@ digits Malayalam Malaiàlam @msg unknown parameter `{path}`; it takes: {keys} paràmetre desconegut `{path}`; admet: {keys} @msg missing `{path}` falta `{path}` @msg `{path}` must be {expected}, not {got} `{path}` ha de ser {expected}, no {got} +@msg `{path}` is nested too deeply `{path}` està imbricat massa profundament @msg unknown preference `{key}` preferència desconeguda `{key}` @msg Preferences imported. S’han importat les preferències. @msg not a preferences file: {e} no és un fitxer de preferències: {e} diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index f27fad5417..2716bb37e3 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -5794,6 +5794,7 @@ mark-style Japanese Japonské @msg unknown parameter `{path}`; it takes: {keys} neznámý parametr `{path}`; přijímá: {keys} @msg missing `{path}` chybí `{path}` @msg `{path}` must be {expected}, not {got} `{path}` musí být {expected}, nikoli {got} +@msg `{path}` is nested too deeply `{path}` je vnořený příliš hluboko Load EPS into Clipboard Načíst EPS do schránky @msg an image of {w} × {h} pixels is too large to draw obraz o velikosti {w} × {h} obrazových bodů je příliš velký na vykreslení @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: není obraz PNG, JPEG, WebP, TIFF ani BMP diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index d9434741c0..bb33689a8a 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5369,6 +5369,7 @@ lighting Light Licht @msg unknown parameter `{path}`; it takes: {keys} unbekannter Parameter `{path}`; akzeptiert werden: {keys} @msg missing `{path}` `{path}` fehlt @msg `{path}` must be {expected}, not {got} `{path}` muss {expected} sein, nicht {got} +@msg `{path}` is nested too deeply `{path}` ist zu tief verschachtelt Load EPS into Clipboard EPS in die Zwischenablage laden @msg an image of {w} × {h} pixels is too large to draw ein Bild mit {w} × {h} Pixeln ist zu groß, um gezeichnet zu werden @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: kein PNG-, JPEG-, WebP-, TIFF- oder BMP-Bild diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 1ceb93c669..104183afc8 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5153,6 +5153,7 @@ digits Malayalam Μαλαγιάλαμ @msg unknown parameter `{path}`; it takes: {keys} άγνωστη παράμετρος `{path}`· δέχεται: {keys} @msg missing `{path}` λείπει το `{path}` @msg `{path}` must be {expected}, not {got} Το `{path}` πρέπει να είναι {expected}, όχι {got} +@msg `{path}` is nested too deeply Το `{path}` έχει υπερβολικά βαθιά ένθεση 1 Layer 1 επίπεδο 1 bit 1 bit 16 Colors 16 χρώματα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 7708e930bb..865eddb2a4 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5735,6 +5735,7 @@ mark-style Japanese Japonesas @msg unknown parameter `{path}`; it takes: {keys} parámetro desconocido `{path}`; acepta: {keys} @msg missing `{path}` falta `{path}` @msg `{path}` must be {expected}, not {got} `{path}` debe ser {expected}, no {got} +@msg `{path}` is nested too deeply `{path}` está anidado a demasiada profundidad Load EPS into Clipboard Cargar EPS en el portapapeles @msg an image of {w} × {h} pixels is too large to draw una imagen de {w} × {h} píxeles es demasiado grande para dibujarse @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: no es una imagen PNG, JPEG, WebP, TIFF ni BMP diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 765400c5a6..b43864d60e 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -5676,6 +5676,7 @@ mark-style Japanese Japanilainen @msg unknown parameter `{path}`; it takes: {keys} tuntematon parametri `{path}`; parametrit: {keys} @msg missing `{path}` `{path}` puuttuu @msg `{path}` must be {expected}, not {got} `{path}` on oltava {expected}, ei {got} +@msg `{path}` is nested too deeply `{path}` on liian syvällä sisäkkäin Load EPS into Clipboard Lataa EPS leikepöydälle @msg an image of {w} × {h} pixels is too large to draw kuva, jonka koko on {w} × {h} pikseliä, on liian suuri piirrettäväksi @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: ei ole PNG-, JPEG-, WebP-, TIFF- tai BMP-kuva diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index f7eb341e39..7902d37ceb 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -5364,6 +5364,7 @@ lighting Light Lumière @msg unknown parameter `{path}`; it takes: {keys} paramètre inconnu `{path}` ; il accepte : {keys} @msg missing `{path}` `{path}` manquant @msg `{path}` must be {expected}, not {got} `{path}` doit être {expected}, pas {got} +@msg `{path}` is nested too deeply `{path}` est imbriqué trop profondément 1 Layer 1 calque 1 bit 1 bit 16 Colors 16 couleurs diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 978b6811fd..546862d379 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -8144,6 +8144,7 @@ antiAlias Sharp शार्प @msg unknown parameter `{path}`; it takes: {keys} अज्ञात पैरामीटर `{path}`; यह लेता है: {keys} @msg missing `{path}` लापता `{path}` @msg `{path}` must be {expected}, not {got} `{path}` {expected} होना चाहिए, {got} नहीं +@msg `{path}` is nested too deeply `{path}` बहुत गहराई तक नेस्ट किया गया है @msg unknown PDF preset `{name}` (presets: {_1}) अज्ञात PDF प्रीसेट `{name}` (presets: {_1}) @msg unknown pieLegend `{v}` (none, standard or wedges) अज्ञात pieLegend `{v}` (one, मानक या wedges) @msg unknown piePosition `{v}` (even, ratio or stacked) अज्ञात piePosition `{v}` (यहां तक कि अनुपात या स्टैक्ड) diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 7bfe5b5682..4d52dc2bbb 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -5777,6 +5777,7 @@ imageTrace Outline Körvonal @msg unknown parameter `{path}`; it takes: {keys} ismeretlen paraméter: `{path}`; ezeket fogadja el: {keys} @msg missing `{path}` hiányzik: `{path}` @msg `{path}` must be {expected}, not {got} `{path}`: {expected} kell, nem ez: {got} +@msg `{path}` is nested too deeply `{path}` túl mélyen van egymásba ágyazva Load EPS into Clipboard EPS betöltése a vágólapra @msg an image of {w} × {h} pixels is too large to draw egy {w} × {h} pixeles kép túl nagy a kirajzoláshoz @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: nem PNG, JPEG, WebP, TIFF vagy BMP kép diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index d9aa6ea3c8..25a7eba5c0 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5411,6 +5411,7 @@ lighting Light Luce @msg unknown parameter `{path}`; it takes: {keys} parametro sconosciuto `{path}`; accetta: {keys} @msg missing `{path}` manca `{path}` @msg `{path}` must be {expected}, not {got} `{path}` deve essere {expected}, non {got} +@msg `{path}` is nested too deeply `{path}` è annidato troppo in profondità Load EPS into Clipboard Carica EPS negli appunti @msg an image of {w} × {h} pixels is too large to draw un'immagine di {w} × {h} pixel è troppo grande per essere disegnata @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: non è un'immagine PNG, JPEG, WebP, TIFF o BMP diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 5e337a9bc4..6ead68811f 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5453,6 +5453,7 @@ lighting Light ライト @msg unknown parameter `{path}`; it takes: {keys} `{path}` は不明なパラメーターです。使えるパラメーター: {keys} @msg missing `{path}` `{path}` が指定されていません @msg `{path}` must be {expected}, not {got} `{path}` は {expected} にしてください ({got} は使えません) +@msg `{path}` is nested too deeply `{path}` の入れ子が深すぎます Load EPS into Clipboard EPS をクリップボードに読み込む @msg an image of {w} × {h} pixels is too large to draw {w} × {h} ピクセルの画像は大きすぎて描画できません @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: PNG、JPEG、WebP、TIFF、BMP のいずれの画像でもありません diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index e5bf998d5c..37355334b1 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5161,6 +5161,7 @@ font-technology Variable ცვლადი @msg unknown parameter `{path}`; it takes: {keys} უცნობი პარამეტრი `{path}`; იღებს: {keys} @msg missing `{path}` აკლია `{path}` @msg `{path}` must be {expected}, not {got} `{path}` უნდა იყოს {expected} და არა {got} +@msg `{path}` is nested too deeply `{path}` ზედმეტად ღრმადაა ჩადგმული file-filter All readable files ყველა წაკითხვადი ფაილი file-filter All placeable files ყველა განთავსებადი ფაილი file-filter Swatch libraries ფერის ნიმუშების ბიბლიოთეკები diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index b10632963f..5cda96323c 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5372,6 +5372,7 @@ lighting Light 광원 @msg unknown parameter `{path}`; it takes: {keys} 알 수 없는 매개변수 `{path}`: 받는 매개변수: {keys} @msg missing `{path}` `{path}`이(가) 없습니다 @msg `{path}` must be {expected}, not {got} `{path}`은(는) {expected}이어야 합니다({got} 사용 불가) +@msg `{path}` is nested too deeply `{path}`이(가) 너무 깊게 중첩되어 있습니다 Load EPS into Clipboard EPS를 클립보드로 불러오기 @msg an image of {w} × {h} pixels is too large to draw {w} × {h}픽셀 이미지는 너무 커서 그릴 수 없습니다 @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: PNG, JPEG, WebP, TIFF 또는 BMP 이미지가 아닙니다 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 2eca92f56b..c0c8940764 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5633,6 +5633,7 @@ mark-style Japanese Japońskie @msg unknown parameter `{path}`; it takes: {keys} nieznany parametr `{path}`; przyjmuje: {keys} @msg missing `{path}` brak `{path}` @msg `{path}` must be {expected}, not {got} `{path}` musi być {expected}, a nie {got} +@msg `{path}` is nested too deeply `{path}` jest zagnieżdżone zbyt głęboko Load EPS into Clipboard Wczytaj EPS do schowka @msg an image of {w} × {h} pixels is too large to draw obraz o wymiarach {w} × {h} pikseli jest zbyt duży, aby go narysować @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: to nie jest obraz PNG, JPEG, WebP, TIFF ani BMP diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 24a3db3fe0..004b195c27 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -4667,6 +4667,7 @@ axis Both Ambos @msg unknown parameter `{path}`; it takes: {keys} parâmetro desconhecido `{path}`; aceita: {keys} @msg missing `{path}` falta `{path}` @msg `{path}` must be {expected}, not {got} `{path}` deve ser {expected}, não {got} +@msg `{path}` is nested too deeply `{path}` está aninhado com profundidade excessiva Load EPS into Clipboard Carregar EPS na Área de Transferência Drawer menu Menu da gaveta Toolbar: Show Controls Barra de ferramentas: Mostrar controles diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 0c0c0bb1f0..913029426d 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -5397,6 +5397,7 @@ lighting Light Свет @msg unknown parameter `{path}`; it takes: {keys} неизвестный параметр `{path}`; принимает: {keys} @msg missing `{path}` отсутствует `{path}` @msg `{path}` must be {expected}, not {got} `{path}` должно быть {expected}, а не {got} +@msg `{path}` is nested too deeply `{path}` вложено слишком глубоко Load EPS into Clipboard Загрузить EPS в буфер обмена @msg an image of {w} × {h} pixels is too large to draw изображение размером {w} × {h} пикселей слишком велико для отрисовки @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: это не изображение PNG, JPEG, WebP, TIFF или BMP diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 911f406a57..6652cf531e 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -5265,6 +5265,7 @@ lighting Light Ljus @msg unknown parameter `{path}`; it takes: {keys} okänd parameter `{path}`; den tar: {keys} @msg missing `{path}` `{path}` saknas @msg `{path}` must be {expected}, not {got} `{path}` måste vara {expected}, inte {got} +@msg `{path}` is nested too deeply `{path}` är för djupt kapslat 1 Layer 1 lager 1 bit 1 bit 16 Colors 16 färger diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index b85b65184b..77add76ff9 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5060,6 +5060,7 @@ lighting Light Işık @msg unknown parameter `{path}`; it takes: {keys} bilinmeyen parametre `{path}`; şunları alır: {keys} @msg missing `{path}` `{path}` eksik @msg `{path}` must be {expected}, not {got} `{path}` {expected} olmalıdır, {got} değil +@msg `{path}` is nested too deeply `{path}` çok derin iç içe geçmiş 1 Layer 1 Katman 1 bit 1 bit 16 Colors 16 Renk diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 8e05bb2fc9..f8de1bea4e 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5391,6 +5391,7 @@ lighting Light Світло @msg unknown parameter `{path}`; it takes: {keys} невідомий параметр `{path}`; приймає: {keys} @msg missing `{path}` відсутнє `{path}` @msg `{path}` must be {expected}, not {got} `{path}` має бути {expected}, а не {got} +@msg `{path}` is nested too deeply `{path}` вкладено надто глибоко Load EPS into Clipboard Завантажити EPS у буфер обміну @msg an image of {w} × {h} pixels is too large to draw зображення розміром {w} × {h} пікселів завелике для відображення @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}: це не зображення PNG, JPEG, WebP, TIFF чи BMP diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index f10ab118e4..4578022349 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5305,6 +5305,7 @@ theme Appearance Giao diện @msg unknown parameter `{path}`; it takes: {keys} tham số không xác định `{path}`; nhận: {keys} @msg missing `{path}` thiếu `{path}` @msg `{path}` must be {expected}, not {got} `{path}` phải là {expected}, không phải {got} +@msg `{path}` is nested too deeply `{path}` lồng nhau quá sâu @msg unknown preference `{key}` tùy chọn không xác định `{key}` @msg Preferences imported. Đã nhập tùy chọn. @msg not a preferences file: {e} không phải tệp tùy chọn: {e} diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index b699270a8b..1a6a153032 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5667,6 +5667,7 @@ lighting Light 光源 @msg unknown parameter `{path}`; it takes: {keys} 未知参数 `{path}`;它接受:{keys} @msg missing `{path}` 缺少 `{path}` @msg `{path}` must be {expected}, not {got} `{path}` 必须是 {expected},而不是 {got} +@msg `{path}` is nested too deeply `{path}` 嵌套过深 Load EPS into Clipboard 将 EPS 载入剪贴板 @msg an image of {w} × {h} pixels is too large to draw {w} × {h} 像素的图像过大,无法绘制 @msg the JPEG XL file is cut short JPEG XL 文件不完整 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 83e4eb4a60..cf5b76f7ec 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5499,6 +5499,7 @@ lighting Light 光源 @msg unknown parameter `{path}`; it takes: {keys} 未知的參數 `{path}`;它接受:{keys} @msg missing `{path}` 未提供 `{path}` @msg `{path}` must be {expected}, not {got} `{path}` 必須是 {expected},而不是 {got} +@msg `{path}` is nested too deeply `{path}` 巢狀層級過深 Load EPS into Clipboard 將 EPS 載入剪貼簿 @msg an image of {w} × {h} pixels is too large to draw {w} × {h} 像素的影像過大,無法繪製 @msg {path}: not a PNG, JPEG, WebP, TIFF or BMP image {path}:不是 PNG、JPEG、WebP、TIFF 或 BMP 影像 diff --git a/docs/mcp.md b/docs/mcp.md index 0dd9f958b3..5de1ed4539 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1784,6 +1784,11 @@ path to a value inside a list or an object, and the document and its undo histor → invalid parameters for `view.saved.edit`: `edits[0].delete` must be true or false, not 1 ``` +A schema nests at most 16 lists and objects. A value that would go past that depth is refused with the same kind of +parameter error, ``invalid parameters for ``: `` is nested too deeply``, where `` is the path to the +value. A schema that is wrong in itself, such as one that nests past 16 levels or puts a list of ids or a point +inside 16 lists, is an internal error in the command, and the command does not run. + An omitted value with a `default` takes it (`rotation` 0 in `view.saved.new`); the journal keeps the params as they were given. Saved steps played from a journal or an action keep a key the schema does not name, unless its value nests more than 16 levels deep, and the values of the parameters it names are checked as in a new call. The schemas of built-in commands belong to API level 3 diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 8938bfce54..390efb691b 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -719,7 +719,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Linux file associations:** the desktop entry lists the MIME type of every format File › Open reads but PSD, PSB, `.ps` PostScript, Targa, PCX, Pixar, JPEG 2000, HEIC/HEIF, JPEG XL and HP-GL, `.ai`, `.ait` and EPS included, and the MIME package declares the types shared-mime-info lacks (VectorCraft documents and templates, `.ait`, Affinity), so file managers list VectorCraft under Open With for them, as Finder does on macOS. `the_linux_desktop_entry_opens_every_readable_format` in `apps/vectorcraft/src/tests.rs` checks both files against `OPEN_EXTS`. On Debian 13, Ubuntu 24.04 and Fedora 42, `desktop-file-validate` accepts the desktop entry, `update-mime-database` accepts the MIME package, and `gio` registers the app for every one of these types. - **Architecture:** 19+ crates with enforced layering (`cargo xtask layers`). Every action is a command (~400 engine + ~50 UI). Undo is unlimited via structural sharing. `command.batch` runs several commands as one transaction. -- **Parameter schemas:** a command can carry a machine-readable parameter schema (the new `vectorcraft-apischema` crate): each parameter's type, range, unit, whether it is required, its default, whether null counts as left out, and the API levels it was added and deprecated at. `engine.commands` lists the schema in the shape of a JSON Schema 2020-12 object schema, a test keeps the command's `params` text equal to the text the schema writes, and a new call has the params checked against it before the command runs (batch steps before the first step), with an error naming the parameter and no change to the document; saved steps keep a key the schema does not name, and their values are checked. The View commands `view.capsLock`, `view.saved.new`, `view.saved.edit`, `view.saved.list`, `view.transparencyGrid` and `view.snapToGlyph.options` have schemas. The schemas belong to API level 1 (`vectorcraft_engine::schemas::API_VERSION`), and a test fails when a schema changes without a new level. Tests: `crates/apischema/src/tests.rs` (with `conformance.rs`, a separate check of the JSON form against the JSON Schema 2020-12 rules), `schemas::tests` and `cmd::views::tests::each_view_command_refuses_a_bad_value_naming_the_parameter` in `vectorcraft-engine`. +- **Parameter schemas:** a command can carry a machine-readable parameter schema (the new `vectorcraft-apischema` crate): each parameter's type, range, unit, whether it is required, its default, whether null counts as left out, and the API levels it was added and deprecated at. `engine.commands` lists the schema in the shape of a JSON Schema 2020-12 object schema, a test keeps the command's `params` text equal to the text the schema writes, and a new call has the params checked against it before the command runs (batch steps before the first step), with an error naming the parameter and no change to the document (a value nested past the schema's 16 levels is such a parameter error, and only a schema that is wrong in itself is an internal error); saved steps keep a key the schema does not name, and their values are checked. The View commands `view.capsLock`, `view.saved.new`, `view.saved.edit`, `view.saved.list`, `view.transparencyGrid` and `view.snapToGlyph.options` have schemas. The schemas belong to API level 1 (`vectorcraft_engine::schemas::API_VERSION`), and a test fails when a schema changes without a new level. Tests: `crates/apischema/src/tests.rs` (with `conformance.rs`, a separate check of the JSON form against the JSON Schema 2020-12 rules), `schemas::tests` and `cmd::views::tests::each_view_command_refuses_a_bad_value_naming_the_parameter` in `vectorcraft-engine`. - **Input schemas in the command listings:** `engine.commands` and the MCP tools `command_list` and `list_commands` give each command with a parameter schema an `inputSchema` next to `schema`: the same schema with the root `type` `"object"`, the form an MCP tool's input schema takes ([MCP schema reference](https://modelcontextprotocol.io/specification/2026-07-28/schema)). A command without a schema lists neither, and `brief` leaves out both schemas with the `params` text. Tests: `schemas::tests::engine_commands_list_an_input_schema_with_an_object_root` and `schemas::tests::an_input_schema_keeps_deprecation` in `vectorcraft-engine`, and `tests::conventions::command_listings_carry_input_schemas` in `vectorcraft-mcp`, which check every listed input schema against the JSON Schema 2020-12 rules. - **Command API reference:** `engine.apiReference` returns a Markdown reference written from the parameter schemas when it is asked for, and MCP serves the same text as the resource `vectorcraft://commands/reference` (`text/markdown; charset=utf-8`). Each command that has a schema gets a section in registry order with its label, menu path, API levels, what it writes, summary and return, and a table of its parameters (type, range with its ends marked included or not, unit, default, required, levels and description; members of objects as dotted rows). Commands without a schema, plug-in commands included, are left out. Schema text is escaped so it stays plain text in its table cell, the same build always returns the same bytes, and a reference past 1 MiB is an error rather than a cut-short text. The command's own schema raised the API level to 2. This is a VectorCraft documentation feature; no score changes. Tests: `cmd::apiref::tests` and `schemas::tests` in `vectorcraft-engine`, `resources::tests` in `vectorcraft-mcp`. - **Automation:**