Merge pull request #50 from silverstripe-terraformers/feature/validation-result

SS4: Catch ValidationExceptions and return ValidationResult messages.
This commit is contained in:
Robbie Averill 2018-03-02 10:47:56 +13:00 committed by GitHub
commit c30b72e058
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 220 additions and 6 deletions

View File

@ -260,6 +260,9 @@ abstract class DataFormatter
return $this->removeFields; return $this->removeFields;
} }
/**
* @return string
*/
public function getOutputContentType() public function getOutputContentType()
{ {
return $this->outputContentType; return $this->outputContentType;
@ -341,14 +344,28 @@ abstract class DataFormatter
/** /**
* Convert a single data object to this format. Return a string. * Convert a single data object to this format. Return a string.
*
* @param DataObjectInterface $do
* @return mixed
*/ */
abstract public function convertDataObject(DataObjectInterface $do); abstract public function convertDataObject(DataObjectInterface $do);
/** /**
* Convert a data object set to this format. Return a string. * Convert a data object set to this format. Return a string.
*
* @param SS_List $set
* @return string
*/ */
abstract public function convertDataObjectSet(SS_List $set); abstract public function convertDataObjectSet(SS_List $set);
/**
* Convert an array to this format. Return a string.
*
* @param $array
* @return string
*/
abstract public function convertArray($array);
/** /**
* @param string $strData HTTP Payload as string * @param string $strData HTTP Payload as string
*/ */

View File

@ -22,6 +22,9 @@ class JSONDataFormatter extends DataFormatter
protected $outputContentType = 'application/json'; protected $outputContentType = 'application/json';
/**
* @return array
*/
public function supportedExtensions() public function supportedExtensions()
{ {
return array( return array(
@ -30,6 +33,9 @@ class JSONDataFormatter extends DataFormatter
); );
} }
/**
* @return array
*/
public function supportedMimeTypes() public function supportedMimeTypes()
{ {
return array( return array(
@ -38,6 +44,15 @@ class JSONDataFormatter extends DataFormatter
); );
} }
/**
* @param $array
* @return string
*/
public function convertArray($array)
{
return Convert::array2json($array);
}
/** /**
* Generate a JSON representation of the given {@link DataObject}. * Generate a JSON representation of the given {@link DataObject}.
* *
@ -163,6 +178,10 @@ class JSONDataFormatter extends DataFormatter
return Convert::array2json($serobj); return Convert::array2json($serobj);
} }
/**
* @param string $strData
* @return array|bool|void
*/
public function convertStringToArray($strData) public function convertStringToArray($strData)
{ {
return Convert::json2array($strData); return Convert::json2array($strData);

View File

@ -4,6 +4,7 @@ namespace SilverStripe\RestfulServer\DataFormatter;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Dev\Debug;
use SilverStripe\RestfulServer\DataFormatter; use SilverStripe\RestfulServer\DataFormatter;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObjectInterface;
@ -24,6 +25,9 @@ class XMLDataFormatter extends DataFormatter
protected $outputContentType = 'text/xml'; protected $outputContentType = 'text/xml';
/**
* @return array
*/
public function supportedExtensions() public function supportedExtensions()
{ {
return array( return array(
@ -31,6 +35,9 @@ class XMLDataFormatter extends DataFormatter
); );
} }
/**
* @return array
*/
public function supportedMimeTypes() public function supportedMimeTypes()
{ {
return array( return array(
@ -39,6 +46,48 @@ class XMLDataFormatter extends DataFormatter
); );
} }
/**
* @param $array
* @return string
* @throws \Exception
*/
public function convertArray($array)
{
$response = Controller::curr()->getResponse();
if ($response) {
$response->addHeader("Content-Type", "text/xml");
}
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n
<response>{$this->convertArrayWithoutHeader($array)}</response>";
}
/**
* @param $array
* @return string
* @throws \Exception
*/
public function convertArrayWithoutHeader($array)
{
$xml = '';
foreach ($array as $fieldName => $fieldValue) {
if (is_array($fieldValue)) {
if (is_numeric($fieldName)) {
$fieldName = 'Item';
}
$xml .= "<{$fieldName}>\n";
$xml .= $this->convertArrayWithoutHeader($fieldValue);
$xml .= "</{$fieldName}>\n";
} else {
$xml .= "<$fieldName>$fieldValue</$fieldName>\n";
}
}
return $xml;
}
/** /**
* Generate an XML representation of the given {@link DataObject}. * Generate an XML representation of the given {@link DataObject}.
* *
@ -56,6 +105,12 @@ class XMLDataFormatter extends DataFormatter
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . $this->convertDataObjectWithoutHeader($obj, $fields); return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . $this->convertDataObjectWithoutHeader($obj, $fields);
} }
/**
* @param DataObject $obj
* @param null $fields
* @param null $relations
* @return string
*/
public function convertDataObjectWithoutHeader(DataObject $obj, $fields = null, $relations = null) public function convertDataObjectWithoutHeader(DataObject $obj, $fields = null, $relations = null)
{ {
$className = $this->sanitiseClassName(get_class($obj)); $className = $this->sanitiseClassName(get_class($obj));
@ -195,6 +250,11 @@ class XMLDataFormatter extends DataFormatter
return $xml; return $xml;
} }
/**
* @param string $strData
* @return array|void
* @throws \Exception
*/
public function convertStringToArray($strData) public function convertStringToArray($strData)
{ {
return Convert::xml2array($strData); return Convert::xml2array($strData);

View File

@ -10,6 +10,8 @@ use SilverStripe\ORM\DataObject;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\ORM\SS_List; use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\ValidationException;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\CMS\Model\SiteTree; use SilverStripe\CMS\Model\SiteTree;
@ -443,8 +445,13 @@ class RestfulServer extends Controller
return $this->unsupportedMediaType(); return $this->unsupportedMediaType();
} }
try {
/** @var DataObject|string */ /** @var DataObject|string */
$obj = $this->updateDataObject($obj, $reqFormatter); $obj = $this->updateDataObject($obj, $reqFormatter);
} catch (ValidationException $e) {
return $this->validationFailure($responseFormatter, $e->getResult());
}
if (is_string($obj)) { if (is_string($obj)) {
return $obj; return $obj;
} }
@ -515,8 +522,13 @@ class RestfulServer extends Controller
$responseFormatter = $this->getResponseDataFormatter($className); $responseFormatter = $this->getResponseDataFormatter($className);
try {
/** @var DataObject|string $obj */ /** @var DataObject|string $obj */
$obj = $this->updateDataObject($obj, $reqFormatter); $obj = $this->updateDataObject($obj, $reqFormatter);
} catch (ValidationException $e) {
return $this->validationFailure($responseFormatter, $e->getResult());
}
if (is_string($obj)) { if (is_string($obj)) {
return $obj; return $obj;
} }
@ -643,6 +655,9 @@ class RestfulServer extends Controller
} }
} }
/**
* @return string
*/
protected function permissionFailure() protected function permissionFailure()
{ {
// return a 401 // return a 401
@ -652,6 +667,9 @@ class RestfulServer extends Controller
return "You don't have access to this item through the API."; return "You don't have access to this item through the API.";
} }
/**
* @return string
*/
protected function notFound() protected function notFound()
{ {
// return a 404 // return a 404
@ -660,6 +678,9 @@ class RestfulServer extends Controller
return "That object wasn't found"; return "That object wasn't found";
} }
/**
* @return string
*/
protected function methodNotAllowed() protected function methodNotAllowed()
{ {
$this->getResponse()->setStatusCode(405); $this->getResponse()->setStatusCode(405);
@ -667,6 +688,9 @@ class RestfulServer extends Controller
return "Method Not Allowed"; return "Method Not Allowed";
} }
/**
* @return string
*/
protected function unsupportedMediaType() protected function unsupportedMediaType()
{ {
$this->response->setStatusCode(415); // Unsupported Media Type $this->response->setStatusCode(415); // Unsupported Media Type
@ -674,6 +698,23 @@ class RestfulServer extends Controller
return "Unsupported Media Type"; return "Unsupported Media Type";
} }
/**
* @param ValidationResult $result
* @return mixed
*/
protected function validationFailure(DataFormatter $responseFormatter, ValidationResult $result)
{
$this->getResponse()->setStatusCode(400);
$this->getResponse()->addHeader('Content-Type', $responseFormatter->getOutputContentType());
$response = [
'type' => ValidationException::class,
'messages' => $result->getMessages(),
];
return $responseFormatter->convertArray($response);
}
/** /**
* A function to authenticate a user * A function to authenticate a user
* *

View File

@ -10,6 +10,7 @@ use SilverStripe\RestfulServer\Tests\Stubs\RestfulServerTestAuthorRating;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\RestfulServer\Tests\Stubs\RestfulServerTestValidationFailure;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
@ -572,4 +573,17 @@ class RestfulServerTest extends SapphireTest
unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_USER']);
unset($_SERVER['PHP_AUTH_PW']); unset($_SERVER['PHP_AUTH_PW']);
} }
public function testValidationErrorWithPOST()
{
$urlSafeClassname = $this->urlSafeClassname(RestfulServerTestValidationFailure::class);
$url = "{$this->baseURI}/api/v1/$urlSafeClassname/";
$data = [
'Content' => 'Test',
];
$response = Director::test($url, $data, null, 'POST');
// Assumption: XML is default output
$responseArr = Convert::xml2array($response->getBody());
$this->assertEquals('SilverStripe\\ORM\\ValidationException', $responseArr['type']);
}
} }

View File

@ -0,0 +1,63 @@
<?php
namespace SilverStripe\RestfulServer\Tests\Stubs;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
/**
* Class RestfulServerTestValidationFailure
* @package SilverStripe\RestfulServer\Tests\Stubs
*
* @property string Content
* @property string Title
*/
class RestfulServerTestValidationFailure extends DataObject implements TestOnly
{
private static $api_access = true;
private static $table_name = 'RestfulServerTestValidationFailure';
private static $db = array(
'Content' => 'Text',
'Title' => 'Text',
);
/**
* @return \SilverStripe\ORM\ValidationResult
*/
public function validate()
{
$result = parent::validate();
if (strlen($this->Content) === 0) {
$result->addFieldError('Content', 'Content required');
}
if (strlen($this->Title) === 0) {
$result->addFieldError('Title', 'Title required');
}
return $result;
}
public function canView($member = null)
{
return true;
}
public function canEdit($member = null)
{
return true;
}
public function canDelete($member = null)
{
return true;
}
public function canCreate($member = null, $context = array())
{
return true;
}
}