From bb5f11b89a8dfce81375f9686d0859f81e574651 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 12:58:58 +0000 Subject: [PATCH] address review: bound tagged gradients to the stop limit --- crates/keyframe/src/tests.rs | 3 +++ crates/keyframe/src/value.rs | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/keyframe/src/tests.rs b/crates/keyframe/src/tests.rs index 9b9d13e88..58aebee2b 100644 --- a/crates/keyframe/src/tests.rs +++ b/crates/keyframe/src/tests.rs @@ -141,6 +141,9 @@ fn gradient_stop_count_is_bounded() { assert!(g.coerce_json(&serde_json::Value::Array(many[..MAX_GRADIENT_STOPS].to_vec())).is_some()); let ops: Vec = (0..=MAX_GRADIENT_STOPS).map(|i| serde_json::json!([i as f64 / MAX_GRADIENT_STOPS as f64, 1.0])).collect(); assert_eq!(g.coerce_json(&serde_json::json!({"colors": ["#000000", "#ffffff"], "opacities": ops})), None); + // The tagged (typed) form is bounded the same way. + let tagged = Value::Gradient(Gradient { colors: (0..=MAX_GRADIENT_STOPS).map(|i| (i as f64, [0.0; 4])).collect(), opacities: vec![(0.0, 1.0)] }); + assert_eq!(g.coerce_json(&tagged.to_json()), None); // A stored gradient with a huge tail: stops past the limit don't take part in sampling. let mut colors: Vec<(f64, [f32; 4])> = (0..MAX_GRADIENT_STOPS).map(|i| (i as f64 / MAX_GRADIENT_STOPS as f64 * 0.5, [0.0, 0.0, 0.0, 1.0])).collect(); colors.extend((0..100_000).map(|_| (1.0, [1.0, 1.0, 1.0, 1.0]))); diff --git a/crates/keyframe/src/value.rs b/crates/keyframe/src/value.rs index 73540de4b..6e87f8933 100644 --- a/crates/keyframe/src/value.rs +++ b/crates/keyframe/src/value.rs @@ -476,9 +476,10 @@ impl Value { } Value::Layer(_) => Some(Value::Layer(j.as_u64())), Value::Path(_) => serde_json::from_value::(j.clone()).ok().map(Value::Path).or_else(|| serde_json::from_value::(j.clone()).ok()), - Value::Gradient(_) => { - serde_json::from_value::(j.clone()).ok().filter(|v| matches!(v, Value::Gradient(_))).or_else(|| gradient_json(j).map(Value::Gradient)) - } + Value::Gradient(_) => serde_json::from_value::(j.clone()) + .ok() + .filter(|v| matches!(v, Value::Gradient(g) if g.colors.len() <= MAX_GRADIENT_STOPS && g.opacities.len() <= MAX_GRADIENT_STOPS)) + .or_else(|| gradient_json(j).map(Value::Gradient)), } } /// JSON for display/automation (numbers/arrays for numeric kinds).