Skip to content

Commit bc66450

Browse files
authored
Merge branch 'master' into add-continue-on-errors-option
2 parents ac00ea3 + 5d64a01 commit bc66450

File tree

9 files changed

+215
-26
lines changed

9 files changed

+215
-26
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0",
3434
"illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0",
3535
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0",
36-
"illuminate/routing": "^6.0|^7.0|^8.0|^9.0|^10.0"
36+
"illuminate/routing": "^6.0|^7.0|^8.0|^9.0|^10.0",
37+
"phpstan/phpdoc-parser": "^1.24"
3738
},
3839
"extra": {
3940
"laravel": {

config/api-postman.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@
8484
'prerequest_script' => '', // This script will execute before every request in the collection.
8585
'test_script' => '', // This script will execute after every request in the collection.
8686

87+
/*
88+
|--------------------------------------------------------------------------
89+
| Include Doc Comments
90+
|--------------------------------------------------------------------------
91+
|
92+
| Determines whether or not to set the PHP Doc comments to the description
93+
| in postman.
94+
|
95+
*/
96+
97+
'include_doc_comments' => false,
98+
8799
/*
88100
|--------------------------------------------------------------------------
89101
| Enable Form Data
@@ -144,4 +156,18 @@
144156

145157
'disk' => 'local',
146158

159+
/*
160+
|--------------------------------------------------------------------------
161+
| Protocol Profile Behavior
162+
|--------------------------------------------------------------------------
163+
|
164+
| Set of configurations used to alter the usual behavior of sending the request.
165+
| These can be defined in a collection at Item or ItemGroup level which will be inherited if applicable.
166+
|
167+
*/
168+
169+
'protocol_profile_behavior' => [
170+
'disable_body_pruning' => false, // Control request body pruning for following methods: GET, COPY, HEAD, PURGE, UNLOCK
171+
],
172+
147173
];

phpunit.xml.bak

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
bootstrap="vendor/autoload.php"
5+
backupGlobals="false"
6+
colors="true"
7+
processIsolation="false"
8+
stopOnFailure="false"
9+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
10+
cacheDirectory=".phpunit.cache"
11+
backupStaticProperties="false"
12+
>
13+
<coverage>
14+
<include>
15+
<directory suffix=".php">src/</directory>
16+
</include>
17+
</coverage>
18+
<testsuites>
19+
<testsuite name="Unit">
20+
<directory suffix="Test.php">./tests/Unit</directory>
21+
</testsuite>
22+
<testsuite name="Feature">
23+
<directory suffix="Test.php">./tests/Feature</directory>
24+
</testsuite>
25+
</testsuites>
26+
<php>
27+
<env name="DB_CONNECTION" value="testing"/>
28+
</php>
29+
</phpunit>

src/Commands/ExportPostmanCommand.php

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
66
use Illuminate\Console\Command;
77
use Illuminate\Contracts\Config\Repository;
88
use Illuminate\Contracts\Validation\Rule;
9+
use Illuminate\Contracts\Validation\ValidationRule;
910
use Illuminate\Foundation\Http\FormRequest;
1011
use Illuminate\Routing\Router;
1112
use Illuminate\Support\Facades\Log;
1213
use Illuminate\Support\Facades\Storage;
1314
use Illuminate\Support\Facades\Validator;
1415
use Illuminate\Support\Str;
1516
use Illuminate\Validation\ValidationRuleParser;
17+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
18+
use PHPStan\PhpDocParser\Lexer\Lexer;
19+
use PHPStan\PhpDocParser\Parser\ConstExprParser;
20+
use PHPStan\PhpDocParser\Parser\PhpDocParser;
21+
use PHPStan\PhpDocParser\Parser\TokenIterator;
22+
use PHPStan\PhpDocParser\Parser\TypeParser;
1623
use ReflectionClass;
1724
use ReflectionFunction;
1825

@@ -45,6 +52,12 @@ class ExportPostmanCommand extends Command
4552
/** @var string */
4653
private $authType;
4754

55+
/** @var Lexer */
56+
private Lexer $lexer;
57+
58+
/** @var PhpDocParser */
59+
private PhpDocParser $phpDocParser;
60+
4861
/** @var array */
4962
private const AUTH_OPTIONS = [
5063
'bearer',
@@ -71,6 +84,7 @@ public function handle(): void
7184
$this->setAuthToken();
7285
$this->setOptions();
7386
$this->initializeStructure();
87+
$this->initializePhpDocParser();
7488

7589
foreach ($this->router->getRoutes() as $route) {
7690
$methods = array_filter($route->methods(), fn ($value) => $value !== 'HEAD');
@@ -92,6 +106,8 @@ public function handle(): void
92106

93107
$requestRules = [];
94108

109+
$requestDescription = '';
110+
95111
$routeAction = $route->getAction();
96112

97113
$reflectionMethod = $this->handleCallable(fn () => $this->getReflectionMethod($routeAction));
@@ -100,6 +116,23 @@ public function handle(): void
100116
continue;
101117
}
102118

119+
if ($this->config['include_doc_comments']) {
120+
try {
121+
$docComment = $reflectionMethod->getDocComment();
122+
$tokens = new TokenIterator($this->lexer->tokenize($docComment));
123+
$phpDocNode = $this->phpDocParser->parse($tokens);
124+
125+
foreach ($phpDocNode->children as $child) {
126+
if ($child instanceof PhpDocTextNode) {
127+
$requestDescription .= ' '.$child->text;
128+
}
129+
}
130+
$requestDescription = Str::squish($requestDescription);
131+
} catch (\Exception $e) {
132+
$this->warn('Error at parsing phpdoc at '.$reflectionMethod->class.'::'.$reflectionMethod->name);
133+
}
134+
}
135+
103136
if ($this->config['enable_formdata']) {
104137
$rulesParameter = collect($reflectionMethod->getParameters())
105138
->filter(function ($value, $key) {
@@ -156,7 +189,7 @@ public function handle(): void
156189
}
157190
}
158191

159-
$request = $this->makeRequest($route, $method, $routeHeaders, $requestRules);
192+
$request = $this->makeRequest($route, $method, $routeHeaders, $requestRules, $requestDescription);
160193

161194
if ($this->isStructured()) {
162195
$routeNames = $route->action['as'] ?? null;
@@ -277,7 +310,7 @@ protected function buildTree(array &$routes, array $segments, array $request): v
277310
}
278311
}
279312

280-
public function makeRequest($route, $method, $routeHeaders, $requestRules)
313+
public function makeRequest($route, $method, $routeHeaders, $requestRules, $requestDescription)
281314
{
282315
$printRules = $this->config['print_rules'];
283316

@@ -298,9 +331,16 @@ public function makeRequest($route, $method, $routeHeaders, $requestRules)
298331
return ['key' => $variable, 'value' => ''];
299332
})->all(),
300333
],
334+
'description' => $requestDescription,
301335
],
302336
];
303337

