forked from loafle/openapi-generator-original
* [php-symfony] Never return 406 when user accepts */* When a query has header "Accept" set to "*/*" it means it accepts everything. It is hence weird to return a 406. This patch ensures it does not occur: when the query accepts everything then we take any produced type. This fixes #13334. This also partly makes the open PR #15560 obsolete (or at least, it provides a workaround) * [php-symfony] Don't crash at runtime on null convertFormat $this->convertFormat may return "null". When it's the case we end up calling ...->serialize($data, null); but this crashes at runtime because that serialize method declares that the 2nd parameter is of type "string" (so null is not accepted). With this patch we avoid having an error 500. Instead we return something that makes perfect sense when the OpenApi specification declares a content of type "text/plain" and that the returned value is for instance a string, an int, or a boolean. * [php Symfony] fix return type for non json/xml api This fixes the generated returned type of controller methods for endpoint with a response declared like content: text/plain: schema: type: <boolean|string|integer|number> or for content: image/png: schema: type: string format: binary Without this commit the generated method *had to* return a value that matched "array|object|null", which does not work in this case. This commit makes it possible to return the proper type.
158 lines
4.7 KiB
PHP
158 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace OpenAPI\Server\Service;
|
|
|
|
use JMS\Serializer\SerializerBuilder;
|
|
use JMS\Serializer\Naming\CamelCaseNamingStrategy;
|
|
use JMS\Serializer\Naming\SerializedNameAnnotationStrategy;
|
|
use JMS\Serializer\Serializer;
|
|
use JMS\Serializer\Visitor\Factory\XmlDeserializationVisitorFactory;
|
|
use DateTime;
|
|
use RuntimeException;
|
|
|
|
class JmsSerializer implements SerializerInterface
|
|
{
|
|
protected Serializer $serializer;
|
|
|
|
public function __construct()
|
|
{
|
|
$namingStrategy = new SerializedNameAnnotationStrategy(new CamelCaseNamingStrategy());
|
|
$this->serializer = SerializerBuilder::create()
|
|
->setDeserializationVisitor('json', new StrictJsonDeserializationVisitorFactory())
|
|
->setDeserializationVisitor('xml', new XmlDeserializationVisitorFactory())
|
|
->setPropertyNamingStrategy($namingStrategy)
|
|
->build();
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function serialize($data, string $format): string
|
|
{
|
|
$convertFormat = $this->convertFormat($format);
|
|
if ($convertFormat !== null) {
|
|
return SerializerBuilder::create()->build()->serialize($data, $convertFormat);
|
|
} else {
|
|
// don't use var_export if $data is already a string: it may corrupt binary strings
|
|
return is_string($data) ? $data : var_export($data, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function deserialize($data, string $type, string $format)
|
|
{
|
|
if ($format == 'string') {
|
|
return $this->deserializeString($data, $type);
|
|
}
|
|
|
|
// If we end up here, let JMS serializer handle the deserialization
|
|
return $this->serializer->deserialize($data, $type, $this->convertFormat($format));
|
|
}
|
|
|
|
private function convertFormat(string $format): ?string
|
|
{
|
|
return match($format) {
|
|
'application/json' => 'json',
|
|
'application/xml' => 'xml',
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
private function deserializeString($data, string $type)
|
|
{
|
|
// Figure out if we have an array format
|
|
if (1 === preg_match('/array<(csv|ssv|tsv|pipes),(.*)>/i', $type, $matches)) {
|
|
return $this->deserializeArrayString($matches[1], $matches[2], $data);
|
|
}
|
|
|
|
switch ($type) {
|
|
case 'int':
|
|
case 'integer':
|
|
if (is_int($data)) {
|
|
return $data;
|
|
}
|
|
|
|
if (is_numeric($data)) {
|
|
return $data + 0;
|
|
}
|
|
|
|
break;
|
|
case 'double':
|
|
case 'float':
|
|
if (is_float($data) || is_numeric($data)) {
|
|
return (float) $data;
|
|
}
|
|
|
|
break;
|
|
case 'string':
|
|
break;
|
|
case 'boolean':
|
|
case 'bool':
|
|
if (is_bool($data)) {
|
|
return $data;
|
|
}
|
|
|
|
if (strtolower($data) === 'true') {
|
|
return true;
|
|
}
|
|
|
|
if (strtolower($data) === 'false') {
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
case 'DateTime':
|
|
case '\DateTime':
|
|
return is_null($data) ? null :new DateTime($data);
|
|
default:
|
|
if (is_null($data)) {
|
|
return null;
|
|
}
|
|
|
|
if (!class_exists($type)) {
|
|
throw new RuntimeException(sprintf("Type %s is unsupported", $type));
|
|
}
|
|
|
|
$reflectionClass = new \ReflectionClass($type);
|
|
if (!$reflectionClass->implementsInterface('\BackedEnum')) {
|
|
throw new RuntimeException(sprintf("Type %s is unsupported", $type));
|
|
}
|
|
|
|
$enum = $type::tryFrom($data);
|
|
if (!$enum) {
|
|
throw new RuntimeException(sprintf("Unknown %s value in %s enum", $data, $type));
|
|
}
|
|
|
|
return $enum;
|
|
}
|
|
|
|
// If we end up here, just return data
|
|
return $data;
|
|
}
|
|
|
|
private function deserializeArrayString(string $format, string $type, $data): array
|
|
{
|
|
if (empty($data)) {
|
|
return [];
|
|
}
|
|
|
|
// Parse the string using the correct separator
|
|
$data = match($format) {
|
|
'csv' => explode(',', $data),
|
|
'ssv' => explode(' ', $data),
|
|
'tsv' => explode("\t", $data),
|
|
'pipes' => explode('|', $data),
|
|
default => [],
|
|
};
|
|
|
|
// Deserialize each of the array elements
|
|
foreach ($data as $key => $item) {
|
|
$data[$key] = $this->deserializeString($item, $type);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|