From f1e014e57eb28f55342b8d3b8d882bfadf05ba9f Mon Sep 17 00:00:00 2001 From: Pierre Villard Date: Sat, 18 Apr 2026 20:56:14 +0200 Subject: [PATCH] fix(python): validate map values (not keys) for additionalProperties.enum The python and pythonFlaskConnexion model.mustache templates emitted setters that validated Map keys against the enum instead of validating the values. Per OpenAPI 3.0, additionalProperties describes map values, so an enum inside it constrains values. The current templates produce code that rejects any realistic payload whose keys are arbitrary strings. Fix the {{#isMapContainer}} branch in both templates to iterate .values() instead of .keys() and update the error message from "Invalid keys in" to "Invalid values in". Add PythonMapEnumValidationTest that uses the existing 3_0_0/map_of_inner_enum.yaml fixture and the GeneratorRunner harness to assert the generated setter validates values. Fixes #1404 --- .../handlebars/python/model.mustache | 6 +- .../pythonFlaskConnexion/model.mustache | 6 +- .../python/PythonMapEnumValidationTest.java | 60 +++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 src/test/java/io/swagger/codegen/v3/generators/python/PythonMapEnumValidationTest.java diff --git a/src/main/resources/handlebars/python/model.mustache b/src/main/resources/handlebars/python/model.mustache index 5ef0d319f9..209410f729 100644 --- a/src/main/resources/handlebars/python/model.mustache +++ b/src/main/resources/handlebars/python/model.mustache @@ -129,10 +129,10 @@ class {{classname}}({{#parent}}{{parent}}{{/parent}}{{^parent}}object{{/parent}} ) {{/isListContainer}} {{#isMapContainer}} - if not set({{{name}}}.keys()).issubset(set(allowed_values)): + if not set({{{name}}}.values()).issubset(set(allowed_values)): raise ValueError( - "Invalid keys in `{{{name}}}` [{0}], must be a subset of [{1}]" # noqa: E501 - .format(", ".join(map(str, set({{{name}}}.keys()) - set(allowed_values))), # noqa: E501 + "Invalid values in `{{{name}}}` [{0}], must be a subset of [{1}]" # noqa: E501 + .format(", ".join(map(str, set({{{name}}}.values()) - set(allowed_values))), # noqa: E501 ", ".join(map(str, allowed_values))) ) {{/isMapContainer}} diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache index 2a2a6ba71c..35a2ec2850 100644 --- a/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache +++ b/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache @@ -99,10 +99,10 @@ class {{classname}}(Model): ) {{/isListContainer}} {{#isMapContainer}} - if not set({{{name}}}.keys()).issubset(set(allowed_values)): + if not set({{{name}}}.values()).issubset(set(allowed_values)): raise ValueError( - "Invalid keys in `{{{name}}}` [{0}], must be a subset of [{1}]" # noqa: E501 - .format(", ".join(map(str, set({{{name}}}.keys()) - set(allowed_values))), # noqa: E501 + "Invalid values in `{{{name}}}` [{0}], must be a subset of [{1}]" # noqa: E501 + .format(", ".join(map(str, set({{{name}}}.values()) - set(allowed_values))), # noqa: E501 ", ".join(map(str, allowed_values))) ) {{/isMapContainer}} diff --git a/src/test/java/io/swagger/codegen/v3/generators/python/PythonMapEnumValidationTest.java b/src/test/java/io/swagger/codegen/v3/generators/python/PythonMapEnumValidationTest.java new file mode 100644 index 0000000000..805e082368 --- /dev/null +++ b/src/test/java/io/swagger/codegen/v3/generators/python/PythonMapEnumValidationTest.java @@ -0,0 +1,60 @@ +package io.swagger.codegen.v3.generators.python; + +import io.swagger.codegen.v3.generators.GeneratorRunner; +import io.swagger.codegen.v3.service.GenerationRequest; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +/** + * Regression test for the Python generator `Map<String, Enum>` setter validation. + * + *

The {@code model.mustache} template used to emit + * {@code set(x.keys()).issubset(allowed_values)} for properties declared as + * {@code type: object} with {@code additionalProperties.enum}. Per OpenAPI 3.0 + * semantics, {@code additionalProperties} describes map values, so the enum + * constraint applies to the values, not the keys. The fix validates + * {@code set(x.values()).issubset(allowed_values)} and updates the error message + * from "Invalid keys in" to "Invalid values in". + */ +public class PythonMapEnumValidationTest { + + @Test + public void mapOfEnumSetterValidatesValuesNotKeys() throws Exception { + final List files = GeneratorRunner.runGenerator( + "python", + "3_0_0/map_of_inner_enum.yaml", + GenerationRequest.CodegenVersion.V3, + false, + true, + true, + null, + options -> {}); + + Assert.assertFalse(files.isEmpty()); + + final File modelFile = files.stream() + .filter(f -> f.getName().equals("employee_with_map_of_enum.py")) + .findFirst() + .orElseThrow(() -> new RuntimeException("employee_with_map_of_enum.py was not generated")); + + final String content = new String(Files.readAllBytes(Paths.get(modelFile.toURI()))); + + Assert.assertTrue( + content.contains("set(project_role.values()).issubset(set(allowed_values))"), + "Setter should validate map values (not keys) against the enum.\nGenerated:\n" + content); + Assert.assertTrue( + content.contains("Invalid values in `project_role`"), + "Setter error message should reference 'Invalid values in'.\nGenerated:\n" + content); + Assert.assertFalse( + content.contains("set(project_role.keys()).issubset(set(allowed_values))"), + "Setter must not validate map keys against the enum.\nGenerated:\n" + content); + Assert.assertFalse( + content.contains("Invalid keys in `project_role`"), + "Setter error message must not reference 'Invalid keys in'.\nGenerated:\n" + content); + } +}