浏览代码

Improve URL param inferring

shalvah 4 年之前
父节点
当前提交
b24c58c531

+ 71 - 14
src/Extracting/Strategies/UrlParameters/GetFromLaravelAPI.php

@@ -30,39 +30,96 @@ class GetFromLaravelAPI extends Strategy
             $type = 'string';
             $parameters[$name] = [
                 'name' => $name,
-                'description' => '',
+                'description' => $this->inferUrlParamDescription($endpointData->uri, $name),
                 'required' => !$optional,
-                'example' => $this->generateDummyValue($type),
-                'type' => $type,
             ];
         }
 
 
         // Infer proper types for any bound models
-        // Eg if $user model has an ID primary key and is injected, {user} param should be ID
-        foreach ($endpointData->method->getParameters() as $param) {
-            $paramType = $param->getType();
+        // Eg Suppose route is /users/{user},
+        // and (User $user) model is typehinted on method
+        // If User model has an int primary key, {user} param should be an int
+
+        $methodArguments = $endpointData->method->getParameters();
+        foreach ($methodArguments as $argument) {
+            $argumentType = $argument->getType();
             try {
-                $parameterClassName = $paramType->getName();
-                $parameterInstance = new $parameterClassName;
-                if ($parameterInstance instanceof Model) {
-                    if (isset($parameters[$param->getName()])) {
-                        $paramName = $param->getName();
+                $argumentClassName = $argumentType->getName();
+                $argumentInstance = new $argumentClassName;
+                if ($argumentInstance instanceof Model) {
+                    if (isset($parameters[$argument->getName()])) {
+                        $paramName = $argument->getName();
                     } else if (isset($parameters['id'])) {
                         $paramName = 'id';
                     } else {
                         continue;
                     }
 
-                    $type = $parameterInstance->getKeyType();
+                    $type = $this->normalizeTypeName($argumentInstance->getKeyType());
                     $parameters[$paramName]['type'] = $type;
-                    $parameters[$paramName]['example'] = $this->generateDummyValue($this->normalizeTypeName($type));
+                    $parameters[$paramName]['example'] = $this->generateDummyValue($type);
                 }
             } catch (\Throwable $e) {
                 continue;
             }
         }
 
-            return $parameters;
+        // Try to infer correct types for URL parameters.
+        foreach ($parameters as $name => $data) {
+            if (isset($data['type'])) continue;
+
+            $type = 'string'; // The default type
+
+            // If the url is /things/{id}, try looking for a Thing model ourselves
+            $urlThing = $this->getNameOfUrlThing($endpointData->uri, $name);
+            if ($urlThing) {
+                $rootNamespace = app()->getNamespace();
+                if (class_exists($class = "{$rootNamespace}Models\\" . Str::title($urlThing))
+                    // For the heathens that don't use a Models\ directory
+                    || class_exists($class = $rootNamespace . Str::title($urlThing))) {
+                    $argumentInstance = new $class;
+                    $type = $this->normalizeTypeName($argumentInstance->getKeyType());
+                    $parameters[$name]['type'] = $type;
+                    $parameters[$name]['example'] = $this->generateDummyValue($type);
+                }
+            }
+
+            $parameters[$name]['example'] = $this->generateDummyValue($type);
+            $parameters[$name]['type'] = $type;
+        }
+
+        return $parameters;
+    }
+
+    protected function inferUrlParamDescription(string $url, string $paramName): string
+    {
+        if ($paramName == "id") {
+            // If $url is sth like /users/{id}, return "The ID of the user."
+            // Make sure to replace underscores, so "side_projects" becomes "side project"
+            $thing = str_replace("_", " ",$this->getNameOfUrlThing($url, $paramName));
+            return "The ID of the $thing.";
+        } else if (Str::is("*_id", $paramName)) {
+            // If $url is sth like /something/{user_id}, return "The ID of the user."
+            $parts = explode("_", $paramName);
+            return "The ID of the $parts[0].";
+        }
+
+        return '';
+    }
+
+    /**
+     * Extract "thing" in the URL /<whatever>/things/{paramName}
+     */
+    protected function getNameOfUrlThing(string $url, string $paramName): ?string
+    {
+        try {
+            $parts = explode("/", $url);
+            $paramIndex = array_search("{{$paramName}}", $parts);
+            $things = $parts[$paramIndex - 1];
+            return Str::singular($things);
+        } catch (\Throwable $e) {
+            return null;
         }
     }
+}

+ 1 - 1
tests/Fixtures/TestController.php

@@ -465,7 +465,7 @@ class TestController extends Controller
         }
     }
 
-    public function withInjectedModel(TestUser $testUser)
+    public function withInjectedModel(TestUser $user)
     {
         return null;
     }

+ 34 - 2
tests/Strategies/UrlParameters/GetFromLaravelAPITest.php

@@ -29,10 +29,42 @@ class GetFromLaravelAPITest extends BaseLaravelTest
 
         $this->assertArraySubset([
             "name" => "id",
-            "description" => "",
+            "description" => "The ID of the user.",
             "required" => true,
-            "type" => "int",
+            "type" => "integer",
         ], $results['id']);
         $this->assertIsInt($results['id']['example']);
     }
+
+    /** @test */
+    public function can_infer_description()
+    {
+        $endpoint = new class extends ExtractedEndpointData {
+            public function __construct(array $parameters = [])
+            {
+                $this->uri = 'everything/{cat_id}';
+                $this->method = new \ReflectionMethod(TestController::class, 'dummy');
+            }
+        };
+
+        $strategy = new GetFromLaravelAPI(new DocumentationConfig([]));
+        $results = $strategy($endpoint, []);
+
+        $this->assertArraySubset([
+            "name" => "cat_id",
+            "description" => "The ID of the cat.",
+            "required" => true,
+            "type" => "string",
+        ], $results['cat_id']);
+
+        $endpoint->uri = 'dogs/{id}';
+        $results = $strategy($endpoint, []);
+
+        $this->assertArraySubset([
+            "name" => "id",
+            "description" => "The ID of the dog.",
+            "required" => true,
+            "type" => "string",
+        ], $results['id']);
+    }
 }