Skip to content

Commit

Permalink
feat: support collect denormalization errors (#5170)
Browse files Browse the repository at this point in the history
* feat: support collect dernormalization errors

* feat: complete collectDenormalizationErrors implementation

Co-authored-by: Alan Poulain <[email protected]>
  • Loading branch information
natacha-h and alanpoulain authored Jan 13, 2023
1 parent 902b135 commit 10da65f
Show file tree
Hide file tree
Showing 37 changed files with 402 additions and 43 deletions.
2 changes: 1 addition & 1 deletion features/main/relation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ Feature: Relations support
"pattern": "^An error occurred$"
},
"hydra:description": {
"pattern": "^Expected IRI or document for resource \"ApiPlatform\\\\Tests\\\\Fixtures\\\\TestBundle\\\\(Document|Entity)\\\\RelatedDummy\", \"integer\" given.$"
"pattern": "^The type of the \"ApiPlatform\\\\Tests\\\\Fixtures\\\\TestBundle\\\\(Document|Entity)\\\\RelatedDummy\" resource must be \"array\" \\(nested document\\) or \"string\" \\(IRI\\), \"integer\" given.$"
}
},
"required": [
Expand Down
67 changes: 67 additions & 0 deletions features/main/validation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,70 @@ Feature: Using validations groups
}
"""
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"

@!mongodb
@createSchema
Scenario: Create a resource with collectDenormalizationErrors
When I add "Content-type" header equal to "application/ld+json"
And I send a "POST" request to "/dummy_collect_denormalization" with body:
"""
{
"foo": 3,
"bar": "baz",
"qux": true,
"uuid": "y",
"relatedDummy": 8,
"relatedDummies": 76
}
"""
Then the response status code should be 422
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description": "This value should be of type unknown.\nqux: This value should be of type string.\nfoo: This value should be of type bool.\nbar: This value should be of type int.\nuuid: This value should be of type uuid.\nrelatedDummy: This value should be of type array|string.\nrelatedDummies: This value should be of type array.",
"violations": [
{
"propertyPath": "",
"message": "This value should be of type unknown.",
"code": "0",
"hint": "Failed to create object because the class misses the \"baz\" property."
},
{
"propertyPath": "qux",
"message": "This value should be of type string.",
"code": "0"
},
{
"propertyPath": "foo",
"message": "This value should be of type bool.",
"code": "0"
},
{
"propertyPath": "bar",
"message": "This value should be of type int.",
"code": "0"
},
{
"propertyPath": "uuid",
"message": "This value should be of type uuid.",
"code": "0",
"hint": "Invalid UUID string: y"
},
{
"propertyPath": "relatedDummy",
"message": "This value should be of type array|string.",
"code": "0"
},
{
"propertyPath": "relatedDummies",
"message": "This value should be of type array.",
"code": "0"
}
]
}
"""
2 changes: 1 addition & 1 deletion features/serializer/vo_relations.feature
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ Feature: Value object as ApiResource
"pattern": "^An error occurred$"
},
"hydra:description": {
"pattern": "^Cannot create an instance of ApiPlatform\\\\Tests\\\\Fixtures\\\\TestBundle\\\\(Document|Entity)\\\\VoDummyCar from serialized data because its constructor requires parameter \"drivers\" to be present.$"
"pattern": "^Cannot create an instance of \"ApiPlatform\\\\Tests\\\\Fixtures\\\\TestBundle\\\\(Document|Entity)\\\\VoDummyCar\" from serialized data because its constructor requires parameter \"drivers\" to be present.$"
}
},
"required": [
Expand Down
4 changes: 2 additions & 2 deletions src/JsonApi/Serializer/ItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,12 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v
* @see http://jsonapi.org/format/#document-resource-object-linkage
*
* @throws RuntimeException
* @throws NotNormalizableValueException
* @throws UnexpectedValueException
*/
protected function denormalizeRelation(string $attributeName, ApiProperty $propertyMetadata, string $className, mixed $value, ?string $format, array $context): object
{
if (!\is_array($value) || !isset($value['id'], $value['type'])) {
throw new NotNormalizableValueException('Only resource linkage supported currently, see: http://jsonapi.org/format/#document-resource-object-linkage.');
throw new UnexpectedValueException('Only resource linkage supported currently, see: http://jsonapi.org/format/#document-resource-object-linkage.');
}

try {
Expand Down
14 changes: 14 additions & 0 deletions src/Metadata/ApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public function __construct(
protected ?array $cacheHeaders = null,
protected ?array $normalizationContext = null,
protected ?array $denormalizationContext = null,
protected ?bool $collectDenormalizationErrors = null,
protected ?array $hydraContext = null,
protected ?array $openapiContext = null, // TODO Remove in 4.0
protected bool|OpenApiOperation|null $openapi = null,
Expand Down Expand Up @@ -537,6 +538,19 @@ public function withDenormalizationContext(array $denormalizationContext): self
return $self;
}

public function getCollectDenormalizationErrors(): ?bool
{
return $this->collectDenormalizationErrors;
}

public function withCollectDenormalizationErrors(bool $collectDenormalizationErrors = null): self
{
$self = clone $this;
$self->collectDenormalizationErrors = $collectDenormalizationErrors;

return $self;
}

/**
* @return string[]|null
*/
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Extractor/XmlResourceExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ private function buildBase(\SimpleXMLElement $resource): array
'securityPostValidationMessage' => $this->phpize($resource, 'securityPostValidationMessage', 'string'),
'normalizationContext' => isset($resource->normalizationContext->values) ? $this->buildValues($resource->normalizationContext->values) : null,
'denormalizationContext' => isset($resource->denormalizationContext->values) ? $this->buildValues($resource->denormalizationContext->values) : null,
'collectDenormalizationErrors' => $this->phpize($resource, 'collectDenormalizationErrors', 'bool'),
'validationContext' => isset($resource->validationContext->values) ? $this->buildValues($resource->validationContext->values) : null,
'filters' => $this->buildArrayValue($resource, 'filter'),
'order' => isset($resource->order->values) ? $this->buildValues($resource->order->values) : null,
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Extractor/YamlResourceExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ private function buildBase(array $resource): array
'output' => $this->phpize($resource, 'output', 'bool|string'),
'normalizationContext' => $this->buildArrayValue($resource, 'normalizationContext'),
'denormalizationContext' => $this->buildArrayValue($resource, 'denormalizationContext'),
'collectDenormalizationErrors' => $this->phpize($resource, 'collectDenormalizationErrors', 'bool'),
'validationContext' => $this->buildArrayValue($resource, 'validationContext'),
'filters' => $this->buildArrayValue($resource, 'filters'),
'order' => $this->buildArrayValue($resource, 'order'),
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Extractor/schema/resources.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
<xsd:attribute type="xsd:string" name="securityPostDenormalizeMessage"/>
<xsd:attribute type="xsd:string" name="securityPostValidation"/>
<xsd:attribute type="xsd:string" name="securityPostValidationMessage"/>
<xsd:attribute type="xsd:boolean" name="collectDenormalizationErrors"/>
</xsd:attributeGroup>

<xsd:attributeGroup name="extendedBase">
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Get.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
3 changes: 2 additions & 1 deletion src/Metadata/GetCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down Expand Up @@ -100,7 +101,7 @@ public function __construct(
?OptionsInterface $stateOptions = null,
array $extraProperties = [],
) {
parent::__construct(self::METHOD_GET, $uriTemplate, $types, $formats, $inputFormats, $outputFormats, $uriVariables, $routePrefix, $routeName, $defaults, $requirements, $options, $stateless, $sunset, $acceptPatch, $status, $host, $schemes, $condition, $controller, $cacheHeaders, $hydraContext, $openapiContext, $openapi, $exceptionToStatus, $queryParameterValidationEnabled, $shortName, $class, $paginationEnabled, $paginationType, $paginationItemsPerPage, $paginationMaximumItemsPerPage, $paginationPartial, $paginationClientEnabled, $paginationClientItemsPerPage, $paginationClientPartial, $paginationFetchJoinCollection, $paginationUseOutputWalkers, $paginationViaCursor, $order, $description, $normalizationContext, $denormalizationContext, $security, $securityMessage, $securityPostDenormalize, $securityPostDenormalizeMessage, $securityPostValidation, $securityPostValidationMessage, $deprecationReason, $filters, $validationContext, $input, $output, $mercure, $messenger, $elasticsearch, $urlGenerationStrategy, $read, $deserialize, $validate, $write, $serialize, $fetchPartial, $forceEager, $priority, $name, $provider, $processor, $stateOptions, $extraProperties);
parent::__construct(self::METHOD_GET, $uriTemplate, $types, $formats, $inputFormats, $outputFormats, $uriVariables, $routePrefix, $routeName, $defaults, $requirements, $options, $stateless, $sunset, $acceptPatch, $status, $host, $schemes, $condition, $controller, $cacheHeaders, $hydraContext, $openapiContext, $openapi, $exceptionToStatus, $queryParameterValidationEnabled, $shortName, $class, $paginationEnabled, $paginationType, $paginationItemsPerPage, $paginationMaximumItemsPerPage, $paginationPartial, $paginationClientEnabled, $paginationClientItemsPerPage, $paginationClientPartial, $paginationFetchJoinCollection, $paginationUseOutputWalkers, $paginationViaCursor, $order, $description, $normalizationContext, $denormalizationContext, $collectDenormalizationErrors, $security, $securityMessage, $securityPostDenormalize, $securityPostDenormalizeMessage, $securityPostValidation, $securityPostValidationMessage, $deprecationReason, $filters, $validationContext, $input, $output, $mercure, $messenger, $elasticsearch, $urlGenerationStrategy, $read, $deserialize, $validate, $write, $serialize, $fetchPartial, $forceEager, $priority, $name, $provider, $processor, $stateOptions, $extraProperties);
$this->itemUriTemplate = $itemUriTemplate;
}

Expand Down
1 change: 1 addition & 0 deletions src/Metadata/GraphQl/DeleteMutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
5 changes: 4 additions & 1 deletion src/Metadata/GraphQl/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand All @@ -75,7 +76,7 @@ public function __construct(
?string $name = null,
?string $provider = null,
?string $processor = null,
protected ?OptionsInterface $stateOptions = null,
?OptionsInterface $stateOptions = null,
array $extraProperties = [],
) {
// Abstract operation properties
Expand All @@ -96,6 +97,7 @@ public function __construct(
$this->description = $description;
$this->normalizationContext = $normalizationContext;
$this->denormalizationContext = $denormalizationContext;
$this->collectDenormalizationErrors = $collectDenormalizationErrors;
$this->security = $security;
$this->securityMessage = $securityMessage;
$this->securityPostDenormalize = $securityPostDenormalize;
Expand All @@ -122,6 +124,7 @@ public function __construct(
$this->name = $name;
$this->provider = $provider;
$this->processor = $processor;
$this->stateOptions = $stateOptions;
$this->extraProperties = $extraProperties;
}

Expand Down
1 change: 1 addition & 0 deletions src/Metadata/GraphQl/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/GraphQl/QueryCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/GraphQl/Subscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
2 changes: 2 additions & 0 deletions src/Metadata/HttpOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down Expand Up @@ -157,6 +158,7 @@ public function __construct(
$this->deprecationReason = $deprecationReason;
$this->normalizationContext = $normalizationContext;
$this->denormalizationContext = $denormalizationContext;
$this->collectDenormalizationErrors = $collectDenormalizationErrors;
$this->validationContext = $validationContext;
$this->filters = $filters;
$this->elasticsearch = $elasticsearch;
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/NotExposed.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
14 changes: 14 additions & 0 deletions src/Metadata/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function __construct(
protected ?string $description = null,
protected ?array $normalizationContext = null,
protected ?array $denormalizationContext = null,
protected ?bool $collectDenormalizationErrors = null,
protected ?string $security = null,
protected ?string $securityMessage = null,
protected ?string $securityPostDenormalize = null,
Expand Down Expand Up @@ -327,6 +328,19 @@ public function withDenormalizationContext(array $denormalizationContext = []):
return $self;
}

public function getCollectDenormalizationErrors(): ?bool
{
return $this->collectDenormalizationErrors;
}

public function withCollectDenormalizationErrors(bool $collectDenormalizationErrors = null): self
{
$self = clone $this;
$self->collectDenormalizationErrors = $collectDenormalizationErrors;

return $self;
}

public function getSecurity(): ?string
{
return $this->security;
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Patch.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Put.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public function __construct(
?string $description = null,
?array $normalizationContext = null,
?array $denormalizationContext = null,
?bool $collectDenormalizationErrors = null,
?string $security = null,
?string $securityMessage = null,
?string $securityPostDenormalize = null,
Expand Down
2 changes: 1 addition & 1 deletion src/RamseyUuid/Serializer/UuidDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
try {
return Uuid::fromString($data);
} catch (InvalidUuidStringException $e) {
throw new NotNormalizableValueException($e->getMessage(), $e->getCode(), $e);
throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, ['uuid'], $context['deserialization_path'] ?? null, true, $e->getCode(), $e);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/Serializer/AbstractConstraintViolationListNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ protected function getMessagesAndViolations(ConstraintViolationListInterface $co
'code' => $violation->getCode(),
];

if ($hint = $violation->getParameters()['hint'] ?? false) {
$violationData['hint'] = $hint;
}

$constraint = $violation instanceof ConstraintViolation ? $violation->getConstraint() : null;
if (
[] !== $this->serializePayloadFields &&
Expand Down
Loading

0 comments on commit 10da65f

Please sign in to comment.