Jelajahi Sumber

Add support for multiple scenario descriptions in @response tag

shalvah 5 tahun lalu
induk
melakukan
b804f02e7c

+ 1 - 0
phpunit.xml

@@ -24,6 +24,7 @@
         </testsuite>
         <testsuite name="Postman Collection Test">
             <file>tests/Unit/PostmanCollectionWriterTest.php</file>
+            <file>tests/Tools/AnnotationParserTest.php</file>
         </testsuite>
         <testsuite name="Strategies">
             <directory>tests/Extracting/Strategies</directory>

+ 1 - 1
resources/views/partials/route.blade.php

@@ -13,7 +13,7 @@
 
 @if(in_array('GET',$route['methods']) || (isset($route['showresponse']) && $route['showresponse']))
 @foreach($route['responses'] as $response)
-> Example response ({{$response['status']}}):
+> Example response ({{$response['description'] ?? $response['status']}}):
 
 ```json
 @if(is_object($response['content']) || is_array($response['content']))

+ 7 - 1
src/Extracting/Strategies/Responses/UseResponseTag.php

@@ -5,6 +5,7 @@ namespace Knuckles\Scribe\Extracting\Strategies\Responses;
 use Illuminate\Routing\Route;
 use Knuckles\Scribe\Extracting\RouteDocBlocker;
 use Knuckles\Scribe\Extracting\Strategies\Strategy;
+use Knuckles\Scribe\Tools\AnnotationParser;
 use Mpociot\Reflection\DocBlock;
 use Mpociot\Reflection\DocBlock\Tag;
 
@@ -59,7 +60,12 @@ class UseResponseTag extends Strategy
             $status = $result[1] ?: 200;
             $content = $result[2] ?: '{}';
 
-            return ['content' => $content, 'status' => (int) $status];
+            ['attributes' => $attributes, 'content' => $content] = AnnotationParser::parseIntoContentAndAttributes($content, ['status', 'scenario']);
+
+            $status = $attributes['status'] ?: $status;
+            $description = $attributes['scenario'] ? "$status, {$attributes['scenario']}" : $status;
+
+            return ['content' => $content, 'status' => (int) $status, 'description' => $description];
         }, $responseTags);
 
         return $responses;

+ 56 - 0
src/Tools/AnnotationParser.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Knuckles\Scribe\Tools;
+
+use Closure;
+use Exception;
+use Illuminate\Routing\Route;
+use League\Flysystem\Adapter\Local;
+use League\Flysystem\Filesystem;
+use ReflectionClass;
+use ReflectionException;
+use ReflectionFunction;
+use ReflectionFunctionAbstract;
+use Symfony\Component\Console\Output\ConsoleOutput;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\VarExporter\VarExporter;
+
+class AnnotationParser
+{
+    /**
+     * Parse an annotation like 'status=400 when="things go wrong" {"message": "failed"}'.
+     * Attributes are always optional.
+     *
+     * @param string $annotationContent
+     */
+    public static function parseIntoContentAndAttributes(string $annotationContent, array $allowedAttributes): array
+    {
+        $result = preg_split('/(\w+)=(\w+|".+?"|\'.+?\')\s*/', trim($annotationContent), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+        $defaults = array_fill_keys($allowedAttributes, null);
+        if (count($result) == 1) { // No key-value pairs
+            return [
+                'content' => trim($result[0]),
+                'attributes' => $defaults];
+        }
+
+        // Separate the main content
+        if (in_array($result[0], $allowedAttributes)) {
+            $content = trim(array_pop($result));
+        } else {
+            $content = trim(array_shift($result));
+        }
+
+        [$keys, $values] = collect($result)->partition(function ($value, $key) {
+            return $key % 2;
+        });
+        $attributes = collect($values)->combine($keys)
+            ->map(function ($value) {
+                return trim($value, '"\' ');
+            })
+            ->toArray();
+        $attributes = array_merge($defaults, $attributes);
+
+        return compact('content', 'attributes');
+    }
+}

+ 68 - 49
tests/Extracting/Strategies/Responses/UseResponseTagTest.php

@@ -12,63 +12,82 @@ class UseResponseTagTest extends TestCase
 {
     use ArraySubsetAsserts;
 
-    /** @test */
-    public function can_fetch_from_response_tag()
+    /**
+     * @test
+     * @dataProvider responseTags
+     */
+    public function allows_multiple_response_tags_for_multiple_statuses_and_scenarios(array $tags, array $expected)
     {
         $strategy = new UseResponseTag(new DocumentationConfig([]));
-        $tags = [
-            new Tag('response', '{
-        "id": 4,
-        "name": "banana",
-        "color": "red",
-        "weight": "1 kg",
-        "delicious": true,
-        "responseTag": true
-      }'),
-        ];
         $results = $strategy->getDocBlockResponses($tags);
+var_dump($expected);
+var_dump($results);
 
-        $this->assertEquals(200, $results[0]['status']);
-        $this->assertArraySubset([
-            'id' => 4,
-            'name' => 'banana',
-            'color' => 'red',
-            'weight' => '1 kg',
-            'delicious' => true,
-        ], json_decode($results[0]['content'], true));
+        $this->assertEquals($expected[0]['status'], $results[0]['status']);
+        $this->assertEquals($expected[1]['status'], $results[1]['status']);
+        $this->assertEquals($expected[0]['description'], $results[0]['description']);
+        $this->assertEquals($expected[1]['description'], $results[1]['description']);
+        $this->assertEquals($expected[0]['content'], json_decode($results[0]['content'], true));
+        $this->assertEquals($expected[1]['content'], json_decode($results[1]['content'], true));
     }
 
-    /** @test */
-    public function allows_multiple_response_tags_for_multiple_statuses()
+    public function responseTags()
     {
-        $strategy = new UseResponseTag(new DocumentationConfig([]));
-        $tags = [
-            new Tag('response', '{
+        $response1 = '{
        "id": 4,
-       "name": "banana",
-       "color": "red",
-       "weight": "1 kg",
-       "delicious": true,
-       "multipleResponseTagsAndStatusCodes": true
-     }'),
-            new Tag('response', '401 {
-       "message": "Unauthorized"
-     }'),
-        ];
-        $results = $strategy->getDocBlockResponses($tags);
+       "name": "banana"
+     }';
+        $response2 = '{
+        "message": "Unauthorized"
+     }';
+        return [
+            "with status as initial position" => [
+                [
+                    new Tag('response', $response1),
+                    new Tag('response', "401 $response2"),
+                ],
+                [
+                    [
+                        'status' => 200,
+                        'description' => '200',
+                        'content' => [
+                            'id' => 4,
+                            'name' => 'banana',
+                        ],
+                    ],
+                    [
+                        'status' => 401,
+                        'description' => '401',
+                        'content' => [
+                            'message' => 'Unauthorized',
+                        ],
+                    ],
+                ],
+            ],
 
-        $this->assertEquals(200, $results[0]['status']);
-        $this->assertEquals(401, $results[1]['status']);
-        $this->assertArraySubset([
-            'id' => 4,
-            'name' => 'banana',
-            'color' => 'red',
-            'weight' => '1 kg',
-            'delicious' => true,
-        ], json_decode($results[0]['content'], true));
-        $this->assertArraySubset([
-            'message' => 'Unauthorized',
-        ], json_decode($results[1]['content'], true));
+            "with attributes" => [
+                [
+                    new Tag('response', "scenario=\"success\" $response1"),
+                    new Tag('response', "status=401 scenario='auth problem' $response2"),
+                ],
+                [
+                    [
+                        'status' => 200,
+                        'description' => '200, success',
+                        'content' => [
+                            'id' => 4,
+                            'name' => 'banana',
+                        ],
+                    ],
+                    [
+                        'status' => 401,
+                        'description' => '401, auth problem',
+                        'content' => [
+                            'message' => 'Unauthorized',
+                        ],
+                    ],
+                ],
+            ],
+        ];
     }
-
 }

+ 53 - 0
tests/Tools/AnnotationParserTest.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace Knuckles\Scribe\Tools;
+
+use PHPUnit\Framework\TestCase;
+
+class AnnotationParserTest extends TestCase
+{
+    /**
+     * @test
+     * @dataProvider annotations
+     */
+    public function can_parse_annotation_into_content_and_attributes(string $annotation, array $expected)
+    {
+        $result = AnnotationParser::parseIntoContentAndAttributes($annotation, ['status', 'scenario']);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    public function annotations()
+    {
+        return [
+            "when attributes come first" => [
+                'status=400 scenario="things go wrong" {"message": "failed"}',
+                [
+                    'attributes' => ['status' => '400', 'scenario' => 'things go wrong'],
+                    'content' => '{"message": "failed"}',
+                ],
+            ],
+            "when attributes come last" => [
+                '{"message": "failed"} status=400 scenario="things go wrong"',
+                [
+                    'attributes' => ['status' => '400', 'scenario' => 'things go wrong'],
+                    'content' => '{"message": "failed"}',
+                ],
+            ],
+            "when there are no attributes" => [
+                '{"message": "failed"} ',
+                [
+                    'attributes' => ['status' => null, 'scenario' => null],
+                    'content' => '{"message": "failed"}',
+                ],
+            ],
+            "when there are some attributes" => [
+                ' status=hey {"message": "failed"} ',
+                [
+                    'attributes' => ['status' => 'hey', 'scenario' => null],
+                    'content' => '{"message": "failed"}',
+                ],
+            ],
+        ];
+    }
+}