Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH Standardise API responses #184

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading