Browse Source

Add support for file upload input user examples and response calls

shalvah 5 years ago
parent
commit
90c9cca32c

+ 1 - 1
docs/whats-new.md

@@ -41,7 +41,7 @@ Plus, there's also a new `scribe:strategy` command that can help you easily gene
 A few other things that might interest some folk:
 A few other things that might interest some folk:
 - [Closure routes can now be documented]()
 - [Closure routes can now be documented]()
 - [Binary responses can now be indicated]()
 - [Binary responses can now be indicated]()
-- [Coming soo] [File upload inputs are supported, too]()
+- [File upload inputs are supported, too]()
 - The output Markdown is now split across multiple files.
 - The output Markdown is now split across multiple files.
 - The default group is now called "Endpoints".
 - The default group is now called "Endpoints".
 - If you're interested in contributing, we've also added a [guide for that](). We've reworked the tests structure as well to make it easier to maintain.
 - If you're interested in contributing, we've also added a [guide for that](). We've reworked the tests structure as well to make it easier to maintain.

+ 30 - 9
src/Extracting/Generator.php

@@ -3,6 +3,7 @@
 namespace Knuckles\Scribe\Extracting;
 namespace Knuckles\Scribe\Extracting;
 
 
 use Faker\Factory;
 use Faker\Factory;
+use Illuminate\Http\UploadedFile;
 use Illuminate\Routing\Route;
 use Illuminate\Routing\Route;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
@@ -84,6 +85,18 @@ class Generator
         $bodyParameters = $this->fetchBodyParameters($controller, $method, $route, $routeRules, $parsedRoute);
         $bodyParameters = $this->fetchBodyParameters($controller, $method, $route, $routeRules, $parsedRoute);
         $parsedRoute['bodyParameters'] = $bodyParameters;
         $parsedRoute['bodyParameters'] = $bodyParameters;
         $parsedRoute['cleanBodyParameters'] = self::cleanParams($bodyParameters);
         $parsedRoute['cleanBodyParameters'] = self::cleanParams($bodyParameters);
+        if (count($parsedRoute['cleanBodyParameters']) && !isset($parsedRoute['headers']['Content-Type'])) {
+            // Set content type if the user forgot to set it
+            $parsedRoute['headers']['Content-Type'] = 'application/json';
+        }
+        [$files, $regularParameters] = collect($parsedRoute['cleanBodyParameters'])->partition(function ($example) {
+            return $example instanceof UploadedFile;
+        });
+        if (count($files)) {
+            $parsedRoute['headers']['Content-Type'] = 'multipart/form-data';
+        }
+        $parsedRoute['fileParameters'] = $files->toArray();
+        $parsedRoute['cleanBodyParameters'] = $regularParameters->toArray();
 
 
         $responses = $this->fetchResponses($controller, $method, $route, $routeRules, $parsedRoute);
         $responses = $this->fetchResponses($controller, $method, $route, $routeRules, $parsedRoute);
         $parsedRoute['responses'] = $responses;
         $parsedRoute['responses'] = $responses;
@@ -194,9 +207,8 @@ class Generator
                         $context[$stage][] = $item;
                         $context[$stage][] = $item;
                         continue;
                         continue;
                     }
                     }
