mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Prototype user modification
This commit is contained in:
parent
2946d950f2
commit
f58005640a
3 changed files with 122 additions and 25 deletions
|
@ -16,7 +16,8 @@ use JKingWeb\Arsse\Misc\HTTP;
|
||||||
use JKingWeb\Arsse\Misc\Date;
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
use JKingWeb\Arsse\Misc\ValueInfo as V;
|
use JKingWeb\Arsse\Misc\ValueInfo as V;
|
||||||
use JKingWeb\Arsse\REST\Exception;
|
use JKingWeb\Arsse\REST\Exception;
|
||||||
use JKingWeb\Arsse\User\ExceptionConflict as UserException;
|
use JKingWeb\Arsse\User\ExceptionConflict;
|
||||||
|
use JKingWeb\Arsse\User\Exception as UserException;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Laminas\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
@ -29,12 +30,26 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
protected const ACCEPTED_TYPES_JSON = ["application/json"];
|
protected const ACCEPTED_TYPES_JSON = ["application/json"];
|
||||||
protected const TOKEN_LENGTH = 32;
|
protected const TOKEN_LENGTH = 32;
|
||||||
protected const VALID_JSON = [
|
protected const VALID_JSON = [
|
||||||
|
// user properties which map directly to Arsse user metadata are listed separately
|
||||||
'url' => "string",
|
'url' => "string",
|
||||||
'username' => "string",
|
'username' => "string",
|
||||||
'password' => "string",
|
'password' => "string",
|
||||||
'user_agent' => "string",
|
'user_agent' => "string",
|
||||||
'title' => "string",
|
'title' => "string",
|
||||||
];
|
];
|
||||||
|
protected const USER_META_MAP = [
|
||||||
|
// Miniflux ID // Arsse ID Default value Extra
|
||||||
|
'is_admin' => ["admin", false, false],
|
||||||
|
'theme' => ["theme", "light_serif", false],
|
||||||
|
'language' => ["lang", "en_US", false],
|
||||||
|
'timezone' => ["tz", "UTC", false],
|
||||||
|
'entry_sorting_direction' => ["sort_asc", false, false],
|
||||||
|
'entries_per_page' => ["page_size", 100, false],
|
||||||
|
'keyboard_shortcuts' => ["shortcuts", true, false],
|
||||||
|
'show_reading_time' => ["reading_time", true, false],
|
||||||
|
'entry_swipe' => ["swipe", true, false],
|
||||||
|
'custom_css' => ["stylesheet", "", true],
|
||||||
|
];
|
||||||
protected const CALLS = [ // handler method Admin Path Body Query
|
protected const CALLS = [ // handler method Admin Path Body Query
|
||||||
'/categories' => [
|
'/categories' => [
|
||||||
'GET' => ["getCategories", false, false, false, false],
|
'GET' => ["getCategories", false, false, false, false],
|
||||||
|
@ -102,7 +117,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
],
|
],
|
||||||
'/users/1' => [
|
'/users/1' => [
|
||||||
'GET' => ["getUserByNum", true, true, false, false],
|
'GET' => ["getUserByNum", true, true, false, false],
|
||||||
'PUT' => ["updateUserByNum", true, true, true, false],
|
'PUT' => ["updateUserByNum", false, true, true, false], // requires admin for users other than self
|
||||||
'DELETE' => ["deleteUserByNum", true, true, false, false],
|
'DELETE' => ["deleteUserByNum", true, true, false, false],
|
||||||
],
|
],
|
||||||
'/users/1/mark-all-as-read' => [
|
'/users/1/mark-all-as-read' => [
|
||||||
|
@ -246,7 +261,17 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
if (!isset($body[$k])) {
|
if (!isset($body[$k])) {
|
||||||
$body[$k] = null;
|
$body[$k] = null;
|
||||||
} elseif (gettype($body[$k]) !== $t) {
|
} elseif (gettype($body[$k]) !== $t) {
|
||||||
return new ErrorResponse(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])]);
|
return new ErrorResponse(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (self::USER_META_MAP as $k => [,$d,]) {
|
||||||
|
$t = gettype($d);
|
||||||
|
if (!isset($body[$k])) {
|
||||||
|
$body[$k] = null;
|
||||||
|
} elseif (gettype($body[$k]) !== $t) {
|
||||||
|
return new ErrorResponse(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422);
|
||||||
|
} elseif ($k === "entry_sorting_direction" && !in_array($body[$k], ["asc", "desc"])) {
|
||||||
|
return new ErrorResponse(["InvalidInputValue", 'field' => $k], 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $body;
|
return $body;
|
||||||
|
@ -285,23 +310,23 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$out[] = [
|
$entry = [
|
||||||
'id' => $info['num'],
|
'id' => $info['num'],
|
||||||
'username' => $u,
|
'username' => $u,
|
||||||
'is_admin' => $info['admin'] ?? false,
|
|
||||||
'theme' => $info['theme'] ?? "light_serif",
|
|
||||||
'language' => $info['lang'] ?? "en_US",
|
|
||||||
'timezone' => $info['tz'] ?? "UTC",
|
|
||||||
'entry_sorting_direction' => ($info['sort_asc'] ?? false) ? "asc" : "desc",
|
|
||||||
'entries_per_page' => $info['page_size'] ?? 100,
|
|
||||||
'keyboard_shortcuts' => $info['shortcuts'] ?? true,
|
|
||||||
'show_reading_time' => $info['reading_time'] ?? true,
|
|
||||||
'last_login_at' => $now,
|
'last_login_at' => $now,
|
||||||
'entry_swipe' => $info['swipe'] ?? true,
|
|
||||||
'extra' => [
|
|
||||||
'custom_css' => $info['stylesheet'] ?? "",
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
foreach (self::USER_META_MAP as $ext => [$int, $default, $extra]) {
|
||||||
|
if (!$extra) {
|
||||||
|
$entry[$ext] = $info[$int] ?? $default;
|
||||||
|
} else {
|
||||||
|
if (!isset($entry['extra'])) {
|
||||||
|
$entry['extra'] = [];
|
||||||
|
}
|
||||||
|
$entry['extra'][$ext] = $info[$int] ?? $default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$entry['entry_sorting_direction'] = ($entry['entry_sorting_direction']) ? "asc" : "desc";
|
||||||
|
$out[] = $entry;
|
||||||
}
|
}
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
@ -326,6 +351,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getUsers(): ResponseInterface {
|
protected function getUsers(): ResponseInterface {
|
||||||
|
$tr = Arsse::$user->begin();
|
||||||
return new Response($this->listUsers(Arsse::$user->list(), false));
|
return new Response($this->listUsers(Arsse::$user->list(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,6 +376,70 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return new Response($this->listUsers([Arsse::$user->id], false)[0] ?? new \stdClass);
|
return new Response($this->listUsers([Arsse::$user->id], false)[0] ?? new \stdClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function updateUserByNum(array $data, array $path): ResponseInterface {
|
||||||
|
try {
|
||||||
|
if (!$this->isAdmin()) {
|
||||||
|
// this function is restricted to admins unless the affected user and calling user are the same
|
||||||
|
if (Arsse::$db->userLookup((int) $path[1]) !== Arsse::$user->id) {
|
||||||
|
return new ErrorResponse("403", 403);
|
||||||
|
} elseif ($data['is_admin']) {
|
||||||
|
// non-admins should not be able to set themselves as admin
|
||||||
|
return new ErrorResponse("InvalidElevation");
|
||||||
|
}
|
||||||
|
$user = Arsse::$user->id;
|
||||||
|
} else {
|
||||||
|
$user = Arsse::$db->userLookup((int) $path[1]);
|
||||||
|
}
|
||||||
|
} catch (ExceptionConflict $e) {
|
||||||
|
return new ErrorResponse("404", 404);
|
||||||
|
}
|
||||||
|
// map Miniflux properties to internal metadata properties
|
||||||
|
$in = [];
|
||||||
|
foreach (self::USER_META_MAP as $i => [$o,,]) {
|
||||||
|
if (isset($data[$i])) {
|
||||||
|
if ($i === "entry_sorting_direction") {
|
||||||
|
$in[$o] = $data[$i] === "asc";
|
||||||
|
} else {
|
||||||
|
$in[$o] = $data[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// make any requested changes
|
||||||
|
try {
|
||||||
|
$tr = Arsse::$user->begin();
|
||||||
|
if (isset($data['username'])) {
|
||||||
|
Arsse::$user->rename($user, $data['username']);
|
||||||
|
$user = $data['username'];
|
||||||
|
}
|
||||||
|
if (isset($data['password'])) {
|
||||||
|
Arsse::$user->passwordSet($user, $data['password']);
|
||||||
|
}
|
||||||
|
if ($in) {
|
||||||
|
Arsse::$user->propertiesSet($user, $in);
|
||||||
|
}
|
||||||
|
// read out the newly-modified user and commit the changes
|
||||||
|
$out = $this->listUsers([$user], true)[0];
|
||||||
|
$tr->commit();
|
||||||
|
} catch (UserException $e) {
|
||||||
|
switch ($e->getCode()) {
|
||||||
|
case 10403:
|
||||||
|
return new ErrorResponse(["DuplicateUser", 'user' => $data['username']], 409);
|
||||||
|
case 20441:
|
||||||
|
return new ErrorResponse(["InvalidTimeone", 'tz' => $data['timezone']], 422);
|
||||||
|
case 10443:
|
||||||
|
return new ErrorResponse("InvalidPageSize", 422);
|
||||||
|
case 10444:
|
||||||
|
return new ErrorResponse(["InvalidUsername", $e->getMessage()], 422);
|
||||||
|
}
|
||||||
|
throw $e; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
// add the input password if a password change was requested
|
||||||
|
if (isset($data['password'])) {
|
||||||
|
$out['password'] = $data['password'];
|
||||||
|
}
|
||||||
|
return new Response($out);
|
||||||
|
}
|
||||||
|
|
||||||
protected function getCategories(): ResponseInterface {
|
protected function getCategories(): ResponseInterface {
|
||||||
$out = [];
|
$out = [];
|
||||||
$meta = Arsse::$user->propertiesGet(Arsse::$user->id, false);
|
$meta = Arsse::$user->propertiesGet(Arsse::$user->id, false);
|
||||||
|
@ -374,7 +464,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$meta = Arsse::$user->propertiesGet(Arsse::$user->id, false);
|
$meta = Arsse::$user->propertiesGet(Arsse::$user->id, false);
|
||||||
return new Response(['id' => $id + 1, 'title' => $data['title'], 'user_id' => $meta['num']]);
|
return new Response(['id' => $id + 1, 'title' => $data['title'], 'user_id' => $meta['num']], 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function updateCategory(array $path, array $data): ResponseInterface {
|
protected function updateCategory(array $path, array $data): ResponseInterface {
|
||||||
|
@ -449,7 +539,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
|
|
||||||
public static function tokenList(string $user): array {
|
public static function tokenList(string $user): array {
|
||||||
if (!Arsse::$db->userExists($user)) {
|
if (!Arsse::$db->userExists($user)) {
|
||||||
throw new UserException("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
throw new ExceptionConflict("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
$out = [];
|
$out = [];
|
||||||
foreach (Arsse::$db->tokenList($user, "miniflux.login") as $r) {
|
foreach (Arsse::$db->tokenList($user, "miniflux.login") as $r) {
|
||||||
|
|
|
@ -13,12 +13,18 @@ return [
|
||||||
'API.Miniflux.Error.404' => 'Resource Not Found',
|
'API.Miniflux.Error.404' => 'Resource Not Found',
|
||||||
'API.Miniflux.Error.InvalidBodyJSON' => 'Invalid JSON payload: {0}',
|
'API.Miniflux.Error.InvalidBodyJSON' => 'Invalid JSON payload: {0}',
|
||||||
'API.Miniflux.Error.InvalidInputType' => 'Input key "{field}" of type {actual} was expected as {expected}',
|
'API.Miniflux.Error.InvalidInputType' => 'Input key "{field}" of type {actual} was expected as {expected}',
|
||||||
|
'API.Miniflux.Error.InvalidInputValue' => 'Supplied value is not valid for input key "{field}"',
|
||||||
'API.Miniflux.Error.Fetch404' => 'Resource not found (404), this feed doesn\'t exists anymore, check the feed URL',
|
'API.Miniflux.Error.Fetch404' => 'Resource not found (404), this feed doesn\'t exists anymore, check the feed URL',
|
||||||
'API.Miniflux.Error.Fetch401' => 'You are not authorized to access this resource (invalid username/password)',
|
'API.Miniflux.Error.Fetch401' => 'You are not authorized to access this resource (invalid username/password)',
|
||||||
'API.Miniflux.Error.Fetch403' => 'Unable to fetch this resource (Status Code = 403)',
|
'API.Miniflux.Error.Fetch403' => 'Unable to fetch this resource (Status Code = 403)',
|
||||||
'API.Miniflux.Error.FetchOther' => 'Unable to fetch this resource',
|
'API.Miniflux.Error.FetchOther' => 'Unable to fetch this resource',
|
||||||
'API.Miniflux.Error.DuplicateCategory' => 'Category "{title}" already exists',
|
'API.Miniflux.Error.DuplicateCategory' => 'Category "{title}" already exists',
|
||||||
'API.Miniflux.Error.InvalidCategory' => 'Invalid category title "{title}"',
|
'API.Miniflux.Error.InvalidCategory' => 'Invalid category title "{title}"',
|
||||||
|
'API.Miniflux.Error.InvalidElevation' => 'Only administrators can change permissions of standard users',
|
||||||
|
'API.Miniflux.Error.DuplicateUser' => 'The user name "{user}" already exists',
|
||||||
|
'API.Miniflux.Error.InvalidUser' => '{0}',
|
||||||
|
'API.Miniflux.Error.InvalidTimezone' => 'Specified time zone "{tz}" is invalid',
|
||||||
|
'API.Miniflux.Error.InvalidPageSize' => 'Page size must be greater than zero',
|
||||||
|
|
||||||
'API.TTRSS.Category.Uncategorized' => 'Uncategorized',
|
'API.TTRSS.Category.Uncategorized' => 'Uncategorized',
|
||||||
'API.TTRSS.Category.Special' => 'Special',
|
'API.TTRSS.Category.Special' => 'Special',
|
||||||
|
|
|
@ -16,6 +16,7 @@ use JKingWeb\Arsse\Misc\Date;
|
||||||
use JKingWeb\Arsse\REST\Miniflux\V1;
|
use JKingWeb\Arsse\REST\Miniflux\V1;
|
||||||
use JKingWeb\Arsse\REST\Miniflux\ErrorResponse;
|
use JKingWeb\Arsse\REST\Miniflux\ErrorResponse;
|
||||||
use JKingWeb\Arsse\User\ExceptionConflict;
|
use JKingWeb\Arsse\User\ExceptionConflict;
|
||||||
|
use JKingWeb\Arsse\User\Exception;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Laminas\Diactoros\Response\JsonResponse as Response;
|
use Laminas\Diactoros\Response\JsonResponse as Response;
|
||||||
use Laminas\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
@ -32,6 +33,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
[
|
[
|
||||||
'id' => 1,
|
'id' => 1,
|
||||||
'username' => "john.doe@example.com",
|
'username' => "john.doe@example.com",
|
||||||
|
'last_login_at' => self::NOW,
|
||||||
'is_admin' => true,
|
'is_admin' => true,
|
||||||
'theme' => "custom",
|
'theme' => "custom",
|
||||||
'language' => "fr_CA",
|
'language' => "fr_CA",
|
||||||
|
@ -40,7 +42,6 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
'entries_per_page' => 200,
|
'entries_per_page' => 200,
|
||||||
'keyboard_shortcuts' => false,
|
'keyboard_shortcuts' => false,
|
||||||
'show_reading_time' => false,
|
'show_reading_time' => false,
|
||||||
'last_login_at' => self::NOW,
|
|
||||||
'entry_swipe' => false,
|
'entry_swipe' => false,
|
||||||
'extra' => [
|
'extra' => [
|
||||||
'custom_css' => "p {}",
|
'custom_css' => "p {}",
|
||||||
|
@ -49,6 +50,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
[
|
[
|
||||||
'id' => 2,
|
'id' => 2,
|
||||||
'username' => "jane.doe@example.com",
|
'username' => "jane.doe@example.com",
|
||||||
|
'last_login_at' => self::NOW,
|
||||||
'is_admin' => false,
|
'is_admin' => false,
|
||||||
'theme' => "light_serif",
|
'theme' => "light_serif",
|
||||||
'language' => "en_US",
|
'language' => "en_US",
|
||||||
|
@ -57,7 +59,6 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
'entries_per_page' => 100,
|
'entries_per_page' => 100,
|
||||||
'keyboard_shortcuts' => true,
|
'keyboard_shortcuts' => true,
|
||||||
'show_reading_time' => true,
|
'show_reading_time' => true,
|
||||||
'last_login_at' => self::NOW,
|
|
||||||
'entry_swipe' => true,
|
'entry_swipe' => true,
|
||||||
'extra' => [
|
'extra' => [
|
||||||
'custom_css' => "",
|
'custom_css' => "",
|
||||||
|
@ -166,7 +167,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRejectBadlyTypedData(): void {
|
public function testRejectBadlyTypedData(): void {
|
||||||
$exp = new ErrorResponse(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 400);
|
$exp = new ErrorResponse(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422);
|
||||||
$this->assertMessage($exp, $this->req("POST", "/discover", ['url' => 2112]));
|
$this->assertMessage($exp, $this->req("POST", "/discover", ['url' => 2112]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,11 +278,11 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
|
||||||
public function provideCategoryAdditions(): iterable {
|
public function provideCategoryAdditions(): iterable {
|
||||||
return [
|
return [
|
||||||
["New", new Response(['id' => 2112, 'title' => "New", 'user_id' => 42])],
|
["New", new Response(['id' => 2112, 'title' => "New", 'user_id' => 42], 201)],
|
||||||
["Duplicate", new ErrorResponse(["DuplicateCategory", 'title' => "Duplicate"], 500)],
|
["Duplicate", new ErrorResponse(["DuplicateCategory", 'title' => "Duplicate"], 500)],
|
||||||
["", new ErrorResponse(["InvalidCategory", 'title' => ""], 500)],
|
["", new ErrorResponse(["InvalidCategory", 'title' => ""], 500)],
|
||||||
[" ", new ErrorResponse(["InvalidCategory", 'title' => " "], 500)],
|
[" ", new ErrorResponse(["InvalidCategory", 'title' => " "], 500)],
|
||||||
[false, new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 400)],
|
[false, new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"],422)],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,12 +308,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
[2, "Duplicate", "constraintViolation", new ErrorResponse(["DuplicateCategory", 'title' => "Duplicate"], 500)],
|
[2, "Duplicate", "constraintViolation", new ErrorResponse(["DuplicateCategory", 'title' => "Duplicate"], 500)],
|
||||||
[2, "", "missing", new ErrorResponse(["InvalidCategory", 'title' => ""], 500)],
|
[2, "", "missing", new ErrorResponse(["InvalidCategory", 'title' => ""], 500)],
|
||||||
[2, " ", "whitespace", new ErrorResponse(["InvalidCategory", 'title' => " "], 500)],
|
[2, " ", "whitespace", new ErrorResponse(["InvalidCategory", 'title' => " "], 500)],
|
||||||
[2, false, "subjectMissing", new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 400)],
|
[2, false, "subjectMissing", new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"],422)],
|
||||||
[1, "New", true, new Response(['id' => 1, 'title' => "New", 'user_id' => 42])],
|
[1, "New", true, new Response(['id' => 1, 'title' => "New", 'user_id' => 42])],
|
||||||
[1, "Duplicate", "constraintViolation", new Response(['id' => 1, 'title' => "Duplicate", 'user_id' => 42])], // This is allowed because the name of the root folder is only a duplicate in circumstances where it is used
|
[1, "Duplicate", "constraintViolation", new Response(['id' => 1, 'title' => "Duplicate", 'user_id' => 42])], // This is allowed because the name of the root folder is only a duplicate in circumstances where it is used
|
||||||
[1, "", "missing", new ErrorResponse(["InvalidCategory", 'title' => ""], 500)],
|
[1, "", "missing", new ErrorResponse(["InvalidCategory", 'title' => ""], 500)],
|
||||||
[1, " ", "whitespace", new ErrorResponse(["InvalidCategory", 'title' => " "], 500)],
|
[1, " ", "whitespace", new ErrorResponse(["InvalidCategory", 'title' => " "], 500)],
|
||||||
[1, false, false, new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 400)],
|
[1, false, false, new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue