Browse Source

Support types in URL and query parameters

shalvah 4 years ago
parent
commit
edfda67ac1

+ 6 - 6
src/Extracting/ParamHelpers.php

@@ -59,7 +59,7 @@ trait ParamHelpers
             },
             },
         ];
         ];
 
 
-        $fakeFactory = $fakeFactories[$this->normalizeParameterType($baseType)] ?? $fakeFactories['string'];
+        $fakeFactory = $fakeFactories[$baseType] ?? $fakeFactories['string'];
 
 
         return $fakeFactory();
         return $fakeFactory();
     }
     }
@@ -79,7 +79,7 @@ trait ParamHelpers
             'array', // todo remove this
             'array', // todo remove this
             'object',
             'object',
         ];
         ];
-        return in_array(preg_replace('/\[]$/', '', $type), $types);
+        return in_array(str_replace('[]', '', $type), $types);
     }
     }
 
 
     /**
     /**
@@ -148,15 +148,15 @@ trait ParamHelpers
             return 'string';
             return 'string';
         }
         }
 
 
-        $base = preg_replace('/\[]/', '', strtolower($typeName));
+        $base = str_replace('[]', '', strtolower($typeName));
         switch ($base) {
         switch ($base) {
             case 'int':
             case 'int':
-                return preg_replace("/$base/", 'integer', $typeName);
+                return str_replace($base, 'integer', $typeName);
             case 'float':
             case 'float':
             case 'double':
             case 'double':
-                return preg_replace("/$base/", 'number', $typeName);
+                return str_replace($base, 'number', $typeName);
             case 'bool':
             case 'bool':
-                return preg_replace("/$base/", 'boolean', $typeName);
+                return str_replace($base, 'boolean', $typeName);
             default:
             default:
                 return $typeName;
                 return $typeName;
         }
         }

+ 31 - 31
src/Extracting/Strategies/BodyParameters/GetFromBodyParamTag.php

@@ -61,41 +61,41 @@ class GetFromBodyParamTag extends Strategy
 
 
     public function getBodyParametersFromDocBlock($tags)
     public function getBodyParametersFromDocBlock($tags)
     {
     {
-        $parameters = collect($tags)
-            ->filter(function ($tag) {
-                return $tag instanceof Tag && $tag->getName() === 'bodyParam';
-            })
-            ->mapWithKeys(function (Tag $tag) {
-                // Format:
-                // @bodyParam <name> <type> <"required" (optional)> <description>
-                // Examples:
-                // @bodyParam text string required The text.
-                // @bodyParam user_id integer The ID of the user.
-                preg_match('/(.+?)\s+(.+?)\s+(required\s+)?([\s\S]*)/', trim($tag->getContent()), $content);
-                $content = preg_replace('/\s+No-example.?/', '', $content);
-                if (empty($content)) {
-                    // this means only name and type were supplied
-                    [$name, $type] = preg_split('/\s+/', $tag->getContent());
-                    $required = false;
+        $parameters = [];
+
+        foreach ($tags as $tag) {
+            if ($tag->getName() !== 'bodyParam') continue;
+
+            $tagContent = trim($tag->getContent());
+            // Format:
+            // @bodyParam <name> <type> <"required" (optional)> <description>
+            // Examples:
+            // @bodyParam text string required The text.
+            // @bodyParam user_id integer The ID of the user.
+            preg_match('/(.+?)\s+(.+?)\s+(required\s+)?([\s\S]*)/', $tagContent, $content);
+            if (empty($content)) {
+                // this means only name and type were supplied
+                [$name, $type] = preg_split('/\s+/', $tagContent);
+                $required = false;
+                $description = '';
+            } else {
+                [$_, $name, $type, $required, $description] = $content;
+                $description = trim(str_replace(['No-example.', 'No-example'], '', $description));
+                if ($description == 'required') {
+                    $required = $description;
                     $description = '';
                     $description = '';
-                } else {
-                    [$_, $name, $type, $required, $description] = $content;
-                    $description = trim($description);
-                    if ($description == 'required' && empty(trim($required))) {
-                        $required = $description;
-                        $description = '';
-                    }
-                    $required = trim($required) == 'required' ? true : false;
                 }
                 }
+                $required = trim($required) === 'required';
+            }
 
 
-                $type = $this->normalizeParameterType($type);
-                [$description, $example] = $this->parseExampleFromParamDescription($description, $type);
-                $value = is_null($example) && ! $this->shouldExcludeExample($tag->getContent())
-                    ? $this->generateDummyValue($type)
-                    : $example;
+            $type = $this->normalizeParameterType($type);
+            [$description, $example] = $this->parseExampleFromParamDescription($description, $type);
+            $value = is_null($example) && !$this->shouldExcludeExample($tagContent)
+                ? $this->generateDummyValue($type)
+                : $example;
 
 
-                return [$name => compact('type', 'description', 'required', 'value')];
-            })->toArray();
+            $parameters[$name] = compact('type', 'description', 'required', 'value');
+        }
 
 
         return $parameters;
         return $parameters;
     }
     }

+ 66 - 31
src/Extracting/Strategies/QueryParameters/GetFromQueryParamTag.php

@@ -59,44 +59,79 @@ class GetFromQueryParamTag extends Strategy
         return $this->getQueryParametersFromDocBlock($methodDocBlock->getTags());
         return $this->getQueryParametersFromDocBlock($methodDocBlock->getTags());
     }
     }
 
 
-    public function getQueryParametersFromDocBlock($tags)
+    /**
+     * @param Tag[] $tags
+     *
+     * @return <string, array>[]
+     */
+    public function getQueryParametersFromDocBlock(array $tags)
     {
     {
-        $parameters = collect($tags)
-            ->filter(function ($tag) {
-                return $tag instanceof Tag && $tag->getName() === 'queryParam';
-            })
-            ->mapWithKeys(function (Tag $tag) {
-                // Format:
-                // @queryParam <name> <"required" (optional)> <description>
-                // Examples:
-                // @queryParam text string required The text.
-                // @queryParam user_id The ID of the user.
-                preg_match('/(.+?)\s+(required\s+)?([\s\S]*)/', $tag->getContent(), $content);
-                $content = preg_replace('/\s?No-example.?/', '', $content);
-                if (empty($content)) {
-                    // this means only name was supplied
-                    [$name] = preg_split('/\s+/', $tag->getContent());
-                    $required = false;
+        $parameters = [];
+
+        foreach ($tags as $tag) {
+            if ($tag->getName() !== 'queryParam') continue;
+
+            // Format:
+            // @queryParam <name> <type (optional)> <"required" (optional)> <description>
+            // Examples:
+            // @queryParam text required The text.
+            // @queryParam user_id integer The ID of the user.
+
+            $tagContent = trim($tag->getContent());
+            preg_match('/(.+?)\s+([a-zA-Z\[\]]+\s+)?(required\s+)?([\s\S]*)/', $tagContent, $content);
+
+            if (empty($content)) {
+                // this means only name was supplied
+                $name = $tagContent;
+                $required = false;
+                $description = '';
+                $type = 'string';
+            } else {
+                [$_, $name, $type, $required, $description] = $content;
+
+                $description = trim(str_replace(['No-example.', 'No-example'], '', $description));
+                if ($description === 'required') {
+                    // No description was supplied
+                    $required = true;
                     $description = '';
                     $description = '';
                 } else {
                 } else {
-                    [$_, $name, $required, $description] = $content;
-                    $description = trim($description);
-                    if ($description == 'required' && empty(trim($required))) {
-                        $required = $description;
-                        $description = '';
-                    }
-                    $required = trim($required) == 'required' ? true : false;
+                    $required = trim($required) === 'required';
                 }
                 }
 
 
-                [$description, $value] = $this->parseExampleFromParamDescription($description, 'string');
-                if (is_null($value) && ! $this->shouldExcludeExample($tag->getContent())) {
-                    $value = Str::contains($description, ['number', 'count', 'page'])
-                        ? $this->generateDummyValue('integer')
-                        : $this->generateDummyValue('string');
+                $type = trim($type);
+                if ($type) {
+                    if ($type === 'required') {
+                        // Type wasn't supplied
+                        $type = 'string';
+                        $required = true;
+                    } else {
+                        $type = $this->normalizeParameterType($type);
+                        // Type in annotation is optional
+                        if (!$this->isSupportedTypeInDocBlocks($type)) {
+                            // Then that wasn't a type, but part of the description
+                            $description = trim("$type $description");
+                            $type = '';
+                        }
+                    }
+                } else if ($this->isSupportedTypeInDocBlocks($description)) {
+                    // Only type was supplied
+                    $type = $description;
+                    $description = '';
                 }
                 }
 
 
-                return [$name => compact('description', 'required', 'value')];
-            })->toArray();
+                $type = empty($type)
+                    ? (Str::contains($description, ['number', 'count', 'page']) ? 'integer' : 'string')
+                    : $this->normalizeParameterType($type);
+
+            }
+
+            [$description, $value] = $this->parseExampleFromParamDescription($description, $type);
+            if (is_null($value) && !$this->shouldExcludeExample($tagContent)) {
+                $value = $this->generateDummyValue($type);
+            }
+
+            $parameters[$name] = compact('description', 'required', 'value', 'type');
+        }
 
 
         return $parameters;
         return $parameters;
     }
     }

