瀏覽代碼

feat(apiResourceAdditional)[ISSUE-413]: added @apiResourceAdditional tag, which can be used for simulating JsonResource additional function

pallam 3 年之前
父節點
當前提交
8d43fab4eb

+ 27 - 1
src/Extracting/Strategies/Responses/UseApiResourceTags.php

@@ -55,7 +55,7 @@ class UseApiResourceTags extends Strategy
     }
 
     /**
-     * Get a response from the @apiResource/@apiResourceCollection and @apiResourceModel tags.
+     * Get a response from the @apiResource/@apiResourceCollection, @apiResourceModel and @apiResourceAdditional tags.
      *
      * @param Tag $apiResourceTag
      * @param Tag[] $allTags
@@ -68,6 +68,7 @@ class UseApiResourceTags extends Strategy
     {
         [$statusCode, $apiResourceClass] = $this->getStatusCodeAndApiResourceClass($apiResourceTag);
         [$model, $factoryStates, $relations, $pagination] = $this->getClassToBeTransformedAndAttributes($allTags);
+        $additionalData = $this->getAdditionalData($this->getApiResourceAdditionalTag($allTags));
         $modelInstance = $this->instantiateApiResourceModel($model, $factoryStates, $relations);
 
         try {
@@ -107,6 +108,9 @@ class UseApiResourceTags extends Strategy
                 : $apiResourceClass::collection($list);
         }
 
+        // Adding additional meta information for our resource response
+        $resource = $resource->additional($additionalData);
+
         $uri = Utils::getUrlWithBoundParameters($endpointData->route->uri(), $endpointData->cleanUrlParameters);
         $method = $endpointData->route->methods()[0];
         $request = Request::create($uri, $method);
@@ -171,6 +175,21 @@ class UseApiResourceTags extends Strategy
         return [$type, $states, $relations, $pagination];
     }
 
+    /**
+     * Returns data for simulating JsonResource additional() function
+     *
+     * @param Tag|null $tag
+     * @return array
+     */
+    private function getAdditionalData(?Tag $tag): array
+    {
+        if (!$tag) {
+            return [];
+        }
+
+        return a::parseIntoAttributes($tag->getContent());
+    }
+
     /**
      * @param string $type
      *
@@ -229,4 +248,11 @@ class UseApiResourceTags extends Strategy
 
         return Arr::first($apiResourceTags);
     }
+
+    public function getApiResourceAdditionalTag(array $tags): ?Tag
+    {
+        return Arr::first($tags, function ($tag) {
+            return ($tag instanceof Tag) && strtolower($tag->getName()) == 'apiresourceadditional';
+        });
+    }
 }

+ 25 - 0
src/Tools/AnnotationParser.php

@@ -30,4 +30,29 @@ class AnnotationParser
             'attributes' => $parsedAttributes
         ];
     }
+
+    /**
+     * Parse an annotation like 'status=400 when="things go wrong"' to key-value array
+     * All non key-value fields will be ignored
+     *
+     * @param string $annotationContent
+     * @return array
+     */
+    public static function parseIntoAttributes(string $annotationContent): array
+    {
+        $attributes = $matches = [];
+
+        preg_match_all(
+            '/([^\s\'"]+|".+?"|\'.+?\')=([^\s\'"]+|".+?"|\'.+?\')/',
+            $annotationContent,
+            $matches,
+            PREG_SET_ORDER,
+        );
+
+        foreach ($matches as $match) {
+            $attributes[trim($match[1], '"\' ')] = trim($match[2], '"\' ');
+        }
+
+        return $attributes;
+    }
 }

+ 79 - 0
tests/Strategies/Responses/UseApiResourceTagsTest.php

@@ -562,4 +562,83 @@ class UseApiResourceTagsTest extends BaseLaravelTest
             ],
         ];
     }
