Skip to content

Commit

Permalink
feat(laravel-api-problem): add class, exception and http laravel api …
Browse files Browse the repository at this point in the history
…problem and stubs
  • Loading branch information
pedrosalpr committed Feb 10, 2024
1 parent 504d49a commit d4395eb
Show file tree
Hide file tree
Showing 11 changed files with 579 additions and 19 deletions.
33 changes: 33 additions & 0 deletions src/Commands/LaravelApiProblemCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Pedrosalpr\LaravelApiProblem\Commands;

use Illuminate\Console\GeneratorCommand;

class LaravelApiProblemCommand extends GeneratorCommand
{
public $signature = 'laravel-api-problem:extend {name}';

public $description = 'Extend class api problem';

protected $type = 'LaravelApiProblem';

protected function getStub(): string
{
return $this->resolveStubPath('/Stubs/dummy.stub');
}

protected function resolveStubPath(string $stub): string
{
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
? $customPath
: __DIR__ . $stub;
}

protected function getDefaultNamespace($rootNamespace): string
{
return "{$rootNamespace}\\ApiProblem";
}
}
49 changes: 49 additions & 0 deletions src/Commands/LaravelApiProblemExceptionCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Pedrosalpr\LaravelApiProblem\Commands;

use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputArgument;

class LaravelApiProblemExceptionCommand extends GeneratorCommand
{
public $signature = 'laravel-api-problem:exception {name}';

public $description = 'Make class api problem exception';

protected $type = 'LaravelApiProblemException';

protected function getStub(): string
{
return $this->resolveStubPath('/Stubs/exception.stub');
}

protected function resolveStubPath(string $stub): string
{
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
? $customPath
: __DIR__ . $stub;
}

protected function getDefaultNamespace($rootNamespace): string
{
return "{$rootNamespace}\\Exceptions\\ApiProblem";
}

protected function replaceClass($stub, $name)
{
$class = str_replace($this->getNamespace($name) . '\\', '', $name);

// Do string replacement
return str_replace('{{ class }}', $class, $stub);
}

protected function getArguments()
{
return [
['name', InputArgument::REQUIRED, 'The name and root of the file.'],
];
}
}
34 changes: 34 additions & 0 deletions src/Commands/Stubs/dummy.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace {{ namespace }};

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pedrosalpr\LaravelApiProblem\Http\LaravelHttpApiProblem;
use Pedrosalpr\LaravelApiProblem\LaravelApiProblem;

class {{ class }} extends LaravelApiProblem
{
public function __construct(
protected \Throwable $exception,
protected Request $request
) {
match (get_class($exception)) {
\Exception::class => $this->dummy(),
default => parent::__construct($exception, $request)
};
}

protected function dummy()
{
$extensions = [
'errors' => "Dummy",
];
$this->apiProblem = new LaravelHttpApiProblem(
Response::HTTP_I_AM_A_TEAPOT,
$this->exception->getMessage(),
$this->getUriInstance(),
$extensions
);
}
}
10 changes: 10 additions & 0 deletions src/Commands/Stubs/exception.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace {{ namespace }};

use Pedrosalpr\LaravelApiProblem\Exceptions\LaravelApiProblemException;

class {{ class }} extends LaravelApiProblemException
{

}
53 changes: 53 additions & 0 deletions src/Exceptions/LaravelApiProblemException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Pedrosalpr\LaravelApiProblem\Exceptions;

use Pedrosalpr\LaravelApiProblem\Http\LaravelHttpApiProblem;

class LaravelApiProblemException extends \Exception
{
public function __construct(
protected int $statusCode,
protected string $detail,
protected string $instance,
protected array $extensions = [],
protected ?string $title = null,
protected string $type = LaravelHttpApiProblem::TYPE_ABOUT_BLANK,
int $code = 0,
?\Throwable $previous = null,
) {
parent::__construct($detail, $code, $previous);
}

public function getStatusCode(): int
{
return $this->statusCode;
}

public function getDetail(): string
{
return $this->detail;
}

public function getInstance(): string
{
return $this->instance;
}

public function getExtensions(): array
{
return $this->extensions;
}

public function getTitle(): ?string
{
return $this->title;
}

public function getType(): string
{
return $this->type;
}
}
131 changes: 131 additions & 0 deletions src/Http/LaravelHttpApiProblem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace Pedrosalpr\LaravelApiProblem\Http;

use Illuminate\Support\Carbon;
use Pedrosalpr\LaravelApiProblem\LaravelApiProblemInterface;

class LaravelHttpApiProblem implements LaravelApiProblemInterface
{
public const TYPE_ABOUT_BLANK = 'about:blank';

private const HEADER_PROBLEM_JSON = 'application/problem+json';

private static $statusTitles = [
// CLIENT ERROR
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Time-out',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Large',
415 => 'Unsupported Media Type',
416 => 'Requested range not satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot',
419 => 'Page Expired',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Unordered Collection',
426 => 'Upgrade Required',
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
444 => 'Connection Closed Without Response',
451 => 'Unavailable For Legal Reasons',
499 => 'Client Closed Request',
// SERVER ERROR
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Time-out',
505 => 'HTTP Version not supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
511 => 'Network Authentication Required',
];

private Carbon $timestamp;

public function __construct(
private int $statusCode,
private string $detail,
private string $instance,
private array $extensions = [],
private ?string $title = null,
private string $type = self::TYPE_ABOUT_BLANK
) {
if ($this->statusCode < 400 || $this->statusCode > 599) {
$this->statusCode = 400;
}
if (!filter_var($this->type, FILTER_VALIDATE_URL) || empty($this->title)) {
$this->title = $this->getTitleForStatusCode($this->statusCode);
$this->type = self::TYPE_ABOUT_BLANK;
}
$this->timestamp = Carbon::now();
}

public function getTitle(): string
{
return $this->title;
}

public function getType(): string
{
return $this->type;
}

public function getDetail(): string
{
return $this->detail;
}

public function getStatusCode(): int
{
return $this->statusCode;
}

public function getExtensions(): array
{
return $this->extensions;
}

public function toArray(): array
{
return array_merge(
[
'status' => $this->statusCode,
'type' => $this->type,
'title' => $this->title,
'detail' => $this->detail,
'instance' => $this->instance,
'timestamp' => $this->timestamp->toJSON()
],
$this->extensions
);
}

public function getHeaderProblemJson(): string
{
return self::HEADER_PROBLEM_JSON;
}

private function getTitleForStatusCode(int $statusCode): string
{
return self::$statusTitles[$statusCode] ?? 'Unknown';
}
}
Loading

0 comments on commit d4395eb

Please sign in to comment.