From bfc007423e563bc27f637362061dff55b0e0e32f Mon Sep 17 00:00:00 2001 From: shalvah Date: Sun, 7 Oct 2018 02:36:29 +0100 Subject: [PATCH 01/14] Update dependencies and remove unused phpunit config option --- composer.json | 16 ++++++++-------- phpunit.xml | 3 +-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index a7fa4a08..d0089b83 100644 --- a/composer.json +++ b/composer.json @@ -15,18 +15,18 @@ } ], "require": { - "php": ">=5.5.0", - "fzaninotto/faker": "~1.0", - "laravel/framework": "~5.4", + "php": ">=7.1.3", + "fzaninotto/faker": "~1.8", + "laravel/framework": "5.6.* || 5.7.*", "mpociot/documentarian": "^0.2.0", "mpociot/reflection-docblock": "^1.0", - "ramsey/uuid": "^3.0" + "ramsey/uuid": "^3.8" }, "require-dev": { - "orchestra/testbench": "~3.0", - "phpunit/phpunit": "~4.0 || ~5.0", - "dingo/api": "1.0.*@dev", - "mockery/mockery": "^0.9.5" + "orchestra/testbench": "3.6.* || 3.7.*", + "phpunit/phpunit": "^7.4.0", + "dingo/api": "2.0.0-alpha1", + "mockery/mockery": "^1.2.0" }, "autoload": { "psr-0": { diff --git a/phpunit.xml b/phpunit.xml index 1458c056..764ce029 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,8 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" - syntaxCheck="false"> + stopOnFailure="true"> tests/ From 1754655e2b9f1ebf714fbb2c2e784016ee71d590 Mon Sep 17 00:00:00 2001 From: shalvah Date: Sun, 7 Oct 2018 02:37:14 +0100 Subject: [PATCH 02/14] Update build config --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 36f2c9ed..d5889592 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: php php: - - 5.6 - - 7.0 - - 7.1 + - 7.1.3 + - 7.2 before_script: - travis_retry composer self-update From 3364d23884317e51b807c7c99c62a28c2297400c Mon Sep 17 00:00:00 2001 From: Shalvah A Date: Sun, 7 Oct 2018 02:42:19 +0100 Subject: [PATCH 03/14] Update from base (#7) * Update doc to address custom validation rules (#247) * Add description for array validation rule * Apply fixes from StyleCI * Remove dd() call --- README.md | 5 ++++- .../ApiDoc/Generators/AbstractGenerator.php | 15 ++++++--------- src/resources/lang/en/rules.php | 1 + tests/ApiDocGeneratorTest.php | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bb74d947..3aa24e18 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ class ExampleController extends Controller { #### Form request validation rules -To display a list of valid parameters, your API methods accepts, this package uses Laravels [Form Requests Validation](https://laravel.com/docs/5.2/validation#form-request-validation). +To display a list of valid parameters, your API methods accepts, this package uses Laravel's [Form Requests Validation](https://laravel.com/docs/5.2/validation#form-request-validation). ```php @@ -138,6 +138,9 @@ public function rules() **Result:** ![Form Request](http://marcelpociot.de/documentarian/form_request.png) +### A note on custom validation rules +This package only supports custom rules defined as classes. You'll also need to define a `__toString()` method in the class, which should return the description that would be displayed in the generated doc. + #### Controller method doc block It is possible to override the results for the response. This will also show the responses for other request methods then GET. diff --git a/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php b/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php index 2f2a21c1..74594489 100644 --- a/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php +++ b/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php @@ -178,19 +178,15 @@ protected function getParameters($routeData, $routeAction, $bindings) protected function simplifyRules($rules) { // this will split all string rules into arrays of strings - $rules = Validator::make([], $rules)->getRules(); - - if (count($rules) === 0) { - return $rules; - } + $newRules = Validator::make([], $rules)->getRules(); // Laravel will ignore the nested array rules unless the key referenced exists and is an array - // So we'll to create an empty array for each array attribute - $values = collect($rules) + // So we'll create an empty array for each array attribute + $values = collect($newRules) ->filter(function ($values) { return in_array('array', $values); })->map(function ($val, $key) { - return ['']; + return [str_random()]; })->all(); // Now this will return the complete ruleset. @@ -532,8 +528,9 @@ protected function parseRule($rule, $attribute, &$attributeData, $seed, $routeDa $attributeData['type'] = $rule; break; case 'array': - $attributeData['value'] = $faker->word; + $attributeData['value'] = [$faker->word]; $attributeData['type'] = $rule; + $attributeData['description'][] = Description::parse($rule)->getDescription(); break; case 'date': $attributeData['value'] = $faker->date(); diff --git a/src/resources/lang/en/rules.php b/src/resources/lang/en/rules.php index c3493267..5bab0bf7 100644 --- a/src/resources/lang/en/rules.php +++ b/src/resources/lang/en/rules.php @@ -5,6 +5,7 @@ 'alpha' => 'Only alphabetic characters allowed', 'alpha_dash' => 'Allowed: alpha-numeric characters, as well as dashes and underscores.', 'alpha_num' => 'Only alpha-numeric characters allowed', + 'array' => 'Must be an array', 'in' => ':attribute', 'not_in' => 'Not in: :attribute', 'min' => 'Minimum: `:attribute`', diff --git a/tests/ApiDocGeneratorTest.php b/tests/ApiDocGeneratorTest.php index 64bee058..b3407514 100644 --- a/tests/ApiDocGeneratorTest.php +++ b/tests/ApiDocGeneratorTest.php @@ -127,7 +127,7 @@ public function testCanParseFormRequestRules() case 'array': $this->assertFalse($attribute['required']); $this->assertSame('array', $attribute['type']); - $this->assertCount(0, $attribute['description']); + $this->assertCount(1, $attribute['description']); break; case 'between': $this->assertFalse($attribute['required']); From e78df66bdc73558355cfb4a42c58976a8f9b212f Mon Sep 17 00:00:00 2001 From: shalvah Date: Sun, 7 Oct 2018 02:51:26 +0100 Subject: [PATCH 04/14] Trim down dependencies --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d0089b83..2a6b5f72 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,9 @@ "require": { "php": ">=7.1.3", "fzaninotto/faker": "~1.8", - "laravel/framework": "5.6.* || 5.7.*", + "illuminate/routing": "5.6.* || 5.7.*", + "illuminate/support": "5.6.* || 5.7.*", + "illuminate/console": "5.6.* || 5.7.*", "mpociot/documentarian": "^0.2.0", "mpociot/reflection-docblock": "^1.0", "ramsey/uuid": "^3.8" From b4286894ef3243cf4e148c0fb0b8a87aa6c736ff Mon Sep 17 00:00:00 2001 From: shalvah Date: Sun, 7 Oct 2018 13:32:46 +0100 Subject: [PATCH 05/14] Downgrade requirements to L5.5 --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 2a6b5f72..27c09513 100644 --- a/composer.json +++ b/composer.json @@ -15,18 +15,18 @@ } ], "require": { - "php": ">=7.1.3", + "php": ">=7.0.0", "fzaninotto/faker": "~1.8", - "illuminate/routing": "5.6.* || 5.7.*", - "illuminate/support": "5.6.* || 5.7.*", - "illuminate/console": "5.6.* || 5.7.*", + "illuminate/routing": "5.5.* || 5.6.* || 5.7.*", + "illuminate/support": "5.5.* 5.6.* || 5.7.*", + "illuminate/console": "5.5.* 5.6.* || 5.7.*", "mpociot/documentarian": "^0.2.0", "mpociot/reflection-docblock": "^1.0", "ramsey/uuid": "^3.8" }, "require-dev": { - "orchestra/testbench": "3.6.* || 3.7.*", - "phpunit/phpunit": "^7.4.0", + "orchestra/testbench": "3.5.* || 3.6.* || 3.7.*", + "phpunit/phpunit": "^6.0.0 || ^7.4.0", "dingo/api": "2.0.0-alpha1", "mockery/mockery": "^1.2.0" }, From 1ea162f3c917a1b3bae4421daf09e3d73c941e3b Mon Sep 17 00:00:00 2001 From: shalvah Date: Sun, 7 Oct 2018 13:37:34 +0100 Subject: [PATCH 06/14] Remove FormRequest parsing (#343) --- .../ApiDoc/Generators/AbstractGenerator.php | 404 ------------------ .../ApiDoc/Parsers/RuleDescriptionParser.php | 86 ---- src/resources/lang/en/rules.php | 35 -- tests/ApiDocGeneratorTest.php | 272 ------------ tests/DingoGeneratorTest.php | 276 ------------ tests/RuleDescriptionParserTest.php | 118 ----- 6 files changed, 1191 deletions(-) delete mode 100644 src/Mpociot/ApiDoc/Parsers/RuleDescriptionParser.php delete mode 100644 src/resources/lang/en/rules.php delete mode 100644 tests/RuleDescriptionParserTest.php diff --git a/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php b/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php index 401c5e64..64486ea5 100644 --- a/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php +++ b/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php @@ -2,9 +2,7 @@ namespace Mpociot\ApiDoc\Generators; -use Faker\Factory; use ReflectionClass; -use Illuminate\Support\Arr; use Illuminate\Support\Str; use League\Fractal\Manager; use Illuminate\Routing\Route; @@ -12,10 +10,6 @@ use League\Fractal\Resource\Item; use Mpociot\Reflection\DocBlock\Tag; use League\Fractal\Resource\Collection; -use Illuminate\Support\Facades\Validator; -use Illuminate\Foundation\Http\FormRequest; -use Mpociot\ApiDoc\Parsers\RuleDescriptionParser as Description; -use Illuminate\Contracts\Validation\Factory as ValidationFactory; abstract class AbstractGenerator { @@ -148,52 +142,9 @@ protected function getDocblockResponse($tags) */ protected function getParameters($routeData, $routeAction, $bindings) { - $validationRules = $this->getRouteValidationRules($routeData['methods'], $routeAction['uses'], $bindings); - $rules = $this->simplifyRules($validationRules); - - foreach ($rules as $attribute => $ruleset) { - $attributeData = [ - 'required' => false, - 'type' => null, - 'default' => '', - 'value' => '', - 'description' => [], - ]; - foreach ($ruleset as $rule) { - $this->parseRule($rule, $attribute, $attributeData, $routeData['id'], $routeData); - } - $routeData['parameters'][$attribute] = $attributeData; - } - return $routeData; } - /** - * Format the validation rules as a plain array. - * - * @param array $rules - * - * @return array - */ - protected function simplifyRules($rules) - { - // this will split all string rules into arrays of strings - $newRules = Validator::make([], $rules)->getRules(); - - // Laravel will ignore the nested array rules unless the key referenced exists and is an array - // So we'll create an empty array for each array attribute - $values = collect($newRules) - ->filter(function ($values) { - return in_array('array', $values); - })->map(function ($val, $key) { - return [str_random()]; - })->all(); - - // Now this will return the complete ruleset. - // Nested array parameters will be present, with '*' replaced by '0' - return Validator::make($values, $rules)->getRules(); - } - /** * @param $route * @param $bindings @@ -282,302 +233,6 @@ protected function getRouteGroup($route) return 'general'; } - /** - * @param array $routeMethods - * @param string $routeAction - * @param array $bindings - * - * @return array - */ - protected function getRouteValidationRules(array $routeMethods, $routeAction, $bindings) - { - list($controller, $method) = explode('@', $routeAction); - $reflection = new ReflectionClass($controller); - $reflectionMethod = $reflection->getMethod($method); - - foreach ($reflectionMethod->getParameters() as $parameter) { - $parameterType = $parameter->getClass(); - if (! is_null($parameterType) && class_exists($parameterType->name)) { - $className = $parameterType->name; - - if (is_subclass_of($className, FormRequest::class)) { - /** @var FormRequest $formRequest */ - $formRequest = new $className; - // Add route parameter bindings - $formRequest->setContainer(app()); - $formRequest->request->add($bindings); - $formRequest->query->add($bindings); - $formRequest->setMethod($routeMethods[0]); - - if (method_exists($formRequest, 'validator')) { - $factory = app(ValidationFactory::class); - - return call_user_func_array([$formRequest, 'validator'], [$factory]) - ->getRules(); - } else { - return call_user_func_array([$formRequest, 'rules'], []); - } - } - } - } - - return []; - } - - /** - * @param array $arr - * @param string $first - * @param string $last - * - * @return string - */ - protected function fancyImplode($arr, $first, $last) - { - $arr = array_map(function ($value) { - return '`'.$value.'`'; - }, $arr); - array_push($arr, implode($last, array_splice($arr, -2))); - - return implode($first, $arr); - } - - protected function splitValuePairs($parameters, $first = 'is ', $last = 'or ') - { - $attribute = ''; - collect($parameters)->map(function ($item, $key) use (&$attribute, $first, $last) { - $attribute .= '`'.$item.'` '; - if (($key + 1) % 2 === 0) { - $attribute .= $last; - } else { - $attribute .= $first; - } - }); - $attribute = rtrim($attribute, $last); - - return $attribute; - } - - /** - * @param string $rule - * @param string $attribute - * @param array $attributeData - * @param int $seed - * - * @return void - */ - protected function parseRule($rule, $attribute, &$attributeData, $seed, $routeData) - { - if (starts_with($attribute, 'array.')) { dd(array_keys($routeData)); } - $faker = Factory::create(); - $faker->seed(crc32($seed)); - - $parsedRule = $this->parseStringRule($rule); - $parsedRule[0] = $this->normalizeRule($parsedRule[0]); - list($rule, $parameters) = $parsedRule; - - switch ($rule) { - case 'required': - $attributeData['required'] = true; - break; - case 'accepted': - $attributeData['required'] = true; - $attributeData['type'] = 'boolean'; - $attributeData['value'] = true; - break; - case 'after': - $attributeData['type'] = 'date'; - $format = isset($attributeData['format']) ? $attributeData['format'] : DATE_RFC850; - - if (strtotime($parameters[0]) === false) { - // the `after` date refers to another parameter in the request - $paramName = $parameters[0]; - $attributeData['description'][] = Description::parse($rule)->with($paramName)->getDescription(); - $attributeData['value'] = date($format, strtotime('+1 day', strtotime($routeData['parameters'][$paramName]['value']))); - } else { - $attributeData['description'][] = Description::parse($rule)->with(date($format, strtotime($parameters[0])))->getDescription(); - $attributeData['value'] = date($format, strtotime('+1 day', strtotime($parameters[0]))); - } - break; - case 'alpha': - $attributeData['description'][] = Description::parse($rule)->getDescription(); - $attributeData['value'] = $faker->word; - break; - case 'alpha_dash': - $attributeData['description'][] = Description::parse($rule)->getDescription(); - break; - case 'alpha_num': - $attributeData['description'][] = Description::parse($rule)->getDescription(); - break; - case 'in': - $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription(); - $attributeData['value'] = $faker->randomElement($parameters); - break; - case 'not_in': - $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription(); - $attributeData['value'] = $faker->word; - break; - case 'min': - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - if (Arr::get($attributeData, 'type') === 'numeric' || Arr::get($attributeData, 'type') === 'integer') { - $attributeData['value'] = $faker->numberBetween($parameters[0]); - } - break; - case 'max': - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - if (Arr::get($attributeData, 'type') === 'numeric' || Arr::get($attributeData, 'type') === 'integer') { - $attributeData['value'] = $faker->numberBetween(0, $parameters[0]); - } - break; - case 'between': - if (! isset($attributeData['type'])) { - $attributeData['type'] = 'numeric'; - } - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - $attributeData['value'] = $faker->numberBetween($parameters[0], $parameters[1]); - break; - case 'before': - $attributeData['type'] = 'date'; - $format = isset($attributeData['format']) ? $attributeData['format'] : DATE_RFC850; - - if (strtotime($parameters[0]) === false) { - // the `before` date refers to another parameter in the request - $paramName = $parameters[0]; - $attributeData['description'][] = Description::parse($rule)->with($paramName)->getDescription(); - $attributeData['value'] = date($format, strtotime('-1 day', strtotime($routeData['parameters'][$paramName]['value']))); - } else { - $attributeData['description'][] = Description::parse($rule)->with(date($format, strtotime($parameters[0])))->getDescription(); - $attributeData['value'] = date($format, strtotime('-1 day', strtotime($parameters[0]))); - } - break; - case 'date_format': - $attributeData['type'] = 'date'; - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - $attributeData['format'] = $parameters[0]; - $attributeData['value'] = date($attributeData['format']); - break; - case 'different': - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - break; - case 'digits': - $attributeData['type'] = 'numeric'; - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - $attributeData['value'] = ($parameters[0] < 9) ? $faker->randomNumber($parameters[0], true) : substr(mt_rand(100000000, mt_getrandmax()), 0, $parameters[0]); - break; - case 'digits_between': - $attributeData['type'] = 'numeric'; - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - break; - case 'file': - $attributeData['type'] = 'file'; - $attributeData['description'][] = Description::parse($rule)->getDescription(); - break; - case 'image': - $attributeData['type'] = 'image'; - $attributeData['description'][] = Description::parse($rule)->getDescription(); - break; - case 'json': - $attributeData['type'] = 'string'; - $attributeData['description'][] = Description::parse($rule)->getDescription(); - $attributeData['value'] = json_encode(['foo', 'bar', 'baz']); - break; - case 'mimetypes': - case 'mimes': - $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription(); - break; - case 'required_if': - $attributeData['description'][] = Description::parse($rule)->with($this->splitValuePairs($parameters))->getDescription(); - break; - case 'required_unless': - $attributeData['description'][] = Description::parse($rule)->with($this->splitValuePairs($parameters))->getDescription(); - break; - case 'required_with': - $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription(); - break; - case 'required_with_all': - $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' and '))->getDescription(); - break; - case 'required_without': - $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription(); - break; - case 'required_without_all': - $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' and '))->getDescription(); - break; - case 'same': - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - break; - case 'size': - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - break; - case 'timezone': - $attributeData['description'][] = Description::parse($rule)->getDescription(); - $attributeData['value'] = $faker->timezone; - break; - case 'exists': - $fieldName = isset($parameters[1]) ? $parameters[1] : $attribute; - $attributeData['description'][] = Description::parse($rule)->with([Str::singular($parameters[0]), $fieldName])->getDescription(); - break; - case 'active_url': - $attributeData['type'] = 'url'; - $attributeData['value'] = $faker->url; - break; - case 'regex': - $attributeData['type'] = 'string'; - $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription(); - break; - case 'boolean': - $attributeData['value'] = true; - $attributeData['type'] = $rule; - break; - case 'array': - $attributeData['value'] = [$faker->word]; - $attributeData['type'] = $rule; - $attributeData['description'][] = Description::parse($rule)->getDescription(); - break; - case 'date': - $attributeData['value'] = $faker->date(); - $attributeData['type'] = $rule; - break; - case 'email': - $attributeData['value'] = $faker->safeEmail; - $attributeData['type'] = $rule; - break; - case 'string': - $attributeData['value'] = $faker->word; - $attributeData['type'] = $rule; - break; - case 'integer': - $attributeData['value'] = $faker->randomNumber(); - $attributeData['type'] = $rule; - break; - case 'numeric': - $attributeData['value'] = $faker->randomNumber(); - $attributeData['type'] = $rule; - break; - case 'url': - $attributeData['value'] = $faker->url; - $attributeData['type'] = $rule; - break; - case 'ip': - $attributeData['value'] = $faker->ipv4; - $attributeData['type'] = $rule; - break; - default: - $unknownRuleDescription = Description::parse($rule)->getDescription(); - if ($unknownRuleDescription) { - $attributeData['description'][] = $unknownRuleDescription; - } - break; - } - - if ($attributeData['value'] === '') { - $attributeData['value'] = $faker->word; - } - - if (is_null($attributeData['type'])) { - $attributeData['type'] = 'string'; - } - } - /** * Call the given URI and return the Response. * @@ -618,65 +273,6 @@ protected function transformHeadersToServerVars(array $headers) return $server; } - /** - * Parse a string based rule. - * - * @param string $rules - * - * @return array - */ - protected function parseStringRule($rules) - { - $parameters = []; - - // The format for specifying validation rules and parameters follows an - // easy {rule}:{parameters} formatting convention. For instance the - // rule "max:3" states that the value may only be three letters. - if (strpos($rules, ':') !== false) { - list($rules, $parameter) = explode(':', $rules, 2); - - $parameters = $this->parseParameters($rules, $parameter); - } - - return [strtolower(trim($rules)), $parameters]; - } - - /** - * Parse a parameter list. - * - * @param string $rule - * @param string $parameter - * - * @return array - */ - protected function parseParameters($rule, $parameter) - { - if (strtolower($rule) === 'regex') { - return [$parameter]; - } - - return str_getcsv($parameter); - } - - /** - * Normalizes a rule so that we can accept short types. - * - * @param string $rule - * - * @return string - */ - protected function normalizeRule($rule) - { - switch ($rule) { - case 'int': - return 'integer'; - case 'bool': - return 'boolean'; - default: - return $rule; - } - } - /** * @param $response * diff --git a/src/Mpociot/ApiDoc/Parsers/RuleDescriptionParser.php b/src/Mpociot/ApiDoc/Parsers/RuleDescriptionParser.php deleted file mode 100644 index 06aa335c..00000000 --- a/src/Mpociot/ApiDoc/Parsers/RuleDescriptionParser.php +++ /dev/null @@ -1,86 +0,0 @@ -rule = "apidoc::rules.{$rule}"; - } - - /** - * @return array|string - */ - public function getDescription() - { - return $this->ruleDescriptionExist() ? $this->makeDescription() : []; - } - - /** - * @param string|array $parameters - * - * @return $this - */ - public function with($parameters) - { - is_array($parameters) ? - $this->parameters += $parameters : - $this->parameters[] = $parameters; - - return $this; - } - - /** - * @return bool - */ - protected function ruleDescriptionExist() - { - return trans()->hasForLocale($this->rule) || trans()->hasForLocale($this->rule, self::DEFAULT_LOCALE); - } - - /** - * @return string - */ - protected function makeDescription() - { - $description = trans()->hasForLocale($this->rule) ? - trans()->get($this->rule) : - trans()->get($this->rule, [], self::DEFAULT_LOCALE); - - return $this->replaceAttributes($description); - } - - /** - * @param string $description$ - * - * @return string - */ - protected function replaceAttributes($description) - { - foreach ($this->parameters as $parameter) { - $description = preg_replace('/:attribute/', $parameter, $description, 1); - } - - return $description; - } - - /** - * @param null $rule - * - * @return static - */ - public static function parse($rule = null) - { - return new static($rule); - } -} diff --git a/src/resources/lang/en/rules.php b/src/resources/lang/en/rules.php deleted file mode 100644 index 5bab0bf7..00000000 --- a/src/resources/lang/en/rules.php +++ /dev/null @@ -1,35 +0,0 @@ - 'Must be a date after: `:attribute`', - 'alpha' => 'Only alphabetic characters allowed', - 'alpha_dash' => 'Allowed: alpha-numeric characters, as well as dashes and underscores.', - 'alpha_num' => 'Only alpha-numeric characters allowed', - 'array' => 'Must be an array', - 'in' => ':attribute', - 'not_in' => 'Not in: :attribute', - 'min' => 'Minimum: `:attribute`', - 'max' => 'Maximum: `:attribute`', - 'between' => 'Between: `:attribute` and `:attribute`', - 'before' => 'Must be a date preceding: `:attribute`', - 'date_format' => 'Date format: `:attribute`', - 'different' => 'Must have a different value than parameter: `:attribute`', - 'digits' => 'Must have an exact length of `:attribute`', - 'digits_between' => 'Must have a length between `:attribute` and `:attribute`', - 'file' => 'Must be a file upload', - 'image' => 'Must be an image (jpeg, png, bmp, gif, or svg)', - 'json' => 'Must be a valid JSON string.', - 'mimetypes' => 'Allowed mime types: :attribute', - 'mimes' => 'Allowed mime types: :attribute', - 'required_if' => 'Required if :attribute', - 'required_unless' => 'Required unless :attribute', - 'required_with' => 'Required if the parameters :attribute are present.', - 'required_with_all' => 'Required if the parameters :attribute are present.', - 'required_without' => 'Required if the parameters :attribute are not present.', - 'required_without_all' => 'Required if the parameters :attribute are not present.', - 'same' => 'Must be the same as `:attribute`', - 'size' => 'Must have the size of `:attribute`', - 'timezone' => 'Must be a valid timezone identifier', - 'exists' => 'Valid :attribute :attribute', - 'regex' => 'Must match this regular expression: `:attribute`', -]; diff --git a/tests/ApiDocGeneratorTest.php b/tests/ApiDocGeneratorTest.php index b3407514..95db8d4c 100644 --- a/tests/ApiDocGeneratorTest.php +++ b/tests/ApiDocGeneratorTest.php @@ -4,7 +4,6 @@ use Illuminate\Routing\Route; use Orchestra\Testbench\TestCase; -use Mpociot\ApiDoc\Tests\Fixtures\TestRequest; use Mpociot\ApiDoc\Generators\LaravelGenerator; use Mpociot\ApiDoc\Tests\Fixtures\TestController; use Mpociot\ApiDoc\ApiDocGeneratorServiceProvider; @@ -76,277 +75,6 @@ public function testCanParseDependencyInjectionInControllerMethods() $this->assertTrue(is_array($parsed)); } - public function testCanParseFormRequestRules() - { - RouteFacade::post('/post', TestController::class.'@parseFormRequestRules'); - $route = new Route(['POST'], '/post', ['uses' => TestController::class.'@parseFormRequestRules']); - $parsed = $this->generator->processRoute($route); - $parameters = $parsed['parameters']; - - $testRequest = new TestRequest(); - $rules = $testRequest->rules(); - - foreach ($rules as $name => $rule) { - $attribute = $parameters[$name]; - - switch ($name) { - - case 'required': - $this->assertTrue($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'accepted': - $this->assertTrue($attribute['required']); - $this->assertSame('boolean', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'active_url': - $this->assertFalse($attribute['required']); - $this->assertSame('url', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'alpha': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Only alphabetic characters allowed', $attribute['description'][0]); - break; - case 'alpha_dash': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Allowed: alpha-numeric characters, as well as dashes and underscores.', $attribute['description'][0]); - break; - case 'alpha_num': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Only alpha-numeric characters allowed', $attribute['description'][0]); - break; - case 'array': - $this->assertFalse($attribute['required']); - $this->assertSame('array', $attribute['type']); - $this->assertCount(1, $attribute['description']); - break; - case 'between': - $this->assertFalse($attribute['required']); - $this->assertSame('numeric', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Between: `5` and `200`', $attribute['description'][0]); - break; - case 'string_between': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Between: `5` and `200`', $attribute['description'][0]); - break; - case 'before': - $this->assertFalse($attribute['required']); - $this->assertSame('date', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be a date preceding: `Saturday, 23-Apr-16 14:31:00 UTC`', $attribute['description'][0]); - break; - case 'boolean': - $this->assertFalse($attribute['required']); - $this->assertSame('boolean', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'date': - $this->assertFalse($attribute['required']); - $this->assertSame('date', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'date_format': - $this->assertFalse($attribute['required']); - $this->assertSame('date', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Date format: `j.n.Y H:iP`', $attribute['description'][0]); - break; - case 'different': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must have a different value than parameter: `alpha_num`', $attribute['description'][0]); - break; - case 'digits': - $this->assertFalse($attribute['required']); - $this->assertSame('numeric', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must have an exact length of `2`', $attribute['description'][0]); - break; - case 'digits_between': - $this->assertFalse($attribute['required']); - $this->assertSame('numeric', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must have a length between `2` and `10`', $attribute['description'][0]); - break; - case 'email': - $this->assertFalse($attribute['required']); - $this->assertSame('email', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'exists': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Valid user firstname', $attribute['description'][0]); - break; - case 'single_exists': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Valid user single_exists', $attribute['description'][0]); - break; - case 'file': - $this->assertFalse($attribute['required']); - $this->assertSame('file', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be a file upload', $attribute['description'][0]); - break; - case 'image': - $this->assertFalse($attribute['required']); - $this->assertSame('image', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be an image (jpeg, png, bmp, gif, or svg)', $attribute['description'][0]); - break; - case 'in': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('`jpeg`, `png`, `bmp`, `gif` or `svg`', $attribute['description'][0]); - break; - case 'integer': - $this->assertFalse($attribute['required']); - $this->assertSame('integer', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'ip': - $this->assertFalse($attribute['required']); - $this->assertSame('ip', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'json': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be a valid JSON string.', $attribute['description'][0]); - break; - case 'max': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Maximum: `10`', $attribute['description'][0]); - break; - case 'min': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Minimum: `20`', $attribute['description'][0]); - break; - case 'mimes': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Allowed mime types: `jpeg`, `bmp` or `png`', $attribute['description'][0]); - break; - case 'not_in': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Not in: `foo` or `bar`', $attribute['description'][0]); - break; - case 'numeric': - $this->assertFalse($attribute['required']); - $this->assertSame('numeric', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'regex': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must match this regular expression: `(.*)`', $attribute['description'][0]); - break; - case 'multiple_required_if': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if `foo` is `bar` or `baz` is `qux`', $attribute['description'][0]); - break; - case 'required_if': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if `foo` is `bar`', $attribute['description'][0]); - break; - case 'required_unless': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required unless `foo` is `bar`', $attribute['description'][0]); - break; - case 'required_with': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if the parameters `foo`, `bar` or `baz` are present.', $attribute['description'][0]); - break; - case 'required_with_all': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if the parameters `foo`, `bar` and `baz` are present.', $attribute['description'][0]); - break; - case 'required_without': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if the parameters `foo`, `bar` or `baz` are not present.', $attribute['description'][0]); - break; - case 'required_without_all': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if the parameters `foo`, `bar` and `baz` are not present.', $attribute['description'][0]); - break; - case 'same': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be the same as `foo`', $attribute['description'][0]); - break; - case 'size': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must have the size of `51`', $attribute['description'][0]); - break; - case 'timezone': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be a valid timezone identifier', $attribute['description'][0]); - break; - case 'url': - $this->assertFalse($attribute['required']); - $this->assertSame('url', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - - } - } - } - - public function testCustomFormRequestValidatorIsSupported() - { - RouteFacade::post('/post', TestController::class.'@customFormRequestValidator'); - $route = new Route(['POST'], '/post', ['uses' => TestController::class.'@customFormRequestValidator']); - $parsed = $this->generator->processRoute($route); - $parameters = $parsed['parameters']; - - $this->assertNotEmpty($parameters); - } - public function testCanParseResponseTag() { RouteFacade::post('/responseTag', TestController::class.'@responseTag'); diff --git a/tests/DingoGeneratorTest.php b/tests/DingoGeneratorTest.php index 2311498d..8f09ab57 100644 --- a/tests/DingoGeneratorTest.php +++ b/tests/DingoGeneratorTest.php @@ -37,10 +37,6 @@ public function setUp() public function testCanParseMethodDescription() { - if (version_compare($this->app->version(), '5.4', '>=')) { - $this->markTestSkipped('Dingo does not support Laravel 5.4'); - } - $api = app('Dingo\Api\Routing\Router'); $api->version('v1', function ($api) { $api->get('/api/test', TestController::class.'@parseMethodDescription'); @@ -55,10 +51,6 @@ public function testCanParseMethodDescription() public function testCanParseRouteMethods() { - if (version_compare($this->app->version(), '5.4', '>=')) { - $this->markTestSkipped('Dingo does not support Laravel 5.4'); - } - $api = app('Dingo\Api\Routing\Router'); $api->version('v1', function ($api) { $api->get('/get', TestController::class.'@dummy'); @@ -83,272 +75,4 @@ public function testCanParseRouteMethods() $this->assertSame(['DELETE'], $parsed['methods']); } - public function testCanParseFormRequestRules() - { - if (version_compare($this->app->version(), '5.4', '>=')) { - $this->markTestSkipped('Dingo does not support Laravel 5.4'); - } - - $api = app('Dingo\Api\Routing\Router'); - $api->version('v1', function ($api) { - $api->post('/post', DingoTestController::class.'@parseFormRequestRules'); - }); - - $route = app('Dingo\Api\Routing\Router')->getRoutes()['v1']->getRoutes()[0]; - $parsed = $this->generator->processRoute($route); - $parameters = $parsed['parameters']; - - $testRequest = new TestRequest(); - $rules = $testRequest->rules(); - - foreach ($rules as $name => $rule) { - $attribute = $parameters[$name]; - - switch ($name) { - - case 'required': - $this->assertTrue($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'accepted': - $this->assertTrue($attribute['required']); - $this->assertSame('boolean', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'active_url': - $this->assertFalse($attribute['required']); - $this->assertSame('url', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'alpha': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Only alphabetic characters allowed', $attribute['description'][0]); - break; - case 'alpha_dash': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Allowed: alpha-numeric characters, as well as dashes and underscores.', $attribute['description'][0]); - break; - case 'alpha_num': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Only alpha-numeric characters allowed', $attribute['description'][0]); - break; - case 'array': - $this->assertFalse($attribute['required']); - $this->assertSame('array', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'between': - $this->assertFalse($attribute['required']); - $this->assertSame('numeric', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Between: `5` and `200`', $attribute['description'][0]); - break; - case 'string_between': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Between: `5` and `200`', $attribute['description'][0]); - break; - case 'before': - $this->assertFalse($attribute['required']); - $this->assertSame('date', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be a date preceding: `Saturday, 23-Apr-16 14:31:00 UTC`', $attribute['description'][0]); - break; - case 'boolean': - $this->assertFalse($attribute['required']); - $this->assertSame('boolean', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'date': - $this->assertFalse($attribute['required']); - $this->assertSame('date', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'date_format': - $this->assertFalse($attribute['required']); - $this->assertSame('date', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Date format: `j.n.Y H:iP`', $attribute['description'][0]); - break; - case 'different': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must have a different value than parameter: `alpha_num`', $attribute['description'][0]); - break; - case 'digits': - $this->assertFalse($attribute['required']); - $this->assertSame('numeric', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must have an exact length of `2`', $attribute['description'][0]); - break; - case 'digits_between': - $this->assertFalse($attribute['required']); - $this->assertSame('numeric', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must have a length between `2` and `10`', $attribute['description'][0]); - break; - case 'email': - $this->assertFalse($attribute['required']); - $this->assertSame('email', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'exists': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Valid user firstname', $attribute['description'][0]); - break; - case 'single_exists': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Valid user single_exists', $attribute['description'][0]); - break; - case 'file': - $this->assertFalse($attribute['required']); - $this->assertSame('file', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be a file upload', $attribute['description'][0]); - break; - case 'image': - $this->assertFalse($attribute['required']); - $this->assertSame('image', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be an image (jpeg, png, bmp, gif, or svg)', $attribute['description'][0]); - break; - case 'in': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('`jpeg`, `png`, `bmp`, `gif` or `svg`', $attribute['description'][0]); - break; - case 'integer': - $this->assertFalse($attribute['required']); - $this->assertSame('integer', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'ip': - $this->assertFalse($attribute['required']); - $this->assertSame('ip', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'json': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be a valid JSON string.', $attribute['description'][0]); - break; - case 'max': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Maximum: `10`', $attribute['description'][0]); - break; - case 'min': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Minimum: `20`', $attribute['description'][0]); - break; - case 'mimes': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Allowed mime types: `jpeg`, `bmp` or `png`', $attribute['description'][0]); - break; - case 'not_in': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Not in: `foo` or `bar`', $attribute['description'][0]); - break; - case 'numeric': - $this->assertFalse($attribute['required']); - $this->assertSame('numeric', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - case 'regex': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must match this regular expression: `(.*)`', $attribute['description'][0]); - break; - case 'multiple_required_if': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if `foo` is `bar` or `baz` is `qux`', $attribute['description'][0]); - break; - case 'required_if': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if `foo` is `bar`', $attribute['description'][0]); - break; - case 'required_unless': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required unless `foo` is `bar`', $attribute['description'][0]); - break; - case 'required_with': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if the parameters `foo`, `bar` or `baz` are present.', $attribute['description'][0]); - break; - case 'required_with_all': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if the parameters `foo`, `bar` and `baz` are present.', $attribute['description'][0]); - break; - case 'required_without': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if the parameters `foo`, `bar` or `baz` are not present.', $attribute['description'][0]); - break; - case 'required_without_all': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Required if the parameters `foo`, `bar` and `baz` are not present.', $attribute['description'][0]); - break; - case 'same': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be the same as `foo`', $attribute['description'][0]); - break; - case 'size': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must have the size of `51`', $attribute['description'][0]); - break; - case 'timezone': - $this->assertFalse($attribute['required']); - $this->assertSame('string', $attribute['type']); - $this->assertCount(1, $attribute['description']); - $this->assertSame('Must be a valid timezone identifier', $attribute['description'][0]); - break; - case 'url': - $this->assertFalse($attribute['required']); - $this->assertSame('url', $attribute['type']); - $this->assertCount(0, $attribute['description']); - break; - - } - } - } } diff --git a/tests/RuleDescriptionParserTest.php b/tests/RuleDescriptionParserTest.php deleted file mode 100644 index f1f6f724..00000000 --- a/tests/RuleDescriptionParserTest.php +++ /dev/null @@ -1,118 +0,0 @@ -translatorMock = m::mock(Translator::class, [$fileLoaderMock, 'es']); - $this->app->instance('translator', $this->translatorMock); - } - - public function tearDown() - { - m::close(); - } - - public function testReturnsAnEmptyDescriptionIfARuleIsNotParsed() - { - $this->translatorMock->shouldReceive('hasForLocale')->twice()->andReturn(false); - - $description = new RuleDescriptionParser(); - - $this->assertEmpty($description->getDescription()); - } - - public function testProvidesANamedContructor() - { - $this->assertInstanceOf(RuleDescriptionParser::class, RuleDescriptionParser::parse()); - } - - public function testReturnsADescriptionInMainLanguageIfAvailable() - { - $this->translatorMock->shouldReceive('hasForLocale')->twice()->with('apidoc::rules.alpha')->andReturn(true); - $this->translatorMock->shouldReceive('get')->once()->with('apidoc::rules.alpha')->andReturn('Solo caracteres alfabeticos permitidos'); - - $description = RuleDescriptionParser::parse('alpha')->getDescription(); - - $this->assertEquals('Solo caracteres alfabeticos permitidos', $description); - } - - public function testReturnsDescriptionInDefaultLanguageIfNotAvailableInMainLanguage() - { - $this->translatorMock->shouldReceive('hasForLocale')->twice()->with('apidoc::rules.alpha')->andReturn(false); - $this->translatorMock->shouldReceive('hasForLocale')->once()->with('apidoc::rules.alpha', 'en')->andReturn(true); - $this->translatorMock->shouldReceive('get')->once()->with('apidoc::rules.alpha', [], 'en')->andReturn('Only alphabetic characters allowed'); - - $description = RuleDescriptionParser::parse('alpha')->getDescription(); - - $this->assertEquals('Only alphabetic characters allowed', $description); - } - - public function testReturnsAnEmptyDescriptionIfNotAvailable() - { - $this->translatorMock->shouldReceive('hasForLocale')->once()->with('apidoc::rules.dummy_rule')->andReturn(false); - $this->translatorMock->shouldReceive('hasForLocale')->once()->with('apidoc::rules.dummy_rule', 'en')->andReturn(false); - - $description = RuleDescriptionParser::parse('dummy_rule')->getDescription(); - - $this->assertEmpty($description); - } - - public function testAllowsToPassParametersToTheDescription() - { - $this->translatorMock->shouldReceive('hasForLocale')->twice()->with('apidoc::rules.digits')->andReturn(false); - $this->translatorMock->shouldReceive('hasForLocale')->once()->with('apidoc::rules.digits', 'en')->andReturn(true); - $this->translatorMock->shouldReceive('get')->once()->with('apidoc::rules.digits', [], 'en')->andReturn('Must have an exact length of `:attribute`'); - - $description = RuleDescriptionParser::parse('digits')->with(2)->getDescription(); - - $this->assertEquals('Must have an exact length of `2`', $description); - } - - public function testAllowsToPassMultipleParametersToTheDescription() - { - $this->translatorMock->shouldReceive('hasForLocale')->twice()->with('apidoc::rules.required_if')->andReturn(false); - $this->translatorMock->shouldReceive('hasForLocale')->once()->with('apidoc::rules.required_if', 'en')->andReturn(true); - $this->translatorMock->shouldReceive('get')->once()->with('apidoc::rules.required_if', [], 'en')->andReturn('Required if `:attribute` is `:attribute`'); - - $description = RuleDescriptionParser::parse('required_if')->with(['2 + 2', 4])->getDescription(); - - $this->assertEquals('Required if `2 + 2` is `4`', $description); - } - - /** - * @param \Illuminate\Foundation\Application $app - * - * @return array - */ - protected function getPackageProviders($app) - { - return [ApiDocGeneratorServiceProvider::class]; - } - - /** - * Define environment setup. - * - * @param \Illuminate\Foundation\Application $app - * - * @return void - */ - protected function getEnvironmentSetUp($app) - { - $app['config']->set('app.locale', 'es'); // Just to be different from default language. - $app['config']->set('app.fallback_locale', 'ch'); // Just to be different from default language. - } -} From 752f7e3ad0f042cb8660b9724daaa9f1a89d0b17 Mon Sep 17 00:00:00 2001 From: Shalvah A Date: Sun, 7 Oct 2018 14:37:53 +0100 Subject: [PATCH 07/14] Update from base (#8) * Update doc to address custom validation rules (#247) * Add description for array validation rule * Apply fixes from StyleCI * Remove dd() call * replace api namespace by apidoc * add needed apidoc renames --- README.md | 16 +++++------ .../ApiDoc/Commands/GenerateDocumentation.php | 2 +- .../ApiDoc/Commands/UpdateDocumentation.php | 2 +- tests/GenerateDocumentationTest.php | 28 +++++++++---------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 3aa24e18..48763a07 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Automatically generate your API documentation from your existing Laravel routes. Take a look at the [example documentation](http://marcelpociot.de/whiteboard/). -`php artisan api:gen --routePrefix="settings/api/*"` +`php artisan apidoc:gen --routePrefix="settings/api/*"` [![Latest Stable Version](https://poser.pugx.org/mpociot/laravel-apidoc-generator/v/stable)](https://packagist.org/packages/mpociot/laravel-apidoc-generator)[![Total Downloads](https://poser.pugx.org/mpociot/laravel-apidoc-generator/downloads)](https://packagist.org/packages/mpociot/laravel-apidoc-generator) [![License](https://poser.pugx.org/mpociot/laravel-apidoc-generator/license)](https://packagist.org/packages/mpociot/laravel-apidoc-generator) @@ -29,16 +29,16 @@ Mpociot\ApiDoc\ApiDocGeneratorServiceProvider::class, ## Usage -To generate your API documentation, use the `api:generate` artisan command. +To generate your API documentation, use the `apidoc:generate` artisan command. ```sh -$ php artisan api:generate --routePrefix="api/v1/*" +$ php artisan apidoc:generate --routePrefix="api/v1/*" ``` You can pass in multiple prefixes by spearating each prefix with comma. ```sh -$ php artisan api:generate --routePrefix="api/v1/*,api/public/*" +$ php artisan apidoc:generate --routePrefix="api/v1/*,api/public/*" ``` It will generate documentation for all of the routes whose prefixes are `api/v1/` and `api/public/` @@ -201,13 +201,13 @@ If your API route accepts a `GET` method, this package tries to call the API rou If your API needs an authenticated user, you can use the `actAsUserId` option to specify a user ID that will be used for making these API calls: ```sh -$ php artisan api:generate --routePrefix="api/*" --actAsUserId=1 +$ php artisan apidoc:generate --routePrefix="api/*" --actAsUserId=1 ``` If you don't want to automatically perform API response calls, use the `noResponseCalls` option. ```sh -$ php artisan api:generate --routePrefix="api/*" --noResponseCalls +$ php artisan apidoc:generate --routePrefix="api/*" --noResponseCalls ``` > Note: The example API responses work best with seeded data. @@ -235,10 +235,10 @@ APP_URL=http://yourapp.app If you want to modify the content of your generated documentation, go ahead and edit the generated `index.md` file. The default location of this file is: `public/docs/source/index.md`. -After editing the markdown file, use the `api:update` command to rebuild your documentation as a static HTML file. +After editing the markdown file, use the `apidoc:update` command to rebuild your documentation as a static HTML file. ```sh -$ php artisan api:update +$ php artisan apidoc:update ``` As an optional parameter, you can use `--location` to tell the update command where your documentation can be found. diff --git a/src/Mpociot/ApiDoc/Commands/GenerateDocumentation.php b/src/Mpociot/ApiDoc/Commands/GenerateDocumentation.php index 0ac2dff2..391b32c1 100644 --- a/src/Mpociot/ApiDoc/Commands/GenerateDocumentation.php +++ b/src/Mpociot/ApiDoc/Commands/GenerateDocumentation.php @@ -21,7 +21,7 @@ class GenerateDocumentation extends Command * * @var string */ - protected $signature = 'api:generate + protected $signature = 'apidoc:generate {--output=public/docs : The output path for the generated documentation} {--routeDomain= : The route domain (or domains) to use for generation} {--routePrefix= : The route prefix (or prefixes) to use for generation} diff --git a/src/Mpociot/ApiDoc/Commands/UpdateDocumentation.php b/src/Mpociot/ApiDoc/Commands/UpdateDocumentation.php index 79a58380..b613b4d8 100644 --- a/src/Mpociot/ApiDoc/Commands/UpdateDocumentation.php +++ b/src/Mpociot/ApiDoc/Commands/UpdateDocumentation.php @@ -12,7 +12,7 @@ class UpdateDocumentation extends Command * * @var string */ - protected $signature = 'api:update + protected $signature = 'apidoc:update {--location=public/docs : The documentation location} '; diff --git a/tests/GenerateDocumentationTest.php b/tests/GenerateDocumentationTest.php index 07b9fe8d..957986dd 100644 --- a/tests/GenerateDocumentationTest.php +++ b/tests/GenerateDocumentationTest.php @@ -64,7 +64,7 @@ protected function getPackageProviders($app) public function testConsoleCommandNeedsPrefixesOrDomainsOrRoutes() { - $output = $this->artisan('api:generate'); + $output = $this->artisan('apidoc:generate'); $this->assertEquals('You must provide either a route prefix, a route domain, a route or a middleware to generate the documentation.'.PHP_EOL, $output); } @@ -75,7 +75,7 @@ public function testConsoleCommandDoesNotWorkWithClosure() }); RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription'); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); $this->assertContains('Skipping route: [GET] api/closure', $output); @@ -95,7 +95,7 @@ public function testConsoleCommandDoesNotWorkWithClosureUsingDingo() }); $api->get('/test', DingoTestController::class.'@parseMethodDescription'); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--router' => 'dingo', '--routePrefix' => 'v1', ]); @@ -109,7 +109,7 @@ public function testCanSkipSingleRoutesCommandDoesNotWorkWithClosure() RouteFacade::get('/api/skip', TestController::class.'@skip'); RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription'); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); $this->assertContains('Skipping route: [GET] api/skip', $output); @@ -119,7 +119,7 @@ public function testCanSkipSingleRoutesCommandDoesNotWorkWithClosure() public function testCanParseResourceRoutes() { RouteFacade::resource('/api/user', TestResourceController::class); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); $fixtureMarkdown = __DIR__.'/Fixtures/resource_index.md'; @@ -134,7 +134,7 @@ public function testCanParsePartialResourceRoutes() 'index', 'create', ], ]); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); $fixtureMarkdown = __DIR__.'/Fixtures/partial_resource_index.md'; @@ -146,7 +146,7 @@ public function testCanParsePartialResourceRoutes() 'index', 'create', ], ]); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); $fixtureMarkdown = __DIR__.'/Fixtures/partial_resource_index.md'; @@ -159,7 +159,7 @@ public function testGeneratedMarkdownFileIsCorrect() RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription'); RouteFacade::get('/api/fetch', TestController::class.'@fetchRouteResponse'); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); @@ -175,7 +175,7 @@ public function testCanPrependAndAppendDataToGeneratedMarkdown() RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription'); RouteFacade::get('/api/fetch', TestController::class.'@fetchRouteResponse'); - $this->artisan('api:generate', [ + $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); @@ -184,7 +184,7 @@ public function testCanPrependAndAppendDataToGeneratedMarkdown() copy($prependMarkdown, __DIR__.'/../public/docs/source/prepend.md'); copy($appendMarkdown, __DIR__.'/../public/docs/source/append.md'); - $this->artisan('api:generate', [ + $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); @@ -197,7 +197,7 @@ public function testAddsBindingsToGetRouteRules() { RouteFacade::get('/api/test/{foo}', TestController::class.'@addRouteBindingsToRequestClass'); - $this->artisan('api:generate', [ + $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', '--bindings' => 'foo,bar', ]); @@ -212,7 +212,7 @@ public function testGeneratedPostmanCollectionFileIsCorrect() RouteFacade::get('/api/test', TestController::class.'@parseMethodDescription'); RouteFacade::post('/api/fetch', TestController::class.'@fetchRouteResponse'); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); @@ -227,7 +227,7 @@ public function testCanAppendCustomHttpHeaders() { RouteFacade::get('/api/headers', TestController::class.'@checkCustomHeaders'); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', '--header' => [ 'Authorization: customAuthToken', @@ -248,7 +248,7 @@ public function testGeneratesUTF8Responses() { RouteFacade::get('/api/utf8', TestController::class.'@utf8'); - $output = $this->artisan('api:generate', [ + $output = $this->artisan('apidoc:generate', [ '--routePrefix' => 'api/*', ]); From ad45bca4e2202ce59c950d4289ec3644e0ee6269 Mon Sep 17 00:00:00 2001 From: shalvah Date: Sun, 7 Oct 2018 14:39:06 +0100 Subject: [PATCH 08/14] Update service provider and refactor namespaces --- composer.json | 2 +- .../views/documentarian.blade.php | 0 .../views/partials/frontmatter.blade.php | 0 .../views/partials/info.blade.php | 0 .../views/partials/route.blade.php | 0 src/ApiDocGeneratorServiceProvider.php | 45 ++++++++++++++ .../Commands/GenerateDocumentation.php | 6 +- .../Commands/UpdateDocumentation.php | 0 .../Generators/AbstractGenerator.php | 2 +- .../ApiDoc => }/Generators/DingoGenerator.php | 0 .../Generators/LaravelGenerator.php | 0 .../ApiDoc/ApiDocGeneratorServiceProvider.php | 58 ------------------- .../ApiDoc => }/Postman/CollectionWriter.php | 0 tests/GenerateDocumentationTest.php | 14 ++--- 14 files changed, 55 insertions(+), 72 deletions(-) rename {src/resources => resources}/views/documentarian.blade.php (100%) rename {src/resources => resources}/views/partials/frontmatter.blade.php (100%) rename {src/resources => resources}/views/partials/info.blade.php (100%) rename {src/resources => resources}/views/partials/route.blade.php (100%) create mode 100644 src/ApiDocGeneratorServiceProvider.php rename src/{Mpociot/ApiDoc => }/Commands/GenerateDocumentation.php (98%) rename src/{Mpociot/ApiDoc => }/Commands/UpdateDocumentation.php (100%) rename src/{Mpociot/ApiDoc => }/Generators/AbstractGenerator.php (99%) rename src/{Mpociot/ApiDoc => }/Generators/DingoGenerator.php (100%) rename src/{Mpociot/ApiDoc => }/Generators/LaravelGenerator.php (100%) delete mode 100644 src/Mpociot/ApiDoc/ApiDocGeneratorServiceProvider.php rename src/{Mpociot/ApiDoc => }/Postman/CollectionWriter.php (100%) diff --git a/composer.json b/composer.json index 27c09513..0b839a6d 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "mockery/mockery": "^1.2.0" }, "autoload": { - "psr-0": { + "psr-4": { "Mpociot\\ApiDoc": "src/" } }, diff --git a/src/resources/views/documentarian.blade.php b/resources/views/documentarian.blade.php similarity index 100% rename from src/resources/views/documentarian.blade.php rename to resources/views/documentarian.blade.php diff --git a/src/resources/views/partials/frontmatter.blade.php b/resources/views/partials/frontmatter.blade.php similarity index 100% rename from src/resources/views/partials/frontmatter.blade.php rename to resources/views/partials/frontmatter.blade.php diff --git a/src/resources/views/partials/info.blade.php b/resources/views/partials/info.blade.php similarity index 100% rename from src/resources/views/partials/info.blade.php rename to resources/views/partials/info.blade.php diff --git a/src/resources/views/partials/route.blade.php b/resources/views/partials/route.blade.php similarity index 100% rename from src/resources/views/partials/route.blade.php rename to resources/views/partials/route.blade.php diff --git a/src/ApiDocGeneratorServiceProvider.php b/src/ApiDocGeneratorServiceProvider.php new file mode 100644 index 00000000..ab62dcac --- /dev/null +++ b/src/ApiDocGeneratorServiceProvider.php @@ -0,0 +1,45 @@ +loadViewsFrom(__DIR__ . '/../resources/views/', 'apidoc'); + + $this->publishes([ + __DIR__ . '/../resources/views' => app()->basePath().'/resources/views/vendor/apidoc', + ], 'views'); + + $this->publishes([ + __DIR__.'/../config/apidoc.php' => config_path('apidoc.php'), + ], 'config'); + + if ($this->app->runningInConsole()) { + $this->commands([ + GenerateDocumentation::class, + UpdateDocumentation::class, + ]); + } + } + + /** + * Register the API doc commands. + * + * @return void + */ + public function register() + { + // + } +} diff --git a/src/Mpociot/ApiDoc/Commands/GenerateDocumentation.php b/src/Commands/GenerateDocumentation.php similarity index 98% rename from src/Mpociot/ApiDoc/Commands/GenerateDocumentation.php rename to src/Commands/GenerateDocumentation.php index 0ac2dff2..2128e6a0 100644 --- a/src/Mpociot/ApiDoc/Commands/GenerateDocumentation.php +++ b/src/Commands/GenerateDocumentation.php @@ -252,12 +252,12 @@ private function setUserToBeImpersonated($actAs) /** * @return mixed */ - private function getRoutes() + private function getRoutes($routePrefix) { if ($this->option('router') === 'laravel') { return RouteFacade::getRoutes(); } else { - return app('Dingo\Api\Routing\Router')->getRoutes(); + return app('Dingo\Api\Routing\Router')->getRoutes($routePrefix)->getRoutes(); } } @@ -272,7 +272,7 @@ private function getRoutes() private function processRoutes(AbstractGenerator $generator, array $allowedRoutes, $routeDomain, $routePrefix, $middleware) { $withResponse = $this->option('noResponseCalls') == false; - $routes = $this->getRoutes(); + $routes = $this->getRoutes($routePrefix); $bindings = $this->getBindings(); $parsedRoutes = []; foreach ($routes as $route) { diff --git a/src/Mpociot/ApiDoc/Commands/UpdateDocumentation.php b/src/Commands/UpdateDocumentation.php similarity index 100% rename from src/Mpociot/ApiDoc/Commands/UpdateDocumentation.php rename to src/Commands/UpdateDocumentation.php diff --git a/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php b/src/Generators/AbstractGenerator.php similarity index 99% rename from src/Mpociot/ApiDoc/Generators/AbstractGenerator.php rename to src/Generators/AbstractGenerator.php index 64486ea5..e3ad05ff 100644 --- a/src/Mpociot/ApiDoc/Generators/AbstractGenerator.php +++ b/src/Generators/AbstractGenerator.php @@ -20,7 +20,7 @@ abstract class AbstractGenerator */ public function getDomain(Route $route) { - return $route->domain(); + return $route->domain() == null ? '*' : $route->domain(); } /** diff --git a/src/Mpociot/ApiDoc/Generators/DingoGenerator.php b/src/Generators/DingoGenerator.php similarity index 100% rename from src/Mpociot/ApiDoc/Generators/DingoGenerator.php rename to src/Generators/DingoGenerator.php diff --git a/src/Mpociot/ApiDoc/Generators/LaravelGenerator.php b/src/Generators/LaravelGenerator.php similarity index 100% rename from src/Mpociot/ApiDoc/Generators/LaravelGenerator.php rename to src/Generators/LaravelGenerator.php diff --git a/src/Mpociot/ApiDoc/ApiDocGeneratorServiceProvider.php b/src/Mpociot/ApiDoc/ApiDocGeneratorServiceProvider.php deleted file mode 100644 index 125f5f14..00000000 --- a/src/Mpociot/ApiDoc/ApiDocGeneratorServiceProvider.php +++ /dev/null @@ -1,58 +0,0 @@ -loadViewsFrom(__DIR__.'/../../resources/views/', 'apidoc'); - $this->loadTranslationsFrom(__DIR__.'/../../resources/lang', 'apidoc'); - - $this->publishes([ - __DIR__.'/../../resources/lang' => $this->resource_path('lang/vendor/apidoc'), - __DIR__.'/../../resources/views' => $this->resource_path('views/vendor/apidoc'), - ]); - } - - /** - * Register the API doc commands. - * - * @return void - */ - public function register() - { - $this->app->singleton('apidoc.generate', function () { - return new GenerateDocumentation(); - }); - $this->app->singleton('apidoc.update', function () { - return new UpdateDocumentation(); - }); - - $this->commands([ - 'apidoc.generate', - 'apidoc.update', - ]); - } - - /** - * Return a fully qualified path to a given file. - * - * @param string $path - * - * @return string - */ - public function resource_path($path = '') - { - return app()->basePath().'/resources'.($path ? '/'.$path : $path); - } -} diff --git a/src/Mpociot/ApiDoc/Postman/CollectionWriter.php b/src/Postman/CollectionWriter.php similarity index 100% rename from src/Mpociot/ApiDoc/Postman/CollectionWriter.php rename to src/Postman/CollectionWriter.php diff --git a/tests/GenerateDocumentationTest.php b/tests/GenerateDocumentationTest.php index 07b9fe8d..00b4856c 100644 --- a/tests/GenerateDocumentationTest.php +++ b/tests/GenerateDocumentationTest.php @@ -34,7 +34,7 @@ public function setUp() public function tearDown() { // delete the generated docs - compatible cross-platform - $dir = __DIR__.'/../public/docs'; + $dir = __DIR__.'/../public/docs';/* if (is_dir($dir)) { $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), @@ -46,7 +46,7 @@ public function tearDown() $todo($fileinfo->getRealPath()); } rmdir($dir); - } + }*/ } /** @@ -84,20 +84,16 @@ public function testConsoleCommandDoesNotWorkWithClosure() public function testConsoleCommandDoesNotWorkWithClosureUsingDingo() { - if (version_compare($this->app->version(), '5.4', '>=')) { - $this->markTestSkipped('Dingo does not support Laravel 5.4'); - } - $api = app('Dingo\Api\Routing\Router'); $api->version('v1', function ($api) { - $api->get('/closure', function () { + $api->get('v1/closure', function () { return 'foo'; }); - $api->get('/test', DingoTestController::class.'@parseMethodDescription'); + $api->get('v1/test', DingoTestController::class.'@parseMethodDescription'); $output = $this->artisan('api:generate', [ '--router' => 'dingo', - '--routePrefix' => 'v1', + '--routePrefix' => 'v1/*', ]); $this->assertContains('Skipping route: [GET] closure', $output); $this->assertContains('Processed route: [GET] test', $output); From e83a4bcde5a2f0881dbf05d0dcf3c571ae4f3461 Mon Sep 17 00:00:00 2001 From: shalvah Date: Mon, 8 Oct 2018 17:37:18 +0100 Subject: [PATCH 09/14] Move to config file (#303) --- config/apidoc.php | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 config/apidoc.php diff --git a/config/apidoc.php b/config/apidoc.php new file mode 100644 index 00000000..f2ff1585 --- /dev/null +++ b/config/apidoc.php @@ -0,0 +1,95 @@ + 'public/docs', + + + /* + * The router to be used (Laravel or Dingo). + */ + 'router' => 'laravel', + + /* + * Generate a Postman collection in addition to HTML docs. + */ + 'postman' => true, + + + /* + * The routes for which documentation should be generated. + * Each group contains rules defining which routes should be included ('match', 'include' and 'exclude' sections) + * and rules which should be applied to them ('apply' section). + */ + 'routes' => [ + [ + /* + * Specify conditions to determine what routes will be parsed in this group. + * A route must fulfill ALL conditions to pass. + */ + 'match' => [ + + /* + * Match only routes whose domains match this pattern (use * as a wildcard to match any characters). + */ + 'domains' => [ + '*', + // 'domain1.*', + ], + + /* + * Match only routes whose paths match this pattern (use * as a wildcard to match any characters). + */ + 'prefixes' => [ + '*', + // 'users/*', + ], + + /* + * Match only routes registered under this version. This option is ignored for Laravel router. + * Note that wildcards are not supported. + */ + 'versions' => [ + 'v1', + ], + ], + + /* + * Include these routes when generating documentation, + * even if they did not match the rules above. + * Note that the route must be referenced by name here. + */ + 'include' => [ + // 'users.index', + ], + + /* + * Exclude these routes when generating documentation, + * even if they matched the rules above. + * Note that the route must be referenced by name here. + */ + 'exclude' => [ + // 'users.create', + ], + + /* + * Specify rules to be applied to all the routes in this group when generating documentation + */ + 'apply' => [ + 'requests' => [ + + /* + * Specify headers to be added to the example requests + */ + 'headers' => [ + // 'Authorization' => 'Bearer: {token}', + // 'Api-Version' => 'v2', + ], + ], + ], + ], + ], +]; From 52702aed2a3f271c3bb37080f3f6bb8b7a92e286 Mon Sep 17 00:00:00 2001 From: shalvah Date: Mon, 8 Oct 2018 17:37:38 +0100 Subject: [PATCH 10/14] Implement RouteMatcher --- src/Tools/RouteMatcher.php | 81 ++++++++ tests/RouteMatcherTest.php | 369 +++++++++++++++++++++++++++++++++++++ 2 files changed, 450 insertions(+) create mode 100644 src/Tools/RouteMatcher.php create mode 100644 tests/RouteMatcherTest.php diff --git a/src/Tools/RouteMatcher.php b/src/Tools/RouteMatcher.php new file mode 100644 index 00000000..bdee992d --- /dev/null +++ b/src/Tools/RouteMatcher.php @@ -0,0 +1,81 @@ +getRoutesToBeDocumented($routeRules,true); + } + + public function getLaravelRoutesToBeDocumented(array $routeRules) + { + return $this->getRoutesToBeDocumented($routeRules); + } + + /** + * @return mixed + */ + public function getRoutesToBeDocumented(array $routeRules, bool $usingDingoRouter = false) + { + $matchedRoutes = []; + + foreach ($routeRules as $routeRule) { + $excludes = $routeRule['exclude'] ?? []; + $includes = $routeRule['include'] ?? []; + $allRoutes = $this->getAllRoutes($usingDingoRouter, $routeRule['match']['versions'] ?? []); + + foreach ($allRoutes as $route) { + /** @var Route $route */ + if (in_array($route->getName(), $excludes)) { + continue; + } + + if ($this->shouldIncludeRoute($route, $routeRule, $includes, $usingDingoRouter)) { + $matchedRoutes[] = [ + 'route' => $route, + 'apply' => $routeRule['apply'] ?? [], + ]; + continue; + } + } + } + + return $matchedRoutes; + } + + private function getAllRoutes(bool $usingDingoRouter, array $versions = []) + { + if (!$usingDingoRouter) { + return RouteFacade::getRoutes(); + } + + $allRouteCollections = app(\Dingo\Api\Routing\Router::class)->getRoutes(); + if (empty($versions)) { + return $allRoutes; + } + + return collect($allRouteCollections) + ->flatMap(function (RouteCollection $collection) { + return $collection->getRoutes(); + })->toArray(); + } + + private function shouldIncludeRoute(Route $route, $routeRule, array $mustIncludes, bool $usingDingoRouter) + { + $matchesVersion = $usingDingoRouter + ? !empty(array_intersect($route->versions(), $routeRule['match']['versions'] ?? [])) + : true; + + return in_array($route->getName(), $mustIncludes) + || (str_is($routeRule['match']['domains'] ?? [], $route->getDomain()) + && str_is($routeRule['match']['prefixes'] ?? [], $route->uri()) + && $matchesVersion); + } + +} diff --git a/tests/RouteMatcherTest.php b/tests/RouteMatcherTest.php new file mode 100644 index 00000000..3c9e1e79 --- /dev/null +++ b/tests/RouteMatcherTest.php @@ -0,0 +1,369 @@ +matcher = new RouteMatcher(); + } + + protected function getPackageProviders($app) + { + return [ + \Dingo\Api\Provider\LaravelServiceProvider::class, + ]; + } + + public function testRespectsDomainsRuleForLaravelRouter() + { + $this->registerLaravelRoutes(); + $routeRules[0]['match']['prefixes'] = ['*']; + + $routeRules[0]['match']['domains'] = ['*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $this->assertCount(12, $routes); + + $routeRules[0]['match']['domains'] = ['domain1.*', 'domain2.*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $this->assertCount(12, $routes); + + $routeRules[0]['match']['domains'] = ['domain1.*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $this->assertCount(6, $routes); + foreach ($routes as $route){ + $this->assertContains('domain1', $route['route']->getDomain()); + } + + $routeRules[0]['match']['domains'] = ['domain2.*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $this->assertCount(6, $routes); + foreach ($routes as $route){ + $this->assertContains('domain2', $route['route']->getDomain()); + } + } + + public function testRespectsDomainsRuleForDingoRouter() + { + $this->registerDingoRoutes(); + $routeRules[0]['match']['versions'] = ['v1']; + $routeRules[0]['match']['prefixes'] = ['*']; + + $routeRules[0]['match']['domains'] = ['*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(12, $routes); + + $routeRules[0]['match']['domains'] = ['domain1.*', 'domain2.*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(12, $routes); + + $routeRules[0]['match']['domains'] = ['domain1.*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(6, $routes); + foreach ($routes as $route){ + $this->assertContains('domain1', $route['route']->getDomain()); + } + + $routeRules[0]['match']['domains'] = ['domain2.*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(6, $routes); + foreach ($routes as $route){ + $this->assertContains('domain2', $route['route']->getDomain()); + } + } + + public function testRespectsPrefixesRuleForLaravelRouter() + { + $this->registerLaravelRoutes(); + $routeRules[0]['match']['domains'] = ['*']; + + $routeRules[0]['match']['prefixes'] = ['*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $this->assertCount(12, $routes); + + $routeRules[0]['match']['prefixes'] = ['prefix1/*', 'prefix2/*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $this->assertCount(8, $routes); + + $routeRules[0]['match']['prefixes'] = ['prefix1/*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $this->assertCount(4, $routes); + foreach ($routes as $route){ + $this->assertTrue(str_is('prefix1/*', $route['route']->uri())); + } + + $routeRules[0]['match']['prefixes'] = ['prefix2/*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $this->assertCount(4, $routes); + foreach ($routes as $route){ + $this->assertTrue(str_is('prefix2/*', $route['route']->uri())); + } + } + + public function testRespectsPrefixesRuleForDingoRouter() + { + $this->registerDingoRoutes(); + $routeRules[0]['match']['versions'] = ['v1']; + $routeRules[0]['match']['domains'] = ['*']; + + $routeRules[0]['match']['prefixes'] = ['*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(12, $routes); + + $routeRules[0]['match']['prefixes'] = ['prefix1/*', 'prefix2/*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(8, $routes); + + $routeRules[0]['match']['prefixes'] = ['prefix1/*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(4, $routes); + foreach ($routes as $route){ + $this->assertTrue(str_is('prefix1/*', $route['route']->uri())); + } + + $routeRules[0]['match']['prefixes'] = ['prefix2/*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(4, $routes); + foreach ($routes as $route){ + $this->assertTrue(str_is('prefix2/*', $route['route']->uri())); + } + } + + public function testRespectsVersionsRuleForDingoRouter() + { + $this->registerDingoRoutes(); + + $routeRules[0]['match']['versions'] = ['v2']; + $routeRules[0]['match']['domains'] = ['*']; + $routeRules[0]['match']['prefixes'] = ['*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(6, $routes); + foreach ($routes as $route){ + $this->assertNotEmpty(array_intersect($route['route']->versions(), ['v2'])); + } + + $routeRules[0]['match']['versions'] = ['v1', 'v2']; + $routeRules[0]['match']['domains'] = ['*']; + $routeRules[0]['match']['prefixes'] = ['*']; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(18, $routes); + } + + public function testWillIncludeRouteIfListedExplicitlyForLaravelRouter() + { + $this->registerLaravelRoutes(); + $mustInclude = 'domain1-1'; + $routeRules[0]['include'] = [$mustInclude]; + + $routeRules[0]['match']['domains'] = ['domain1.*']; + $routeRules[0]['match']['prefixes'] = ['prefix1/*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $oddRuleOut = collect($routes)->filter(function ($route) use ($mustInclude) { + return $route['route']->getName() === $mustInclude; + }); + $this->assertCount(1, $oddRuleOut); + } + + public function testWillIncludeRouteIfListedExplicitlyForDingoRouter() + { + $this->registerDingoRoutes(); + + $mustInclude = 'v2.domain2'; + $routeRules = [ + [ + 'match' => [ + 'domains' => ['domain1.*'], + 'prefixes' => ['prefix1/*'], + 'versions' => ['v1'] + ], + 'include' => [$mustInclude], + ], + ]; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $oddRuleOut = collect($routes)->filter(function ($route) use ($mustInclude) { + return $route['route']->getName() === $mustInclude; + }); + $this->assertCount(1, $oddRuleOut); + } + + public function testWillExcludeRouteIfListedExplicitlyForLaravelRouter() + { + $this->registerLaravelRoutes(); + $mustNotInclude = 'prefix1.domain1-1'; + $routeRules[0]['exclude'] = [$mustNotInclude]; + + $routeRules[0]['match']['domains'] = ['domain1.*']; + $routeRules[0]['match']['prefixes'] = ['prefix1/*']; + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $oddRuleOut = collect($routes)->filter(function ($route) use ($mustNotInclude) { + return $route['route']->getName() === $mustNotInclude; + }); + $this->assertCount(0, $oddRuleOut); + } + + public function testWillExcludeRouteIfListedExplicitlyForDingoRouter() + { + $this->registerDingoRoutes(); + + $mustNotInclude = 'v2.domain2'; + $routeRules = [ + [ + 'match' => [ + 'domains' => ['domain2.*'], + 'prefixes' => ['*'], + 'versions' => ['v2'] + ], + 'exclude' => [$mustNotInclude], + ], + ]; + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $oddRuleOut = collect($routes)->filter(function ($route) use ($mustNotInclude) { + return $route['route']->getName() === $mustNotInclude; + }); + $this->assertCount(0, $oddRuleOut); + } + + public function testMergesRoutesFromDifferentRuleGroupsForLaravelRouter() + { + $this->registerLaravelRoutes(); + + $routeRules = [ + [ + 'match' => [ + 'domains' => ['domain1.*'], + 'prefixes' => ['prefix1/*'], + ], + ], + [ + 'match' => [ + 'domains' => ['domain2.*'], + 'prefixes' => ['prefix2*'], + ], + ], + ]; + + $routes = $this->matcher->getRoutesToBeDocumented($routeRules); + $this->assertCount(4, $routes); + + $routes = collect($routes); + $firstRuleGroup = $routes->filter(function ($route) { + return str_is('prefix1/*', $route['route']->uri()) + && str_is('domain1.*', $route['route']->getDomain()); + }); + $this->assertCount(2, $firstRuleGroup); + + $secondRuleGroup = $routes->filter(function ($route) { + return str_is('prefix2/*', $route['route']->uri()) + && str_is('domain2.*', $route['route']->getDomain()); + }); + $this->assertCount(2, $secondRuleGroup); + } + + public function testMergesRoutesFromDifferentRuleGroupsForDingoRouter() + { + $this->registerDingoRoutes(); + $routeRules = [ + [ + 'match' => [ + 'domains' => ['*'], + 'prefixes' => ['*'], + 'versions' => ['v1'], + ], + ], + [ + 'match' => [ + 'domains' => ['*'], + 'prefixes' => ['*'], + 'versions' => ['v2'], + ], + ], + ]; + + $routes = $this->matcher->getDingoRoutesToBeDocumented($routeRules); + $this->assertCount(18, $routes); + + $routes = collect($routes); + $firstRuleGroup = $routes->filter(function ($route) { + return !empty(array_intersect($route['route']->versions(), ['v1'])); + }); + $this->assertCount(12, $firstRuleGroup); + + $secondRuleGroup = $routes->filter(function ($route) { + return !empty(array_intersect($route['route']->versions(), ['v2'])); + }); + $this->assertCount(6, $secondRuleGroup); + } + + private function registerLaravelRoutes() + { + RouteFacade::group(['domain' => 'domain1.app.test'], function () { + RouteFacade::post('/domain1-1', function () { return 'hi'; })->name('domain1-1'); + RouteFacade::get('domain1-2', function () { return 'hi'; })->name('domain1-2'); + RouteFacade::get('/prefix1/domain1-1', function () { return 'hi'; })->name('prefix1.domain1-1'); + RouteFacade::get('prefix1/domain1-2', function () { return 'hi'; })->name('prefix1.domain1-2'); + RouteFacade::get('/prefix2/domain1-1', function () { return 'hi'; })->name('prefix2.domain1-1'); + RouteFacade::get('prefix2/domain1-2', function () { return 'hi'; })->name('prefix2.domain1-2'); + }); + RouteFacade::group(['domain' => 'domain2.app.test'], function () { + RouteFacade::post('/domain2-1', function () { return 'hi'; })->name('domain2-1'); + RouteFacade::get('domain2-2', function () { return 'hi'; })->name('domain2-2'); + RouteFacade::get('/prefix1/domain2-1', function () { return 'hi'; })->name('prefix1.domain2-1'); + RouteFacade::get('prefix1/domain2-2', function () { return 'hi'; })->name('prefix1.domain2-2'); + RouteFacade::get('/prefix2/domain2-1', function () { return 'hi'; })->name('prefix2.domain2-1'); + RouteFacade::get('prefix2/domain2-2', function () { return 'hi'; })->name('prefix2.domain2-2'); + }); + } + + private function registerDingoRoutes() + { + + $api = app('api.router'); + $api->version('v1', function (Router $api) { + $api->group(['domain' => 'domain1.app.test'], function (Router $api) { + $api->post('/domain1-1', function () { return 'hi'; })->name('v1.domain1-1'); + $api->get('domain1-2', function () { return 'hi'; })->name('v1.domain1-2'); + $api->get('/prefix1/domain1-1', function () { return 'hi'; })->name('v1.prefix1.domain1-1'); + $api->get('prefix1/domain1-2', function () { return 'hi'; })->name('v1.prefix1.domain1-2'); + $api->get('/prefix2/domain1-1', function () { return 'hi'; })->name('v1.prefix2.domain1-1'); + $api->get('prefix2/domain1-2', function () { return 'hi'; })->name('v1.prefix2.domain1-2'); + }); + $api->group(['domain' => 'domain2.app.test'], function (Router $api) { + $api->post('/domain2-1', function () { return 'hi'; })->name('v1.domain2-1'); + $api->get('domain2-2', function () { return 'hi'; })->name('v1.domain2-2'); + $api->get('/prefix1/domain2-1', function () { return 'hi'; })->name('v1.prefix1.domain2-1'); + $api->get('prefix1/domain2-2', function () { return 'hi'; })->name('v1.prefix1.domain2-2'); + $api->get('/prefix2/domain2-1', function () { return 'hi'; })->name('v1.prefix2.domain2-1'); + $api->get('prefix2/domain2-2', function () { return 'hi'; })->name('v1.prefix2.domain2-2'); + }); + }); + $api->version('v2', function (Router $api) { + $api->group(['domain' => 'domain1.app.test'], function (Router $api) { + $api->post('/domain1', function () { return 'hi'; })->name('v2.domain1'); + $api->get('/prefix1/domain1', function () { return 'hi'; })->name('v2.prefix1.domain1'); + $api->get('/prefix2/domain1', function () { return 'hi'; })->name('v2.prefix2.domain1'); + }); + $api->group(['domain' => 'domain2.app.test'], function (Router $api) { + $api->post('/domain2', function () { return 'hi'; })->name('v2.domain2'); + $api->get('/prefix1/domain2', function () { return 'hi'; })->name('v2.prefix1.domain2'); + $api->get('/prefix2/domain2', function () { return 'hi'; })->name('v2.prefix2.domain2'); + }); + }); + } +} From 693dd60ef0e2d9d467c0fb4bd033cf8dbe0a7981 Mon Sep 17 00:00:00 2001 From: shalvah Date: Mon, 8 Oct 2018 17:39:05 +0100 Subject: [PATCH 11/14] Clean up imports and fix namesapce --- composer.json | 2 +- tests/RouteMatcherTest.php | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 0b839a6d..bb0fa47d 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ }, "autoload": { "psr-4": { - "Mpociot\\ApiDoc": "src/" + "Mpociot\\ApiDoc\\": "src/" } }, "autoload-dev": { diff --git a/tests/RouteMatcherTest.php b/tests/RouteMatcherTest.php index 3c9e1e79..ecdf573e 100644 --- a/tests/RouteMatcherTest.php +++ b/tests/RouteMatcherTest.php @@ -2,14 +2,9 @@ namespace Mpociot\ApiDoc\Tests; -use Dingo\Api\Routing\Route as DingoRoute; use Dingo\Api\Routing\Router; -use Illuminate\Routing\Route; -use Mpociot\ApiDoc\Tools\RouteMatcher; use Orchestra\Testbench\TestCase; -use Mpociot\ApiDoc\Generators\LaravelGenerator; -use Mpociot\ApiDoc\Tests\Fixtures\TestController; -use Mpociot\ApiDoc\ApiDocGeneratorServiceProvider; +use Mpociot\ApiDoc\Tools\RouteMatcher; use Illuminate\Support\Facades\Route as RouteFacade; class RouteMatcherTest extends TestCase From 92a88e1db60c3c7e7a5df7192e89e2e7d4fb06c4 Mon Sep 17 00:00:00 2001 From: shalvah Date: Mon, 8 Oct 2018 17:50:20 +0100 Subject: [PATCH 12/14] Update PHPUnit config --- phpunit.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 764ce029..8875a97f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -15,10 +15,10 @@ - src/Mpociot/ + src/ - src/Mpociot/ApiDoc/ApiDocGeneratorServiceProvider.php - src/resources/views/documentarian.blade.php + src/ApiDocGeneratorServiceProvider.php + resources/views/documentarian.blade.php From a04327a09432f9b99e27a751d2bda24d1e2af1f6 Mon Sep 17 00:00:00 2001 From: shalvah Date: Mon, 8 Oct 2018 17:56:44 +0100 Subject: [PATCH 13/14] Remove response calls (#356) --- src/Commands/GenerateDocumentation.php | 134 ++++--------------------- src/Generators/AbstractGenerator.php | 32 ++---- src/Tools/RouteMatcher.php | 9 +- 3 files changed, 29 insertions(+), 146 deletions(-) diff --git a/src/Commands/GenerateDocumentation.php b/src/Commands/GenerateDocumentation.php index a3929426..7b32afb7 100644 --- a/src/Commands/GenerateDocumentation.php +++ b/src/Commands/GenerateDocumentation.php @@ -2,6 +2,7 @@ namespace Mpociot\ApiDoc\Commands; +use Mpociot\ApiDoc\Tools\RouteMatcher; use ReflectionClass; use Illuminate\Routing\Route; use Illuminate\Console\Command; @@ -12,7 +13,6 @@ use Mpociot\ApiDoc\Generators\DingoGenerator; use Mpociot\ApiDoc\Generators\LaravelGenerator; use Mpociot\ApiDoc\Generators\AbstractGenerator; -use Illuminate\Support\Facades\Route as RouteFacade; class GenerateDocumentation extends Command { @@ -22,21 +22,7 @@ class GenerateDocumentation extends Command * @var string */ protected $signature = 'apidoc:generate - {--output=public/docs : The output path for the generated documentation} - {--routeDomain= : The route domain (or domains) to use for generation} - {--routePrefix= : The route prefix (or prefixes) to use for generation} - {--routes=* : The route names to use for generation} - {--middleware= : The middleware to use for generation} - {--noResponseCalls : Disable API response calls} - {--noPostmanCollection : Disable Postman collection creation} - {--useMiddlewares : Use all configured route middlewares} - {--authProvider=users : The authentication provider to use for API response calls} - {--authGuard=web : The authentication guard to use for API response calls} - {--actAsUserId= : The user ID to use for API response calls} - {--router=laravel : The router to be used (Laravel or Dingo)} {--force : Force rewriting of existing routes} - {--bindings= : Route Model Bindings} - {--header=* : Custom HTTP headers to add to the example requests. Separate the header name and value with ":"} '; /** @@ -46,14 +32,13 @@ class GenerateDocumentation extends Command */ protected $description = 'Generate your API documentation from existing Laravel routes.'; - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() + + private $routeMatcher; + + public function __construct(RouteMatcher $routeMatcher) { parent::__construct(); + $this->routeMatcher = $routeMatcher; } /** @@ -63,39 +48,21 @@ public function __construct() */ public function handle() { + $routes = config('apidoc.router') == 'dingo' + ? $this->routeMatcher->getDingoRoutesToBeDocumented(config('apidoc.routes')) + : $this->routeMatcher->getLaravelRoutesToBeDocumented(config('apidoc.routes')); + if ($this->option('router') === 'laravel') { $generator = new LaravelGenerator(); } else { $generator = new DingoGenerator(); } - $allowedRoutes = $this->option('routes'); - $routeDomain = $this->option('routeDomain'); - $routePrefix = $this->option('routePrefix'); - $middleware = $this->option('middleware'); - - $this->setUserToBeImpersonated($this->option('actAsUserId')); - - if ($routePrefix === null && $routeDomain === null && ! count($allowedRoutes) && $middleware === null) { - $this->error('You must provide either a route prefix, a route domain, a route or a middleware to generate the documentation.'); - - return false; - } - - $generator->prepareMiddleware($this->option('useMiddlewares')); - - $routePrefixes = explode(',', $routePrefix ?: '*'); - $routeDomains = explode(',', $routeDomain ?: '*'); - - $parsedRoutes = []; - foreach ($routeDomains as $routeDomain) { - foreach ($routePrefixes as $routePrefix) { - $parsedRoutes += $this->processRoutes($generator, $allowedRoutes, $routeDomain, $routePrefix, $middleware); - } - } - $parsedRoutes = collect($parsedRoutes)->groupBy('resource')->sort(function ($a, $b) { - return strcmp($a->first()['resource'], $b->first()['resource']); + $parsedRoutes = $this->processRoutes($generator, $routes); + $parsedRoutes = collect($parsedRoutes)->groupBy('resource') + ->sort(function ($a, $b) { + return strcmp($a->first()['resource'], $b->first()['resource']); }); $this->writeMarkdown($parsedRoutes); @@ -210,85 +177,24 @@ private function writeMarkdown($parsedRoutes) } } - /** - * @return array - */ - private function getBindings() - { - $bindings = $this->option('bindings'); - if (empty($bindings)) { - return []; - } - - $bindings = explode('|', $bindings); - $resultBindings = []; - foreach ($bindings as $binding) { - list($name, $id) = explode(',', $binding); - $resultBindings[$name] = $id; - } - - return $resultBindings; - } - - /** - * @param $actAs - */ - private function setUserToBeImpersonated($actAs) - { - if (! empty($actAs)) { - if (version_compare($this->laravel->version(), '5.2.0', '<')) { - $userModel = config('auth.model'); - $user = $userModel::find($actAs); - $this->laravel['auth']->setUser($user); - } else { - $provider = $this->option('authProvider'); - $userModel = config("auth.providers.$provider.model"); - $user = $userModel::find($actAs); - $this->laravel['auth']->guard($this->option('authGuard'))->setUser($user); - } - } - } - - /** - * @return mixed - */ - private function getRoutes($routePrefix) - { - if ($this->option('router') === 'laravel') { - return RouteFacade::getRoutes(); - } else { - return app('Dingo\Api\Routing\Router')->getRoutes($routePrefix)->getRoutes(); - } - } /** - * @param AbstractGenerator $generator - * @param $allowedRoutes - * @param $routeDomain - * @param $routePrefix - * + * @param AbstractGenerator $generator + * @param array $routes * @return array + * */ - private function processRoutes(AbstractGenerator $generator, array $allowedRoutes, $routeDomain, $routePrefix, $middleware) + private function processRoutes(AbstractGenerator $generator, array $routes) { - $withResponse = $this->option('noResponseCalls') == false; - $routes = $this->getRoutes($routePrefix); - $bindings = $this->getBindings(); $parsedRoutes = []; - foreach ($routes as $route) { + foreach ($routes as ['route' => $route, 'apply' => $apply]) { /** @var Route $route */ - if (in_array($route->getName(), $allowedRoutes) - || (str_is($routeDomain, $generator->getDomain($route)) - && str_is($routePrefix, $generator->getUri($route))) - || in_array($middleware, $route->middleware()) - ) { if ($this->isValidRoute($route) && $this->isRouteVisibleForDocumentation($route->getAction()['uses'])) { - $parsedRoutes[] = $generator->processRoute($route, $bindings, $this->option('header'), $withResponse && in_array('GET', $generator->getMethods($route))); + $parsedRoutes[] = $generator->processRoute($route, $apply); $this->info('Processed route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route)); } else { $this->warn('Skipping route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route)); } - } } return $parsedRoutes; diff --git a/src/Generators/AbstractGenerator.php b/src/Generators/AbstractGenerator.php index e3ad05ff..7b89b5c5 100644 --- a/src/Generators/AbstractGenerator.php +++ b/src/Generators/AbstractGenerator.php @@ -45,23 +45,17 @@ public function getMethods(Route $route) /** * @param \Illuminate\Routing\Route $route - * @param array $bindings - * @param bool $withResponse + * @param array $apply Rules to apply when generating documentation for this route * * @return array */ - public function processRoute($route, $bindings = [], $headers = [], $withResponse = true) + public function processRoute($route, $apply = []) { - $routeDomain = $route->domain(); $routeAction = $route->getAction(); $routeGroup = $this->getRouteGroup($routeAction['uses']); $routeDescription = $this->getRouteDescription($routeAction['uses']); $showresponse = null; - // set correct route domain - $headers[] = "HTTP_HOST: {$routeDomain}"; - $headers[] = "SERVER_NAME: {$routeDomain}"; - $response = null; $docblockResponse = $this->getDocblockResponse($routeDescription['tags']); if ($docblockResponse) { @@ -77,27 +71,20 @@ public function processRoute($route, $bindings = [], $headers = [], $withRespons $showresponse = true; } } - if (! $response && $withResponse) { - try { - $response = $this->getRouteResponse($route, $bindings, $headers); - } catch (\Exception $e) { - echo "Couldn't get response for route: ".implode(',', $this->getMethods($route)).$route->uri().']: '.$e->getMessage()."\n"; - } - } $content = $this->getResponseContent($response); - return $this->getParameters([ + return [ 'id' => md5($this->getUri($route).':'.implode($this->getMethods($route))), 'resource' => $routeGroup, 'title' => $routeDescription['short'], 'description' => $routeDescription['long'], 'methods' => $this->getMethods($route), 'uri' => $this->getUri($route), - 'parameters' => [], + 'parameters' => $this->getParametersFromDocBlock($routeAction['uses']), 'response' => $content, 'showresponse' => $showresponse, - ], $routeAction, $bindings); + ]; } /** @@ -134,15 +121,12 @@ protected function getDocblockResponse($tags) } /** - * @param array $routeData * @param array $routeAction - * @param array $bindings - * - * @return mixed + * @return array */ - protected function getParameters($routeData, $routeAction, $bindings) + protected function getParametersFromDocBlock($routeAction) { - return $routeData; + return []; } /** diff --git a/src/Tools/RouteMatcher.php b/src/Tools/RouteMatcher.php index bdee992d..fd7f00b6 100644 --- a/src/Tools/RouteMatcher.php +++ b/src/Tools/RouteMatcher.php @@ -18,9 +18,6 @@ public function getLaravelRoutesToBeDocumented(array $routeRules) return $this->getRoutesToBeDocumented($routeRules); } - /** - * @return mixed - */ public function getRoutesToBeDocumented(array $routeRules, bool $usingDingoRouter = false) { $matchedRoutes = []; @@ -56,17 +53,13 @@ private function getAllRoutes(bool $usingDingoRouter, array $versions = []) } $allRouteCollections = app(\Dingo\Api\Routing\Router::class)->getRoutes(); - if (empty($versions)) { - return $allRoutes; - } - return collect($allRouteCollections) ->flatMap(function (RouteCollection $collection) { return $collection->getRoutes(); })->toArray(); } - private function shouldIncludeRoute(Route $route, $routeRule, array $mustIncludes, bool $usingDingoRouter) + private function shouldIncludeRoute(Route $route, array $routeRule, array $mustIncludes, bool $usingDingoRouter) { $matchesVersion = $usingDingoRouter ? !empty(array_intersect($route->versions(), $routeRule['match']['versions'] ?? [])) From 0bef95849f2c134ab933871b37c258f0bbcad508 Mon Sep 17 00:00:00 2001 From: Shalvah A Date: Mon, 8 Oct 2018 18:01:43 +0100 Subject: [PATCH 14/14] Update from base (#9) * Update doc to address custom validation rules (#247) * Add description for array validation rule * Apply fixes from StyleCI * Remove dd() call * replace api namespace by apidoc * add needed apidoc renames