Form errors parser. Mirrored actual form-errors response by FOSRest. Made sure that FieldErrors is not duplicated.

This commit is contained in:
Bez Hermoso 2014-07-28 13:14:59 -07:00 committed by Bez Hermoso
parent 6d50c200ba
commit 859421df9a
5 changed files with 861 additions and 688 deletions

View File

@ -275,6 +275,8 @@ class SwaggerFormatter implements FormatterInterface
$message = sprintf('See standard HTTP status code reason for %s', $statusCode);
}
$className = !empty($prop['type']['form_errors']) ? $prop['type']['class'] . '.ErrorResponse' : $prop['type']['class'];
if (isset($prop['type']['collection']) && $prop['type']['collection'] === true) {
/*
@ -283,14 +285,14 @@ class SwaggerFormatter implements FormatterInterface
*/
$alias = $prop['type']['collectionName'];
$newName = sprintf('%s[%s]', $prop['type']['class'], $alias);
$newName = sprintf('%s[%s]', $className, $alias);
$collId =
$this->registerModel(
$newName,
array(
$alias => array(
'dataType' => null,
'subType' => $prop['type']['class'],
'subType' => $className,
'actualType' => DataTypes::COLLECTION,
'required' => true,
'readonly' => true,
@ -307,10 +309,11 @@ class SwaggerFormatter implements FormatterInterface
'responseModel' => $collId
);
} else {
$responseModel = array(
'code' => $statusCode,
'message' => $message,
'responseModel' => $this->registerModel($prop['type']['class'], $prop['model'], ''),
'responseModel' => $this->registerModel($className, $prop['model'], ''),
);
}
$responseMessages[$statusCode] = $responseModel;

128
Parser/FormErrorsParser.php Normal file
View File

@ -0,0 +1,128 @@
<?php
/*
* This file is part of the NelmioApiDocBundle.
*
* (c) Nelmio <hello@nelm.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Nelmio\ApiDocBundle\Parser;
use Nelmio\ApiDocBundle\DataTypes;
/**
* @author Bez Hermoso <bezalelhermoso@gmail.com>
*/
class FormErrorsParser implements ParserInterface, PostParserInterface
{
/**
* Return true/false whether this class supports parsing the given class.
*
* @param array $item containing the following fields: class, groups. Of which groups is optional
*
* @return boolean
*/
public function supports(array $item)
{
return isset($item['form_errors']) && $item['form_errors'] === true;
}
public function parse(array $item)
{
return array();
}
/**
* Overrides the root parameters to contain these parameters instead:
* - status_code: 400
* - message: "Validation failed"
* - errors: contains the original parameters, but all types are changed to array of strings (array of errors for each field)
*
* @param array $item
* @param array $parameters
*
* @return array
*/
public function postParse(array $item, array $parameters)
{
$params = $parameters;
foreach ($params as $name => $data) {
$params[$name] = null;
}
$params['status_code'] = array(
'dataType' => 'integer',
'actualType' => DataTypes::INTEGER,
'subType' => null,
'required' => false,
'description' => 'The status code',
'readonly' => true,
'default' => 400,
);
$params['message'] = array(
'dataType' => 'string',
'actualType' => DataTypes::STRING,
'subType' => null,
'required' => false,
'description' => 'The error message',
'default' => 'Validation failed.',
);
$params['errors'] = array(
'dataType' => 'errors',
'actualType' => DataTypes::MODEL,
'subType' => sprintf('%s.FormErrors', $item['class']),
'required' => false,
'description' => 'Errors',
'readonly' => true,
'children' => $this->doPostParse($parameters),
);
return $params;
}
protected function doPostParse(array $parameters, $attachFieldErrors = true, array $propertyPath = array())
{
$data = array();
foreach ($parameters as $name => $parameter) {
$data[$name] = array(
'dataType' => 'parameter errors',
'actualType' => DataTypes::MODEL,
'subType' => 'FieldErrors',
'required' => false,
'description' => 'Errors on the parameter',
'readonly' => true,
'children' => array(
'errors' => array(
'dataType' => 'array of errors',
'actualType' => DataTypes::COLLECTION,
'subType' => 'string',
'required' => false,
'dscription' => '',
'readonly' => true,
),
),
);
if ($parameter['actualType'] === DataTypes::MODEL) {
$propertyPath[] = $name;
$data[$name]['subType'] = sprintf('%s.FieldErrors[%s]', $parameter['subType'], implode('.', $propertyPath));
$data[$name]['children'] = $this->doPostParse($parameter['children'], $attachFieldErrors, $propertyPath);
} else {
if ($attachFieldErrors === false) {
unset($data[$name]['children']);
}
$attachFieldErrors = false;
}
}
return $data;
}
}

View File

@ -15,6 +15,7 @@
<parameter key="nelmio_api_doc.extractor.handler.phpdoc.class">Nelmio\ApiDocBundle\Extractor\Handler\PhpDocHandler</parameter>
<parameter key="nelmio_api_doc.parser.collection_parser.class">Nelmio\ApiDocBundle\Parser\CollectionParser</parameter>
<parameter key="nelmio_api_doc.parser.form_errors_parser.class">Nelmio\ApiDocBundle\Parser\FormErrorsParser</parameter>
</parameters>
<services>
@ -58,6 +59,9 @@
<service id="nelmio_api_doc.parser.collection_parser" class="%nelmio_api_doc.parser.collection_parser.class%">
<tag name="nelmio_api_doc.extractor.parser" />
</service>
<service id="nelmio_api_doc.parser.form_errors_parser" class="%nelmio_api_doc.parser.form_errors_parser.class%">
<tag name="nelmio_api_doc.extractor.parser" />
</service>
</services>
</container>

View File

@ -48,7 +48,10 @@ class ResourceController
* @ApiDoc(
* description="Create a new resource.",
* input={"class" = "Nelmio\ApiDocBundle\Tests\Fixtures\Form\SimpleType", "name" = ""},
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsNested"
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsNested",
* responseMap={
* 400 = {"class" = "Nelmio\ApiDocBundle\Tests\Fixtures\Form\SimpleType", "form_errors" = true}
* }
* )
*/
public function createResourceAction()

View File

@ -16,6 +16,7 @@ use Nelmio\ApiDocBundle\Tests\WebTestCase;
*/
class SwaggerFormatterTest extends WebTestCase
{
/**
* @var ApiDocExtractor
*/
@ -79,19 +80,19 @@ class SwaggerFormatterTest extends WebTestCase
),
array(
'path' => '/tests',
'description' => NULL,
'description' => null,
),
array(
'path' => '/tests',
'description' => NULL,
'description' => null,
),
array(
'path' => '/tests2',
'description' => NULL,
'description' => null,
),
array(
'path' => '/TestResource',
'description' => NULL,
'description' => null,
),
),
);
@ -128,19 +129,19 @@ class SwaggerFormatterTest extends WebTestCase
'resourcePath' => '/resources',
'apis' =>
array(
0 =>
array(
'path' => '/resources.{_format}',
'operations' =>
array(
0 =>
array(
'method' => 'GET',
'summary' => 'List resources.',
'nickname' => 'get_resources',
'parameters' =>
array(
0 =>
array(
'paramType' => 'path',
'name' => '_format',
@ -148,21 +149,20 @@ class SwaggerFormatterTest extends WebTestCase
'required' => true,
'enum' =>
array(
0 => 'json',
1 => 'xml',
2 => 'html',
'json',
'xml',
'html',
),
),
),
'responseMessages' =>
array(
0 =>
array(
'code' => 200,
'message' => 'Returned on success.',
'responseModel' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Model.Test[tests]',
),
1 =>
array(
'code' => 404,
'message' => 'Returned if resource cannot be found.',
@ -170,7 +170,6 @@ class SwaggerFormatterTest extends WebTestCase
),
'type' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Model.Test[tests]',
),
1 =>
array(
'method' => 'POST',
'summary' => 'Create a new resource.',
@ -245,31 +244,35 @@ class SwaggerFormatterTest extends WebTestCase
'message' => 'See standard HTTP status code reason for 200',
'responseModel' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Model.JmsNested',
),
1 =>
array(
'code' => 400,
'message' => 'See standard HTTP status code reason for 400',
'responseModel' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Form.SimpleType.ErrorResponse'
),
),
'type' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Model.JmsNested',
),
),
),
1 =>
array(
'path' => '/resources/{id}.{_format}',
'operations' =>
array(
0 =>
array(
'method' => 'GET',
'summary' => 'Retrieve a resource by ID.',
'nickname' => 'get_resources',
'parameters' =>
array(
0 =>
array(
'paramType' => 'path',
'name' => 'id',
'type' => 'string',
'required' => true,
),
1 =>
array(
'paramType' => 'path',
'name' => '_format',
@ -277,31 +280,28 @@ class SwaggerFormatterTest extends WebTestCase
'required' => true,
'enum' =>
array(
0 => 'json',
1 => 'xml',
2 => 'html',
'json',
'xml',
'html',
),
),
),
'responseMessages' =>
array (
array(),
),
),
1 =>
array(
'method' => 'DELETE',
'summary' => 'Delete a resource by ID.',
'nickname' => 'delete_resources',
'parameters' =>
array(
0 =>
array(
'paramType' => 'path',
'name' => 'id',
'type' => 'string',
'required' => true,
),
1 =>
array(
'paramType' => 'path',
'name' => '_format',
@ -309,15 +309,14 @@ class SwaggerFormatterTest extends WebTestCase
'required' => true,
'enum' =>
array(
0 => 'json',
1 => 'xml',
2 => 'html',
'json',
'xml',
'html',
),
),
),
'responseMessages' =>
array (
),
array(),
),
),
),
@ -327,7 +326,7 @@ class SwaggerFormatterTest extends WebTestCase
'Nelmio.ApiDocBundle.Tests.Fixtures.Model.Test' =>
array(
'id' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Model.Test',
'description' => NULL,
'description' => null,
'properties' =>
array(
'a' =>
@ -344,7 +343,7 @@ class SwaggerFormatterTest extends WebTestCase
),
'required' =>
array(
0 => 'a',
'a',
),
),
'Nelmio.ApiDocBundle.Tests.Fixtures.Model.Test[tests]' =>
@ -356,7 +355,7 @@ class SwaggerFormatterTest extends WebTestCase
'tests' =>
array(
'type' => 'array',
'description' => NULL,
'description' => null,
'items' =>
array(
'$ref' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Model.Test',
@ -365,7 +364,7 @@ class SwaggerFormatterTest extends WebTestCase
),
'required' =>
array(
0 => 'tests',
'tests',
),
),
'Nelmio.ApiDocBundle.Tests.Fixtures.Model.JmsTest' =>
@ -415,8 +414,7 @@ class SwaggerFormatterTest extends WebTestCase
),
),
'required' =>
array (
),
array(),
),
'Nelmio.ApiDocBundle.Tests.Fixtures.Model.JmsNested' =>
array(
@ -471,16 +469,84 @@ With multiple lines.',
),
),
'required' =>
array (
array(),
),
'FieldErrors' =>
array(
'id' => 'FieldErrors',
'description' => 'Errors on the parameter',
'properties' => array(
'errors' => array(
'type' => 'array',
'description' => 'array of errors',
'items' => array(
'type' => 'string',
),
),
),
'required' => array()
),
'Nelmio.ApiDocBundle.Tests.Fixtures.Form.SimpleType.FormErrors' =>
array(
'id' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Form.SimpleType.FormErrors',
'description' => 'Errors',
'properties' => array(
'simple' => array(
'$ref' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Form.SimpleType.FieldErrors[simple]',
)
),
'required' => array()
),
'Nelmio.ApiDocBundle.Tests.Fixtures.Form.SimpleType.ErrorResponse' => array(
'id' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Form.SimpleType.ErrorResponse',
'description' => '',
'properties' => array(
'status_code' => array(
'type' => 'integer',
'description' => 'The status code',
'format' => 'int32',
),
'message' => array(
'type' => 'string',
'description' => 'The error message',
),
'errors' => array(
'$ref' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Form.SimpleType.FormErrors',
),
),
'required' => array(),
),
'Nelmio.ApiDocBundle.Tests.Fixtures.Form.SimpleType.FieldErrors[simple]' =>
array(
'id' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Form.SimpleType.FieldErrors[simple]',
'description' => 'Errors on the parameter',
'properties' => array(
'a' => array(
'$ref' => 'FieldErrors',
),
'b' => array(
'$ref' => 'FieldErrors',
),
'c' => array(
'$ref' => 'FieldErrors',
),
'd' => array(
'$ref' => 'FieldErrors',
),
'e' => array(
'$ref' => 'FieldErrors',
),
'g' => array(
'$ref' => 'FieldErrors',
),
),
'required' => array(),
),
),
'produces' =>
array (
),
array(),
'consumes' =>
array (
),
array(),
'authorizations' =>
array(
'apiKey' =>
@ -501,19 +567,19 @@ With multiple lines.',
'resourcePath' => '/other-resources',
'apis' =>
array(
0 =>
array(
'path' => '/other-resources.{_format}',
'operations' =>
array(
0 =>
array(
'method' => 'GET',
'summary' => 'List another resource.',
'nickname' => 'get_other-resources',
'parameters' =>
array(
0 =>
array(
'paramType' => 'path',
'name' => '_format',
@ -521,15 +587,15 @@ With multiple lines.',
'required' => true,
'enum' =>
array(
0 => 'json',
1 => 'xml',
2 => 'html',
'json',
'xml',
'html',
),
),
),
'responseMessages' =>
array(
0 =>
array(
'code' => 200,
'message' => 'See standard HTTP status code reason for 200',
@ -540,26 +606,24 @@ With multiple lines.',
),
),
),
1 =>
array(
'path' => '/other-resources/{id}.{_format}',
'operations' =>
array(
0 =>
array(
'method' => 'PUT',
'summary' => 'Update a resource bu ID.',
'nickname' => 'put_other-resources',
'parameters' =>
array(
0 =>
array(
'paramType' => 'path',
'name' => 'id',
'type' => 'string',
'required' => true,
),
1 =>
array(
'paramType' => 'path',
'name' => '_format',
@ -567,31 +631,28 @@ With multiple lines.',
'required' => true,
'enum' =>
array(
0 => 'json',
1 => 'xml',
2 => 'html',
'json',
'xml',
'html',
),
),
),
'responseMessages' =>
array (
array(),
),
),
1 =>
array(
'method' => 'PATCH',
'summary' => 'Update a resource bu ID.',
'nickname' => 'patch_other-resources',
'parameters' =>
array(
0 =>
array(
'paramType' => 'path',
'name' => 'id',
'type' => 'string',
'required' => true,
),
1 =>
array(
'paramType' => 'path',
'name' => '_format',
@ -599,15 +660,14 @@ With multiple lines.',
'required' => true,
'enum' =>
array(
0 => 'json',
1 => 'xml',
2 => 'html',
'json',
'xml',
'html',
),
),
),
'responseMessages' =>
array (
),
array(),
),
),
),
@ -617,7 +677,7 @@ With multiple lines.',
'Nelmio.ApiDocBundle.Tests.Fixtures.Model.JmsTest' =>
array(
'id' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Model.JmsTest',
'description' => NULL,
'description' => null,
'properties' =>
array(
'foo' =>
@ -661,8 +721,7 @@ With multiple lines.',
),
),
'required' =>
array (
),
array(),
),
'Nelmio.ApiDocBundle.Tests.Fixtures.Model.JmsNested' =>
array(
@ -717,8 +776,7 @@ With multiple lines.',
),
),
'required' =>
array (
),
array(),
),
'Nelmio.ApiDocBundle.Tests.Fixtures.Model.JmsTest[]' =>
array(
@ -729,7 +787,7 @@ With multiple lines.',
'' =>
array(
'type' => 'array',
'description' => NULL,
'description' => null,
'items' =>
array(
'$ref' => 'Nelmio.ApiDocBundle.Tests.Fixtures.Model.JmsTest',
@ -738,16 +796,14 @@ With multiple lines.',
),
'required' =>
array(
0 => '',
'',
),
),
),
'produces' =>
array (
),
array(),
'consumes' =>
array (
),
array(),
'authorizations' =>
array(
'apiKey' =>
@ -801,10 +857,8 @@ With multiple lines.',
),
),
'responseMessages' =>
array (
array(),
),
),
array(
'method' => 'GET',
'summary' => 'index action',
@ -818,14 +872,12 @@ With multiple lines.',
'type' => 'string',
'required' => true,
),
array(
'paramType' => 'query',
'name' => 'a',
'type' => 'integer',
'description' => null,
),
array(
'paramType' => 'query',
'name' => 'b',
@ -834,10 +886,8 @@ With multiple lines.',
),
),
'responseMessages' =>
array (
array(),
),
),
array(
'method' => 'POST',
'summary' => 'create test',
@ -851,27 +901,23 @@ With multiple lines.',
'type' => 'string',
'required' => true,
),
array(
'paramType' => 'form',
'name' => 'a',
'type' => 'string',
'description' => 'A nice description',
),
array(
'paramType' => 'form',
'name' => 'b',
'type' => 'string',
),
array(
'paramType' => 'form',
'name' => 'c',
'type' => 'boolean',
'defaultValue' => false,
),
array(
'paramType' => 'form',
'name' => 'd',
@ -880,44 +926,37 @@ With multiple lines.',
),
),
'responseMessages' =>
array (
array(),
),
),
array(
'method' => 'POST',
'summary' => 'create test',
'nickname' => 'post_tests',
'parameters' =>
array(
array(
'paramType' => 'path',
'name' => '_format',
'type' => 'string',
'required' => true,
),
array(
'paramType' => 'form',
'name' => 'a',
'type' => 'string',
'description' => 'A nice description',
),
array(
'paramType' => 'form',
'name' => 'b',
'type' => 'string',
),
array(
'paramType' => 'form',
'name' => 'c',
'type' => 'boolean',
'defaultValue' => false,
),
array(
'paramType' => 'form',
'name' => 'd',
@ -926,21 +965,17 @@ With multiple lines.',
),
),
'responseMessages' =>
array (
),
array(),
),
),
),
),
'models' =>
array (
),
array(),
'produces' =>
array (
),
array(),
'consumes' =>
array (
),
array(),
'authorizations' =>
array(
'apiKey' => array(