diff --git a/src/Exceptions/BadRequestException.php b/src/Exceptions/BadRequestException.php new file mode 100644 index 0000000..0e1500a --- /dev/null +++ b/src/Exceptions/BadRequestException.php @@ -0,0 +1,15 @@ +term_id = $error->data->term_id ?? null; + + parent::__construct($error->message, $code); + } + + public function getTermId(): ?int + { + return $this->term_id; + } +} diff --git a/src/Objects/WordPressError.php b/src/Objects/WordPressError.php new file mode 100644 index 0000000..9fe24ef --- /dev/null +++ b/src/Objects/WordPressError.php @@ -0,0 +1,16 @@ + * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function list(): array { $data = $this->request('get', '/categories'); if (!is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return array_map( @@ -37,15 +35,14 @@ public function list(): array * * @param array $arguments * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function create(array $arguments): CategoryObject { $data = $this->request('post', '/categories', $arguments); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return CategoryObject::from($data); @@ -54,8 +51,7 @@ public function create(array $arguments): CategoryObject /** * https://developer.wordpress.org/rest-api/reference/categories/#retrieve-a-category * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function retrieve(int $categoryId, string $context = 'view'): CategoryObject { @@ -66,7 +62,7 @@ public function retrieve(int $categoryId, string $context = 'view'): CategoryObj ]); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return CategoryObject::from($data); @@ -77,8 +73,7 @@ public function retrieve(int $categoryId, string $context = 'view'): CategoryObj * * @param array $arguments * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function update(int $categoryId, array $arguments): CategoryObject { @@ -87,7 +82,7 @@ public function update(int $categoryId, array $arguments): CategoryObject $data = $this->request('patch', $uri, $arguments); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return CategoryObject::from($data); @@ -96,8 +91,7 @@ public function update(int $categoryId, array $arguments): CategoryObject /** * https://developer.wordpress.org/rest-api/reference/categories/#delete-a-category * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function delete(int $categoryId): bool { diff --git a/src/Requests/GeneralRequest.php b/src/Requests/GeneralRequest.php index 94c5b97..b7c08d7 100644 --- a/src/Requests/GeneralRequest.php +++ b/src/Requests/GeneralRequest.php @@ -5,6 +5,7 @@ namespace Storipress\WordPress\Requests; use stdClass; +use Storipress\WordPress\Exceptions\WordPressException; class GeneralRequest extends Request { @@ -13,8 +14,7 @@ class GeneralRequest extends Request * @param array $arguments * @return stdClass|array * - * @throws \Storipress\WordPress\Exceptions\HttpException - * @throws \Storipress\WordPress\Exceptions\UnexpectedValueException + * @throws WordPressException */ public function get(string $path, array $arguments): stdClass|array { @@ -26,8 +26,7 @@ public function get(string $path, array $arguments): stdClass|array * @param array $arguments * @return stdClass|array * - * @throws \Storipress\WordPress\Exceptions\HttpException - * @throws \Storipress\WordPress\Exceptions\UnexpectedValueException + * @throws WordPressException */ public function post(string $path, array $arguments): stdClass|array { @@ -39,8 +38,7 @@ public function post(string $path, array $arguments): stdClass|array * @param array $arguments * @return stdClass|array * - * @throws \Storipress\WordPress\Exceptions\HttpException - * @throws \Storipress\WordPress\Exceptions\UnexpectedValueException + * @throws WordPressException */ public function patch(string $path, array $arguments): stdClass|array { @@ -51,8 +49,7 @@ public function patch(string $path, array $arguments): stdClass|array * @param non-empty-string $path * @param array $arguments * - * @throws \Storipress\WordPress\Exceptions\HttpException - * @throws \Storipress\WordPress\Exceptions\UnexpectedValueException + * @throws WordPressException */ public function delete(string $path, array $arguments): bool { diff --git a/src/Requests/Post.php b/src/Requests/Post.php index a2f7209..02b1dae 100644 --- a/src/Requests/Post.php +++ b/src/Requests/Post.php @@ -4,8 +4,7 @@ namespace Storipress\WordPress\Requests; -use Storipress\WordPress\Exceptions\HttpException; -use Storipress\WordPress\Exceptions\UnexpectedValueException; +use Storipress\WordPress\Exceptions\WordPressException; use Storipress\WordPress\Objects\Post as PostObject; class Post extends Request @@ -15,15 +14,14 @@ class Post extends Request * * @return array * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function list(): array { $data = $this->request('get', '/posts'); if (!is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return array_map( @@ -37,15 +35,14 @@ public function list(): array * * @param array $arguments * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function create(array $arguments): PostObject { $data = $this->request('post', '/posts', $arguments); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return PostObject::from($data); @@ -54,8 +51,7 @@ public function create(array $arguments): PostObject /** * https://developer.wordpress.org/rest-api/reference/posts/#retrieve-a-post * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function retrieve(int $postId, string $context = 'view'): PostObject { @@ -66,7 +62,7 @@ public function retrieve(int $postId, string $context = 'view'): PostObject ]); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return PostObject::from($data); @@ -77,8 +73,7 @@ public function retrieve(int $postId, string $context = 'view'): PostObject * * @param array $arguments * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function update(int $postId, array $arguments): PostObject { @@ -87,7 +82,7 @@ public function update(int $postId, array $arguments): PostObject $data = $this->request('patch', $uri, $arguments); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return PostObject::from($data); @@ -96,8 +91,7 @@ public function update(int $postId, array $arguments): PostObject /** * https://developer.wordpress.org/rest-api/reference/posts/#delete-a-post * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function delete(int $postId, bool $force = false): bool { diff --git a/src/Requests/Request.php b/src/Requests/Request.php index b123034..f47e936 100644 --- a/src/Requests/Request.php +++ b/src/Requests/Request.php @@ -4,11 +4,22 @@ namespace Storipress\WordPress\Requests; +use Illuminate\Http\Client\Response; +use JsonSchema\Constraints\Constraint; +use JsonSchema\Validator; use stdClass; -use Storipress\WordPress\Exceptions\HttpException; -use Storipress\WordPress\Exceptions\HttpUnknownError; +use Storipress\WordPress\Exceptions\BadRequestException; +use Storipress\WordPress\Exceptions\CannotCreateException; +use Storipress\WordPress\Exceptions\CannotUpdateException; +use Storipress\WordPress\Exceptions\DuplicateTermSlugException; +use Storipress\WordPress\Exceptions\ForbiddenException; use Storipress\WordPress\Exceptions\NotFoundException; +use Storipress\WordPress\Exceptions\TermExistsException; +use Storipress\WordPress\Exceptions\UnauthorizedException; use Storipress\WordPress\Exceptions\UnexpectedValueException; +use Storipress\WordPress\Exceptions\UnknownException; +use Storipress\WordPress\Exceptions\WordPressException; +use Storipress\WordPress\Objects\WordPressError; use Storipress\WordPress\WordPress; abstract class Request @@ -27,8 +38,7 @@ public function __construct( * @param array $options * @return ($method is 'delete' ? bool : stdClass|array) * - * @throws UnexpectedValueException - * @throws HttpException + * @throws UnexpectedValueException|WordPressException */ protected function request( string $method, @@ -53,11 +63,22 @@ protected function request( $options, ); + if (!($response instanceof Response)) { + throw $this->unexpectedValueException(); + } + + $payload = $response->object(); + + // @phpstan-ignore-next-line + if (!($payload instanceof stdClass) && !is_array($payload)) { + throw $this->unexpectedValueException(); + } + if (!$response->successful()) { $this->error( + $payload, $response->body(), $response->status(), - $response->headers(), ); } @@ -65,13 +86,7 @@ protected function request( return $response->successful(); } - $data = $response->object(); - - if (!($data instanceof stdClass) && !is_array($data)) { - throw new UnexpectedValueException(); - } - - return $data; + return $payload; } public function getUrl(string $path, string $prefix, bool $pretty): string @@ -95,16 +110,62 @@ public function getUrl(string $path, string $prefix, bool $pretty): string } /** - * @param array> $headers - * - * @throws HttpException + * @throws WordPressException */ - protected function error(string $message, int $code, array $headers): void + protected function error(stdClass $payload, string $message, int $status): void { - throw match ($code) { - 404 => new NotFoundException($message, $code), + if ($this->validate($payload)) { + $error = WordPressError::from($payload); + + throw match ($error->code) { + 'term_exists' => new TermExistsException($error, $status), + 'duplicate_term_slug' => new DuplicateTermSlugException($error, $status), + 'rest_cannot_create' => new CannotCreateException($error, $status), + 'rest_cannot_update' => new CannotUpdateException($error, $status), + default => new UnknownException($error, $status), + }; + } - default => new HttpUnknownError($message, $code), + $error = WordPressError::from((object) [ + 'code' => (string) $status, + 'message' => $message, + 'data' => (object) [], + ]); + + throw match ($status) { + 400 => new BadRequestException($error), + 401 => new UnauthorizedException($error), + 403 => new ForbiddenException($error), + 404 => new NotFoundException($error), + default => new UnknownException($error, $status), }; } + + protected function validate(stdClass $data): bool + { + $file = realpath( + sprintf('%s/../Schemas/exception.json', __DIR__), + ); + + if ($file === false) { + return false; + } + + $path = sprintf('file://%s', $file); + + $validator = new Validator(); + + $validator->validate($data, ['$ref' => $path], Constraint::CHECK_MODE_NORMAL | Constraint::CHECK_MODE_VALIDATE_SCHEMA); + + return $validator->isValid(); + } + + protected function unexpectedValueException(): UnexpectedValueException + { + return new UnexpectedValueException(WordPressError::from((object) [ + 'message' => 'Unexpected value.', + 'code' => '500', + 'data' => (object) [], + ])); + } } diff --git a/src/Requests/Tag.php b/src/Requests/Tag.php index 6b86c2a..9e55aaa 100644 --- a/src/Requests/Tag.php +++ b/src/Requests/Tag.php @@ -4,8 +4,7 @@ namespace Storipress\WordPress\Requests; -use Storipress\WordPress\Exceptions\HttpException; -use Storipress\WordPress\Exceptions\UnexpectedValueException; +use Storipress\WordPress\Exceptions\WordPressException; use Storipress\WordPress\Objects\Tag as TagObject; class Tag extends Request @@ -15,15 +14,14 @@ class Tag extends Request * * @return array * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function list(): array { $data = $this->request('get', '/tags'); if (!is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return array_map( @@ -37,15 +35,14 @@ public function list(): array * * @param array $arguments * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function create(array $arguments): TagObject { $data = $this->request('post', '/tags', $arguments); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return TagObject::from($data); @@ -54,8 +51,7 @@ public function create(array $arguments): TagObject /** * https://developer.wordpress.org/rest-api/reference/tags/#retrieve-a-tag * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function retrieve(int $tagId, string $context = 'view'): TagObject { @@ -66,7 +62,7 @@ public function retrieve(int $tagId, string $context = 'view'): TagObject ]); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return TagObject::from($data); @@ -77,8 +73,7 @@ public function retrieve(int $tagId, string $context = 'view'): TagObject * * @param array $arguments * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function update(int $tagId, array $arguments): TagObject { @@ -87,7 +82,7 @@ public function update(int $tagId, array $arguments): TagObject $data = $this->request('patch', $uri, $arguments); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return TagObject::from($data); @@ -96,8 +91,7 @@ public function update(int $tagId, array $arguments): TagObject /** * https://developer.wordpress.org/rest-api/reference/tags/#delete-a-tag * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function delete(int $tagId): bool { diff --git a/src/Requests/User.php b/src/Requests/User.php index 263b8c8..28fecff 100644 --- a/src/Requests/User.php +++ b/src/Requests/User.php @@ -4,8 +4,7 @@ namespace Storipress\WordPress\Requests; -use Storipress\WordPress\Exceptions\HttpException; -use Storipress\WordPress\Exceptions\UnexpectedValueException; +use Storipress\WordPress\Exceptions\WordPressException; use Storipress\WordPress\Objects\User as UserObject; class User extends Request @@ -15,15 +14,14 @@ class User extends Request * * @return array * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function list(): array { $data = $this->request('get', '/users'); if (!is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return array_map( @@ -37,15 +35,14 @@ public function list(): array * * @param array $arguments * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function create(array $arguments): UserObject { $data = $this->request('post', '/users', $arguments); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return UserObject::from($data); @@ -54,8 +51,7 @@ public function create(array $arguments): UserObject /** * https://developer.wordpress.org/rest-api/reference/users/#retrieve-a-user * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function retrieve(int $userId, string $context = 'view'): UserObject { @@ -66,7 +62,7 @@ public function retrieve(int $userId, string $context = 'view'): UserObject ]); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return UserObject::from($data); @@ -77,8 +73,7 @@ public function retrieve(int $userId, string $context = 'view'): UserObject * * @param array $arguments * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function update(int $userId, array $arguments): UserObject { @@ -87,7 +82,7 @@ public function update(int $userId, array $arguments): UserObject $data = $this->request('patch', $uri, $arguments); if (is_array($data)) { - throw new UnexpectedValueException(); + throw $this->unexpectedValueException(); } return UserObject::from($data); @@ -96,8 +91,7 @@ public function update(int $userId, array $arguments): UserObject /** * https://developer.wordpress.org/rest-api/reference/users/#delete-a-user * - * @throws HttpException - * @throws UnexpectedValueException + * @throws WordPressException */ public function delete(int $userId, int $reassign): bool { diff --git a/src/Schemas/exception.json b/src/Schemas/exception.json new file mode 100644 index 0000000..da792dd --- /dev/null +++ b/src/Schemas/exception.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "id": "/schemas/exception", + "type": "object", + "additionalProperties": true, + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "data": { + "type": [ + "object", + "null" + ], + "properties": { + "status": { + "type": "integer" + }, + "term_id": { + "type": "integer" + } + } + }, + "additional_data": { + "type": "array" + } + }, + "required":[ + "code", + "message", + "data" + ] +} diff --git a/tests/ArchitectureTest.php b/tests/ArchitectureTest.php index e01127c..8138af6 100644 --- a/tests/ArchitectureTest.php +++ b/tests/ArchitectureTest.php @@ -1,6 +1,6 @@ expect('Storipress\WordPress\Exceptions') ->classes() - ->toExtend(Exception::class); + ->toExtend(WordPressException::class);