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 24, 2024
1 parent f5d54ac commit 08faf53
Show file tree
Hide file tree
Showing 3 changed files with 44 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
76 changes: 34 additions & 42 deletions src/Controllers/LinkFieldController.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,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 @@ -106,17 +106,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 @@ -133,11 +129,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 @@ -150,10 +146,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(201);
}

/**
Expand All @@ -173,32 +166,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 @@ -209,7 +201,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 @@ -273,13 +265,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 @@ -294,7 +286,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 @@ -306,7 +298,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 @@ -320,10 +312,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(201);
}

/**
Expand Down Expand Up @@ -407,11 +396,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 @@ -423,11 +412,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 @@ -440,7 +429,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 @@ -454,13 +443,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 @@ -476,7 +465,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 @@ -489,7 +478,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 @@ -503,7 +492,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 All @@ -517,6 +506,9 @@ private function ownerFromRequest(): DataObject
{
$ownerID = $this->getOwnerIDFromRequest();
$ownerClass = $this->getOwnerClassFromRequest();
if (!is_a($ownerClass, DataObject::class, true)) {
$this->jsonError(404);
}
$ownerRelation = $this->ownerRelationFromRequest();
/** @var DataObject $obj */
$obj = Injector::inst()->get($ownerClass);
Expand All @@ -540,7 +532,7 @@ private function ownerFromRequest(): DataObject
return $owner;
}
}
$this->jsonError(404, _t(__CLASS__ . '.INVALID_OWNER', 'Invalid Owner'));
$this->jsonError(404);
}

/**
Expand All @@ -552,7 +544,7 @@ private function ownerRelationFromRequest(): 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 08faf53

Please sign in to comment.