Skip to content

Commit bdb4ae7

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

6 files changed

Lines changed: 113 additions & 13 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: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
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\ConstraintViolationInterface;
20+
use Symfony\Component\Validator\Exception\ValidationFailedException;
1921
use Symfony\Component\Validator\Exception\ValidatorException;
2022

2123
class ExceptionListener
@@ -36,33 +38,82 @@ public function onKernelException(ExceptionEvent $event): void
3638
{
3739
$exception = $event->getThrowable();
3840

41+
if ($exception instanceof ValidationFailedException) {
42+
$event->setResponse(
43+
new JsonResponse([
44+
'message' => 'Validation failed',
45+
'errors' => $this->parseFlatValidationMessage($exception->getMessage()),
46+
], 422)
47+
);
48+
49+
return;
50+
}
51+
3952
foreach (self::EXCEPTION_STATUS_MAP as $class => $statusCode) {
4053
if ($exception instanceof $class) {
41-
$status = $statusCode ?? $exception->getStatusCode();
54+
$status = $statusCode ?? (
55+
method_exists($exception, 'getStatusCode')
56+
? $exception->getStatusCode()
57+
: 400
58+
);
59+
4260
$event->setResponse(
4361
new JsonResponse([
44-
'message' => $exception->getMessage()
62+
'message' => $exception->getMessage(),
4563
], $status)
4664
);
65+
4766
return;
4867
}
4968
}
5069

5170
if ($exception instanceof HttpExceptionInterface) {
5271
$event->setResponse(
5372
new JsonResponse([
54-
'message' => $exception->getMessage()
73+
'message' => $exception->getMessage(),
5574
], $exception->getStatusCode())
5675
);
76+
5777
return;
5878
}
5979

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

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,11 @@ 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+
56+
json_decode($response->getContent(), true)
57+
);
5158
}
5259

5360
public function testSubscriptionCreationExceptionHandled(): void
@@ -61,7 +68,10 @@ public function testSubscriptionCreationExceptionHandled(): void
6168

6269
$this->assertInstanceOf(JsonResponse::class, $response);
6370
$this->assertEquals(409, $response->getStatusCode());
64-
$this->assertEquals(['message' => 'Subscription error'], json_decode($response->getContent(), true));
71+
$this->assertEquals(
72+
['message' => 'Subscription error'],
73+
json_decode($response->getContent(), true)
74+
);
6575
}
6676

6777
public function testGenericExceptionHandled(): void
@@ -74,6 +84,8 @@ public function testGenericExceptionHandled(): void
7484

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

0 commit comments

Comments
 (0)