+ 1 - 1
src/Extracting/Strategies/ResponseFields/GetFromResponseFieldTag.php

@@ -58,7 +58,7 @@ class GetFromResponseFieldTag extends Strategy
                 // Support optional type in annotation
                 // Support optional type in annotation
                 if (!$this->isSupportedTypeInDocBlocks($type)) {
                 if (!$this->isSupportedTypeInDocBlocks($type)) {
                     // Then that wasn't a type, but part of the description
                     // Then that wasn't a type, but part of the description
-                    $description = "$type $description";
+                    $description = trim("$type $description");
 
 
                     // Try to get a type from first 2xx response
                     // Try to get a type from first 2xx response
                     $validResponse = collect($responses)->first(function ($r) {
                     $validResponse = collect($responses)->first(function ($r) {

+ 47 - 30
src/Extracting/Strategies/UrlParameters/GetFromUrlParamTag.php

@@ -54,44 +54,61 @@ class GetFromUrlParamTag extends Strategy
         return $this->getUrlParametersFromDocBlock($methodDocBlock->getTags());
         return $this->getUrlParametersFromDocBlock($methodDocBlock->getTags());
     }
     }
 
 
+    /**
+     * @param Tag[] $tags
+     *
+     * @return <string, array>[]
+     */
     public function getUrlParametersFromDocBlock($tags)
     public function getUrlParametersFromDocBlock($tags)
     {
     {
-        $parameters = collect($tags)
-            ->filter(function ($tag) {
-                return $tag instanceof Tag && $tag->getName() === 'urlParam';
-            })
-            ->mapWithKeys(function (Tag $tag) {
-                // Format:
-                // @urlParam <name> <"required" (optional)> <description>
-                // Examples:
-                // @urlParam id required The id of the post.
-                // @urlParam user_id The ID of the user.
-                preg_match('/(.+?)\s+(required\s+)?([\s\S]*)/', $tag->getContent(), $content);
-                $content = preg_replace('/\s?No-example.?/', '', $content);
-                if (empty($content)) {
-                    // This means only name was supplied
-                    [$name] = preg_split('/\s+/', $tag->getContent());
-                    $required = false;
+        $parameters = [];
+
+        foreach ($tags as $tag) {
+            if ($tag->getName() !== 'urlParam') continue;
+
+            $tagContent = trim($tag->getContent());
+            // Format:
+            // @urlParam <name> <type (optional)> <"required" (optional)> <description>
+            // Examples:
+            // @urlParam id string required The id of the post.
+            // @urlParam user_id The ID of the user.
+
+            // We match on all the possible types for URL parameters. It's a limited range, so no biggie.
+            preg_match('/(\w+?)\s+((int|integer|string|float|double|number)\s+)?(required\s+)?([\s\S]*)/', $tagContent, $content);
+            if (empty($content)) {
+                // This means only name was supplied
+                $name = trim($tagContent);
+                $required = false;
+                $description = '';
+                $type = 'string';
+            } else {
+                [$_, $name, $__, $type, $required, $description] = $content;
+                $description = trim(str_replace(['No-example.', 'No-example'], '', $description));
+                if ($description === 'required') {
+                    $required = true;
                     $description = '';
                     $description = '';
                 } else {
                 } else {
-                    [$_, $name, $required, $description] = $content;
-                    $description = trim($description);
-                    if ($description == 'required' && empty(trim($required))) {
-                        $required = $description;
-                        $description = '';
-                    }
-                    $required = trim($required) == 'required' ? true : false;
+                    $required = trim($required) === 'required';
                 }
                 }
 
 
-                [$description, $value] = $this->parseExampleFromParamDescription($description, 'string');
-                if (is_null($value) && ! $this->shouldExcludeExample($tag->getContent())) {
-                    $value = Str::contains($description, ['number', 'count', 'page'])
-                        ? $this->generateDummyValue('integer')
-                        : $this->generateDummyValue('string');
+                if (empty($type) && $this->isSupportedTypeInDocBlocks($description)) {
+                    // Only type was supplied
+                    $type = $description;
+                    $description = '';
                 }
                 }
 
 
-                return [$name => compact('description', 'required', 'value')];
-            })->toArray();
+                $type = empty($type)
+                    ? (Str::contains($description, ['number', 'count', 'page']) ? 'integer' : 'string')
+                    : $this->normalizeParameterType($type);
+            }
+
+            [$description, $value] = $this->parseExampleFromParamDescription($description, $type);
+            if (is_null($value) && !$this->shouldExcludeExample($tagContent)) {
+                $value = $this->generateDummyValue($type);
+            }
+
+            $parameters[$name] = compact('description', 'required', 'value', 'type');
+        }
 
 
         return $parameters;
         return $parameters;
     }
     }

+ 1 - 1
tests/Fixtures/TestController.php

@@ -131,7 +131,7 @@ class TestController extends Controller
      * @queryParam location_id required The id of the location.
      * @queryParam location_id required The id of the location.
      * @queryParam user_id required The id of the user. Example: me
      * @queryParam user_id required The id of the user. Example: me
      * @queryParam page required The page number. Example: 4
      * @queryParam page required The page number. Example: 4
-     * @queryParam filters.* The filters.
+     * @queryParam filters The filters.
      * @queryParam url_encoded  Used for testing that URL parameters will be URL-encoded where needed. Example: + []&=
      * @queryParam url_encoded  Used for testing that URL parameters will be URL-encoded where needed. Example: + []&=
      */
      */
     public function withQueryParameters()
     public function withQueryParameters()

+ 0 - 1
tests/Fixtures/TestRequest.php

@@ -8,7 +8,6 @@ use Illuminate\Foundation\Http\FormRequest;
  * @queryParam location_id required The id of the location.
  * @queryParam location_id required The id of the location.
  * @queryParam user_id required The id of the user. Example: me
  * @queryParam user_id required The id of the user. Example: me
  * @queryParam page required The page number. Example: 4
  * @queryParam page required The page number. Example: 4
- * @queryParam filters.* The filters.
  * @queryParam url_encoded  Used for testing that URL parameters will be URL-encoded where needed. Example: + []&=
  * @queryParam url_encoded  Used for testing that URL parameters will be URL-encoded where needed. Example: + []&=
  * @bodyParam user_id int required The id of the user. Example: 9
  * @bodyParam user_id int required The id of the user. Example: 9
  * @bodyParam room_id string The id of the room.
  * @bodyParam room_id string The id of the room.

+ 56 - 10
tests/Strategies/QueryParameters/GetFromQueryParamTagTest.php

@@ -21,31 +21,81 @@ class GetFromQueryParamTagTest extends TestCase
         $tags = [
         $tags = [
             new Tag('queryParam', 'location_id required The id of the location.'),
             new Tag('queryParam', 'location_id required The id of the location.'),
             new Tag('queryParam', 'user_id required The id of the user. Example: me'),
             new Tag('queryParam', 'user_id required The id of the user. Example: me'),
-            new Tag('queryParam', 'page required The page number. Example: 4'),
-            new Tag('queryParam', 'filters.* The filters.'),
-            new Tag('queryParam', 'url_encoded Used for testing that URL parameters will be URL-encoded where needed. Example: + []&='),
+            new Tag('queryParam', 'page The page number. Example: 4'),
+            new Tag('queryParam', 'with_type number Example: 13'),
+            new Tag('queryParam', 'with_list_type int[]'),
+            new Tag('queryParam', 'fields string[] The fields. Example: ["age", "name"]'),
+            new Tag('queryParam', 'filters object The filters. '),
+            new Tag('queryParam', 'filters.class double Class. Example: 11'),
+            new Tag('queryParam', 'filters.other string required Other things.'),
+            new Tag('queryParam', 'noExampleNoDescription No-example.'),
+            new Tag('queryParam', 'noExample Something No-example'),
         ];
         ];
         $results = $strategy->getQueryParametersFromDocBlock($tags);
         $results = $strategy->getQueryParametersFromDocBlock($tags);
 
 
         $this->assertArraySubset([
         $this->assertArraySubset([
             'location_id' => [
             'location_id' => [
+                'type' => 'string',
                 'required' => true,
                 'required' => true,
                 'description' => 'The id of the location.',
                 'description' => 'The id of the location.',
             ],
             ],
             'user_id' => [
             'user_id' => [
+                'type' => 'string',
                 'required' => true,
                 'required' => true,
                 'description' => 'The id of the user.',
                 'description' => 'The id of the user.',
                 'value' => 'me',
                 'value' => 'me',
             ],
             ],
             'page' => [
             'page' => [
-                'required' => true,
+                'type' => 'integer',
+                'required' => false,
                 'description' => 'The page number.',
                 'description' => 'The page number.',
-                'value' => '4',
+                'value' => 4,
+            ],
+            'with_type' => [
+                'type' => 'number',
+                'required' => false,
+                'description' => '',
+                'value' => 13.0,
             ],
             ],
-            'filters.*' => [
+            'with_list_type' => [
+                'type' => 'integer[]',
+                'required' => false,
+                'description' => '',
+            ],
+            'fields' => [
+                'type' => 'string[]',
+                'required' => false,
+                'description' => 'The fields.',
+                'value' => ['age', 'name']
+            ],
+            'filters' => [
+                'type' => 'object',
                 'required' => false,
                 'required' => false,
                 'description' => 'The filters.',
                 'description' => 'The filters.',
             ],
             ],
+            'filters.class' => [
+                'type' => 'number',
+                'required' => false,
+                'description' => 'Class.',
+                'value' => 11.0
+            ],
+            'filters.other' => [
+                'type' => 'string',
+                'required' => true,
+                'description' => 'Other things.',
+            ],
+            'noExampleNoDescription' => [
+                'type' => 'string',
+                'required' => false,
+                'description' => '',
+                'value' => null
+            ],
+            'noExample' => [
+                'type' => 'string',
+                'required' => false,
+                'description' => 'Something',
+                'value' => null
+            ],
         ], $results);
         ], $results);
     }
     }
 
 
@@ -74,10 +124,6 @@ class GetFromQueryParamTagTest extends TestCase
                 'description' => 'The page number.',
                 'description' => 'The page number.',
                 'value' => '4',
                 'value' => '4',
             ],
             ],
