Skip to content

Commit

Permalink
ENH Standardise API responses
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Jan 29, 2024
1 parent 76b38e7 commit 8fbe94b
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 121 deletions.
12 changes: 1 addition & 11 deletions lang/en.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
en:
SilverStripe\LinkField\Controllers\LinkFieldController:
BAD_DATA: 'Bad data'
CREATE_LINK: 'Create link'
EMPTY_DATA: 'Empty data'
INVALID_ID: 'Invalid ID'
INVALID_OWNER: 'Invalid Owner'
INVALID_OWNER_CLASS: 'Invalid ownerClass'
INVALID_OWNER_ID: 'Invalid ownerID'
INVALID_OWNER_RELATION: 'Invalid ownerRelation'
INVALID_TOKEN: 'Invalid CSRF token'
INVALID_TYPEKEY: 'Invalid typeKey'
MENUTITLE: SilverStripe\LinkField\Controllers\LinkFieldController
UNAUTHORIZED: Unauthorized
MENUTITLE: 'Link fields'
UPDATE_LINK: 'Update link'
SilverStripe\LinkField\Form\Traits\AllowedLinkClassesTrait:
INVALID_TYPECLASS: '"{class}": {typeclass} is not a valid Link Type'
Expand Down
73 changes: 31 additions & 42 deletions src/Controllers/LinkFieldController.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,17 @@ public function linkForm(): Form
if ($id) {
$link = Link::get()->byID($id);
if (!$link) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_ID', 'Invalid ID'));
$this->jsonError(404);
}
$operation = 'edit';
if (!$link->canView()) {
$this->jsonError(403, _t(__CLASS__ . '.UNAUTHORIZED', 'Unauthorized'));
$this->jsonError(403);
}
} else {
$typeKey = $this->typeKeyFromRequest();
$link = LinkTypeService::create()->byKey($typeKey);
if (!$link) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_TYPEKEY', 'Invalid typeKey'));
$this->jsonError(404);
}
$operation = 'create';
}
Expand All @@ -107,17 +107,13 @@ public function linkData(HTTPRequest $request): HTTPResponse
$data[$link->ID] = $this->getLinkData($link);
}
}

$response = $this->getResponse();
$response->addHeader('Content-type', 'application/json');
$response->setBody(json_encode($data));
return $response;
return $this->jsonSuccess(200, $data);
}

private function getLinkData(Link $link): array
{
if (!$link->canView()) {
$this->jsonError(403, _t(__CLASS__ . '.UNAUTHORIZED', 'Unauthorized'));
$this->jsonError(403);
}
$data = $link->jsonSerialize();
$data['canDelete'] = $link->canDelete();
Expand All @@ -134,11 +130,11 @@ public function linkDelete(): HTTPResponse
{
$link = $this->linkFromRequest();
if (!$link->canDelete()) {
$this->jsonError(403, _t(__CLASS__ . '.UNAUTHORIZED', 'Unauthorized'));
$this->jsonError(403);
}
// Check security token on destructive operation
if (!SecurityToken::inst()->checkRequest($this->getRequest())) {
$this->jsonError(400, _t(__CLASS__ . '.INVALID_TOKEN', 'Invalid CSRF token'));
$this->jsonError(400);
}
// delete() will also delete any published version immediately
$link->delete();
Expand All @@ -151,10 +147,7 @@ public function linkDelete(): HTTPResponse
$owner->write();
}
// Send response
$response = $this->getResponse();
$response->addHeader('Content-type', 'application/json');
$response->setBody(json_encode(['success' => true]));
return $response;
return $this->jsonSuccess(204);
}

/**
Expand All @@ -174,32 +167,31 @@ public function getLinkForm(): Form
public function save(array $data, Form $form): HTTPResponse
{
if (empty($data)) {
$this->jsonError(400, _t(__CLASS__ . '.EMPTY_DATA', 'Empty data'));
$this->jsonError(400);
}

/** @var Link $link */
$id = $this->itemIDFromRequest();
if ($id) {
// Editing an existing Link
$operation = 'edit';
$link = Link::get()->byID($id);
if (!$link) {
$this->jsonErorr(404, _t(__CLASS__ . '.INVALID_ID', 'Invalid ID'));
$this->jsonError(404);
}
if (!$link->canEdit()) {
$this->jsonError(403, _t(__CLASS__ . '.UNAUTHORIZED', 'Unauthorized'));
$this->jsonError(403);
}
} else {
// Creating a new Link
$operation = 'create';
$typeKey = $this->typeKeyFromRequest();
$className = LinkTypeService::create()->byKey($typeKey) ?? '';
if (!$className) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_TYPEKEY', 'Invalid typeKey'));
$this->jsonError(404);
}
$link = $className::create();
if (!$link->canCreate()) {
$this->jsonError(403, _t(__CLASS__ . '.UNAUTHORIZED', 'Unauthorized'));
$this->jsonError(403);
}
}

Expand All @@ -210,7 +202,7 @@ public function save(array $data, Form $form): HTTPResponse
if ((isset($data['ID']) && ((int) $data['ID'] !== $id))
|| isset($data['Sort'])
) {
$this->jsonError(400, _t(__CLASS__ . '.BAD_DATA', 'Bad data'));
$this->jsonError(400);
}

