Skip to content

add singleAttribute option for the @MCElement annotation#2583

Merged
t-burch merged 3 commits into
masterfrom
single-attribut
Jan 9, 2026
Merged

add singleAttribute option for the @MCElement annotation#2583
t-burch merged 3 commits into
masterfrom
single-attribut

Conversation

@christiangoerdes

@christiangoerdes christiangoerdes commented Jan 9, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Single-attribute elements can now be configured with inline scalar YAML values; these are converted to the appropriate types and validated, and supported for nested/list scenarios.
  • Tests

    • Added tests covering inline single-attribute YAML configurations, including list and nested-child cases.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 9, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds MCElement.singleAttribute(), enables inline scalar YAML mapping for single-attribute elements, updates JSON Schema generation to emit scalar inline variants, adapts YAML parser to accept scalars for such elements, and adds tests covering inline single-attribute parsing.

Changes

Cohort / File(s) Summary
Annotation
annot/src/main/java/com/predic8/membrane/annot/MCElement.java
Added boolean singleAttribute() property (default false) to MCElement.
YAML parsing
annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java, annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java
GenericYamlParser: early-branch handling for non-object YAML nodes when target is annotated @MCElement(singleAttribute=true), SCALAR_MAPPER added, helpers applySingleAttributeScalar and findSingleAttributeSetter introduced. McYamlIntrospector: added isSingleAttribute(Class<?>).
JSON Schema generation
annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java, annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/AnyOf.java, annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaFactory.java
Added createSingleAttributeInlineVariant and incorporate inline scalar variant into anyOf; generalized internal collections and factory APIs from SchemaObjectAbstractSchema<?>.
Tests
annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java
Added singleAttributeInline and singleAttributeInlineList tests; removed obsolete getCause(Throwable) helper.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested Labels

7.x

Suggested Reviewers

  • rrayst
  • predic8

Poem

🐇 I nibble a scalar, tidy and light,
One attribute fits, snug and right.
Parsers and schemas dance in a line—
Inline values now sip carrot-wine. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and accurately summarizes the main change: adding a singleAttribute option to the @mcelement annotation, which is the primary feature across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@christiangoerdes

Copy link
Copy Markdown
Collaborator Author

/ok-to-test

@coderabbitai coderabbitai 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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java (1)

713-769: Good coverage for list scenario with noEnvelope.

This test effectively validates the combination of singleAttribute=true and noEnvelope=true. The test uses String type which complements the int type test above.

Consider adding negative test cases:

  • An element with singleAttribute=true but multiple @MCAttribute setters (should fail at parse time)
  • Invalid type conversion (e.g., non-numeric string for an int attribute)
annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java (1)

390-405: Consider improving error context for validation failures.

The findSingleAttributeSetter method throws a ParsingException with a synthetic nullNode() when validation fails. This loses location context that could help users debug configuration issues.

