forked from loafle/openapi-generator-original
* [PHP-Symfony] fixes validation of date-time parameter This fixes parts of #14930. Without this patch a parameter declared as date-time is validated against Symfony's "DateTime" constraint, which always fails. Because this constraint expects a string with format "Y-m-d H:i:s". It fails because the generated code performs the check after the deserialization, so the variable checked is not a string anymore. Besides this, even if we performed that validation on the string, that would not work well because OpenApi specification expects date-time to conform to RFC 3339 and that "Y-m-d H:i:s" would reject RFC 3339 compliant dates. With this change we ensure that the string provided by the web user could be parsed by PHP to DateTime, which solves both issues. (Note however that it means that it now accepts more formats than just RFC 3339 compliant ones for those parameters (it would accept all formats accepted by PHP DateTime). That being said it's compliant with the guideline ""be conservative in what you send, be liberal in what you accept") * [PHP-Symfony] Fix handling of null date-time parameter This fixes one of the issue described on #14930, namely that the deserializeString method of the generated class JsmSerializer returns null for other types of string, but not for date-time. Instead it returns a DateTime which represents "now" (because that what `new DateTime(null)` does). Consequently when an API declares a date-time parameter as non-required and when that endpoint is called without that parameters, then the user code would end up having "now" instead of "null" for this parameter.
139 lines
3.9 KiB
PHP
139 lines
3.9 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
|
|
{
|
|
return SerializerBuilder::create()->build()->serialize($data, $this->convertFormat($format));
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
{
|
|
switch ($format) {
|
|
case 'application/json':
|
|
return 'json';
|
|
case 'application/xml':
|
|
return 'xml';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function deserializeString($data, string $type)
|
|
{
|
|
// Figure out if we have an array format
|
|
if (1 === preg_match('/array<(csv|ssv|tsv|pipes),(int|string)>/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 '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:
|
|
throw new RuntimeException(sprintf("Type %s is unsupported", $type));
|
|
}
|
|
|
|
// 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
|
|
switch ($format) {
|
|
case 'csv':
|
|
$data = explode(',', $data);
|
|
break;
|
|
case 'ssv':
|
|
$data = explode(' ', $data);
|
|
break;
|
|
case 'tsv':
|
|
$data = explode("\t", $data);
|
|
break;
|
|
case 'pipes':
|
|
$data = explode('|', $data);
|
|
break;
|
|
default;
|
|
$data = [];
|
|
}
|
|
|
|
// Deserialize each of the array elements
|
|
foreach ($data as $key => $item) {
|
|
$data[$key] = $this->deserializeString($item, $type);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|