// Update DataObject from form data
Expand Down Expand Up @@ -274,13 +266,13 @@ public function linkSort()
$request = $this->getRequest();
// Check security token
if (!SecurityToken::inst()->checkRequest($request)) {
$this->jsonError(400, _t(__CLASS__ . '.INVALID_TOKEN', 'Invalid CSRF token'));
$this->jsonError(400);
}
$json = json_decode($request->getBody() ?? '');
$newLinkIDs = $json?->newLinkIDs;
// If someone's passing a JSON object or other non-array here, they're doing something wrong
if (!is_array($newLinkIDs) || empty($newLinkIDs)) {
$this->jsonError(400, _t('LinkField.BAD_DATA', 'Bad data'));
$this->jsonError(400);
}
// Fetch and validate links
$links = Link::get()->filter(['ID' => $newLinkIDs])->toArray();
Expand All @@ -295,7 +287,7 @@ public function linkSort()
$ownerRelation = $link->OwnerRelation;
}
if ($link->OwnerID !== $ownerID || $link->OwnerRelation !== $ownerRelation) {
$this->jsonError(400, _t('LinkField.BAD_DATA', 'Bad data'));
$this->jsonError(400);
}
$linkIDToLink[$link->ID] = $link;
}
Expand All @@ -307,7 +299,7 @@ public function linkSort()
// There's also corresponding logic in Link::onBeforeWrite() to also have a minimum of 1
$sort = $i + 1;
if ($link->Sort !== $sort && !$link->canEdit()) {
$this->jsonError(403, _t(__CLASS__ . '.UNAUTHORIZED', 'Unauthorized'));
$this->jsonError(403);
}
}
// Update Sort field on links
Expand All @@ -321,10 +313,7 @@ public function linkSort()
}
}
// Send response
$response = $this->getResponse();
$response->addHeader('Content-type', 'application/json');
$response->setBody(json_encode(['success' => true]));
return $response;
return $this->jsonSuccess(204);
}

/**
Expand Down Expand Up @@ -413,11 +402,11 @@ private function linkFromRequest(): Link
{
$itemID = $this->itemIDFromRequest();
if (!$itemID) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_ID', 'Invalid ID'));
$this->jsonError(404);
}
$link = Link::get()->byID($itemID);
if (!$link) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_ID', 'Invalid ID'));
$this->jsonError(404);
}
return $link;
}
Expand All @@ -429,11 +418,11 @@ private function linksFromRequest(): DataList
{
$itemIDs = $this->itemIDsFromRequest();
if (empty($itemIDs)) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_ID', 'Invalid ID'));
$this->jsonError(404);
}
$links = Link::get()->byIDs($itemIDs);
if (!$links->exists()) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_ID', 'Invalid ID'));
$this->jsonError(404);
}
return $links;
}
Expand All @@ -446,7 +435,7 @@ private function itemIDFromRequest(): int
$request = $this->getRequest();
$itemID = (string) $request->param('ItemID');
if (!ctype_digit($itemID)) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_ID', 'Invalid ID'));
$this->jsonError(404);
}
return (int) $itemID;
}
Expand All @@ -460,13 +449,13 @@ private function itemIDsFromRequest(): array
$itemIDs = $request->getVar('itemIDs');

if (!is_array($itemIDs)) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_ID', 'Invalid ID'));
$this->jsonError(404);
}

$idsAsInt = [];
foreach ($itemIDs as $id) {
if (!is_int($id) && !ctype_digit($id)) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_ID', 'Invalid ID'));
$this->jsonError(404);
}
$idsAsInt[] = (int) $id;
}
Expand All @@ -482,7 +471,7 @@ private function typeKeyFromRequest(): string
$request = $this->getRequest();
$typeKey = (string) $request->getVar('typeKey');
if (strlen($typeKey) === 0 || !preg_match('#^[a-z\-]+$#', $typeKey)) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_TYPEKEY', 'Invalid typeKey'));
$this->jsonError(404);
}
return $typeKey;
}
Expand All @@ -495,7 +484,7 @@ private function getOwnerClassFromRequest(): string
$request = $this->getRequest();
$ownerClass = $request->getVar('ownerClass') ?: $request->postVar('OwnerClass');
if (!is_a($ownerClass, DataObject::class, true)) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_OWNER_CLASS', 'Invalid ownerClass'));
$this->jsonError(404);
}

return $ownerClass;
Expand All @@ -509,7 +498,7 @@ private function getOwnerIDFromRequest(): int
$request = $this->getRequest();
$ownerID = (int) ($request->getVar('ownerID') ?: $request->postVar('OwnerID'));
if ($ownerID === 0) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_OWNER_ID', 'Invalid ownerID'));
$this->jsonError(404);
}

return $ownerID;
Expand Down Expand Up @@ -546,7 +535,7 @@ private function getOwnerFromRequest(): DataObject
return $owner;
}
}
$this->jsonError(404, _t(__CLASS__ . '.INVALID_OWNER', 'Invalid Owner'));
$this->jsonError(404);
}

/**
Expand All @@ -558,7 +547,7 @@ private function getOwnerRelationFromRequest(): string
$request = $this->getRequest();
$ownerRelation = $request->getVar('ownerRelation') ?: $request->postVar('OwnerRelation');
if (!$ownerRelation) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_OWNER_RELATION', 'Invalid ownerRelation'));
$this->jsonError(404);
}

return $ownerRelation;
Expand Down
Loading

0 comments on commit 8fbe94b

Please sign in to comment.