♻️ Suggested improvement: pass the node for better error context
-    private static Method findSingleAttributeSetter(Class<?> clazz) {
+    private static Method findSingleAttributeSetter(Class<?> clazz, JsonNode node) {
         List<Method> setters = new ArrayList<>();
         ReflectionUtils.doWithMethods(clazz, m -> {
             if (m.isAnnotationPresent(MCAttribute.class) && m.getParameterCount() == 1) {
                 setters.add(m);
             }
         });

         if (setters.size() != 1) {
             throw new ParsingException(
                     "@MCElement(singleAttribute=true) requires exactly one @MCAttribute setter, but found " + setters.size() + ".",
-                    JsonNodeFactory.instance.nullNode()
+                    node
             );
         }
         return setters.getFirst();
     }

Then update the call site at line 370:

-        Method setter = findSingleAttributeSetter(clazz);
+        Method setter = findSingleAttributeSetter(clazz, node);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3776cb3 and 3dea521.

📒 Files selected for processing (7)
  • annot/src/main/java/com/predic8/membrane/annot/MCElement.java
  • annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java
  • annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/AnyOf.java
  • annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaFactory.java
  • annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java
  • annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java
  • annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-23T13:04:11.388Z
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.

Applied to files:

  • annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java
🧬 Code graph analysis (2)
annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java (3)
annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java (1)
  • McYamlIntrospector (29-203)
annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java (1)
  • MethodSetter (33-216)
annot/src/main/java/com/predic8/membrane/annot/yaml/NodeValidationUtils.java (1)
  • NodeValidationUtils (19-42)
annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java (1)
annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaFactory.java (1)
  • SchemaFactory (19-74)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (java)
🔇 Additional comments (11)
annot/src/main/java/com/predic8/membrane/annot/MCElement.java (1)

67-74: LGTM! Consider enhancing the Javadoc with constraint details.

The new annotation property is well-implemented with a sensible default. The Javadoc example is helpful. Consider adding a note about the constraint that the element must have exactly one @MCAttribute setter for this to work (as enforced in GenericYamlParser.findSingleAttributeSetter).

annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java (1)

198-201: LGTM!

The utility method follows the established pattern of isNoEnvelope and provides null-safe annotation checking.

annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java (1)

664-711: Good test coverage for the basic inline scalar case.

The test validates the core functionality with an integer attribute. Consider adding a companion test to verify the object form (child: { value: 12 }) still works alongside the inline scalar form, confirming backward compatibility.

annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java (3)

61-61: LGTM - ObjectMapper instance is thread-safe for read operations.

Using a shared static ObjectMapper for scalar conversions is appropriate since convertValue is thread-safe.


190-199: Well-structured short-circuit for scalar inline handling.

The logic correctly:

  1. Detects non-object nodes
  2. Checks if singleAttribute is enabled
  3. Applies the scalar value and handles lifecycle callbacks
  4. Falls through to ensureMappingStart for invalid cases when singleAttribute is false

365-388: Solid implementation with proper error handling.

The method correctly handles:

  • Null/null-node values
  • Type conversion via Jackson
  • Reflective invocation with proper exception unwrapping

Minor note: setAccessible(true) at line 381 is a precaution, but public setters annotated with @MCAttribute should already be accessible. It's harmless to keep for robustness.

annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java (3)

162-168: Well-designed schema composition for single-attribute elements.

Using anyOf to allow both the object form and the scalar inline form is the correct JSON Schema approach for supporting multiple valid shapes.


173-204: Thorough compile-time validation for singleAttribute constraints.

The method properly validates:

  • Exactly one non-excluded @MCAttribute exists
  • No @MCTextContent or @MCChildElement is declared

This ensures misconfigured annotations fail fast during annotation processing rather than at runtime.


299-300: Type generalization to AbstractSchema<?> is appropriate.

The change from SchemaObject to AbstractSchema<?> is necessary to accommodate the new AnyOf schema type in these collections. This aligns with the SchemaFactory.anyOf(List<AbstractSchema<?>>) signature.

Also applies to: 337-337, 358-358, 430-431

annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/AnyOf.java (1)

24-29: Type migration to AbstractSchema<?> is consistent and verified.

The field, constructor parameter, and iteration variable are correctly updated to AbstractSchema<?> across all three locations. This aligns with the SchemaFactory.anyOf() signature change and enables the broader schema type support. The json(ObjectNode) method exists in AbstractSchema<?>, confirming the call on line 39 is correct.

annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaFactory.java (1)

64-68: The type signature is appropriately flexible.

The anyOf() method accepts List<AbstractSchema<?>>, which allows callers to pass any schema type while remaining type-safe. All callers in JsonSchemaGenerator pass lists containing SchemaObject or subclass instances, which are compatible with the signature since SchemaObject extends AbstractSchema.

@membrane-ci-server

Copy link
Copy Markdown

This pull request needs "/ok-to-test" from an authorized committer.

rrayst
rrayst previously requested changes Jan 9, 2026
Comment thread annot/src/main/java/com/predic8/membrane/annot/MCElement.java
Comment thread annot/src/test/java/com/predic8/membrane/annot/YAMLParsingTest.java

@coderabbitai coderabbitai 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.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java (1)

270-282: Guard the (SchemaObject) parent2 cast to avoid possible ClassCastException.

With processMCChilds(..., AbstractSchema<?> so), when called with SchemaArray (e.g., at line 124 in the noEnvelope=true branch), parent2 can remain a SchemaArray if a child element has isList() == false. The unconditional cast at line 280 will then crash at runtime. Even if today's model guarantees "object here", a guard makes failures actionable.

Proposed hardening (guard cast + clearer error)
-            addChildsAsProperties(m, main, cei, (SchemaObject) parent2, isComponentsList(i, cei), cei.isList());
+            if (!(parent2 instanceof SchemaObject parentObj)) {
+                throw new ProcessingException(
+                        "Expected SchemaObject while processing child elements, but found: " + parent2.getClass().getSimpleName(),
+                        i.getElement()
+                );
+            }
+            addChildsAsProperties(m, main, cei, parentObj, isComponentsList(i, cei), cei.isList());
🧹 Nitpick comments (3)
annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java (1)

17-27: Imports refactor looks fine; consider consistency for class-level annotation lookup.

You now rely on Spring’s findAnnotation elsewhere; isNoEnvelope / isSingleAttribute use Class#getAnnotation. If composed/meta-annotations are a thing here, consider switching these class-level lookups to findAnnotation(clazz, MCElement.class) for consistency/coverage.

annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java (2)

147-156: Scalar inline anyOf variant is a solid approach; consider carrying element-level description to the anyOf.

Right now the element description is on parser, but tooling that reads only the top-level schema node may lose it when wrapped in anyOf. If supported by your schema model, consider also setting the description on the anyOf itself.


158-189: Inline variant validation is strong; minor cleanup: avoid double-setting type.

SchemaFactory.from(type) already sets the schema type in the factory; calling .type(type) again is redundant.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3dea521 and a4b42d1.

📒 Files selected for processing (2)
  • annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java
  • annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-23T13:04:11.388Z
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.

Applied to files:

  • annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java
🧬 Code graph analysis (1)
annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java (1)
annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaFactory.java (1)
  • SchemaFactory (19-74)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Automated tests
  • GitHub Check: Analyze (java)
🔇 Additional comments (5)
annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java (2)

148-158: anyMatch refactor is clearer and avoids unnecessary work.


197-200: isSingleAttribute(Class<?>) helper is straightforward; consider validating “presence implies schema-shape”.

This method is fine as a predicate, but downstream code should still enforce the structural constraints (exactly one attribute, no children/text) where it matters (parser/schema).

annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java (3)

284-305: AbstractSchema<?> list refactor looks good and aligns with SchemaFactory.anyOf(List<AbstractSchema<?>>).


343-355: Signature update to List<AbstractSchema<?>> is consistent; flow parser def creation still looks correct.


415-433: getComponents generic refactor is consistent and keeps component variants object-shaped.

@t-burch t-burch merged commit 0204f9a into master Jan 9, 2026
4 of 5 checks passed
@t-burch t-burch deleted the single-attribut branch January 9, 2026 11:50
@coderabbitai coderabbitai Bot mentioned this pull request Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants