Skip to content

Commit

Permalink
Merge branch 'release/1.0.0-alpha.15'
Browse files Browse the repository at this point in the history
  • Loading branch information
nekofar committed May 20, 2023
2 parents 4e7a11a + 6159bd4 commit b4f778b
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 67 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
# Changelog
All notable changes to this project will be documented in this file.

## [1.0.0-alpha.15] - 2023-05-20

### Bug Fixes

- Solve an issue on `message` in `Payload` json
- Solve json decode of `PayloadStatus`

### Features

- Create a decorator for response factory
- Add new method to send response with jsend spec

### Miscellaneous Tasks

- Install slim/http as a dependency

### Testing

- Create new tests for `Response` and improve old tests

## [1.0.0-alpha.14] - 2023-05-16

### Bug Fixes
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"require": {
"php": ">=8.1",
"ext-json": "*",
"slim/http": "^1.3",
"slim/psr7": "^1.6",
"slim/slim": "^4.11"
},
Expand Down
2 changes: 1 addition & 1 deletion src/Payload.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function jsonSerialize(): array
}

if (null !== $this->message) {
$payload['message'] = $this->code;
$payload['message'] = $this->message;
}

if (null !== $this->code) {
Expand Down
8 changes: 7 additions & 1 deletion src/PayloadStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
namespace Nekofar\Slim\JSend;

use InvalidArgumentException;
use JsonSerializable;

/**
*
*/
final class PayloadStatus
final class PayloadStatus implements JsonSerializable
{
public const STATUS_SUCCESS = 'success';
public const STATUS_FAIL = 'fail';
Expand Down Expand Up @@ -48,6 +49,11 @@ public static function fromString(string $status): self
return new self($status);
}

public function jsonSerialize(): string
{
return $this->name;
}

/**
* @throws InvalidArgumentException
*/
Expand Down
65 changes: 19 additions & 46 deletions src/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,72 +11,45 @@

namespace Nekofar\Slim\JSend;

use JsonException;
use Psr\Http\Message\ResponseInterface;
use Slim\Http\Response as DecoratedResponse;

/**
* @mixin \Slim\Psr7\Response
*/
final class Response
final class Response extends DecoratedResponse
{
private ResponseInterface $response;

private PayloadInterface $payload;

/**
* Create a new response instance.
*/
public function __construct(
ResponseInterface $response,
) {
$this->response = $response->withHeader(
'Content-Type',
'application/json',
);
}

/**
* Gets the payload of the response.
* Return an instance with the specified response payload.
*/
public function getPayload(): ?PayloadInterface
public function withPayload(PayloadInterface $payload): self
{
return $this->payload;
return $this->withJson($payload);
}

/**
* Return an instance with the specified response payload.
*
* @throws JsonException
* Set the response payload as a success.
*/
public function withPayload(PayloadInterface $payload): self
public function withSuccessPayload(mixed $data = null): self
{
$this->payload = $payload;
$payload = Payload::success($data);

$this->response->getBody()->write(
json_encode($this->payload, JSON_THROW_ON_ERROR),
);

return $this;
return $this->withPayload($payload);
}

/**
* Create a new Response from another response.
*
* @return static
* Set the response payload as a failure.
*/
public static function fromResponse(ResponseInterface $response): static
public function withFailPayload(mixed $data = null): self
{
return new self($response);
$payload = Payload::fail($data);

return $this->withPayload($payload);
}

/**
* Proxies calls to the original response object.
*
* @param array<int, object|callable|null> $arguments
* Set the response payload as an error.
*/
public function __call(string $method, array $arguments): mixed
public function withErrorPayload(string $message, ?int $code = null, mixed $data = null): self
{
/* @phpstan-ignore-next-line */
return $this->response->{$method}(...$arguments);
$payload = Payload::error($message, $code, $data);

return $this->withPayload($payload);
}
}
25 changes: 25 additions & 0 deletions src/ResponseFactoryDecorator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Nekofar\Slim\JSend;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;

final class ResponseFactoryDecorator implements ResponseFactoryInterface
{
public function __construct(
private readonly ResponseFactoryInterface $responseFactory,
private readonly StreamFactoryInterface $streamFactory,
) {
}

public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
{
$response = $this->responseFactory->createResponse($code, $reasonPhrase);

return new Response($response, $this->streamFactory);
}
}
124 changes: 105 additions & 19 deletions tests/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,120 @@

namespace Tests;

use JsonException;
use Nekofar\Slim\JSend\Payload;
use Nekofar\Slim\JSend\PayloadStatus;
use Nekofar\Slim\JSend\Response as JSendResponse;
use Nekofar\Slim\JSend\Response;
use Nekofar\Slim\JSend\ResponseFactoryDecorator;
use PHPUnit\Framework\TestCase;
use Slim\Psr7\Response;
use Psr\Http\Message\ResponseFactoryInterface;
use Slim\Psr7\Factory\ResponseFactory;
use Slim\Psr7\Factory\StreamFactory;

/**
*
*/
final class ResponseTest extends TestCase
{
/**
* @throws JsonException
*/
private ResponseFactoryInterface $responseFactory;

public function testWithPayload(): void
{
$payload = new Payload(PayloadStatus::fromString(
PayloadStatus::STATUS_SUCCESS,
));
$response = JSendResponse::fromResponse(new Response());
$response = $response->withPayload($payload);

self::assertTrue($response->hasHeader('Content-Type'));
self::assertSame('application/json', $response->getHeaderLine('Content-Type'));

self::assertEquals($payload, $response->getPayload());
self::assertSame(json_encode($payload), (string) $response->getBody());
self::assertSame(json_encode($payload), json_encode($response->getPayload()));
$data = ['foo' => 'bar'];
$payload = Payload::success($data);

/** @var Response $originalResponse */
$originalResponse = $this->responseFactory->createResponse();
$response = $originalResponse->withPayload($payload);

self::assertEquals($originalResponse->getStatusCode(), $response->getStatusCode());
self::assertEquals('application/json', $response->getHeaderLine('Content-Type'));

$body = $response->getBody();
$body->rewind();
$dataJson = $body->getContents();

$originalBody = $originalResponse->getBody();
$originalBody->rewind();
$originalContents = $originalBody->getContents();

// Test that the original body hasn't been replaced
self::assertNotEquals($dataJson, $originalContents);
self::assertEquals('{"status":"success","data":{"foo":"bar"}}', $dataJson);

$response = $response->withStatus(201)->withJson([]);
self::assertEquals(201, $response->getStatusCode());
}

public function testWithSuccessPayload(): void
{
/** @var Response $originalResponse */
$originalResponse = $this->responseFactory->createResponse();
$response = $originalResponse->withSuccessPayload(['foo' => 'bar']);

self::assertEquals($originalResponse->getStatusCode(), $response->getStatusCode());
self::assertEquals('application/json', $response->getHeaderLine('Content-Type'));

$body = $response->getBody();
$body->rewind();
$dataJson = $body->getContents();

$originalBody = $originalResponse->getBody();
$originalBody->rewind();
$originalContents = $originalBody->getContents();

// Test that the original body hasn't been replaced
self::assertNotEquals($dataJson, $originalContents);
self::assertEquals('{"status":"success","data":{"foo":"bar"}}', $dataJson);
}

public function testWithFailPayload(): void
{
/** @var Response $originalResponse */
$originalResponse = $this->responseFactory->createResponse();
$response = $originalResponse->withFailPayload(['foo' => 'bar']);

self::assertEquals($originalResponse->getStatusCode(), $response->getStatusCode());
self::assertEquals('application/json', $response->getHeaderLine('Content-Type'));

$body = $response->getBody();
$body->rewind();
$dataJson = $body->getContents();

$originalBody = $originalResponse->getBody();
$originalBody->rewind();
$originalContents = $originalBody->getContents();

// Test that the original body hasn't been replaced
self::assertNotEquals($dataJson, $originalContents);
self::assertEquals('{"status":"fail","data":{"foo":"bar"}}', $dataJson);
}

public function testWithErrorPayload(): void
{
/** @var Response $originalResponse */
$originalResponse = $this->responseFactory->createResponse();
$response = $originalResponse->withErrorPayload('An error occurred', 5, ['foo' => 'bar']);

self::assertEquals($originalResponse->getStatusCode(), $response->getStatusCode());
self::assertEquals('application/json', $response->getHeaderLine('Content-Type'));

$body = $response->getBody();
$body->rewind();
$dataJson = $body->getContents();

$originalBody = $originalResponse->getBody();
$originalBody->rewind();
$originalContents = $originalBody->getContents();

// Test that the original body hasn't been replaced
self::assertNotEquals($dataJson, $originalContents);
self::assertEquals('{"status":"error","data":{"foo":"bar"},"message":"An error occurred","code":5}', $dataJson);
}

protected function setUp(): void
{
$this->responseFactory = new ResponseFactoryDecorator(
new ResponseFactory(),
new StreamFactory(),
);
}
}

0 comments on commit b4f778b

Please sign in to comment.