Skip to content

Commit

Permalink
Merge pull request #9 from silasyudi/develop
Browse files Browse the repository at this point in the history
Switch serializer to JMS and supports array of objects
  • Loading branch information
silasyudi committed Jun 29, 2023
2 parents 06827d6 + 892a347 commit 400208e
Show file tree
Hide file tree
Showing 25 changed files with 424 additions and 188 deletions.
27 changes: 5 additions & 22 deletions Documentation/REST.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ respectively, on objects in controller methods.
The SilasYudi\RestBootBundle\EventListener\RestListener listener checks for the presence of annotations
@Body or @Query on controller methods on triggered routes.

If so, the content is converted to an object through Symfony's [SerializerInterface](https://symfony.com/doc/current/components/serializer.html).
If so, the content is converted to an object through JMS's [SerializerInterface](https://jmsyst.com/libs/serializer).
If this annotation is @Body, the content of the request's *body* is used for the conversion. If the annotation is @Query,
the *query string* of the request URL is used for conversion.

Expand All @@ -27,25 +27,7 @@ the *query string* of the request URL is used for conversion.

#### Requirements

To convert a payload from a POST, PUT, PATCH or DELETE request, it must be sent in the body of the request,
in [supported format](https://symfony.com/doc/current/serializer.html#adding-normalizers-and-encoders) by Symfony's
SerializerInterface (such as JSON or XML).

#### Content-type

It is important that the appropriate content-type is sent in the request header.
In the absence of this information or if the format is not accepted for serialization,
the application will use JSON format as default.

With this setting in your *services.yml* you can change the default content-type and the accepted formats for serialization:

```yaml
parameters:
restboot.rest.payload.format: 'xml' #changing to XML
restboot.rest.payload.accepted.formats: ['json', 'xml']
```

Note that the format name is used here instead media-type.
To convert a payload from a POST, PUT, PATCH or DELETE request, it must be sent in the body of the request in JSON.

#### Using annotation in the controller

Expand Down Expand Up @@ -129,9 +111,10 @@ If you made specific settings for the SerializerInterface, your object must be i
- *boolean* types MUST:
- have the value *true* represented as: boolean *true*, integer or decimal not equal to 0, or string other than empty string or string "0".
- have the value *false* represented as: boolean *false*, integer or decimal equals 0, or string equals empty string or string "0".
- Dates MUST be represented in RFC3339 format (Y-m-d\TH:i:sP)
- Dates MUST have an annotation `@JMS\Serializer\Annotation\Type` to specify the format (see the [documentation](https://jmsyst.com/libs/serializer/master/reference/annotations#type)).
- Arrays MUST have an annotation `@JMS\Serializer\Annotation\Type` to specify the type (see the [documentation](https://jmsyst.com/libs/serializer/master/reference/annotations#type)).

For more information, see the [Serializer Component](https://symfony.com/doc/current/components/serializer.html) documentation.
For more information, see the [Serializer](https://jmsyst.com/libs/serializer) documentation.

## RestListener Priority

Expand Down
29 changes: 7 additions & 22 deletions Documentation/REST_PT_BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ respectivamente, em objetos nos métodos dos controladores.
O *listener* SilasYudi\RestBootBundle\EventListener\RestListener verifica a presença das anotações
@Body ou @Query nos métodos dos controladores nas rotas acionadas.

Se possuir, é realizada a conversão do conteúdo para objeto através do [SerializerInterface](https://symfony.com/doc/current/components/serializer.html)
do Symfony. Se essa anotação for @Body, é utilizado o conteúdo do *body* da requisição para a conversão. Se a anotação
Se possuir, é realizada a conversão do conteúdo para objeto através do [SerializerInterface](https://jmsyst.com/libs/serializer)
do JMS. Se essa anotação for @Body, é utilizado o conteúdo do *body* da requisição para a conversão. Se a anotação
for @Query, é utilizado a *query string* da URL da requisição para conversão.

## Uso
Expand All @@ -27,24 +27,8 @@ for @Query, é utilizado a *query string* da URL da requisição para conversão

#### Requisitos

Para converter um *payload* de uma requisição POST, PUT, PATCH ou DELETE, este deve ser enviada no *body* da requisição,
em [formato suportado](https://symfony.com/doc/current/serializer.html#adding-normalizers-and-encoders) pelo
SerializerInterface do Symfony (como por exemplo, JSON ou XML).

#### Content-type

É importante que no cabeçalho da requisição seja enviado o *content-type* apropriado. Na falta desta informação ou
se o *content-type* não for um formato válido para serialização, a aplicação utilizará o formato JSON como padrão.

Você pode alterar o content-type padrão e os formatos aceitos para serialização nas configurações do Symfony, da seguinte forma:

```yaml
parameters:
restboot.rest.payload.format: 'xml' #alterando para XML
restboot.rest.payload.accepted.formats: ['json', 'xml']
```

Observe que aqui não se usa o media-type, mas sim o nome do formato.
Para converter um *payload* de uma requisição POST, PUT, PATCH ou DELETE, este deve ser enviada no *body* da requisição
em formato JSON.

#### Uso da anotação no controlador

Expand Down Expand Up @@ -128,9 +112,10 @@ Se você fez configurações específicas para o SerializerInterface, o seu obje
- Tipos *boolean* DEVEM:
- ter o valor *true* representado como: booleano *true*, inteiro ou decimal diferente de 0, ou string diferente de string vazia ou string "0".
- ter o valor *false* representado como: booleano *false*, inteiro ou decimal igual a 0, ou string igual string vazia ou string "0".
- Datas DEVEM ser representadas no formato RFC3339 (Y-m-d\TH:i:sP)
- Datas DEVEM possuir uma anotação `@JMS\Serializer\Annotation\Type` para especificar o formato (consulte a [documentação](https://jmsyst.com/libs/serializer/master/reference/annotations#type)).
- Arrays DEVEM possuir uma anotação `@JMS\Serializer\Annotation\Type` para especificar o tipo (consulte a [documentação](https://jmsyst.com/libs/serializer/master/reference/annotations#type)).

Para mais informações, consulte a documentação do [Serializer Component](https://symfony.com/doc/current/components/serializer.html).
Para mais informações, consulte a documentação do [Serializer](https://jmsyst.com/libs/serializer).

## Prioridade do RestListener

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
"doctrine/annotations": "^2.0",
"doctrine/doctrine-bundle": "^2",
"doctrine/orm": "^2",
"jms/serializer": "^3.26",
"symfony/flex": "^1.3",
"symfony/framework-bundle": "^4.4 | ^5",
"symfony/property-access": "^4.4 | ^5",
"symfony/property-info": "^4.4 | ^5",
"symfony/serializer": "^4.4 | ^5",
"symfony/yaml": "^4.4 | ^5"
},
"require-dev": {
Expand Down
13 changes: 5 additions & 8 deletions src/Resources/config/rest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ services:
autowire: true
autoconfigure: true

SilasYudi\RestBootBundle\Serializer\DefaultFormat:
arguments:
- '%restboot.rest.payload.format%'

SilasYudi\RestBootBundle\Serializer\AcceptedFormats:
arguments:
- '%restboot.rest.payload.accepted.formats%'

SilasYudi\RestBootBundle\Rest\Converter\BodyConverter:
tags: [ name: 'restboot.rest.converter' ]

Expand All @@ -27,3 +19,8 @@ services:
tags:
- { name: 'kernel.event_listener', event: 'kernel.controller', method: 'onKernelController', priority: '%restboot.rest.priority.controller%' }
public: true

JMS\Serializer\SerializerBuilder:

JMS\Serializer\SerializerInterface:
factory: ['@JMS\Serializer\SerializerBuilder', 'build']
15 changes: 4 additions & 11 deletions src/Rest/Converter/AbstractConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,28 @@

namespace SilasYudi\RestBootBundle\Rest\Converter;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\SerializerInterface;
use JMS\Serializer\SerializerInterface;
use SilasYudi\RestBootBundle\Rest\ValueObject\ConverterContext;
use SilasYudi\RestBootBundle\Serializer\DefaultFormat;
use Symfony\Component\HttpFoundation\Request;

abstract class AbstractConverter
{

protected SerializerInterface $serializer;
protected DefaultFormat $defaultFormat;

public function __construct(SerializerInterface $serializer, DefaultFormat $defaultFormat)
public function __construct(SerializerInterface $serializer)
{
$this->serializer = $serializer;
$this->defaultFormat = $defaultFormat;
}

final public function apply(Request $request, ConverterContext $context): void
{
$entity = $this->serializer->deserialize(
$this->getContent($request),
$context->getEntityType(),
$this->getFormat($request),
['disable_type_enforcement' => true]
'json'
);
$request->attributes->set($context->getEntityName(), $entity);
}

abstract protected function getContent(Request $request): string;

abstract protected function getFormat(Request $request): string;
}
24 changes: 0 additions & 24 deletions src/Rest/Converter/BodyConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,11 @@
namespace SilasYudi\RestBootBundle\Rest\Converter;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\SerializerInterface;
use SilasYudi\RestBootBundle\Serializer\AcceptedFormats;
use SilasYudi\RestBootBundle\Serializer\DefaultFormat;

class BodyConverter extends AbstractConverter
{

protected AcceptedFormats $acceptedFormats;

public function __construct(
SerializerInterface $serializer,
DefaultFormat $defaultFormat,
AcceptedFormats $acceptedFormats
) {
parent::__construct($serializer, $defaultFormat);
$this->acceptedFormats = $acceptedFormats;
}

protected function getContent(Request $request): string
{
return $request->getContent();
}

protected function getFormat(Request $request): string
{
$format = $request->getContentType();

return $this->acceptedFormats->isAcceptedFormat($format)
? $format
: $this->defaultFormat->getFormat();
}
}
13 changes: 0 additions & 13 deletions src/Rest/Converter/QueryConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,11 @@
namespace SilasYudi\RestBootBundle\Rest\Converter;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\SerializerInterface;
use SilasYudi\RestBootBundle\Serializer\DefaultFormat;

class QueryConverter extends AbstractConverter
{

public function __construct(SerializerInterface $serializer)
{
parent::__construct($serializer, new DefaultFormat('json'));
}

protected function getContent(Request $request): string
{
return json_encode($request->query->all());
}

protected function getFormat(Request $request): string
{
return $this->defaultFormat->getFormat();
}
}
18 changes: 0 additions & 18 deletions src/Serializer/AcceptedFormats.php

This file was deleted.

18 changes: 0 additions & 18 deletions src/Serializer/DefaultFormat.php

This file was deleted.

28 changes: 28 additions & 0 deletions tests/EventListener/Rest/RestListenerBodyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace SilasYudi\RestBootBundle\Tests\EventListener\Rest;

use SilasYudi\RestBootBundle\Tests\Util\Entity\Primitives;
use Symfony\Component\HttpFoundation\Request;
use SilasYudi\RestBootBundle\Tests\EventListener\RestListenerTest;
use SilasYudi\RestBootBundle\Tests\Util\Controller\Rest\ControllerBody;
Expand Down Expand Up @@ -48,4 +49,31 @@ public function testScenarioWithoutSupportedContentTypeShouldGetDefaultContentTy
$this->assertEquals(123, $address->getNumber());
$this->assertNull($address->getComplement());
}

public function testPrimitives(): void
{
$request = $this->getRequest('TestPrimitives');
$this->runEvent([$this->getControllerInstance(), 'primitives'], $request);

/** @var Primitives $primitives */
$primitives = $request->get('primitives');
$this->assertFalse($primitives->isBoolStringVazia());
$this->assertFalse($primitives->isBoolStringZero());
$this->assertTrue($primitives->isBoolStringInteiroPositivo());
$this->assertTrue($primitives->isBoolStringInteiroNegativo());
$this->assertTrue($primitives->isBoolStringNaoVazia());
$this->assertEquals('', $primitives->getStringVazia());
$this->assertEquals('a', $primitives->getStringNaoVazia());
$this->assertNull($primitives->getNulo());
$this->assertNull($primitives->getNotIsSet());
$this->assertEquals(1, $primitives->getPositivo());
$this->assertEquals(-1, $primitives->getNegativo());
$this->assertEquals(1, $primitives->getStringPositivo());
$this->assertEquals(-1, $primitives->getStringNegativo());
$this->assertEquals(1.2, $primitives->getFloatPositivo());
$this->assertEquals(-1.2, $primitives->getFloatNegativo());
$this->assertEquals(1.2, $primitives->getFloatStringPositivo());
$this->assertEquals(-1.2, $primitives->getFloatStringNegativo());
$this->assertEmpty($primitives->getArrayVazio());
}
}
21 changes: 12 additions & 9 deletions tests/EventListener/RestListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace SilasYudi\RestBootBundle\Tests\EventListener;

use EmptyIterator;
use SilasYudi\RestBootBundle\Tests\Util\Entity\Primitives;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
Expand All @@ -27,20 +28,23 @@ protected function setUp(): void
/**
* @dataProvider providers
*/
public function testScenarioArrayWithKeysAndDateTimeAndNotIssetSecondLevelObject(callable $controller): void
public function testScenarioPrimitivesArrayAndObjectsArray(callable $controller): void
{
$request = $this->getRequest('ScenarioArrayWithKeysAndDateTimeAndNotIssetSecondLevelObject');
$request = $this->getRequest('ScenarioPrimitivesArrayAndObjectsArray');
$this->runEvent($controller, $request);

/** @var Person $person */
$person = $request->get('person');
$this->assertEquals('Silas', $person->getName());
$this->assertEquals(['Ninja', 'Mestre'], $person->getNicknames());
$this->assertEquals(30, $person->getAge());
$this->assertTrue($person->isMale());
$this->assertEquals('2000-01-01 12:34:56', $person->getBirtydate()->format('Y-m-d H:i:s'));
$this->assertEquals('2000-01-01 12:34:56', $person->getBirthdate()->format('Y-m-d H:i:s'));
$this->assertEquals(65.432, $person->getWeight());
$this->assertEquals('99999-9999', $person->getPhones()['personal']);
$this->assertEquals('88888-8888', $person->getPhones()['work']);
$this->assertEquals('personal', $person->getPhones()[0]->getName());
$this->assertEquals('99999-9999', $person->getPhones()[0]->getNumber());
$this->assertEquals('work', $person->getPhones()[1]->getName());
$this->assertEquals('88888-8888', $person->getPhones()[1]->getNumber());
$this->assertEquals(-12, $person->getGameScore()->getBalanceWinningsLosses());
$this->assertEquals(-1234.56, $person->getGameScore()->getBalancePoints());
$this->assertNull($person->getAddress());
Expand All @@ -49,19 +53,18 @@ public function testScenarioArrayWithKeysAndDateTimeAndNotIssetSecondLevelObject
/**
* @dataProvider providers
*/
public function testScenarioArrayWithoutKeysAndSetSecondLevelObject(callable $controller): void
public function testScenarioSetSecondLevelObject(callable $controller): void
{
$request = $this->getRequest('ScenarioArrayWithoutKeysAndSetSecondLevelObject');
$request = $this->getRequest('ScenarioSetSecondLevelObject');
$this->runEvent($controller, $request);

/** @var Person $person */
$person = $request->get('person');
$this->assertEquals('Carol', $person->getName());
$this->assertEquals(30, $person->getAge());
$this->assertFalse($person->isMale());
$this->assertEquals('2000-01-01 12:34:56 -03:00', $person->getBirtydate()->format('Y-m-d H:i:s P'));
$this->assertEquals('2000-01-01 12:34:56', $person->getBirthdate()->format('Y-m-d H:i:s'));
$this->assertEquals(65.432, $person->getWeight());
$this->assertEquals(['99999-9999', '88888-8888'], $person->getPhones());
$this->assertEquals(-12, $person->getGameScore()->getBalanceWinningsLosses());
$this->assertEquals(-1234.56, $person->getGameScore()->getBalancePoints());

Expand Down
8 changes: 8 additions & 0 deletions tests/Util/Controller/Rest/ControllerBody.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use SilasYudi\RestBootBundle\Rest\Annotation\Query;
use SilasYudi\RestBootBundle\Tests\Util\Entity\Address;
use SilasYudi\RestBootBundle\Tests\Util\Entity\Person;
use SilasYudi\RestBootBundle\Tests\Util\Entity\Primitives;

class ControllerBody
{
Expand Down Expand Up @@ -56,4 +57,11 @@ public function withoutTypeParameter($person)
public function withTwoAnnotations(Person $person, Address $address)
{
}

/**
* @Body(parameter="primitives")
*/
public function primitives(Primitives $primitives)
{
}
}
Loading

0 comments on commit 400208e

Please sign in to comment.