338+
if ($this->config['protocol_profile_behavior']['disable_body_pruning']) {
339+
$data['protocolProfileBehavior'] = [
340+
'disableBodyPruning' => true,
341+
];
342+
}
343+
304344
if ($requestRules) {
305345
$ruleData = [];
306346

@@ -313,10 +353,18 @@ public function makeRequest($route, $method, $routeHeaders, $requestRules)
313353
];
314354
}
315355

316-
$data['request']['body'] = [
317-
'mode' => 'urlencoded',
318-
'urlencoded' => $ruleData,
319-
];
356+
if ($method === 'GET') {
357+
foreach ($ruleData as &$rule) {
358+
unset($rule['type']);
359+
$rule['disabled'] = false;
360+
}
361+
$data['request']['url']['query'] = $ruleData;
362+
} else {
363+
$data['request']['body'] = [
364+
'mode' => 'urlencoded',
365+
'urlencoded' => $ruleData,
366+
];
367+
}
320368
}
321369

322370
return $data;
@@ -336,7 +384,7 @@ protected function parseRulesIntoHumanReadable($attribute, $rules): string
336384
if (! $this->config['rules_to_human_readable']) {
337385
foreach ($rules as $i => $rule) {
338386
// because we don't support custom rule classes, we remove them from the rules
339-
if (is_subclass_of($rule, Rule::class)) {
387+
if (is_subclass_of($rule, Rule::class) || is_subclass_of($rule, ValidationRule::class)) {
340388
unset($rules[$i]);
341389
}
342390
}
@@ -383,6 +431,17 @@ protected function parseRulesIntoHumanReadable($attribute, $rules): string
383431
return '';
384432
}
385433

434+
/**
435+
* Initializes the phpDocParser and lexer.
436+
*/
437+
protected function initializePhpDocParser(): void
438+
{
439+
$this->lexer = new Lexer();
440+
$constExprParser = new ConstExprParser();
441+
$typeParser = new TypeParser($constExprParser);
442+
$this->phpDocParser = new PhpDocParser($typeParser, $constExprParser);
443+
}
444+
386445
protected function initializeStructure(): void
387446
{
388447
$this->structure = [
@@ -497,7 +556,7 @@ protected function handleEdgeCases(array $messages): array
497556
*/
498557
protected function safelyStringifyClassBasedRule($probableRule): string
499558
{
500-
if (! is_object($probableRule) || is_subclass_of($probableRule, Rule::class) || ! method_exists($probableRule, '__toString')) {
559+
if (! is_object($probableRule) || is_subclass_of($probableRule, Rule::class) || is_subclass_of($probableRule, ValidationRule::class) || ! method_exists($probableRule, '__toString')) {
501560
return '';
502561
}
503562

tests/Feature/ExportPostmanTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,41 @@ public function test_rules_printing_export_works()
193193
$this->assertCount(1, $fields->where('key', 'field_6')->where('description', 'in:"1","2","3"'));
194194
}
195195

196+
public function test_rules_printing_get_export_works()
197+
{
198+
config([
199+
'api-postman.enable_formdata' => true,
200+
'api-postman.print_rules' => true,
201+
'api-postman.rules_to_human_readable' => false,
202+
]);
203+
204+
$this->artisan('export:postman')->assertExitCode(0);
205+
206+
$this->assertTrue(true);
207+
208+
$collection = collect(json_decode(Storage::get('postman/'.config('api-postman.filename')), true)['item']);
209+
210+
$targetRequest = $collection
211+
->where('name', 'example/getWithFormRequest')
212+
->first();
213+
214+
$fields = collect($targetRequest['request']['url']['query']);
215+
$this->assertCount(1, $fields->where('key', 'field_1')->where('description', 'required'));
216+
$this->assertCount(1, $fields->where('key', 'field_2')->where('description', 'required, integer'));
217+
$this->assertCount(1, $fields->where('key', 'field_5')->where('description', 'required, integer, max:30, min:1'));
218+
$this->assertCount(1, $fields->where('key', 'field_6')->where('description', 'in:"1","2","3"'));
219+
220+
// Check for the required structure of the get request query
221+
foreach ($fields as $field) {
222+
$this->assertEqualsCanonicalizing([
223+
'key' => $field['key'],
224+
'value' => null,
225+
'disabled' => false,
226+
'description' => $field['description'],
227+
], $field);
228+
}
229+
}
230+
196231
public function test_rules_printing_export_to_human_readable_works()
197232
{
198233
config([
@@ -257,6 +292,25 @@ public function test_event_export_works()
257292
}
258293
}
259294

295+
public function test_php_doc_comment_export()
296+
{
297+
config([
298+
'api-postman.include_doc_comments' => true,
299+
]);
300+
301+
$this->artisan('export:postman')->assertExitCode(0);
302+
303+
$this->assertTrue(true);
304+
305+
$collection = collect(json_decode(Storage::get('postman/'.config('api-postman.filename')), true)['item']);
306+
307+
$targetRequest = $collection
308+
->where('name', 'example/phpDocRoute')
309+
->first();
310+
311+
$this->assertEquals($targetRequest['request']['description'], 'This is the php doc route. Which is also multi-line. and has a blank line.');
312+
}
313+
260314
public static function providerFormDataEnabled(): array
261315
{
262316
return [

tests/Feature/ExportPostmanWithCacheTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ public function test_cached_export_works()
3838

3939
$collection = json_decode(Storage::get('postman/'.config('api-postman.filename')), true);
4040

41-
$routes = $this->app['router']->getRoutes();
41+
$routes = $this->app['router']->getRoutes()->getRoutesByName();
42+
43+
// Filter out workbench routes from orchestra/workbench
44+
$routes = array_filter($routes, function ($route) {
45+
return strpos($route->uri(), 'workbench') === false;
46+
});
4247

4348
$collectionItems = $collection['item'];
4449

tests/Fixtures/ExampleController.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,23 @@ public function storeWithFormRequest(ExampleFormRequest $request): string
3535
{
3636
return 'storeWithFormRequest';
3737
}
38+
39+
public function getWithFormRequest(ExampleFormRequest $request): string
40+
{
41+
return 'getWithFormRequest';
42+
}
43+
44+
/**
45+
* This is the php doc route.
46+
* Which is also multi-line.
47+
*
48+
* and has a blank line.
49+
*
50+
* @param string $non-existing param
51+
* @return string
52+
*/
53+
public function phpDocRoute(): string
54+
{
55+
return 'phpDocRoute';
56+
}
3857
}

tests/Fixtures/UppercaseRule.php

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,23 @@
22

33
namespace AndreasElia\PostmanGenerator\Tests\Fixtures;
44

5-
use Illuminate\Contracts\Validation\Rule;
5+
use Closure;
6+
use Illuminate\Contracts\Validation\ValidationRule;
67

7-
class UppercaseRule implements Rule
8+
class UppercaseRule implements ValidationRule
89
{
910
/**
10-
* Determine if the validation rule passes.
11+
* Run the validation rule.
1112
*
1213
* @param string $attribute
1314
* @param mixed $value
14-
* @return bool
15+
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
16+
* @return void
1517
*/
16-
public function passes($attribute, $value)
18+
public function validate(string $attribute, mixed $value, Closure $fail): void
1719
{
18-
return strtoupper($value) === $value;
19-
}
20-
21-
/**
22-
* Get the validation error message.
23-
*
24-
* @return string
25-
*/
26-
public function message()
27-
{
28-
return 'The :attribute must be uppercase.';
20+
if (strtoupper($value) !== $value) {
21+
$fail("The {$attribute} must be uppercase.");
22+
}
2923
}
3024
}

0 commit comments

Comments
 (0)