diff --git a/modules/openapi-generator/src/main/resources/php-symfony/serialization/JmsSerializer.mustache b/modules/openapi-generator/src/main/resources/php-symfony/serialization/JmsSerializer.mustache index 6c93e947f425..16a5504c886d 100644 --- a/modules/openapi-generator/src/main/resources/php-symfony/serialization/JmsSerializer.mustache +++ b/modules/openapi-generator/src/main/resources/php-symfony/serialization/JmsSerializer.mustache @@ -9,6 +9,7 @@ use JMS\Serializer\Serializer; use JMS\Serializer\Visitor\Factory\XmlDeserializationVisitorFactory; use DateTime; use RuntimeException; +use JMS\Serializer\Exception\RuntimeException as SerializerRuntimeException; class JmsSerializer implements SerializerInterface { @@ -122,7 +123,7 @@ class JmsSerializer implements SerializerInterface $enum = $type::tryFrom($data); if (!$enum) { - throw new RuntimeException(sprintf("Unknown %s value in %s enum", $data, $type)); + throw new SerializerRuntimeException(sprintf("Unknown %s value in %s enum", $data, $type)); } return $enum; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpSymfonyServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpSymfonyServerCodegenTest.java index 291d25da5ca5..7875dabe9727 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpSymfonyServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpSymfonyServerCodegenTest.java @@ -242,6 +242,76 @@ public void testPetstoreDottedEnumRefQueryParameterUsesShortClassInApiInterface( output.deleteOnExit(); } + /** + * Guards {@code php-symfony} {@code JmsSerializer.mustache}: invalid query values for a generated PHP + * {@code BackedEnum} are deserialized in {@code JmsSerializer::deserializeString()}. The generated + * {@code DefaultController} wraps {@code deserialize(...)} with {@code catch (SerializerRuntimeException)}, + * an alias of {@code JMS\Serializer\Exception\RuntimeException}. Only the unknown-enum branch must throw that + * type; other string-deserialization errors may keep using PHP's global {@code RuntimeException}. + *

+ * This test asserts the generated {@code JmsSerializer.php} keeps {@code use RuntimeException;} and adds + * {@code use JMS\Serializer\Exception\RuntimeException as SerializerRuntimeException;}, throws + * {@code SerializerRuntimeException} for {@code tryFrom} failure, and that {@code DefaultController.php} still + * catches {@code SerializerRuntimeException}. + *

+ * Spec: {@code src/test/resources/3_1/php-symfony/jms-enum-query-invalid-deserialization.yaml}. Background: + * {@code fix_jms_enum_ex.md}. + */ + @Test + public void testJmsSerializerUsesJmsRuntimeExceptionForBackedEnumStringDeserializationErrors() throws Exception { + Map properties = new HashMap<>(); + properties.put("invokerPackage", "Org\\OpenAPITools\\PetstoreEnum"); + + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("php-symfony") + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_1/php-symfony/jms-enum-query-invalid-deserialization.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + File jmsSerializer = files.stream() + .filter(f -> "JmsSerializer.php".equals(f.getName()) && f.getPath().contains("Service" + File.separator)) + .findFirst() + .orElseThrow(() -> new AssertionError("JmsSerializer.php not generated")); + + String jms = Files.readString(jmsSerializer.toPath(), StandardCharsets.UTF_8); + + Assert.assertTrue( + jms.contains("Unknown %s value in %s enum"), + "Expected BackedEnum tryFrom failure message in generated JmsSerializer"); + Assert.assertTrue( + Pattern.compile("^use RuntimeException;\\s*$", Pattern.MULTILINE).matcher(jms).find(), + "JmsSerializer should keep use RuntimeException for generic unsupported-type errors"); + Assert.assertTrue( + jms.contains("use JMS\\Serializer\\Exception\\RuntimeException as SerializerRuntimeException;"), + "JmsSerializer must alias JMS RuntimeException as SerializerRuntimeException (same as DefaultController)"); + Assert.assertTrue( + jms.contains("throw new SerializerRuntimeException(sprintf(\"Unknown %s value in %s enum\", $data, $type));"), + "Invalid BackedEnum tryFrom must throw SerializerRuntimeException so DefaultController catch applies"); + + File defaultController = files.stream() + .filter(f -> "DefaultController.php".equals(f.getName()) && f.getPath().contains("Controller" + File.separator)) + .findFirst() + .orElseThrow(() -> new AssertionError("DefaultController.php not generated")); + String controller = Files.readString(defaultController.toPath(), StandardCharsets.UTF_8); + Assert.assertTrue( + controller.contains("use JMS\\Serializer\\Exception\\RuntimeException as SerializerRuntimeException;"), + "Expected DefaultController to catch SerializerRuntimeException alias"); + Assert.assertTrue( + controller.contains("catch (SerializerRuntimeException $exception)"), + "Expected deserialize() to catch SerializerRuntimeException"); + + assertGeneratedPhpSyntaxValid(jmsSerializer); + assertGeneratedPhpSyntaxValid(defaultController); + + output.deleteOnExit(); + } + /** * Optional {@code in: query} parameter: {@code required: false}, schema is an enum {@code $ref} with a valid * {@code default} (see OpenAPI 3.x). Omitting the query key must be equivalent to sending that default; the diff --git a/modules/openapi-generator/src/test/resources/3_1/php-symfony/jms-enum-query-invalid-deserialization.yaml b/modules/openapi-generator/src/test/resources/3_1/php-symfony/jms-enum-query-invalid-deserialization.yaml new file mode 100644 index 000000000000..e3dbc168fb0c --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/php-symfony/jms-enum-query-invalid-deserialization.yaml @@ -0,0 +1,25 @@ +openapi: 3.1.0 +info: + title: php-symfony JMS invalid query enum deserialization + version: '1.0' +paths: + /pets: + get: + operationId: listPets + parameters: + - name: status + in: query + required: false + schema: + $ref: '#/components/schemas/Pet.Model.PetStatus' + responses: + '200': + description: OK +components: + schemas: + Pet.Model.PetStatus: + type: string + enum: + - available + - pending + - sold diff --git a/samples/server/petstore/php-symfony/SymfonyBundle-php/Service/JmsSerializer.php b/samples/server/petstore/php-symfony/SymfonyBundle-php/Service/JmsSerializer.php index 234056cf41d5..cccfc7d11300 100644 --- a/samples/server/petstore/php-symfony/SymfonyBundle-php/Service/JmsSerializer.php +++ b/samples/server/petstore/php-symfony/SymfonyBundle-php/Service/JmsSerializer.php @@ -9,6 +9,7 @@ use JMS\Serializer\Visitor\Factory\XmlDeserializationVisitorFactory; use DateTime; use RuntimeException; +use JMS\Serializer\Exception\RuntimeException as SerializerRuntimeException; class JmsSerializer implements SerializerInterface { @@ -122,7 +123,7 @@ private function deserializeString($data, string $type) $enum = $type::tryFrom($data); if (!$enum) { - throw new RuntimeException(sprintf("Unknown %s value in %s enum", $data, $type)); + throw new SerializerRuntimeException(sprintf("Unknown %s value in %s enum", $data, $type)); } return $enum;