diff --git a/crates/doc/src/dimension.rs b/crates/doc/src/dimension.rs index 81713baf1..5ca27311e 100644 --- a/crates/doc/src/dimension.rs +++ b/crates/doc/src/dimension.rs @@ -123,7 +123,7 @@ pub enum TextAlignment { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(default, rename_all = "camelCase", deny_unknown_fields)] +#[serde(default, rename_all = "camelCase")] pub struct DimensionOptions { pub units: LengthUnit, pub precision: usize, @@ -182,6 +182,18 @@ impl Default for DimensionOptions { } impl DimensionOptions { + /// Options given to a command or a tool: unknown keys are refused there, while files load + /// tolerantly so a newer version's options don't stop a document opening. + pub fn from_params(v: serde_json::Value) -> Result { + if let (Some(given), Ok(serde_json::Value::Object(known))) = (v.as_object(), serde_json::to_value(Self::default())) + && let Some(k) = given.keys().find(|k| !known.contains_key(*k)) + { + return Err(format!("Unknown dimension option {k}")); + } + let o: Self = serde_json::from_value(v).map_err(|e| e.to_string())?; + o.validate()?; + Ok(o) + } pub fn factor(&self) -> Result { let (a, b) = self.scale.split_once(':').ok_or("Scale must be drawing:real, for example 1:10")?; let parse = |s: &str| s.trim().parse::().ok().filter(|n| n.is_finite() && *n > 0.0 && *n <= 1e9); @@ -236,7 +248,7 @@ impl DimensionOptions { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[serde(rename_all = "camelCase")] pub struct SegmentRef { pub node: NodeId, pub subpath: usize, @@ -263,7 +275,7 @@ impl SegmentRef { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)] +#[serde(tag = "kind", rename_all = "camelCase")] pub enum DimensionSource { Segment { edge: SegmentRef, @@ -312,7 +324,7 @@ impl DimensionSource { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[serde(rename_all = "camelCase")] pub struct PointRef { pub node: NodeId, pub subpath: usize, @@ -326,7 +338,7 @@ impl PointRef { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[serde(rename_all = "camelCase")] pub struct Dimension { pub mode: DimensionMode, pub a: Point, diff --git a/crates/engine/src/cmd/dimension.rs b/crates/engine/src/cmd/dimension.rs index 2e3ffae73..efc3e5fc7 100644 --- a/crates/engine/src/cmd/dimension.rs +++ b/crates/engine/src/cmd/dimension.rs @@ -46,9 +46,7 @@ fn error(s: impl ToString) -> EngineError { fn options(s: &Session, p: &Value) -> Result { let default = s.tool_options_of("dimension"); let value = p.get("options").cloned().unwrap_or_else(|| default.get("style").cloned().unwrap_or(json!({}))); - let o: DimensionOptions = serde_json::from_value(value).map_err(error)?; - o.validate().map_err(error)?; - Ok(o) + DimensionOptions::from_params(value).map_err(error) } fn point(p: &Value, key: &str) -> Result { diff --git a/crates/engine/src/tooling.rs b/crates/engine/src/tooling.rs index fc0ef5586..a2cf5c956 100644 --- a/crates/engine/src/tooling.rs +++ b/crates/engine/src/tooling.rs @@ -455,8 +455,7 @@ impl Session { } if tool.unwrap_or(self.tool_id()) == "dimension" { if let Some(style) = values.get("style") { - let options: vectorcraft_doc::dimension::DimensionOptions = serde_json::from_value(style.clone()).map_err(|e| e.to_string())?; - options.validate()?; + vectorcraft_doc::dimension::DimensionOptions::from_params(style.clone())?; } if let Some(mode) = values.get("mode") && !mode.as_str().is_some_and(|m| ["linear", "angular", "radial", "radius", "diameter"].contains(&m)) diff --git a/crates/engine/tests/dimensions.rs b/crates/engine/tests/dimensions.rs index 9826bd2af..f65a292c7 100644 --- a/crates/engine/tests/dimensions.rs +++ b/crates/engine/tests/dimensions.rs @@ -357,3 +357,35 @@ fn dimension_cannot_measure_its_own_generated_art() { let child = s.doc().unwrap().doc.node(NodeId(id)).unwrap().children().unwrap()[0].id; assert!(s.execute("dimension.create", &json!({"source":{"kind":"segment","edge":{"node":child,"subpath":0,"segment":0}}})).is_err()); } + +#[test] +fn unknown_dimension_options_are_refused_by_commands_but_a_newer_file_still_opens() { + let mut s = session(); + let line = edge(&mut s); + let source = json!({"kind":"segment","edge":{"node":line,"subpath":0,"segment":0}}); + let bad = s.execute("dimension.create", &json!({"source":source,"options":{"units":"in","color":"red"}})); + assert!(bad.unwrap_err().to_string().contains("Unknown dimension option color")); + assert!(s.set_tool_option_cmd(&json!({"tool":"dimension","values":{"style":{"precison":2}}})).is_err()); + let id = dim(&mut s, line); + let saved = vectorcraft_format::save(&s.doc().unwrap().doc, false); + let mut v: Value = serde_json::from_slice(&saved).unwrap(); + // A later version's dimension carries fields this one doesn't know. + fn add_unknown(v: &mut Value) { + match v { + Value::Object(m) => { + if let Some(Value::Object(d)) = m.get_mut("dimension") { + d.insert("later".into(), json!(1)); + if let Some(Value::Object(o)) = d.get_mut("options") { + o.insert("later".into(), json!(true)); + } + } + m.values_mut().for_each(add_unknown); + } + Value::Array(a) => a.iter_mut().for_each(add_unknown), + _ => {} + } + } + add_unknown(&mut v); + let doc = vectorcraft_format::load(&serde_json::to_vec(&v).unwrap()).unwrap(); + assert!(doc.node(NodeId(id)).unwrap().extras().dimension.is_some()); +} diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 570f3778b..5a223e467 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4851,6 +4851,7 @@ mark-style Japanese Japonské @msg Scale ratio must be between 1:1000000 and 1000000:1 Poměr měřítka musí být mezi 1:1000000 a 1000000:1 @msg Second measured segment no longer exists Druhý měřený segment už neexistuje @msg Unknown dimension mode Neznámý režim kóty +@msg Unknown dimension option {k} Neznámá volba kóty {k} @msg Unknown dimension style Neznámý styl kóty @msg Unknown unit Neznámá jednotka # Revolve cap description diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index def112ce7..597e7056f 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4380,6 +4380,7 @@ theme Appearance Erscheinungsbild @msg Scale ratio must be between 1:1000000 and 1000000:1 Das Maßstabsverhältnis muss zwischen 1:1000000 und 1000000:1 liegen @msg Second measured segment no longer exists Das zweite gemessene Segment existiert nicht mehr @msg Unknown dimension mode Unbekannter Bemaßungsmodus +@msg Unknown dimension option {k} Unbekannte Bemaßungsoption {k} @msg Unknown dimension style Unbekannter Bemaßungsstil @msg Unknown unit Unbekannte Einheit # Revolve cap description diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index dd32182cf..8538bbf99 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4792,6 +4792,7 @@ mark-style Japanese Japonesas @msg Scale ratio must be between 1:1000000 and 1000000:1 La relación de escala debe estar entre 1:1000000 y 1000000:1 @msg Second measured segment no longer exists El segundo segmento medido ya no existe @msg Unknown dimension mode Modo de cota desconocido +@msg Unknown dimension option {k} Opción de cota desconocida {k} @msg Unknown dimension style Estilo de cota desconocido @msg Unknown unit Unidad desconocida # Revolve cap description diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 7917f1950..2eb9d59ab 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4850,6 +4850,7 @@ theme Appearance Apparence @msg Scale ratio must be between 1:1000000 and 1000000:1 Le rapport d’échelle doit être compris entre 1:1000000 et 1000000:1 @msg Second measured segment no longer exists Le second segment mesuré n’existe plus @msg Unknown dimension mode Mode de cote inconnu +@msg Unknown dimension option {k} Option de cote inconnue {k} @msg Unknown dimension style Style de cote inconnu @msg Unknown unit Unité inconnue # Revolve cap description diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 184fa5c55..26655c258 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4817,6 +4817,7 @@ imageTrace Outline Körvonal @msg Scale ratio must be between 1:1000000 and 1000000:1 A lépték aránya 1:1000000 és 1000000:1 között lehet @msg Second measured segment no longer exists A második mért szakasz már nem létezik @msg Unknown dimension mode Ismeretlen méretezési mód +@msg Unknown dimension option {k} Ismeretlen méretezési beállítás: {k} @msg Unknown dimension style Ismeretlen méretezési stílus @msg Unknown unit Ismeretlen mértékegység # Revolve cap description diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 9cd975ed5..674ebb6ae 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4425,6 +4425,7 @@ lighting Light Luce @msg Scale ratio must be between 1:1000000 and 1000000:1 Il rapporto di scala deve essere compreso tra 1:1000000 e 1000000:1 @msg Second measured segment no longer exists Il secondo segmento misurato non esiste più @msg Unknown dimension mode Modalità di quota sconosciuta +@msg Unknown dimension option {k} Opzione di quota sconosciuta {k} @msg Unknown dimension style Stile di quota sconosciuto @msg Unknown unit Unità sconosciuta # Revolve cap description diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 0b6d27759..aea1c8059 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4420,6 +4420,7 @@ lighting Light ライト @msg Scale ratio must be between 1:1000000 and 1000000:1 縮尺の比率は 1:1000000 から 1000000:1 の間にしてください @msg Second measured segment no longer exists 2 番目の測定セグメントはもう存在しません @msg Unknown dimension mode 寸法のモードが不明です +@msg Unknown dimension option {k} 不明な寸法オプション {k} @msg Unknown dimension style 寸法のスタイルが不明です @msg Unknown unit 単位が不明です # Revolve cap description diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 229be3ef0..0365abac3 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4389,6 +4389,7 @@ lighting Light 광원 @msg Scale ratio must be between 1:1000000 and 1000000:1 축척 비율은 1:1000000에서 1000000:1 사이여야 합니다 @msg Second measured segment no longer exists 두 번째 측정 세그먼트가 더 이상 없습니다 @msg Unknown dimension mode 알 수 없는 치수 모드 +@msg Unknown dimension option {k} 알 수 없는 치수 옵션 {k} @msg Unknown dimension style 알 수 없는 치수 스타일 @msg Unknown unit 알 수 없는 단위 # Revolve cap description diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index ce9e2b268..98e0c3a15 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4692,6 +4692,7 @@ mark-style Japanese Japońskie @msg Scale ratio must be between 1:1000000 and 1000000:1 Współczynnik skali musi mieścić się między 1:1000000 a 1000000:1 @msg Second measured segment no longer exists Drugi mierzony odcinek już nie istnieje @msg Unknown dimension mode Nieznany tryb wymiaru +@msg Unknown dimension option {k} Nieznana opcja wymiaru {k} @msg Unknown dimension style Nieznany styl wymiaru @msg Unknown unit Nieznana jednostka # Revolve cap description diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 7a75c4bce..0eb092e65 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4421,6 +4421,7 @@ lighting Light Свет @msg Scale ratio must be between 1:1000000 and 1000000:1 Соотношение масштаба должно быть от 1:1000000 до 1000000:1 @msg Second measured segment no longer exists Второй измеряемый сегмент больше не существует @msg Unknown dimension mode Неизвестный режим размера +@msg Unknown dimension option {k} Неизвестный параметр размера {k} @msg Unknown dimension style Неизвестный стиль размера @msg Unknown unit Неизвестная единица # Revolve cap description diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 47ea60f9c..5d5902e11 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4405,6 +4405,7 @@ lighting Light Світло @msg Scale ratio must be between 1:1000000 and 1000000:1 Співвідношення масштабу має бути від 1:1000000 до 1000000:1 @msg Second measured segment no longer exists Другий виміряний сегмент більше не існує @msg Unknown dimension mode Невідомий режим розміру +@msg Unknown dimension option {k} Невідомий параметр розміру {k} @msg Unknown dimension style Невідомий стиль розміру @msg Unknown unit Невідома одиниця # Revolve cap description diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index dbf15afd9..9d0dd9292 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4404,6 +4404,7 @@ theme Appearance Giao diện @msg Scale ratio must be between 1:1000000 and 1000000:1 Tỷ lệ phải nằm trong khoảng 1:1000000 đến 1000000:1 @msg Second measured segment no longer exists Đoạn được đo thứ hai không còn tồn tại @msg Unknown dimension mode Chế độ kích thước không xác định +@msg Unknown dimension option {k} Tùy chọn kích thước không xác định {k} @msg Unknown dimension style Kiểu kích thước không xác định @msg Unknown unit Đơn vị không xác định # Revolve cap description