From 457a9b28e4149925cf4daa6bdceedf5d60a0c998 Mon Sep 17 00:00:00 2001 From: Fabian Ehrentraud Date: Thu, 7 May 2026 10:08:58 +0200 Subject: [PATCH] feat: add skipUnusedModels option to validate command and Gradle task Adds a new `--skip-unused-models` flag to the CLI `validate` command and a `skipUnusedModels` property to the Gradle `ValidateTask`/extension. When enabled, the recommendation warning for schemas defined in `components/schemas` that are not referenced by any operation is suppressed. This is useful for specs where shared schemas are intentionally defined without being directly bound to operations (e.g. shared model libraries bundled across multiple spec files). Fixes #17679 --- .../openapitools/codegen/cmd/Validate.java | 6 ++ .../gradle/plugin/OpenApiGeneratorPlugin.kt | 1 + .../OpenApiGeneratorValidateExtension.kt | 5 ++ .../gradle/plugin/tasks/ValidateTask.kt | 9 +++ .../src/test/kotlin/ValidateTaskDslTest.kt | 81 +++++++++++++++++++ .../petstore-v3.0-with-unused-schema.yaml | 45 +++++++++++ 6 files changed, 147 insertions(+) create mode 100644 modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-with-unused-schema.yaml diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Validate.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Validate.java index 8af47269fcb4..ec35f9ae596c 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Validate.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Validate.java @@ -45,6 +45,10 @@ public class Validate extends OpenApiGeneratorCommand { @Option(name = {"--recommend"}, title = "recommend spec improvements") private Boolean recommend; + @Option(name = {"--skip-unused-models"}, title = "skip unused models warning", + description = "skips the recommendation warning for schemas defined in components/schemas that are not referenced by any operation") + private Boolean skipUnusedModels; + @Option( name = {"-a", "--auth"}, title = "authorization", @@ -71,6 +75,8 @@ public void execute() { if (recommend != null) ruleConfiguration.setEnableRecommendations(recommend); else ruleConfiguration.setEnableRecommendations(false); + if (Boolean.TRUE.equals(skipUnusedModels)) ruleConfiguration.setEnableUnusedSchemasRecommendation(false); + OpenApiEvaluator evaluator = new OpenApiEvaluator(ruleConfiguration); ValidationResult validationResult = evaluator.validate(specification); diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt index 901383ac6378..30ffd7bd75d9 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt @@ -87,6 +87,7 @@ class OpenApiGeneratorPlugin : Plugin { remoteInputSpec.set(validate.remoteInputSpec) recommend.set(validate.recommend) treatWarningsAsErrors.set(validate.treatWarningsAsErrors) + skipUnusedModels.set(validate.skipUnusedModels) } register("openApiGenerate", GenerateTask::class.java).configure { diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt index f6a00c5668a4..621e0e23c580 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt @@ -49,6 +49,11 @@ open class OpenApiGeneratorValidateExtension(private val project: Project) { */ val treatWarningsAsErrors: Property = project.objects.property().convention(false) + /** + * Whether to suppress warnings for schemas defined in components/schemas that are not referenced by any operation. + */ + val skipUnusedModels: Property = project.objects.property().convention(false) + // ======================================================================== // Backwards-compatibility bridge setter for Groovy DSL // This allows Groovy users to use assignment syntax: inputSpec = "path" diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt index c7f2085d8ad9..fb4b139636e1 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt @@ -70,9 +70,14 @@ abstract class ValidateTask : DefaultTask() { @get:Input abstract val treatWarningsAsErrors: Property + @get:Optional + @get:Input + abstract val skipUnusedModels: Property + init { recommend.convention(true) treatWarningsAsErrors.convention(false) + skipUnusedModels.convention(false) } @Suppress("unused") @@ -98,6 +103,7 @@ abstract class ValidateTask : DefaultTask() { val recommendations = recommend.get() val failOnWarnings = treatWarningsAsErrors.get() + val shouldSkipUnusedModels = skipUnusedModels.get() logger.lifecycle("Validating spec $specLocation") @@ -110,6 +116,9 @@ abstract class ValidateTask : DefaultTask() { val ruleConfiguration = RuleConfiguration() ruleConfiguration.isEnableRecommendations = recommendations + if (shouldSkipUnusedModels) { + ruleConfiguration.isEnableUnusedSchemasRecommendation = false + } val evaluator = OpenApiEvaluator(ruleConfiguration) val validationResult = evaluator.validate(result.openAPI) diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt index 04d170bea7ec..bcd8413ae049 100644 --- a/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt @@ -503,6 +503,87 @@ paths: ) } + @Test(dataProvider = "gradle_version_provider") + fun `openApiValidate should warn about unused models by default`(gradleVersion: String?, format: String) { + val propertyFormat = PropertyFormat.valueOf(format) + // Arrange + val projectFiles = mapOf( + "spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0-with-unused-schema.yaml") + ) + + withProject( + """ + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiValidate { + | ${inputSpecProperty("spec.yaml", propertyFormat)} + | } + """.trimMargin(), projectFiles + ) + + // Act + val result = getGradleRunner(gradleVersion) + .withProjectDir(temp) + .withArguments("openApiValidate") + .withPluginClasspath() + .build() + + // Assert + assertTrue( + result.output.contains("Unused model: UnusedSchema"), + "Expected unused model warning to be present by default." + ) + assertEquals( + SUCCESS, result.task(":openApiValidate")?.outcome, + "Expected a successful run, but found ${result.task(":openApiValidate")?.outcome}" + ) + } + + @Test(dataProvider = "gradle_version_provider") + fun `openApiValidate should suppress unused model warnings when skipUnusedModels is true`(gradleVersion: String?, format: String) { + val propertyFormat = PropertyFormat.valueOf(format) + // Arrange + val projectFiles = mapOf( + "spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0-with-unused-schema.yaml") + ) + + withProject( + """ + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiValidate { + | ${inputSpecProperty("spec.yaml", propertyFormat)} + | skipUnusedModels = true + | } + """.trimMargin(), projectFiles + ) + + // Act + val result = getGradleRunner(gradleVersion) + .withProjectDir(temp) + .withArguments("openApiValidate") + .withPluginClasspath() + .build() + + // Assert + assertTrue( + result.output.contains("Unused model: UnusedSchema").not(), + "Expected unused model warning to be suppressed when skipUnusedModels = true." + ) + assertTrue( + result.output.contains("Spec is valid."), + "Expected spec to be reported as valid." + ) + assertEquals( + SUCCESS, result.task(":openApiValidate")?.outcome, + "Expected a successful run, but found ${result.task(":openApiValidate")?.outcome}" + ) + } + @Test(dataProvider = "gradle_version_only_provider") fun `openApiValidate should support task-level configuration with Kotlin DSL set String syntax`(gradleVersion: String?) { // Create a valid spec file diff --git a/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-with-unused-schema.yaml b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-with-unused-schema.yaml new file mode 100644 index 000000000000..7f29ae540ab1 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-with-unused-schema.yaml @@ -0,0 +1,45 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + responses: + '200': + description: A list of pets + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + UnusedSchema: + description: This schema is defined but never referenced by any operation. + type: object + properties: + id: + type: integer