-                    // Using a for loop rather than array_merge or +=
-                    // so it does not renumber numeric keys
-                    // and also allows values to be overwritten
+                    // We're using a for loop rather than array_merge or +=
+                    // so it does not renumber numeric keys and also allows values to be overwritten
 
 
                     // Don't allow overwriting if an empty value is trying to replace a set one
                     // Don't allow overwriting if an empty value is trying to replace a set one
                     if (! in_array($context[$stage], [null, ''], true) && in_array($item, [null, ''], true)) {
                     if (! in_array($context[$stage], [null, ''], true) && in_array($item, [null, ''], true)) {
@@ -214,6 +226,7 @@ class Generator
     /**
     /**
      * Create samples at index 0 for array parameters.
      * Create samples at index 0 for array parameters.
      * Also filter out parameters which were excluded from having examples.
      * Also filter out parameters which were excluded from having examples.
+     * And convert all file params that have string examples to actual files
      *
      *
      * @param array $params
      * @param array $params
      *
      *
@@ -221,7 +234,7 @@ class Generator
      */
      */
     public static function cleanParams(array $params)
     public static function cleanParams(array $params)
     {
     {
-        $values = [];
+        $cleanParams = [];
 
 
         // Remove params which have no examples and are optional.
         // Remove params which have no examples and are optional.
         $params = array_filter($params, function ($details) {
         $params = array_filter($params, function ($details) {
@@ -229,14 +242,22 @@ class Generator
         });
         });
 
 
         foreach ($params as $paramName => $details) {
         foreach ($params as $paramName => $details) {
+            if (($details['type'] ?? '') === 'file' && is_string($details['value'])) {
+                // Convert any string file examples to instances of UploadedFile
+                $filePath = $details['value'];
+                $fileName = basename($filePath);
+                $details['value'] = new UploadedFile(
+                    $filePath, $fileName, mime_content_type($filePath), 0,false
+                );
+            }
             self::generateConcreteSampleForArrayKeys(
             self::generateConcreteSampleForArrayKeys(
                 $paramName,
                 $paramName,
                 $details['value'],
                 $details['value'],
-                $values
+                $cleanParams
             );
             );
         }
         }
 
 
-        return $values;
+        return $cleanParams;
     }
     }
 
 
     /**
     /**
@@ -245,18 +266,18 @@ class Generator
      *
      *
      * @param string $paramName
      * @param string $paramName
      * @param mixed $paramExample
      * @param mixed $paramExample
-     * @param array $values The array that holds the result
+     * @param array $cleanParams The array that holds the result
      *
      *
      * @return void
      * @return void
      */
      */
-    protected static function generateConcreteSampleForArrayKeys($paramName, $paramExample, array &$values = [])
+    protected static function generateConcreteSampleForArrayKeys($paramName, $paramExample, array &$cleanParams = [])
     {
     {
         if (Str::contains($paramName, '[')) {
         if (Str::contains($paramName, '[')) {
             // Replace usages of [] with dot notation
             // Replace usages of [] with dot notation
             $paramName = str_replace(['][', '[', ']', '..'], ['.', '.', '', '.*.'], $paramName);
             $paramName = str_replace(['][', '[', ']', '..'], ['.', '.', '', '.*.'], $paramName);
         }
         }
         // Then generate a sample item for the dot notation
         // Then generate a sample item for the dot notation
-        Arr::set($values, str_replace(['.*', '*.'], ['.0','0.'], $paramName), $paramExample);
+        Arr::set($cleanParams, str_replace(['.*', '*.'], ['.0','0.'], $paramName), $paramExample);
     }
     }
 
 
     public function addAuthField(array $parsedRoute)
     public function addAuthField(array $parsedRoute)

+ 8 - 2
src/Extracting/ParamHelpers.php

@@ -3,6 +3,7 @@
 namespace Knuckles\Scribe\Extracting;
 namespace Knuckles\Scribe\Extracting;
 
 
 use Faker\Factory;
 use Faker\Factory;
+use Illuminate\Http\UploadedFile;
 use stdClass;
 use stdClass;
 
 
 trait ParamHelpers
 trait ParamHelpers
@@ -43,6 +44,10 @@ trait ParamHelpers
             'object' => function () {
             'object' => function () {
                 return new stdClass();
                 return new stdClass();
             },
             },
+            'file' => function () use ($faker) {
+                $file = UploadedFile::fake()->create('test.jpg')->size(10);
+                return $file;
+            },
         ];
         ];
 
 
         $fakeFactory = $fakeFactories[$type] ?? $fakeFactories['string'];
         $fakeFactory = $fakeFactories[$type] ?? $fakeFactories['string'];
@@ -89,7 +94,7 @@ trait ParamHelpers
         ];
         ];
 
 
         // First, we handle booleans. We can't use a regular cast,
         // First, we handle booleans. We can't use a regular cast,
-        //because PHP considers string 'false' as true.
+        // because PHP considers string 'false' as true.
         if ($value == 'false' && ($type == 'boolean' || $type == 'bool')) {
         if ($value == 'false' && ($type == 'boolean' || $type == 'bool')) {
             return false;
             return false;
         }
         }
