mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
fix(config): normalize nullable arrays and objects instead of refusing them (#642)
* fix(config): normalize nullable arrays and objects instead of refusing them
`element_style._nullable` widens every `customization.modes.<mode>` override
with 'null' so a blank means "inherit the base", which turns a colour declared
"array" into ["array", "null"]. `normalize_config_values` only knew how to
convert null/integer/number/boolean out of a union, so a valid [0, 249, 0]
matched nothing and logged
Could not normalize field customization.modes.upcoming.odds_text.text_color:
value=[0, 249, 0], type=<class 'list'>, schema_type=['array', 'null']
The warning was the harmless half. It `continue`d past the single-type handling
below, where `prop_type == 'array'` coerces items, so a nullable array never had
its items normalized while a plain one did. A form posts numbers as strings, so
["0", "249", "0"] survived to the validator and was rejected with "Expected type
integer, got str" -- setting a per-mode colour in the web UI failed outright.
Every per-mode override of a structural or string type was exposed, across all
eight scoreboard plugins, not only colours.
Re-enter the single-type handling with the matched member rather than bailing,
accept a string that matches, and warn only on a genuine mismatch so the
diagnostic still reaches the validator.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ET8e5weDrb5Ju5QTKLU7zh
* fix(config): convert only integral numbers for integer array items
Review catch on the previous commit. Routing nullable arrays into the shared
item handling made its integer coercion reachable for them, and that coercion
called int(v) on any number: a client sending [2.5, 249, 0] for an RGB array
got 2 stored and a 200 back, so a wrong value was silently corrected into a
valid-looking one rather than refused.
Convert only genuinely integral values, at both the union-item and the plain
'array' item branch so the two cannot drift. A whole float -- 2.0, which is all
JSON can express for an integer -- still converts. This also settles an
inconsistency that predates the change: int('2.5') raises, so the string form
was always preserved and rejected while the numeric form was truncated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ET8e5weDrb5Ju5QTKLU7zh
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2080,10 +2080,29 @@ def _prepare_plugin_config_for_save(plugin_id, plugin_config, schema, schema_mgr
|
||||
normalized[key] = value.strip().lower() in ('true', '1', 'on', 'yes')
|
||||
continue
|
||||
|
||||
# Nothing converted: keep the value for validation to report.
|
||||
logger.warning(f"Could not normalize field {field_path}: value={repr(value)}, type={type(value)}, schema_type={prop_type}")
|
||||
normalized[key] = value
|
||||
continue
|
||||
# The scalar conversions above are the only ones this branch
|
||||
# knows, so a union naming a structural or string type fell
|
||||
# through here even when the value already matched it --
|
||||
# customization.modes.<mode> makes every override nullable
|
||||
# (see element_style._nullable), so every per-mode colour is
|
||||
# ['array', 'null'] and warned on a perfectly valid [r, g, b].
|
||||
# Worse than the noise: `continue` skipped the single-type
|
||||
# handling below, so a nullable array never had its items
|
||||
# normalized and form-posted ["0", "249", "0"] stayed strings
|
||||
# where a plain 'array' field would have become ints. Re-enter
|
||||
# that handling with the matched member instead.
|
||||
if isinstance(value, list) and 'array' in prop_type:
|
||||
prop_type = 'array'
|
||||
elif isinstance(value, dict) and 'object' in prop_type:
|
||||
prop_type = 'object'
|
||||
elif isinstance(value, str) and 'string' in prop_type:
|
||||
normalized[key] = value
|
||||
continue
|
||||
else:
|
||||
# Nothing converted: keep the value for validation to report.
|
||||
logger.warning(f"Could not normalize field {field_path}: value={repr(value)}, type={type(value)}, schema_type={prop_type}")
|
||||
normalized[key] = value
|
||||
continue
|
||||
|
||||
if isinstance(value, dict) and prop_type == 'object' and 'properties' in prop_schema:
|
||||
# Recursively normalize nested objects
|
||||
@@ -2112,7 +2131,16 @@ def _prepare_plugin_config_for_save(plugin_id, plugin_config, schema, schema_mgr
|
||||
except (ValueError, TypeError, OverflowError):
|
||||
pass
|
||||
elif isinstance(v, (int, float)):
|
||||
normalized_array.append(int(v))
|
||||
# Only a genuinely integral value converts.
|
||||
# int(2.5) == 2 would store a silently
|
||||
# corrected number where the client sent a
|
||||
# wrong one; leaving it lets the validator
|
||||
# reject it. A whole float (2.0 out of JSON)
|
||||
# is integral and still converts.
|
||||
if isinstance(v, int) or float(v).is_integer():
|
||||
normalized_array.append(int(v))
|
||||
else:
|
||||
normalized_array.append(v)
|
||||
continue
|
||||
elif 'number' in item_type:
|
||||
if isinstance(v, str):
|
||||
@@ -2138,7 +2166,11 @@ def _prepare_plugin_config_for_save(plugin_id, plugin_config, schema, schema_mgr
|
||||
except (ValueError, TypeError, OverflowError):
|
||||
normalized_array.append(v)
|
||||
elif isinstance(v, (int, float)):
|
||||
normalized_array.append(int(v))
|
||||
# Integral only -- see the union branch above.
|
||||
if isinstance(v, int) or float(v).is_integer():
|
||||
normalized_array.append(int(v))
|
||||
else:
|
||||
normalized_array.append(v)
|
||||
else:
|
||||
normalized_array.append(v)
|
||||
normalized[key] = normalized_array
|
||||
|
||||
Reference in New Issue
Block a user