+
+    /** @test */
+    public function can_parse_apiresourceadditional_tags()
+    {
+        $config = new DocumentationConfig([]);
+
+        $route = new Route(['POST'], "/somethingRandom", ['uses' => [TestController::class, 'dummy']]);
+
+        $strategy = new UseApiResourceTags($config);
+        $tags = [
+            new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'),
+            new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser'),
+            new Tag('apiResourceAdditional', 'a=b "custom field"=c e="custom value" "another field"="true value"')
+        ];
+        $results = $strategy->getApiResourceResponse($strategy->getApiResourceTag($tags), $tags, ExtractedEndpointData::fromRoute($route));
+
+        $this->assertArraySubset([
+            [
+                'status' => 200,
+                'content' => json_encode([
+                    'data' => [
+                        'id' => 4,
+                        'name' => 'Tested Again',
+                        'email' => 'a@b.com',
+                    ],
+                    'a' => 'b',
+                    'custom field' => 'c',
+                    'e' => 'custom value',
+                    'another field' => 'true value',
+                ]),
+            ],
+        ], $results);
+    }
+
+    /** @test */
+    public function can_parse_apiresourcecollection_tags_with_collection_class_pagination_and_apiresourceadditional_tag()
+    {
+        $config = new DocumentationConfig([]);
+
+        $route = new Route(['POST'], "/somethingRandom", ['uses' => [TestController::class, 'dummy']]);
+
+        $strategy = new UseApiResourceTags($config);
+        $tags = [
+            new Tag('apiResourceCollection', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResourceCollection'),
+            new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser paginate=1,simple'),
+            new Tag('apiResourceAdditional', 'a=b'),
+        ];
+        $results = $strategy->getApiResourceResponse($strategy->getApiResourceTag($tags), $tags, ExtractedEndpointData::fromRoute($route));
+
+        $this->assertArraySubset([
+            [
+                'status' => 200,
+                'content' => json_encode([
+                    'data' => [
+                        [
+                            'id' => 4,
+                            'name' => 'Tested Again',
+                            'email' => 'a@b.com',
+                        ],
+                    ],
+                    'links' => [
+                        'self' => 'link-value',
+                        "first" => '/?page=1',
+                        "last" => null,
+                        "prev" => null,
+                        "next" => '/?page=2',
+                    ],
+                    'meta' => [
+                        "current_page" => 1,
+                        "from" => 1,
+                        "path" => '/',
+                        "per_page" => "1",
+                        "to" => 1,
+                    ],
+                    'a' => 'b',
+                ]),
+            ],
+        ], $results);
+    }
 }

+ 38 - 2
tests/Unit/AnnotationParserTest.php

@@ -9,7 +9,7 @@ class AnnotationParserTest extends TestCase
 {
     /**
      * @test
-     * @dataProvider annotations
+     * @dataProvider contentAttributesAnnotations
      */
     public function can_parse_annotation_into_content_and_attributes(string $annotation, array $expected)
     {
@@ -18,7 +18,7 @@ class AnnotationParserTest extends TestCase
         $this->assertEquals($expected, $result);
     }
 
-    public function annotations()
+    public function contentAttributesAnnotations()
     {
         return [
             "when attributes come first" => [
@@ -51,4 +51,40 @@ class AnnotationParserTest extends TestCase
             ],
         ];
     }
+
+    /**
+     * @test
+     * @dataProvider attributesAnnotations
+     */
+    public function can_parse_annotation_into_attributes(string $annotation, array $expected)
+    {
+        $result = AnnotationParser::parseIntoAttributes($annotation);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    public function attributesAnnotations()
+    {
+        return [
+            "when attributes filled" => [
+                'status=400 scenario="things go wrong" "dummy field"="dummy data", "snaked_data"=value',
+                [
+                    'status' => '400',
+                    'scenario' => 'things go wrong',
+                    'dummy field' => 'dummy data',
+                    'snaked_data' => 'value'
+                ]
+            ],
+            "when attributes not filled" => [
+                '{"message": "failed"}',
+                []
+            ],
+            "when attributes wrong" => [
+                'status= scenario="things go wrong"',
+                [
+                    'scenario' => 'things go wrong'
+                ]
+            ]
+        ];
+    }
 }