diff --git a/src/Annotation/Attribute.php b/src/Annotation/Attribute.php index 1317c21..6a2e981 100644 --- a/src/Annotation/Attribute.php +++ b/src/Annotation/Attribute.php @@ -13,7 +13,7 @@ class Attribute public $identifier; /** - * @var string + * @var mixed */ public $type = 'string'; diff --git a/src/Annotation/DataStructures.php b/src/Annotation/DataStructures.php new file mode 100644 index 0000000..5b635a5 --- /dev/null +++ b/src/Annotation/DataStructures.php @@ -0,0 +1,14 @@ + + */ + public $value; +} diff --git a/src/Annotation/Property.php b/src/Annotation/Property.php new file mode 100644 index 0000000..10056b9 --- /dev/null +++ b/src/Annotation/Property.php @@ -0,0 +1,29 @@ + */ public $attributes; + + /** + * @var Dingo\Blueprint\Annotation\Type + */ + public $type; } diff --git a/src/Annotation/Response.php b/src/Annotation/Response.php index c677277..c763a7b 100644 --- a/src/Annotation/Response.php +++ b/src/Annotation/Response.php @@ -31,4 +31,9 @@ class Response * @var array */ public $attributes; + + /** + * @var Dingo\Blueprint\Annotation\Type + */ + public $type; } diff --git a/src/Annotation/Type.php b/src/Annotation/Type.php new file mode 100644 index 0000000..6573f28 --- /dev/null +++ b/src/Annotation/Type.php @@ -0,0 +1,24 @@ + + */ + public $properties; +} diff --git a/src/Blueprint.php b/src/Blueprint.php index b95a66e..6cac707 100644 --- a/src/Blueprint.php +++ b/src/Blueprint.php @@ -2,6 +2,7 @@ namespace Dingo\Blueprint; +use Dingo\Blueprint\Annotation\Type; use ReflectionClass; use RuntimeException; use Illuminate\Support\Str; @@ -135,7 +136,9 @@ protected function generateContentsFromResources(Collection $resources, $name) $contents .= sprintf('# %s', $name); $contents .= $this->line(2); - $resources->each(function ($resource) use (&$contents) { + $globalDataStructures = new Collection(); + + $resources->each(function ($resource) use (&$contents, $globalDataStructures) { if ($resource->getActions()->isEmpty()) { return; } @@ -151,6 +154,10 @@ protected function generateContentsFromResources(Collection $resources, $name) $this->appendParameters($contents, $parameters); } + if (($dataStructures = $resource->getDataStructures()) && ! $dataStructures->isEmpty()) { + $globalDataStructures->push($dataStructures); + } + $resource->getActions()->each(function ($action) use (&$contents, $resource) { $contents .= $this->line(2); $contents .= $action->getDefinition(); @@ -192,6 +199,15 @@ protected function generateContentsFromResources(Collection $resources, $name) $contents .= $this->line(2); }); + if (! $globalDataStructures->isEmpty()) { + $contents .= $this->line(1); + $contents .= '# Data Structures'; + + $globalDataStructures->each(function ($dataStructure) use (&$contents) { + $this->appendDataStructures($contents, $dataStructure); + }); + } + return stripslashes(trim($contents)); } @@ -217,15 +233,35 @@ protected function appendAttributes(&$contents, Collection $attributes, $indent $contents .= sprintf(': %s', $attribute->sample); } + $arrayType = false; + if (is_array($attribute->type)) { + $arrayType = true; + $attribute->type = array_shift($attribute->type); + } + + $type = $this->resolveType($attribute->type); + $contents .= sprintf( ' (%s, %s) - %s', - $attribute->type, + $arrayType ? "array[${type}]" : $type, $attribute->required ? 'required' : 'optional', $attribute->description ); }); } + /** + * Append a type attribute to a resource or action. + * + * @param string $contents + * @param Type $type + * @param int $indent + */ + protected function appendType(&$contents, Type $type, $indent = 0) + { + $this->appendSection($contents, sprintf('Attributes (%s)', $type->identifier), $indent); + } + /** * Append the parameters subsection to a resource or action. * @@ -284,7 +320,9 @@ protected function appendResponse(&$contents, Annotation\Response $response, Res $this->appendHeaders($contents, array_merge($resource->getResponseHeaders(), $response->headers)); } - if (isset($response->attributes)) { + if (isset($response->type)) { + $this->appendType($contents, $response->type, 1); + } elseif (isset($response->attributes)) { $this->appendAttributes($contents, collect($response->attributes), 1); } @@ -316,7 +354,9 @@ protected function appendRequest(&$contents, $request, Resource $resource) $this->appendHeaders($contents, array_merge($resource->getRequestHeaders(), $request->headers)); } - if (isset($request->attributes)) { + if (isset($request->type)) { + $this->appendType($contents, $request->type, 1); + } elseif (isset($request->attributes)) { $this->appendAttributes($contents, collect($request->attributes), 1); } @@ -388,6 +428,66 @@ protected function appendSection(&$contents, $name, $indent = 0, $lines = 2) $contents .= '+ '.$name; } + /** + * Append data structures. + * + * @param $contents + * @param $allStructures + */ + protected function appendDataStructures(&$contents, $allStructures) + { + $allStructures->each(function ($dataStructures) use (&$contents) { + collect($dataStructures->value)->each(function ($type) use (&$contents) { + + $contents .= $this->line(2); + $contents .= sprintf('## %s (%s)', $type->identifier, $this->resolveType($type->type)); + $contents .= $this->line(1); + + if (isset($type->properties)) { + $this->appendProperties($contents, collect($type->properties), -1, false); + } + }); + }); + } + + /** + * Resolves the name of a type from either a string or a Type class. + * + * @param string|Type $type + * + * @return string + */ + protected function resolveType($type) + { + return $type instanceof Type ? $type->identifier : $type; + } + + /** + * Append the properties to a type. + * + * @param string $contents + * @param \Illuminate\Support\Collection $properties + * + * @return void + */ + protected function appendProperties(&$contents, Collection $properties) + { + $properties->each(function ($property) use (&$contents) { + $contents .= $this->line(); + $contents .= sprintf('+ %s', $property->identifier); + + if ($property->sample) { + $contents .= sprintf(': %s', $property->sample); + } + + $contents .= sprintf( + ' (%s) - %s', + $this->resolveType($property->type), + $property->description + ); + }); + } + /** * Prepare a body. * diff --git a/src/Resource.php b/src/Resource.php index 103e40e..96841d9 100644 --- a/src/Resource.php +++ b/src/Resource.php @@ -2,6 +2,7 @@ namespace Dingo\Blueprint; +use Dingo\Blueprint\Annotation\DataStructures; use Illuminate\Support\Collection; use ReflectionClass; @@ -107,6 +108,18 @@ public function getActions() return $this->actions; } + /** + * Get the data structures belonging to the resource. + * + * @return \Illuminate\Support\Collection + */ + public function getDataStructures() + { + return $this->annotations->filter(function ($annotation) { + return $annotation instanceof DataStructures; + }); + } + /** * get the resource URI. * diff --git a/tests/BlueprintTest.php b/tests/BlueprintTest.php index e37a019..e64bdeb 100644 --- a/tests/BlueprintTest.php +++ b/tests/BlueprintTest.php @@ -487,4 +487,60 @@ public function testGeneratingSimpleBlueprints() $this->assertEquals(trim($expected), $blueprint->generate($resources, 'testing', 'v1', null)); } + + public function testGeneratingBlueprintsWithDataStructures() + { + $resources = new Collection([new Stubs\AccountController]); + + $blueprint = new Blueprint(new SimpleAnnotationReader, new Filesystem); + + $expected = <<<'EOT' +FORMAT: 1A + +# testing + +# Account [/accounts] + +## Show all accounts. [GET /accounts] + + ++ Response 200 (application/json) + + + Attributes + + success: true (boolean, required) - Status of the request + + data (array[Account], required) - The account's data + +## Show a specific account. [GET /accounts/1] + + ++ Response 200 (application/json) + + + Attributes + + success: true (boolean, required) - Status of the request + + data (Deposit, required) - The account's data + +## Create a new account. [POST /accounts] + + ++ Request (application/json) + + + Attributes (Account) + ++ Response 200 (application/json) + + + Attributes (Account) + + +# Data Structures + +## Account (object) + ++ name: Savings (string) - The account name ++ balance: 1200 (number) - The account balance + +## Deposit (Account) + +EOT; + $this->assertEquals(trim($expected), $blueprint->generate($resources, 'testing', 'v1', null)); + } } diff --git a/tests/Stubs/AccountController.php b/tests/Stubs/AccountController.php new file mode 100644 index 0000000..8aea747 --- /dev/null +++ b/tests/Stubs/AccountController.php @@ -0,0 +1,56 @@ +