Skip to content

CAMEL-24704: camel-yaml-dsl - beans keyed by the bean name fail with the list form to write - #26921

Merged
davsclaus merged 2 commits into
apache:mainfrom
ammachado:CAMEL-24704
Oct 19, 2026
Merged

davsclaus merged 2 commits into
apache:mainfrom
ammachado:CAMEL-24704

Conversation

@ammachado

@ammachado ammachado commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

CAMEL-24704: beans written keyed by the bean name now fails with the list form to write, at runtime as well as in the validator. The list stays the only form of beans:.

This PR first accepted the map form next to the list, as the JIRA proposed. Following the review, it now takes the other option: keep one form (the list) and steer people and AI models to it through the error messages, so docs, the writer, the schemas and Kaoto stay on a single style. The map-form implementation is kept on a local branch in case the project later decides to switch fully to the map form (that would be a dev list decision).

The two shapes people and models write first:

- beans:
    myProcessor:                 # a map keyed by the bean name
      type: "#class:com.example.MyProcessor"
- beans:
    - myProcessor:               # a list item keyed by the bean name
        type: "#class:com.example.MyProcessor"

camel validate already explained how to rewrite both, but camel run failed with Node type map is invalid, expected array or Unsupported field: myProcessor.

Runtime (camel-yaml-dsl-deserializers)

  • BeansDeserializer.checkBeansIsAList(Node) is called for the top-level beans, and for the beans of routeTemplate and templatedRoute.
  • A map: beans is a list: each bean starts with "- ": - name: myProcessor followed by type: "#class:com.example.MyBean" (indented under the -). When the first key is name: (the - of the list item was forgotten), the hint uses its value.
  • An item with a single key holding a map, other than the bean's own map properties (properties, constructors): a bean item is written as - name: myProcessor followed by type: ... (the name is a property, not the key).
  • These are the validator's existing hints, so camel run and camel validate now say the same thing.

Validator (camel-yaml-dsl-validator)

  • The two bean item hints (an item keyed by name, and id/ref/class instead of name/type) matched /\d+/beans/\d+ in full, so only the top-level beans got them. They now match .*/beans/\d+, like the existing map hint (.*/beans), which also covers routeTemplate and templatedRoute.

No schema, doc or upgrade-guide change: what is accepted is unchanged, only the messages are.

Tests: BeansTest covers the map, the forgotten - , an item keyed by name, a routeTemplate map and a templatedRoute keyed item, plus a guard that a lone properties: item still reports the missing name. YamlValidatorPropertyHintTest covers the template hints. Against main's code, 5 of the 6 new BeansTest tests and the new validator test fail (the guard passes by design). With this change: camel-yaml-dsl-validator 262/262, and BeansTest, RouteTemplateTest, TemplatedRouteTest 41/41.

Target

  • I checked that the commit is targeting the correct branch (Camel 4 uses the main branch)

Tracking

  • If this is a large change, bug fix, or code improvement, I checked there is a JIRA issue filed for the change (usually before you start working on it).

Apache Camel coding standards and style

  • I checked that each commit in the pull request has a meaningful subject line and body.
  • I have run mvn clean install -DskipTests locally from root folder and I have committed all auto-generated changes.

The root build passed (run with mvnd). The change adds no generated content, and the build left no uncommitted changes from this PR. It did regenerate model.properties with a wasm entry, which came from untracked src/generated files left in my worktree from before CAMEL-25677 removed the wasm language. I deleted those local files and did not commit anything for it.

AI-assisted contributions

  • If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., Co-authored-by trailers) and the PR description identifies the AI tool used.

🤖 Generated with Claude Code on behalf of Adriano Machado (@ammachado)

This was generated by an AI agent and may contain inaccuracies. Please verify before relying on it.

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid feature PR — well-structured code, comprehensive error handling, and thorough test coverage across all three beans: entry points. The map-to-list conversion in asBeanDefinitions() is clean, the schema generation correctly produces oneOf with a keyed definition, and the scanBeansBlocks() refactoring properly isolates blocks to prevent cross-block name/type leakage. One doc nit below.

Milestone: this targets main and the feature is @since 4.23 — should carry milestone 4.23.0.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Comment thread dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc Outdated
@ammachado ammachado added this to the 4.23.0 milestone Sep 27, 2026
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers
  • dsl/camel-yaml-dsl/camel-yaml-dsl-validator
  • dsl/camel-yaml-dsl/camel-yaml-dsl