@@ -98,6 +103,7 @@ trait ParamHelpers
             return $casts[$type]($value);
             return $casts[$type]($value);
         }
         }
 
 
+        // Return the value unchanged if there's no applicable cast
         return $value;
         return $value;
     }
     }
 
 
@@ -152,7 +158,7 @@ trait ParamHelpers
         if (preg_match('/(.*)\bExample:\s*(.+)\s*/', $description, $content)) {
         if (preg_match('/(.*)\bExample:\s*(.+)\s*/', $description, $content)) {
             $description = trim($content[1]);
             $description = trim($content[1]);
 
 
-            // examples are parsed as strings by default, we need to cast them properly
+            // Examples are parsed as strings by default, we need to cast them properly
             $example = $this->castToType($content[2], $type);
             $example = $this->castToType($content[2], $type);
         }
         }
 
 

+ 1 - 2
src/Extracting/RouteDocBlocker.php

@@ -60,8 +60,7 @@ class RouteDocBlocker
     protected static function normalizeClassName($classNameOrInstance): string
     protected static function normalizeClassName($classNameOrInstance): string
     {
     {
         if (is_object($classNameOrInstance)) {
         if (is_object($classNameOrInstance)) {
-            // route handlers are not destroyed until the script
-            // ends so this should be perfectly safe.
+            // Route handlers are not destroyed until the script ends so this should be perfectly safe.
             $classNameOrInstance = get_class($classNameOrInstance) . '::' . spl_object_id($classNameOrInstance);
             $classNameOrInstance = get_class($classNameOrInstance) . '::' . spl_object_id($classNameOrInstance);
         }
         }
 
 

+ 73 - 36
src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php

@@ -57,7 +57,7 @@ class GetFromFormRequest extends Strategy
                 $bodyParametersFromFormRequest = $this->getBodyParametersFromValidationRules(
                 $bodyParametersFromFormRequest = $this->getBodyParametersFromValidationRules(
                     $this->getRouteValidationRules($formRequest),
                     $this->getRouteValidationRules($formRequest),
                     $this->getCustomParameterData($formRequest)
                     $this->getCustomParameterData($formRequest)
-                    );
+                );
 
 
                 return $bodyParametersFromFormRequest;
                 return $bodyParametersFromFormRequest;
             }
             }
@@ -92,7 +92,7 @@ class GetFromFormRequest extends Strategy
             return call_user_func_array([$formRequest, 'bodyParameters'], []);
             return call_user_func_array([$formRequest, 'bodyParameters'], []);
         }
         }
 
 
-        c::warn("No bodyParameters() method found in ".get_class($formRequest)." Scribe will only be able to extract basic information from the rules() method.");
+        c::warn("No bodyParameters() method found in " . get_class($formRequest) . " Scribe will only be able to extract basic information from the rules() method.");
 
 
         return [];
         return [];
     }
     }
@@ -107,7 +107,7 @@ class GetFromFormRequest extends Strategy
             if (count($customParameterData) && !isset($customParameterData[$parameter])) {
             if (count($customParameterData) && !isset($customParameterData[$parameter])) {
                 c::debug("No data found for parameter '$parameter' from your bodyParameters() method. Add an entry for '$parameter' so you can add description and example.");
                 c::debug("No data found for parameter '$parameter' from your bodyParameters() method. Add an entry for '$parameter' so you can add description and example.");
             }
             }
-            $parameterInfo = $customParameterData[$parameter] ?? [];
+            $userSpecifiedParameterInfo = $customParameterData[$parameter] ?? [];
 
 
             $parameterData = [
             $parameterData = [
                 'required' => false,
                 'required' => false,
@@ -115,16 +115,28 @@ class GetFromFormRequest extends Strategy
                 'value' => self::$MISSING_VALUE,
                 'value' => self::$MISSING_VALUE,
                 'description' => '',
                 'description' => '',
             ];
             ];
+
+            // Make sure the user-specified example overwrites others.
+            if (isset($userSpecifiedParameterInfo['example'])) {
+                $parameterData['value'] = $userSpecifiedParameterInfo['example'];
+            }
+
             foreach ($ruleset as $rule) {
             foreach ($ruleset as $rule) {
                 $this->parseRule($rule, $parameterData);
                 $this->parseRule($rule, $parameterData);
             }
             }
 
 