-            'filters.*' => [
-                'required' => false,
-                'description' => 'The filters.',
-            ],
         ], $results);
         ], $results);
     }
     }
 
 

+ 60 - 0
tests/Strategies/UrlParameters/GetFromUrlParamTagTest.php

@@ -19,18 +19,78 @@ class GetFromUrlParamTagTest extends TestCase
         $tags = [
         $tags = [
             new Tag('urlParam', 'id required The id of the order.'),
             new Tag('urlParam', 'id required The id of the order.'),
             new Tag('urlParam', 'lang The language to serve in.'),
             new Tag('urlParam', 'lang The language to serve in.'),
+            new Tag('urlParam', 'withType number With type, maybe.'),
+            new Tag('urlParam', 'withTypeDefinitely integer required With type.'),
+            new Tag('urlParam', 'barebones'),
+            new Tag('urlParam', 'barebonesType number'),
+            new Tag('urlParam', 'barebonesRequired required'),
+            new Tag('urlParam', 'withExampleOnly Example: 12'),
+            new Tag('urlParam', 'withExampleOnlyButTyped int Example: 12'),
+            new Tag('urlParam', 'noExampleNoDescription No-example.'),
+            new Tag('urlParam', 'noExample Something No-example'),
         ];
         ];
         $results = $strategy->getUrlParametersFromDocBlock($tags);
         $results = $strategy->getUrlParametersFromDocBlock($tags);
 
 
         $this->assertArraySubset([
         $this->assertArraySubset([
             'id' => [
             'id' => [
+                'type' => 'string',
                 'required' => true,
                 'required' => true,
                 'description' => 'The id of the order.',
                 'description' => 'The id of the order.',
             ],
             ],
             'lang' => [
             'lang' => [
+                'type' => 'string',
                 'required' => false,
                 'required' => false,
                 'description' => 'The language to serve in.',
                 'description' => 'The language to serve in.',
             ],
             ],
+            'withType' => [
+                'type' => 'number',
+                'required' => false,
+                'description' => 'With type, maybe.',
+            ],
+            'withTypeDefinitely' => [
+                'type' => 'integer',
+                'required' => true,
+                'description' => 'With type.',
+            ],
+            'barebones' => [
+                'type' => 'string',
+                'required' => false,
+                'description' => '',
+            ],
+            'barebonesType' => [
+                'type' => 'number',
+                'required' => false,
+                'description' => '',
+            ],
+            'barebonesRequired' => [
+                'type' => 'string',
+                'required' => true,
+                'description' => '',
+            ],
+            'withExampleOnly' => [
+                'type' => 'string',
+                'required' => false,
+                'description' => '',
+                'value' => '12',
+            ],
+            'withExampleOnlyButTyped' => [
+                'type' => 'integer',
+                'required' => false,
+                'description' => '',
+                'value' => 12
+            ],
+            'noExampleNoDescription' => [
+                'type' => 'string',
+                'required' => false,
+                'description' => '',
+                'value' => null
+            ],
+            'noExample' => [
+                'type' => 'string',
+                'required' => false,
+                'description' => 'Something',
+                'value' => null
+            ],
         ], $results);
         ], $results);
     }
     }