mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
fix(config): deduplicate uniqueItems arrays before schema validation (#292)
* fix(config): deduplicate uniqueItems arrays before schema validation When saving plugin config via the web UI, the form data is merged with the existing stored config. If a user adds an item that already exists (e.g. adding stock symbol "FNMA" when it's already in the list), the merged array contains duplicates. Schemas with `uniqueItems: true` then reject the config, making it impossible to save. Add a recursive dedup pass that runs after normalization/filtering but before validation. It walks the schema tree, finds arrays with the uniqueItems constraint, and removes duplicates while preserving order. Co-Authored-By: 5ymb01 <noreply@github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: recurse into array items and add tests for uniqueItems dedup Address CodeRabbit review: _dedup_unique_arrays now also recurses into array elements whose items schema is an object, so nested uniqueItems constraints inside arrays-of-objects are enforced. Add 11 unit tests covering: - flat arrays with/without duplicates - order preservation - arrays without uniqueItems left untouched - nested objects (feeds.stock_symbols pattern) - arrays of objects with inner uniqueItems arrays - edge cases (empty array, missing keys, integers) - real-world stock-news plugin config shape Co-Authored-By: 5ymb01 <noreply@github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: extract dedup_unique_arrays to shared validators module Move _dedup_unique_arrays from an inline closure in save_plugin_config to src/web_interface/validators.dedup_unique_arrays so tests import and exercise the production code path instead of a duplicated copy. Addresses CodeRabbit review: tests now validate the real function, preventing regressions from diverging copies. Co-Authored-By: 5ymb01 <noreply@github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: 5ymb01 <5ymb01@users.noreply.github.com> Co-authored-by: 5ymb01 <noreply@github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -215,3 +215,49 @@ def sanitize_plugin_config(config: dict) -> dict:
|
||||
|
||||
return sanitized
|
||||
|
||||
|
||||
def dedup_unique_arrays(cfg: dict, schema_node: dict) -> None:
|
||||
"""Recursively deduplicate arrays with uniqueItems constraint.
|
||||
|
||||
Walks the JSON Schema tree alongside the config dict and removes
|
||||
duplicate entries from any array whose schema specifies
|
||||
``uniqueItems: true``, preserving insertion order (first occurrence
|
||||
kept). Also recurses into:
|
||||
|
||||
- Object properties containing nested objects or arrays
|
||||
- Array elements whose ``items`` schema is an object with its own
|
||||
properties (so nested uniqueItems constraints are enforced)
|
||||
|
||||
This is intended to run **after** form-data normalisation but
|
||||
**before** JSON Schema validation, to prevent spurious validation
|
||||
failures when config merging introduces duplicates (e.g. a stock
|
||||
symbol already present in the saved config is submitted again from
|
||||
the web form).
|
||||
|
||||
Args:
|
||||
cfg: The plugin configuration dict to mutate in-place.
|
||||
schema_node: The corresponding JSON Schema node (must contain
|
||||
a ``properties`` mapping at the current level).
|
||||
"""
|
||||
props = schema_node.get('properties', {})
|
||||
for key, prop_schema in props.items():
|
||||
if key not in cfg:
|
||||
continue
|
||||
prop_type = prop_schema.get('type')
|
||||
if prop_type == 'array' and isinstance(cfg[key], list):
|
||||
# Deduplicate this array if uniqueItems is set
|
||||
if prop_schema.get('uniqueItems'):
|
||||
seen: list = []
|
||||
for item in cfg[key]:
|
||||
if item not in seen:
|
||||
seen.append(item)
|
||||
cfg[key] = seen
|
||||
# Recurse into array elements if items schema is an object
|
||||
items_schema = prop_schema.get('items', {})
|
||||
if isinstance(items_schema, dict) and items_schema.get('type') == 'object':
|
||||
for element in cfg[key]:
|
||||
if isinstance(element, dict):
|
||||
dedup_unique_arrays(element, items_schema)
|
||||
elif prop_type == 'object' and isinstance(cfg[key], dict):
|
||||
dedup_unique_arrays(cfg[key], prop_schema)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user