+            // Set autogenerated examples if none was supplied.
+            // Each rule returns a 'setter' function, so we can lazily evaluate the last one only if we need it.
+            if ($parameterData['value'] === self::$MISSING_VALUE && isset($parameterData['setter'])) {
+                $parameterData['value'] = $parameterData['setter']();
+            }
+
             // Make sure the user-specified description comes first.
             // Make sure the user-specified description comes first.
-            $businessDescription = $parameterInfo['description'] ?? '';
+            $userSpecifiedDescription = $userSpecifiedParameterInfo['description'] ?? '';
             $validationDescription = trim($parameterData['description'] ?: '');
             $validationDescription = trim($parameterData['description'] ?: '');
-            $fullDescription = trim($businessDescription . ' ' .trim($validationDescription));
+            $fullDescription = trim($userSpecifiedDescription . ' ' . trim($validationDescription));
             // Let's have our sentences end with full stops, like civilized people.🙂
             // Let's have our sentences end with full stops, like civilized people.🙂
-            $parameterData['description'] = $fullDescription ? rtrim($fullDescription, '.').'.' : $fullDescription;
+            $parameterData['description'] = $fullDescription ? rtrim($fullDescription, '.') . '.' : $fullDescription;
 
 
             // Set default values for type
             // Set default values for type
             if (is_null($parameterData['type'])) {
             if (is_null($parameterData['type'])) {
@@ -135,13 +147,8 @@ class GetFromFormRequest extends Strategy
                 $parameterData['value'] = $this->generateDummyValue($parameterData['type']);
                 $parameterData['value'] = $this->generateDummyValue($parameterData['type']);
             }
             }
 
 
-            // Make sure the user-specified example overwrites others.
-            if (isset($parameterInfo['example'])) {
-                $parameterData['value'] = $parameterInfo['example'];
-            }
-
             if (!is_null($parameterData['value']) && $parameterData['value'] !== self::$MISSING_VALUE) {
             if (!is_null($parameterData['value']) && $parameterData['value'] !== self::$MISSING_VALUE) {
-                // Cast is important since values had been cast to string when serializing the validator
+                // The cast is important since values may have been cast to string in the validator
                 $parameterData['value'] = $this->castToType($parameterData['value'], $parameterData['type']);
                 $parameterData['value'] = $this->castToType($parameterData['value'], $parameterData['type']);
             }
             }
 
 
@@ -184,7 +191,7 @@ class GetFromFormRequest extends Strategy
 
 
         // Now this will return the complete ruleset.
         // Now this will return the complete ruleset.
         // Nested array parameters will be present, with '*' replaced by '0'
         // Nested array parameters will be present, with '*' replaced by '0'
