From 95feaf4e07c5fe575c3acfbc246e0f2bd941fad1 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Sat, 27 Jun 2026 16:14:39 +0000 Subject: [PATCH] fix(kotlin): add @Valid annotation for cascade validation on array/object/reference fields The Kotlin ConstraintsPreset was missing the @Valid annotation for array, object, and reference properties, which disabled cascading validation. This is the same issue that was fixed for the Java generator in #2175. Changes: - Add @Valid annotation for ConstrainedArrayModel, ConstrainedObjectModel, and ConstrainedReferenceModel properties - Add javax.validation.Valid (or jakarta.validation.Valid) import - Add test case verifying @Valid is rendered for nested object and array props Fixes #2573 --- .../kotlin/presets/ConstraintsPreset.ts | 14 ++++++++ .../kotlin/presets/ConstraintsPreset.spec.ts | 35 +++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/generators/kotlin/presets/ConstraintsPreset.ts b/src/generators/kotlin/presets/ConstraintsPreset.ts index 447abadae6..8e737cd186 100644 --- a/src/generators/kotlin/presets/ConstraintsPreset.ts +++ b/src/generators/kotlin/presets/ConstraintsPreset.ts @@ -3,6 +3,8 @@ import { ConstrainedFloatModel, ConstrainedIntegerModel, ConstrainedMetaModel, + ConstrainedObjectModel, + ConstrainedReferenceModel, ConstrainedStringModel } from '../../../models'; import { KotlinPreset } from '../KotlinPreset'; @@ -20,6 +22,9 @@ export const KOTLIN_CONSTRAINTS_PRESET: KotlinPreset = { renderer.dependencyManager.addDependency( `${importFrom}.validation.constraints.*` ); + renderer.dependencyManager.addDependency( + `${importFrom}.validation.Valid` + ); return content; }, property({ renderer, property, content }) { @@ -29,6 +34,15 @@ export const KOTLIN_CONSTRAINTS_PRESET: KotlinPreset = { annotations.push(renderer.renderAnnotation('NotNull', null, 'get:')); } + // Add @Valid for cascade validation on array/object/reference fields + if ( + property.property instanceof ConstrainedReferenceModel || + property.property instanceof ConstrainedObjectModel || + property.property instanceof ConstrainedArrayModel + ) { + annotations.push(renderer.renderAnnotation('Valid', null, 'get:')); + } + annotations.push( ...getTypeSpecificAnnotations(property.property, renderer) ); diff --git a/test/generators/kotlin/presets/ConstraintsPreset.spec.ts b/test/generators/kotlin/presets/ConstraintsPreset.spec.ts index 23e84ffcca..45ebb816d0 100644 --- a/test/generators/kotlin/presets/ConstraintsPreset.spec.ts +++ b/test/generators/kotlin/presets/ConstraintsPreset.spec.ts @@ -12,11 +12,23 @@ describe('KOTLIN_CONSTRAINTS_PRESET', () => { required: ['min_number_prop', 'max_number_prop'] }; + const docWithNestedObject = { + $id: 'NestedClazz', + type: 'object', + properties: { + array_prop: { type: 'array', items: { type: 'string' } }, + obj_prop: { type: 'object', properties: { inner: { type: 'string' } } } + } + }; + test('should render javax constraints annotations by default', async () => { const generator = new KotlinGenerator({ presets: [KOTLIN_CONSTRAINTS_PRESET] }); - const expectedDependencies = ['import javax.validation.constraints.*']; + const expectedDependencies = [ + 'import javax.validation.constraints.*', + 'import javax.validation.Valid' + ]; const models = await generator.generate(doc); expect(models).toHaveLength(1); @@ -36,7 +48,10 @@ describe('KOTLIN_CONSTRAINTS_PRESET', () => { ] }); - const expectedDependencies = ['import javax.validation.constraints.*']; + const expectedDependencies = [ + 'import javax.validation.constraints.*', + 'import javax.validation.Valid' + ]; const models = await generator.generate(doc); expect(models).toHaveLength(1); @@ -56,11 +71,25 @@ describe('KOTLIN_CONSTRAINTS_PRESET', () => { ] }); - const expectedDependencies = ['import jakarta.validation.constraints.*']; + const expectedDependencies = [ + 'import jakarta.validation.constraints.*', + 'import jakarta.validation.Valid' + ]; const models = await generator.generate(doc); expect(models).toHaveLength(1); expect(models[0].result).toMatchSnapshot(); expect(models[0].dependencies).toEqual(expectedDependencies); }); + + test('should render @Valid annotation for array and object properties', async () => { + const generator = new KotlinGenerator({ + presets: [KOTLIN_CONSTRAINTS_PRESET] + }); + + const models = await generator.generate(docWithNestedObject); + expect(models.length).toBeGreaterThanOrEqual(1); + expect(models[0].result).toContain('@get:Valid'); + expect(models[0].result).toMatchSnapshot(); + }); });