🔬 Scalpel shadow comparison — Scalpel: 7 of 706 tested, 9 compile-only — current: 7 all tested

Maveniverse Scalpel detected 7 affected modules (current approach: 7).

Skip-tests mode would test 7 modules (3 direct + 6 downstream), skip tests for 9 (generated code, meta-modules)

Modules Scalpel would test (7)
  • camel-jbang-mcp ← downstream of org.apache.camel:camel-yaml-dsl-validator
  • camel-jbang-plugin-mcp ← downstream of org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-tui ← downstream of org.apache.camel:camel-yaml-dsl-validator
  • camel-jbang-plugin-validate ← downstream of org.apache.camel:camel-yaml-dsl-validator
  • camel-launcher-container ← downstream of org.apache.camel:camel-launcher
  • camel-yaml-dsl-validator ← dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/SchemaHints.java, dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorPropertyHintTest.java
  • camel-yaml-dsl-validator-maven-plugin ← downstream of org.apache.camel:camel-yaml-dsl-validator
Modules with tests skipped (9)
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-launcher
  • coverage

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (18 modules, 3m 27s total)

Total reactor time: 3m 27s

Module Duration Status
Camel :: Launcher 50.5s SUCCESS
Camel :: JBang :: MCP 41.5s SUCCESS
Camel :: JBang :: Plugin :: Validate 29.1s SUCCESS
Camel :: YAML DSL 20.4s SUCCESS
Camel :: YAML DSL :: Validator 15.5s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 12.7s SUCCESS
Camel :: JBang :: Plugin :: Testing 11.8s SUCCESS
Camel :: YAML DSL :: Deserializers 11.5s SUCCESS
Camel :: Coverage 4.2s SUCCESS
Camel :: YAML DSL :: Validator Maven Plugin 2.7s SUCCESS
Camel :: JBang :: Plugin :: Generate 1.6s SUCCESS
Camel :: JBang :: Plugin :: Edit 1.3s SUCCESS
Camel :: JBang :: Plugin :: MCP 1.1s SUCCESS
Camel :: JBang :: Main 1.1s SUCCESS
Camel :: JBang :: Integration tests 0.9s SUCCESS
Camel :: Launcher :: Container 0.8s SUCCESS
Camel :: JBang :: Core n/a
Camel :: JBang :: Plugin :: TUI n/a

Top 20 slowest modules:

  • Camel :: Launcher (50.5s)
  • Camel :: JBang :: MCP (41.5s)
  • Camel :: JBang :: Plugin :: Validate (29.1s)
  • Camel :: YAML DSL (20.4s)
  • Camel :: YAML DSL :: Validator (15.5s)
  • Camel :: JBang :: Plugin :: Kubernetes (12.7s)
  • Camel :: JBang :: Plugin :: Testing (11.8s)
  • Camel :: YAML DSL :: Deserializers (11.5s)
  • Camel :: Coverage (4.2s)
  • Camel :: YAML DSL :: Validator Maven Plugin (2.7s)
  • Camel :: JBang :: Plugin :: Generate (1.6s)
  • Camel :: JBang :: Plugin :: Edit (1.3s)
  • Camel :: JBang :: Plugin :: MCP (1.1s)
  • Camel :: JBang :: Main (1.1s)
  • Camel :: JBang :: Integration tests (0.9s)
  • Camel :: Launcher :: Container (0.8s)

⚙️ View full build and test results

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ammachado, this is a careful implementation of the JIRA proposal. The list stays canonical: the canonical schema, the writer and --canonical still use it, and the map form is only in the lenient schema. The map-to-list conversion reuses every existing check and the pre-parse dedup. The "forgotten - " case gets its own error, so the two forms don't blur, and the upgrade-guide note for schema consumers is appreciated.

One small consistency point, inline: the new hint suggests the flow form myBean: {type: ...}, but the jbang bean-reference scanner only recognizes the block form. A model that follows the hint then gets "bean not declared", and camel_write_file refuses the file. Either write the hint in block form, or teach MAP_KEY_PATTERN the flow form.

This review was generated by an AI agent on behalf of davsclaus and may contain inaccuracies. Please verify all suggestions before applying.

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up on the hints (this replaces my earlier "write the hint in block form" suggestion; my approval of the change itself stands).

The hints are what steer AI models writing Camel YAML, so they should only teach the canonical form. The list stays canonical in this PR (canonical schema, writer, docs), so a hint should not advertise the map form as an alternative, in flow style or block style. Two spots, inline:

  1. The missing-- hint offers myBean: {type: ...} as an alternative. I'd drop that tail so it only shows the list form. This also avoids the BeanRefChecks blind spot for the flow form, where a model that follows the hint has its file refused as "bean not declared".
  2. The widened id/ref/class hint reads oddly for a map-form bean: for myBean: {class: ...} it says "(name instead of class, type instead of class)", but the name is already the key. The new test only checks the "type instead of class" part, so it doesn't catch this.

Separately, MAP_KEY_PATTERN still doesn't see a flow-style map bean (myBean: {type: "#class:..."}). The runtime accepts it and a model may write it unprompted, so a small fix plus a test would be good to have.

This review was generated by an AI agent on behalf of davsclaus and may contain inaccuracies. Please verify all suggestions before applying.

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stepping back from the hint details: I think the PR does exactly what CAMEL-24704 proposed, but on reflection that proposal (accept the map form, keep the list canonical) leaves us with two styles for the same thing. All docs, examples, the YAML writer, the canonical schema and Kaoto use the list, while the runtime and the lenient schema quietly accept a map. Users and AI models will copy whichever they see first, and every tool that reads the schema has to handle both. I don't want us in that half-and-half state.

So I think we should pick one:

  1. Switch fully to the name-keyed map. Change all docs and examples, the YAML writer, the canonical schema, the hints and Kaoto to the map form, and deprecate the list form (still accepted, but no longer shown anywhere). If we go this way it should be a deliberate decision, discussed on the dev list since it affects Kaoto and other schema consumers. It should cover beans first, with the same plan for the other name-keyed lists in the JIRA (parameters, headers, variables, param).
  2. Stay with the list only. Don't accept the map form, and fix the "AI writes a map" problem through the hints alone: a clear "beans is a list: - name: myBean ..." message on the map shape, which the validator already largely gives.

We need to think more about this before going further, so I'm changing my review to request changes to hold the merge until it's decided. Sorry for the back-and-forth, @ammachado. The implementation itself is careful, and if we choose option 1 most of it (deserializer, schema oneOf, the mapKey annotation) is the foundation we'd need anyway.

This review was generated by an AI agent on behalf of davsclaus and may contain inaccuracies. Please verify all suggestions before applying.

@davsclaus
davsclaus marked this pull request as draft September 27, 2026 08:33
@apupier
apupier removed their request for review October 8, 2026 14:29
ammachado and others added 2 commits October 18, 2026 19:39
…the list form to write

beans: is a list of items with a name: property. People and AI models
tend to write it keyed by the bean name instead, as a map
(beans: {myBean: {type: ...}}) or as list items (- myBean: {type: ...}).
camel run then failed with "Node type map is invalid, expected array" or
"Unsupported field: myBean", which does not say what to write.

BeansDeserializer.checkBeansIsAList now fails on both shapes with the list
form to write, using the name that was written:

- a map: "beans is a list: each bean starts with \"- \": - name: myBean
  followed by type: ...". When the first key is name: (the "- " of the
  list item was forgotten) the hint uses its value.
- an item with a single key holding a map, other than the bean's own map
  properties (properties, constructors): "a bean item is written as
  - name: myBean ... (the name is a property, not the key)".

The top-level beans, routeTemplate and templatedRoute all call it. These
are the hints the validator already gives, so camel run and camel
validate say the same thing. The list stays the only form of beans:.

_Claude Code on behalf of Adriano Machado (@ammachado)_

_This was generated by an AI agent and may contain inaccuracies.
Please verify before relying on it._

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…r routeTemplate and templatedRoute

The hints for a bean item keyed by the bean name (- myBean: {type: ...})
and for id/ref/class instead of name/type matched the location
/\d+/beans/\d+ in full, so only the top-level beans got them. The beans
of a routeTemplate (/0/routeTemplate/beans/0) and a templatedRoute are
the same list, and now match .*/beans/\d+, like the "beans is a list"
hint for a map already did with .*/beans.

_Claude Code on behalf of Adriano Machado (@ammachado)_

_This was generated by an AI agent and may contain inaccuracies.
Please verify before relying on it._

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@ammachado ammachado changed the title CAMEL-24704: camel-yaml-dsl - accept beans written as a map keyed by name CAMEL-24704: camel-yaml-dsl - beans keyed by the bean name fail with the list form to write Oct 18, 2026
@ammachado

Copy link
Copy Markdown
Contributor Author

Thanks @davsclaus, agreed that the half-and-half state is the worst of both. I went with option 2: the list stays the only form of beans:, and the PR is now only about the messages.

What changed (rebased on main, force-pushed):

  • The map form is no longer accepted. mapKey, the schema oneOf, the map reading in BeansDeserializer, the BeanRefChecks map scanning, the docs NOTE and the upgrade-guide entry are gone.
  • camel run now fails on both shapes with the list form to write, instead of Node type map is invalid, expected array / Unsupported field: myBean. It uses the validator's existing wording, so camel run and camel validate say the same thing. This applies to the top-level beans and to those of routeTemplate and templatedRoute.
  • The validator's bean item hints matched only the top-level beans (/\d+/beans/\d+ in full). They now also cover routeTemplate and templatedRoute.

On the inline threads: the hints now teach only the list form (no myBean: {type: ...} alternative anywhere). The MAP_KEY_PATTERN flow-form gap and the "name instead of" wording for map-form beans no longer apply, since neither the map scanning nor the widened map-form hint is in the PR anymore.

I kept the map-form implementation on a local branch. If the project later decides on option 1 through the dev list, it is a starting point for that, and for the other name-keyed lists.

Claude Code on behalf of Adriano Machado (@ammachado)

This was generated by an AI agent and may contain inaccuracies. Please verify before relying on it.

@ammachado
ammachado marked this pull request as ready for review October 18, 2026 23:51

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ammachado, and thanks for taking option 2 so cleanly. The list stays the only form of beans:, and the PR is now only about messages, so my request for changes is resolved.

I read the new diff:

  • checkBeansIsAList runs for the top-level beans (both the pre-parse in YamlRoutesBuilderLoader and the normal path go through BeansDeserializer.construct) and for routeTemplate / templatedRoute. The map case reuses the name from a forgotten - name:, which is a nice touch.
  • The keyed-item check skips properties and constructors. Those are the only map-valued bean properties in the generated BeanFactoryDefinition deserializer, so a valid bean item cannot be refused.
  • The validator regexes now match .*/beans/\d+, so the "name instead of ..." wording only reaches list items, which settles my earlier point about it.
  • The tests cover each shape, including the lone properties: guard. No sleeps.

I resolved my four inline threads: the map form, the flow-form hint and the BeanRefChecks gap are all gone.

One small, non-blocking nit: the PR says camel run and camel validate now say the same thing, but the wording differs a little. The runtime says beans is a list: each bean starts with "- ": - name: <the real name> ..., while the validator's .*/beans hint says beans is a list: - name: myBean .... Aligning the validator text with the runtime one would make them identical. Fine as a follow-up.

The CI failure is not caused by this PR. CatalogSamplesTest.callingAServiceIsTheHttpComponentNotEnrich expects uri: http in the first http sample, and CAMEL-25745 (#28041) just added a first sample on main whose URI is quoted (uri: "http://localhost:8080/stock/..."). So main fails that test too. Once main is fixed, a re-run should be green. Approving.

Claude Code on behalf of Claus Ibsen. This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

@davsclaus

Copy link
Copy Markdown
Contributor

Closing and reopening to re-run CI against main: the CatalogSamplesTest.callingAServiceIsTheHttpComponentNotEnrich failure in camel-jbang-core came from main and is fixed there (bbd6f75). Nothing to change in this PR.

Claude Code on behalf of Claus Ibsen

@davsclaus davsclaus closed this Oct 19, 2026
@davsclaus davsclaus reopened this Oct 19, 2026
@davsclaus davsclaus closed this Oct 19, 2026
@davsclaus davsclaus reopened this Oct 19, 2026
@davsclaus davsclaus modified the milestones: 4.23.0, 4.24.0 Oct 19, 2026
@davsclaus davsclaus added the enhancement New feature or request label Oct 19, 2026
@davsclaus
davsclaus merged commit 0433343 into apache:main Oct 19, 2026
16 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dsl enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants