Skip to content

Commit a2dec0c

Browse files
committed
First version
* README.md text updated; * Created a .gitignore; * Renamed library name; * Version changed from 1.1.0-alpha to 1.0.0; * Removed ejetar/accept-header-interpreter, eject/laravel-formatter, illuminate/support and illuminate/http libraries; * Added the symfony/serializer, symfony/yaml and laravel/framework libraries; * Added 'prefer-stable' and 'extra' in composer.json; * ExceptionHandler class created; * Renamed namespace for ApiResponseFormatter; * Renamed middleware; * Middleware modified alias; * Request class created; * Created custom responses: CsvResponse, XmlResponse and YamlResponse; * TestCase created (no implementation yet, only initial structure has been created)
1 parent 7c27611 commit a2dec0c

File tree

13 files changed

+727
-99
lines changed

13 files changed

+727
-99
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/.idea
2+
/vendor
3+
/node_modules
4+
/composer.lock

README.md

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,28 @@
77
+ [Output](#output)
88
* [Example 2](#example-2)
99
+ [Output](#output-1)
10+
* [Example 3](#example-3)
11+
+ [Output](#output-2)
12+
* [Example 4](#example-4)
13+
+ [Output](#output-3)
1014
- [Installation](#installation)
1115
- [Get started](#get-started)
1216
- [Changelog](#changelog)
1317
- [Contributing](#contributing)
1418
- [License](#license)
15-
- [Credits](#credits)
1619

1720
## About
1821
A simple and fast 🚀 library that displays responses in various formats, according to the Accept header, entered by the user. The library currently displays responses in JSON, XML, CSV, and YAML.
1922

2023
## How it works
21-
The API Response Formatter package provides the developer with a middleware called response_formatter. This middleware takes Response and converts it to a certain format based on the user's request.
24+
The API Response Formatter package provides the developer with a middleware called api-response-formatter. This middleware takes Response and converts it to a certain format based on the user's request.
2225

2326
In practical terms:
24-
1. Read the contents of the Accept header to know which response format to display to the user.
25-
2. Take the original response content provided by Laravel and convert it to the desired format;
26-
3. Display the response to the user;
27+
1. Reads the contents of the Accept header to know which response format to display to the user.
28+
2. Takes the original response content provided by Laravel and convert it to the desired format;
29+
3. Displays the response to the user;
30+
31+
It also provides a custom ExceptionHandler, which allows exceptions to also be thrown in the formats you want. A very nice trick of this handler is that when the route is /api/*, it forces the response to be in JSON, CSV, YAML or XML (if no type is informed via Accept, then JSON is selected by default). This prevents the API, at some point of failure, returns an HTML error.
2732

2833
### Example 1
2934
```
@@ -59,15 +64,40 @@ Accept: application/json
5964
"surname": "Girardi",
6065
"email": "[email protected]",
6166
"created_at": "2019-04-24 20:34:03",
62-
"updated_at": "2019-04-24 20:34:03",
67+
"updated_at": "2019-04-24 20:34:03"
6368
}
6469
]
6570
```
71+
### Example 3
72+
```
73+
GET /api/v1/users
74+
Accept: text/csv
75+
```
76+
#### Output
77+
```csv
78+
id,name,surname,email,created_at,updated_at
79+
30,Guilherme,Girardi,[email protected],"2019-04-24 20:34:03","2019-04-24 20:34:03"
80+
```
81+
### Example 4
82+
```
83+
GET /api/v1/users
84+
Accept: application/x-yaml
85+
```
86+
#### Output
87+
```yaml
88+
id: 30
89+
name: Guilherme
90+
surname: Girardi
91+
92+
created_at: '2019-04-24 20:34:03'
93+
updated_at: '2019-04-24 20:34:03'
94+
```
6695
6796
## Installation
68-
First, run: `composer require ejetar/api-response-formatter`
69-
70-
After, add `Ejetar\ApiResponseFormatter\Providers\AppServiceProvider::class` in `providers` array in `config/app.php` file.
97+
1. First, run: `composer require ejetar/api-response-formatter`;
98+
2. Add `Ejetar\ApiResponseFormatter\Providers\AppServiceProvider::class` in `providers` array in `config/app.php` file;
99+
3. In `app/Exceptions/Handler.php`, replace `Illuminate\Foundation\Exceptions\Handler` with `Ejetar\ApiResponseFormatter\Exceptions\Handler`;
100+
4. In `public/index.php`, replace `Illuminate\Http\Request::capture()` with `Ejetar\ApiResponseFormatter\Requests\Request::capture()`;
71101

72102
## Get started
73103
To start using this package is very simple, just call Middlware `api-response-formatter` in your `routes/api.php` file.

composer.json

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
{
2-
"name": "ejetar/api-response-formatter",
2+
"name": "ejetar/laravel-api-response-formatter",
33
"description": "A simple and fast \uD83D\uDE80 library that displays responses in various formats, according to the Accept header, entered by the user. The library currently displays responses in JSON, XML, CSV, and YAML.\n",
44
"homepage": "https://ejetar.com/projects/api-response-formatter",
5-
"version": "1.1.0-alpha",
6-
"readme": "README.md",
75
"type": "library",
86
"license": "MIT",
7+
"version": "1.0.0",
98
"authors": [
109
{
1110
"name": "Guilherme Almeida Girardi",
@@ -31,21 +30,18 @@
3130
"laravel"
3231
],
3332
"require": {
34-
"php": ">=7.2.5",
35-
"ejetar/accept-header-interpreter": "~1.1.0-alpha",
36-
"ejetar/laravel-formatter": "~3.0",
37-
"illuminate/support": "~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|~6.0|~7.0",
38-
"illuminate/http": "~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|~6.0|~7.0"
33+
"symfony/serializer": "^5.1",
34+
"symfony/yaml": "^5.1",
35+
"laravel/framework": "^7.17"
3936
},
4037
"require-dev": {
4138
"phpunit/phpunit": "^9.0",
4239
"symfony/var-dumper": "~5.0",
4340
"orchestra/testbench": "~3.4|~3.5|~3.6|~3.7|~3.8|~4.0|~5.0"
4441
},
45-
"minimum-stability": "stable",
4642
"autoload": {
4743
"psr-4": {
48-
"Ejetar\\ApiResponseFormatter\\": "src/"
44+
"Ejetar\\ApiResponseFormatter\\": "src/app/"
4945
}
5046
},
5147
"autoload-dev": {
@@ -55,5 +51,14 @@
5551
},
5652
"scripts": {
5753
"test": "vendor\\bin\\phpunit"
54+
},
55+
"minimum-stability": "stable",
56+
"prefer-stable": true,
57+
"extra": {
58+
"laravel": {
59+
"providers": [
60+
"Ejetar\\ApiResponseFormatter\\Providers\\AppServiceProvider"
61+
]
62+
}
5863
}
5964
}

src/app/Exceptions/Handler.php

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
3+
namespace Ejetar\ApiResponseFormatter\Exceptions;
4+
5+
use Ejetar\ApiResponseFormatter\Responses\CsvResponse;
6+
use Ejetar\ApiResponseFormatter\Responses\XmlResponse;
7+
use Ejetar\ApiResponseFormatter\Responses\YamlResponse;
8+
use Illuminate\Contracts\Support\Responsable;
9+
use Illuminate\Http\Exceptions\HttpResponseException;
10+
use Illuminate\Routing\Router;
11+
use Illuminate\Validation\ValidationException;
12+
use Throwable;
13+
use Illuminate\Auth\AuthenticationException;
14+
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
15+
16+
class Handler extends ExceptionHandler {
17+
/**
18+
* Prepare a CSV response for the given exception.
19+
*
20+
* @param \Illuminate\Http\Request $request
21+
* @param \Throwable $e
22+
* @return CsvResponse
23+
*/
24+
protected function prepareCsvResponse($request, Throwable $e) {
25+
return new CsvResponse(
26+
$this->convertExceptionToArray($e),
27+
$this->isHttpException($e) ? $e->getStatusCode() : 500,
28+
$this->isHttpException($e) ? $e->getHeaders() : []
29+
);
30+
}
31+
32+
/**
33+
* Prepare a YAML response for the given exception.
34+
*
35+
* @param \Illuminate\Http\Request $request
36+
* @param \Throwable $e
37+
* @return YamlResponse
38+
*/
39+
protected function prepareYamlResponse($request, Throwable $e) {
40+
return new YamlResponse(
41+
$this->convertExceptionToArray($e),
42+
$this->isHttpException($e) ? $e->getStatusCode() : 500,
43+
$this->isHttpException($e) ? $e->getHeaders() : []
44+
);
45+
}
46+
47+
/**
48+
* Prepare a XML response for the given exception.
49+
*
50+
* @param \Illuminate\Http\Request $request
51+
* @param \Throwable $e
52+
* @return XmlResponse
53+
*/
54+
protected function prepareXmlResponse($request, Throwable $e) {
55+
return new XmlResponse(
56+
$this->convertExceptionToArray($e),
57+
$this->isHttpException($e) ? $e->getStatusCode() : 500,
58+
$this->isHttpException($e) ? $e->getHeaders() : []
59+
);
60+
}
61+
62+
/**
63+
* Convert a validation exception into a XML response.
64+
*
65+
* @param \Illuminate\Http\Request $request
66+
* @param \Illuminate\Validation\ValidationException $exception
67+
* @return XmlResponse
68+
*/
69+
protected function invalidXml($request, ValidationException $exception) {
70+
return new XmlResponse([
71+
'message' => $exception->getMessage(),
72+
'errors' => $exception->errors(),
73+
], $exception->status);
74+
}
75+
76+
/**
77+
* Convert a validation exception into a CSV response.
78+
*
79+
* @param \Illuminate\Http\Request $request
80+
* @param \Illuminate\Validation\ValidationException $exception
81+
* @return CsvResponse
82+
*/
83+
protected function invalidCsv($request, ValidationException $exception) {
84+
return new CsvResponse([
85+
'message' => $exception->getMessage(),
86+
'errors' => $exception->errors(),
87+
], $exception->status);
88+
}
89+
90+
/**
91+
* Convert a validation exception into a YAML response.
92+
*
93+
* @param \Illuminate\Http\Request $request
94+
* @param \Illuminate\Validation\ValidationException $exception
95+
* @return YamlResponse
96+
*/
97+
protected function invalidYaml($request, ValidationException $exception) {
98+
return new YamlResponse([
99+
'message' => $exception->getMessage(),
100+
'errors' => $exception->errors(),
101+
], $exception->status);
102+
}
103+
104+
/**
105+
* Create a response object from the given validation exception.
106+
*
107+
* @param \Illuminate\Validation\ValidationException $e
108+
* @param \Illuminate\Http\Request $request
109+
* @return \Symfony\Component\HttpFoundation\Response
110+
*/
111+
protected function convertValidationExceptionToResponse(ValidationException $e, $request) {
112+
if ($e->response)
113+
return $e->response;
114+
115+
if ($request->expectsJson()) {
116+
return $this->invalidJson($request, $e);
117+
118+
} elseif ($request->expectsXml()) {
119+
return $this->invalidXml($request, $e);
120+
121+
} elseif ($request->expectsCsv()) {
122+
return $this->invalidCsv($request, $e);
123+
124+
} elseif ($request->expectsYaml()) {
125+
return $this->invalidYaml($request, $e);
126+
127+
} else {
128+
$this->invalid($request, $e);
129+
}
130+
}
131+
132+
/**
133+
* Render an exception into an HTTP response.
134+
*
135+
* @param \Illuminate\Http\Request $request
136+
* @param Throwable $exception
137+
* @return \Illuminate\Http\Response
138+
*/
139+
public function render($request, Throwable $e) {
140+
if (method_exists($e, 'render') && $response = $e->render($request)) {
141+
return Router::toResponse($request, $response);
142+
} elseif ($e instanceof Responsable) {
143+
return $e->toResponse($request);
144+
}
145+
146+
$e = $this->prepareException($e);
147+
148+
if ($e instanceof HttpResponseException) {
149+
return $e->getResponse();
150+
} elseif ($e instanceof AuthenticationException) {
151+
return $this->unauthenticated($request, $e);
152+
} elseif ($e instanceof ValidationException) {
153+
return $this->convertValidationExceptionToResponse($e, $request);
154+
}
155+
156+
if ($request->expectsJson()) {
157+
return $this->prepareJsonResponse($request, $e);
158+
159+
} elseif ($request->expectsXml()) {
160+
return $this->prepareXmlResponse($request, $e);
161+
162+
} elseif ($request->expectsCsv()) {
163+
return $this->prepareCsvResponse($request, $e);
164+
165+
} elseif ($request->expectsYaml()) {
166+
return $this->prepareYamlResponse($request, $e);
167+
168+
} else {
169+
if (\Request::is('api/*'))
170+
return $this->prepareJsonResponse($request, $e);
171+
else
172+
return $this->prepareResponse($request, $e);
173+
}
174+
}
175+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Ejetar\ApiResponseFormatter\Http\Middleware;
4+
5+
use Ejetar\ApiResponseFormatter\Responses\CsvResponse;
6+
use Ejetar\ApiResponseFormatter\Responses\XmlResponse;
7+
use Ejetar\ApiResponseFormatter\Responses\YamlResponse;
8+
use Illuminate\Http\JsonResponse;
9+
use Illuminate\Http\Request;
10+
11+
class ApiResponseFormatter {
12+
public function handle(Request $request, $next, $scopeRequired = null) {
13+
$response = $next($request);
14+
15+
//If the Accept Header was entered
16+
$original_content = $response->getOriginalContent();
17+
if (!empty($original_content)) {
18+
$data = is_array($original_content) ? $original_content : $original_content->toArray();
19+
$status = $response->status();
20+
$headers = $response->headers->all();
21+
22+
if ($request->expectsJson()) {
23+
return new JsonResponse($data, $status, $headers);
24+
25+
} elseif ($request->expectsXml()) {
26+
return new XmlResponse($data, $status, $headers);
27+
28+
} elseif ($request->expectsYaml()) {
29+
return new YamlResponse($data, $status, $headers);
30+
31+
} elseif ($request->expectsCsv()) {
32+
return new CsvResponse($data, $status, $headers);
33+
34+
} else {
35+
return new JsonResponse([
36+
'message' => 'Mime-type not accepted.'
37+
], 406);
38+
}
39+
} else {
40+
return $original_content;
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)