Skip to content

Added Configuration Schema and Validation - #529

Open
fashxp wants to merge 97 commits into
2026.xfrom
schema-validation
Open

Added Configuration Schema and Validation#529
fashxp wants to merge 97 commits into
2026.xfrom
schema-validation

Conversation

@fashxp

@fashxp fashxp commented Jan 9, 2026

Copy link
Copy Markdown
Member

Import configurations are large, deeply nested documents whose valid shape lives only in the Studio
UI and in the factories that consume them. A hand-written or generated configuration therefore fails
at import time, with an error that points at a symptom rather than at the offending key.

This PR gives every configuration component a machine-readable schema, adds a validation service that
checks a whole configuration against it, and exposes both to AI agents through an optional set of MCP
tools.

Schema and validation

Components now describe their own settings through three optional interfaces:

Interface Implemented by Purpose
SchemaAwareInterface loaders, interpreters, resolvers, operators, data targets a TreeBuilder for the settings plus a one-line description
DataTargetFieldValidatorInterface data targets check the field the target is pointed at
TransformationTypeAwareInterface operators declare input and output transformation types

ConfigurationValidationService processes those trees over a complete configuration and returns
errors carrying the path that failed. It also computes each mapping item's transformationResultType,
which the Studio mapping editor reads to offer the right target attributes.

From the console:

bin/console datahub:data-importer:validate-config <name>
bin/console datahub:data-importer:validate-config --schema
bin/console datahub:data-importer:validate-config --schema-section=loaderConfig

MCP tools (optional)

Eleven tools in three groups, registered only when mcp/sdk is installed — normally as a
transitive dependency of pimcore/pimcore-agent-bundle, which also serves them. Every tool refuses a
caller without the plugin_datahub_config permission.

Group Tools
pimcore-data-importer-read get_import_config_context, get_import_config_examples, get_class_fields_for_loading, list_import_configs, get_import_config, validate_import_config, enrich_import_config, get_import_status
pimcore-data-importer-direct-write create_import_config, save_import_config
pimcore-data-importer-execute run_import_config

save_import_config validates before it persists, so an invalid configuration is refused rather than
stored. A bundled skill (src/Resources/skills/data-importer-configuration/SKILL.md) tells the agent
how to compose a configuration and which mistakes to avoid.

Docs and examples

  • doc/06_Extending/01_Custom_Strategies.md — implementing the three interfaces in a custom strategy
  • doc/06_Extending/03_Configuration_Validation.md — validation service and schema introspection
  • doc/07_MCP_Tools.md — the tools, their call order and their arguments
  • doc/examples/ — three annotated YAML configurations

Compatibility

No BC break. The three interfaces are additive and optional; no existing interface gained a method
and no service was renamed or removed. mcp/sdk is a suggest, not a require, so installations
without it are unaffected apart from the MCP tools not being registered.

The minimum pimcore/studio-backend-bundle moves to ^2026.3, for the shared MCP tool
registration and error handler.

fashxp added 25 commits January 9, 2026 09:58
- Extract collectServicesByType() helper method in ConfigurationDefinitionPass to eliminate 8 duplicated foreach loops
- Add validateSchemaAwareSettings() helper method in ConfigurationValidationService to reduce duplication across validation methods
- Fix line length issue in ConfigurationDefinitionPass (split long line)
- Reduces cognitive complexity and improves maintainability
Add @phpstan-ignore-next-line comments for children() method calls
that fail type inference with Symfony 6.4.0 (lowest dependencies).
The children() method returns NodeParentInterface in older versions
instead of NodeBuilder, causing PHPStan to complain about undefined
methods like scalarNode(), enumNode(), etc.
Move @phpstan-ignore-next-line comments to the correct lines where
the actual failing method calls occur (scalarNode, variableNode,
arrayNode, enumNode) instead of on the children() line. PHPStan
complains about these method calls, not about children() itself.
# Conflicts:
#	src/DataSource/Loader/HttpLoader.php
@sonarqubecloud

Copy link
Copy Markdown

# Conflicts:
#	composer.json
#	src/Resources/config/pimcore/routing.yml
unified tool naming
updated docs
added create and save data importer config tool
fashxp and others added 29 commits August 20, 2026 07:58
NotWriteableException is a sibling of ForbiddenException rather than a subclass, so it fell
through to the generic handler and reached the agent as an opaque failure.

Co-Authored-By: Claude <noreply@anthropic.com>
run_import_config gets its own group so triggering imports can be granted separately from
authoring configurations. Both delegate to the Studio import service, which enforces the
per configuration rights the Studio button enforces.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The class field has to be the brick container, and the brick field has to be filterable, the
same rule the non-brick path already applied.

Co-Authored-By: Claude <noreply@anthropic.com>
A double encoded escape reached fgetcsv() as two characters, which failed the
preview with a raw ValueError. The schema now rejects it while the config is
being written, and the interpreter names the offending setting.

Co-Authored-By: Claude <noreply@anthropic.com>
The enum listed recurring and cron only, so a configuration scheduled for a
single run failed validation on a value the UI itself produces.

Co-Authored-By: Claude <noreply@anthropic.com>
Nothing validated the property, so a guess was accepted and stored, and the
tool descriptions demanded it anyway. Validation already derives the type from
the pipeline; save_import_config now writes that value into each mapping item.

Co-Authored-By: Claude <noreply@anthropic.com>
The two new tests built their subjects with newInstanceWithoutConstructor and
read private state, which Sonar flags as an accessibility bypass. All three are
registered services, so the test container can hand them over directly.

Co-Authored-By: Claude <noreply@anthropic.com>
Reading a few rows, proposing a mapping before checking what the class stores,
and leaving records without a key each produced a broken configuration that
still validated.

Co-Authored-By: Claude <noreply@anthropic.com>
Both errors that cost the agent a round trip now carry the answer: the loadable
attributes, and the operator that produces an accepted type. Catalogues default
to naming settings rather than describing them.

Co-Authored-By: Claude <noreply@anthropic.com>
Items are applied in order and each writes its target outright, so splitting
one field across several items silently discards all but the last.

Co-Authored-By: Claude <noreply@anthropic.com>
Mutually exclusive source columns are a real pattern, but only with
writeIfSourceIsEmpty false; the defaults let the last item overwrite.

Co-Authored-By: Claude <noreply@anthropic.com>
The 132 character line was the only gate blocker. The SqlFileInterpreter
override merely narrowed a return type nothing reads, and the fake hosts in
the tool tests no longer look like IP addresses.

Co-Authored-By: Claude <noreply@anthropic.com>
getServiceSchema() emits type, description and settings, never a label, so the
two label-only stubs stopped matching once the brief catalogue started
whitelisting keys.

Co-Authored-By: Claude <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants