Skip to content

Commit aba457c

Browse files
committed
Configure request object normalizer
1 parent 9a574dc commit aba457c

6 files changed

Lines changed: 112 additions & 14 deletions

File tree

config/services/normalizers.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,11 @@ services:
1515
$classMetadataFactory: '@?serializer.mapping.class_metadata_factory'
1616
$nameConverter: '@Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter'
1717

18+
phplist.request_serializer:
19+
class: Symfony\Component\Serializer\Serializer
20+
arguments:
21+
$normalizers:
22+
- '@Symfony\Component\Serializer\Normalizer\ObjectNormalizer'
23+
1824
PhpList\RestBundle\:
1925
resource: '../../src/*/Serializer/*'

config/services/validators.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
services:
22
PhpList\RestBundle\Common\Validator\RequestValidator:
33
arguments:
4-
$serializer: '@Symfony\Component\Serializer\Normalizer\ObjectNormalizer'
4+
$serializer: '@phplist.request_serializer'
55
$validator: '@validator'
66

77
PhpList\RestBundle\Identity\Validator\Constraint\UniqueEmailValidator:
@@ -50,4 +50,3 @@ services:
5050
autowire: true
5151
autoconfigure: true
5252
tags: [ 'validator.constraint_validator' ]
53-

src/Common/EventListener/ExceptionListener.php

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
1717
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
1818
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
19+
use Symfony\Component\Validator\Exception\ValidationFailedException;
1920
use Symfony\Component\Validator\Exception\ValidatorException;
2021

2122
class ExceptionListener
@@ -24,7 +25,6 @@ class ExceptionListener
2425
SubscriptionCreationException::class => null,
2526
AttributeDefinitionCreationException::class => null,
2627
AdminAttributeCreationException::class => null,
27-
ValidatorException::class => 400,
2828
AccessDeniedException::class => 403,
2929
AccessDeniedHttpException::class => 403,
3030
AttachmentFileNotFoundException::class => 404,
@@ -36,33 +36,82 @@ public function onKernelException(ExceptionEvent $event): void
3636
{
3737
$exception = $event->getThrowable();
3838

39+
if ($exception instanceof ValidationFailedException || $exception instanceof ValidatorException) {
40+
$event->setResponse(
41+
new JsonResponse([
42+
'message' => 'Validation failed',
43+
'errors' => $this->parseFlatValidationMessage($exception->getMessage()),
44+
], 422)
45+
);
46+
47+
return;
48+
}
49+
3950
foreach (self::EXCEPTION_STATUS_MAP as $class => $statusCode) {
4051
if ($exception instanceof $class) {
41-
$status = $statusCode ?? $exception->getStatusCode();
52+
$status = $statusCode ?? (
53+
method_exists($exception, 'getStatusCode')
54+
? $exception->getStatusCode()
55+
: 400
56+
);
57+
4258
$event->setResponse(
4359
new JsonResponse([
44-
'message' => $exception->getMessage()
60+
'message' => $exception->getMessage(),
4561
], $status)
4662
);
63+
4764
return;
4865
}
4966
}
5067

5168
if ($exception instanceof HttpExceptionInterface) {
5269
$event->setResponse(
5370
new JsonResponse([
54-
'message' => $exception->getMessage()
71+
'message' => $exception->getMessage(),
5572
], $exception->getStatusCode())
5673
);
74+
5775
return;
5876
}
5977

6078
if ($exception instanceof Exception) {
6179
$event->setResponse(
6280
new JsonResponse([
63-
'message' => $exception->getMessage()
81+
'message' => $exception->getMessage(),
6482
], 500)
6583
);
6684
}
6785
}
86+
87+
/**
88+
* @return array<string, array<int, string>>
89+
*/
90+
private function parseFlatValidationMessage(string $message): array
91+
{
92+
$errors = [];
93+
$lines = preg_split('/\r\n|\r|\n/', $message) ?: [];
94+
95+
foreach ($lines as $line) {
96+
$line = trim($line);
97+
98+
if ($line === '') {
99+
continue;
100+
}
101+
102+
$parts = explode(':', $line, 2);
103+
104+
if (count($parts) !== 2) {
105+
$errors['_global'][] = $line;
106+
continue;
107+
}
108+
109+
$field = trim($parts[0]);
110+
$errorMessage = trim($parts[1]);
111+
112+
$errors[$field][] = $errorMessage;
113+
}
114+
115+
return $errors;
116+
}
68117
}

src/Common/SwaggerSchemasResponse.php

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,22 @@
66

77
use OpenApi\Attributes as OA;
88

9+
#[OA\Schema(
10+
schema: 'ErrorDetails',
11+
type: 'object',
12+
example: [
13+
'format.formatOptions[0]' => ['The value you selected is not a valid choice.'],
14+
'schedule.repeatUntil' => ['This value is not a valid datetime.'],
15+
'schedule.requeueUntil' => ['This value is not a valid datetime.'],
16+
],
17+
additionalProperties: new OA\AdditionalProperties(
18+
type: 'array',
19+
items: new OA\Items(type: 'string')
20+
)
21+
)]
922
#[OA\Schema(
1023
schema: 'UnauthorizedResponse',
24+
required: ['message'],
1125
properties: [
1226
new OA\Property(
1327
property: 'message',
@@ -19,17 +33,23 @@
1933
)]
2034
#[OA\Schema(
2135
schema: 'ValidationErrorResponse',
36+
required: ['message', 'errors'],
2237
properties: [
2338
new OA\Property(
2439
property: 'message',
2540
type: 'string',
26-
example: 'Some fields are invalid'
41+
example: 'Validation failed'
42+
),
43+
new OA\Property(
44+
property: 'errors',
45+
ref: '#/components/schemas/ErrorDetails'
2746
)
2847
],
2948
type: 'object'
3049
)]
3150
#[OA\Schema(
3251
schema: 'BadRequestResponse',
52+
required: ['message'],
3353
properties: [
3454
new OA\Property(
3555
property: 'message',
@@ -41,6 +61,7 @@
4161
)]
4262
#[OA\Schema(
4363
schema: 'AlreadyExistsResponse',
64+
required: ['message'],
4465
properties: [
4566
new OA\Property(
4667
property: 'message',
@@ -62,7 +83,18 @@
6283
],
6384
type: 'object'
6485
)]
65-
86+
#[OA\Schema(
87+
schema: 'GenericErrorResponse',
88+
required: ['message'],
89+
properties: [
90+
new OA\Property(
91+
property: 'message',
92+
type: 'string',
93+
example: 'An unexpected error occurred.'
94+
)
95+
],
96+
type: 'object'
97+
)]
6698
#[OA\Schema(
6799
schema: 'CursorPagination',
68100
properties: [

src/Messaging/Request/CreateMessageRequest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class CreateMessageRequest implements RequestInterface
3838
public MessageOptionsRequest $options;
3939

4040
#[TemplateExists]
41-
public ?int $templateId;
41+
public ?int $templateId = null;
4242

4343
public function getDto(): MessageDtoInterface
4444
{

tests/Integration/Common/EventListener/ExceptionListenerTest.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ public function testAccessDeniedExceptionHandled(): void
3434

3535
$this->assertInstanceOf(JsonResponse::class, $response);
3636
$this->assertEquals(403, $response->getStatusCode());
37-
$this->assertEquals(['message' => 'Forbidden'], json_decode($response->getContent(), true));
37+
$this->assertEquals(
38+
['message' => 'Forbidden'],
39+
json_decode($response->getContent(), true)
40+
);
3841
}
3942

4043
public function testHttpExceptionHandled(): void
@@ -47,7 +50,10 @@ public function testHttpExceptionHandled(): void
4750

4851
$this->assertInstanceOf(JsonResponse::class, $response);
4952
$this->assertEquals(404, $response->getStatusCode());
50-
$this->assertEquals(['message' => 'Not found'], json_decode($response->getContent(), true));
53+
$this->assertEquals(
54+
['message' => 'Not found'],
55+
json_decode($response->getContent(), true)
56+
);
5157
}
5258

5359
public function testSubscriptionCreationExceptionHandled(): void
@@ -61,7 +67,10 @@ public function testSubscriptionCreationExceptionHandled(): void
6167

6268
$this->assertInstanceOf(JsonResponse::class, $response);
6369
$this->assertEquals(409, $response->getStatusCode());
64-
$this->assertEquals(['message' => 'Subscription error'], json_decode($response->getContent(), true));
70+
$this->assertEquals(
71+
['message' => 'Subscription error'],
72+
json_decode($response->getContent(), true)
73+
);
6574
}
6675

6776
public function testGenericExceptionHandled(): void
@@ -74,6 +83,9 @@ public function testGenericExceptionHandled(): void
7483

7584
$this->assertInstanceOf(JsonResponse::class, $response);
7685
$this->assertEquals(500, $response->getStatusCode());
77-
$this->assertEquals(['message' => 'Something went wrong'], json_decode($response->getContent(), true));
86+
$this->assertEquals(
87+
['message' => 'Something went wrong'],
88+
json_decode($response->getContent(), true)
89+
);
7890
}
7991
}

0 commit comments

Comments
 (0)