-        $newRules =  Validator::make($values, $rules)->getRules();
+        $newRules = Validator::make($values, $rules)->getRules();
 
 
         // Transform the key names back from 'ids.0' to 'ids.*'
         // Transform the key names back from 'ids.0' to 'ids.*'
         return collect($newRules)->mapWithKeys(function ($val, $paramName) use ($rules) {
         return collect($newRules)->mapWithKeys(function ($val, $paramName) use ($rules) {
@@ -222,28 +229,42 @@ class GetFromFormRequest extends Strategy
             */
             */
             case 'bool':
             case 'bool':
             case 'boolean':
             case 'boolean':
-                $parameterData['value'] = Arr::random([true, false]);
+                $parameterData['setter'] = function () {
+                    return Arr::random([true, false]);
+                };
                 $parameterData['type'] = 'boolean';
                 $parameterData['type'] = 'boolean';
                 break;
                 break;
             case 'string':
             case 'string':
-                $parameterData['value'] = $this->generateDummyValue('string');
+                $parameterData['setter'] = function () {
+                    return $this->generateDummyValue('string');
+                };
                 $parameterData['type'] = 'string';
                 $parameterData['type'] = 'string';
                 break;
                 break;
             case 'int':
             case 'int':
             case 'integer':
             case 'integer':
-                $parameterData['value'] = $this->generateDummyValue('integer');
+                $parameterData['setter'] = function () {
+                    return $this->generateDummyValue('integer');
+                };
                 $parameterData['type'] = 'integer';
                 $parameterData['type'] = 'integer';
                 break;
                 break;
             case 'numeric':
             case 'numeric':
-                $parameterData['value'] = $this->generateDummyValue('number');
+                $parameterData['setter'] = function () {
+                    return $this->generateDummyValue('number');
+                };
                 $parameterData['type'] = 'number';
                 $parameterData['type'] = 'number';
                 break;
                 break;
             case 'array':
             case 'array':
-                $parameterData['value'] = [$this->generateDummyValue('string')];
+                $parameterData['setter'] = function () {
+                    return [$this->generateDummyValue('string')];
+                };
                 $parameterData['type'] = $rule;
                 $parameterData['type'] = $rule;
                 break;
                 break;
             case 'file':
             case 'file':
                 $parameterData['type'] = 'file';
                 $parameterData['type'] = 'file';
+                $parameterData['description'] .= 'The value must be a file.';
+                $parameterData['setter'] = function () {
+                    return $this->generateDummyValue('file');
+                };
                 break;
                 break;
 
 
             /**
             /**
@@ -252,39 +273,53 @@ class GetFromFormRequest extends Strategy
             case 'timezone':
             case 'timezone':
                 // Laravel's message merely says "The value must be a valid zone"
                 // Laravel's message merely says "The value must be a valid zone"
                 $parameterData['description'] .= "The value must be a valid time zone, such as <code>Africa/Accra</code>. ";
                 $parameterData['description'] .= "The value must be a valid time zone, such as <code>Africa/Accra</code>. ";
-                $parameterData['value'] = $this->getFaker()->timezone;
+                $parameterData['setter'] = function () {
+                    return $this->getFaker()->timezone;
+                };
                 break;
                 break;
             case 'email':
             case 'email':
-                $parameterData['description'] .= d::getDescription($rule).' ';
-                $parameterData['value'] = $this->getFaker()->safeEmail;
+                $parameterData['description'] .= d::getDescription($rule) . ' ';
+                $parameterData['setter'] = function () {
+                    return $this->getFaker()->safeEmail;
+                };
                 $parameterData['type'] = 'string';
                 $parameterData['type'] = 'string';
                 break;
                 break;
             case 'url':
             case 'url':
-                $parameterData['value'] = $this->getFaker()->url;
+                $parameterData['setter'] = function () {
+                    return $this->getFaker()->url;
+                };
                 $parameterData['type'] = 'string';
                 $parameterData['type'] = 'string';
                 // Laravel's message is "The value format is invalid". Ugh.🤮
                 // Laravel's message is "The value format is invalid". Ugh.🤮
                 $parameterData['description'] .= "The value must be a valid URL. ";
                 $parameterData['description'] .= "The value must be a valid URL. ";
                 break;
                 break;
             case 'ip':
             case 'ip':
-                $parameterData['description'] .= d::getDescription($rule).' ';
-                $parameterData['value'] = $this->getFaker()->ipv4;
+                $parameterData['description'] .= d::getDescription($rule) . ' ';
                 $parameterData['type'] = 'string';
                 $parameterData['type'] = 'string';
+                $parameterData['setter'] = function () {
+                    return $this->getFaker()->ipv4;
+                };
                 break;
                 break;
             case 'json':
             case 'json':
                 $parameterData['type'] = 'string';
                 $parameterData['type'] = 'string';
-                $parameterData['description'] .= d::getDescription($rule).' ';
-                $parameterData['value'] = json_encode([$this->getFaker()->word, $this->getFaker()->word,]);
+                $parameterData['description'] .= d::getDescription($rule) . ' ';
+                $parameterData['setter'] = function () {
+                    return json_encode([$this->getFaker()->word, $this->getFaker()->word,]);
+                };
                 break;
                 break;
             case 'date':
             case 'date':
                 $parameterData['type'] = 'string';
                 $parameterData['type'] = 'string';
-                $parameterData['description'] .= d::getDescription($rule).' ';
-                $parameterData['value'] = date(\DateTime::ISO8601, time());
+                $parameterData['description'] .= d::getDescription($rule) . ' ';
+                $parameterData['setter'] = function () {
+                    return date(\DateTime::ISO8601, time());
+                };
                 break;
                 break;
             case 'date_format':
             case 'date_format':
                 $parameterData['type'] = 'string';
                 $parameterData['type'] = 'string';
                 // Laravel description here is "The value must match the format Y-m-d". Not descriptive enough.
                 // Laravel description here is "The value must match the format Y-m-d". Not descriptive enough.
                 $parameterData['description'] .= "The value must be a valid date in the format {$arguments[0]} ";
                 $parameterData['description'] .= "The value must be a valid date in the format {$arguments[0]} ";
-                $parameterData['value'] = date($arguments[0], time());
+                $parameterData['setter'] = function () use ($arguments) {
+                    return date($arguments[0], time());
+                };
                 break;
                 break;
 
 
             /**
             /**
@@ -295,17 +330,17 @@ class GetFromFormRequest extends Strategy
             case 'min':
             case 'min':
                 $parameterData['type'] = $parameterData['type'] ?: 'number';
                 $parameterData['type'] = $parameterData['type'] ?: 'number';
                 $parameterData['description'] .= Description::getDescription($rule, [':min' => $arguments[0]], 'numeric').' ';
                 $parameterData['description'] .= Description::getDescription($rule, [':min' => $arguments[0]], 'numeric').' ';
-                $parameterData['value'] = $this->getFaker()->numberBetween($arguments[0]);
+                $parameterData['setter'] = function () { return $this->getFaker()->numberBetween($arguments[0]); };
                 break;
                 break;
             case 'max':
             case 'max':
                 $parameterData['type'] = $parameterData['type'] ?: 'number';
                 $parameterData['type'] = $parameterData['type'] ?: 'number';
                 $parameterData['description'] .= Description::getDescription($rule, [':max' => $arguments[0]], 'numeric').' ';
                 $parameterData['description'] .= Description::getDescription($rule, [':max' => $arguments[0]], 'numeric').' ';
-                $parameterData['value'] = $this->getFaker()->numberBetween(0, $arguments[0]);
+                $parameterData['setter'] = function () { return $this->getFaker()->numberBetween(0, $arguments[0]); };
                 break;
                 break;
             case 'between':
             case 'between':
                 $parameterData['type'] = $parameterData['type'] ?: 'number';
                 $parameterData['type'] = $parameterData['type'] ?: 'number';
                 $parameterData['description'] .= Description::getDescription($rule, [':min' => $arguments[0], ':max' => $arguments[1]], 'numeric').' ';
                 $parameterData['description'] .= Description::getDescription($rule, [':min' => $arguments[0], ':max' => $arguments[1]], 'numeric').' ';
-                $parameterData['value'] = $this->getFaker()->numberBetween($arguments[0], $arguments[1]);
+                $parameterData['setter'] = function () { return $this->getFaker()->numberBetween($arguments[0], $arguments[1]); };
                 break;*/
                 break;*/
 
 
             /**
             /**
@@ -313,7 +348,7 @@ class GetFromFormRequest extends Strategy
              */
              */
             case 'image':
             case 'image':
                 $parameterData['type'] = 'file';
                 $parameterData['type'] = 'file';
-                $parameterData['description'] .= d::getDescription($rule).' ';
+                $parameterData['description'] .= d::getDescription($rule) . ' ';
                 break;
                 break;
 
 
             /**
             /**
@@ -321,9 +356,11 @@ class GetFromFormRequest extends Strategy
              */
              */
             case 'in':
             case 'in':
                 // Not using the rule description here because it only says "The attribute is invalid"
                 // Not using the rule description here because it only says "The attribute is invalid"
-                $description = 'The value must be one of '. w::getListOfValuesAsFriendlyHtmlString($arguments);
-                $parameterData['description'] .= $description.' ';
-                $parameterData['value'] = Arr::random($arguments);
+                $description = 'The value must be one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments);
+                $parameterData['description'] .= $description . ' ';
+                $parameterData['setter'] = function () use ($arguments) {
+                    return Arr::random($arguments);
+                };
                 break;
                 break;
 
 
             default:
             default:

+ 18 - 3
src/Extracting/Strategies/Responses/ResponseCalls.php

@@ -8,6 +8,7 @@ use Exception;
 use Illuminate\Contracts\Http\Kernel;
 use Illuminate\Contracts\Http\Kernel;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
 use Illuminate\Http\Response;
 use Illuminate\Http\Response;
+use Illuminate\Http\UploadedFile;
 use Illuminate\Routing\Route;
 use Illuminate\Routing\Route;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
 use Knuckles\Scribe\Extracting\DatabaseTransactionHelpers;
 use Knuckles\Scribe\Extracting\DatabaseTransactionHelpers;
@@ -59,7 +60,17 @@ class ResponseCalls extends Strategy
         $bodyParameters = array_merge($context['cleanBodyParameters'] ?? [], $rulesToApply['bodyParams'] ?? []);
         $bodyParameters = array_merge($context['cleanBodyParameters'] ?? [], $rulesToApply['bodyParams'] ?? []);
         $queryParameters = array_merge($context['cleanQueryParameters'] ?? [], $rulesToApply['queryParams'] ?? []);
         $queryParameters = array_merge($context['cleanQueryParameters'] ?? [], $rulesToApply['queryParams'] ?? []);
         $urlParameters = $context['cleanUrlParameters'] ?? [];
         $urlParameters = $context['cleanUrlParameters'] ?? [];
-        $request = $this->prepareRequest($route, $rulesToApply, $urlParameters, $bodyParameters, $queryParameters, $context['headers'] ?? []);
+
+        $hardcodedFileParams = $rulesToApply['fileParams'] ?? [];
+        $hardcodedFileParams = collect($hardcodedFileParams)->map(function ($filePath) {
+            $fileName = basename($filePath);
+            return new UploadedFile(
+                $filePath, $fileName, mime_content_type($filePath), 0,false
+            );
+        })->toArray();
+        $fileParameters = array_merge($context['fileParameters'] ?? [], $hardcodedFileParams);
+
+        $request = $this->prepareRequest($route, $rulesToApply, $urlParameters, $bodyParameters, $queryParameters, $fileParameters, $context['headers'] ?? []);
 
 
         try {
         try {
             $response = $this->makeApiCall($request, $route);
             $response = $this->makeApiCall($request, $route);
@@ -95,12 +106,16 @@ class ResponseCalls extends Strategy
     /**
     /**
      * @param Route $route
      * @param Route $route
      * @param array $rulesToApply
      * @param array $rulesToApply
+     * @param array $urlParams
      * @param array $bodyParams
      * @param array $bodyParams
      * @param array $queryParams
      * @param array $queryParams
      *
      *
+     * @param array $fileParameters
+     * @param array $headers
+     *
      * @return Request
      * @return Request
      */
      */
-    protected function prepareRequest(Route $route, array $rulesToApply, array $urlParams, array $bodyParams, array $queryParams, array $headers)
+    protected function prepareRequest(Route $route, array $rulesToApply, array $urlParams, array $bodyParams, array $queryParams, array $fileParameters, array $headers)
     {
     {
         $uri = Utils::getFullUrl($route, $urlParams);
         $uri = Utils::getFullUrl($route, $urlParams);
         $routeMethods = $this->getMethods($route);
         $routeMethods = $this->getMethods($route);
@@ -114,7 +129,7 @@ class ResponseCalls extends Strategy
         // The second is so they get added to the request bag
         // The second is so they get added to the request bag
         // (where Symfony usually reads from and Laravel sometimes does)
         // (where Symfony usually reads from and Laravel sometimes does)
         // Adding to both ensures consistency
         // Adding to both ensures consistency
-        $request = Request::create($uri, $method, [], $cookies, [], $this->transformHeadersToServerVars($headers), json_encode($bodyParams));
+        $request = Request::create($uri, $method, [], $cookies, $fileParameters, $this->transformHeadersToServerVars($headers), json_encode($bodyParams));
         // Doing it again to catch any ones we didn't transform properly.
         // Doing it again to catch any ones we didn't transform properly.
         $request = $this->addHeaders($request, $route, $headers);
         $request = $this->addHeaders($request, $route, $headers);