Browse Source

Add support for file upload input in examples

shalvah 5 years ago
parent
commit
ffa811baa3

+ 7 - 0
config/scribe.php

@@ -244,6 +244,13 @@ INTRO
                     'bodyParams' => [
                         // 'key' => 'value',
                     ],
+
+                    /*
+                     * Files which should be sent with the API call.
+                     */
+                    'fileParams' => [
+                        // 'key' => '/home/me/image.png',
+                    ],
                 ],
             ],
         ],

+ 10 - 1
resources/views/partials/example-requests/curl.blade.php → resources/views/partials/example-requests/bash.blade.php

@@ -6,7 +6,16 @@ curl -X {{$route['methods'][0]}} \
 @endif
 @endforeach
 @endif
-@if(count($route['cleanBodyParameters']))
+@if(count($route['fileParameters']))
+@foreach($route['cleanBodyParameters'] as $parameter => $value)
+@foreach( \Knuckles\Scribe\Tools\WritingUtils::getParameterNamesAndValuesForFormData($parameter,$value) as $key => $actualValue)
+    -F "{!! "$key=".$actualValue !!}" \
+@endforeach
+@endforeach
+@foreach($route['fileParameters'] as $parameter => $file)
+    -F "{!! "$parameter=@".$file->path() !!}" @if(! ($loop->last))\@endif
+@endforeach
+@elseif(count($route['cleanBodyParameters']))
     -d '{!! json_encode($route['cleanBodyParameters']) !!}'
 @endif
 

+ 12 - 5
resources/views/partials/example-requests/javascript.blade.php

@@ -17,13 +17,20 @@ let headers = {
 @if(!array_key_exists('Accept', $route['headers']))
     "Accept": "application/json",
 @endif
-@if(!array_key_exists('Content-Type', $route['headers']))
-    "Content-Type": "application/json",
-@endif
 };
 @endif
-@if(count($route['cleanBodyParameters']))
 
+@if(count($route['fileParameters']))
+const body = new FormData();
+@foreach($route['cleanBodyParameters'] as $parameter => $value)
+@foreach( \Knuckles\Scribe\Tools\WritingUtils::getParameterNamesAndValuesForFormData($parameter,$value) as $key => $actualValue)
+body.append('{!! $key !!}', '{!! $actualValue !!}');
+@endforeach
+@endforeach
+@foreach($route['fileParameters'] as $parameter => $file)
+body.append('{!! $parameter !!}', document.querySelector('input[name="{!! $parameter !!}"]').files[0]);
+@endforeach
+@elseif(count($route['cleanBodyParameters']))
 let body = {!! json_encode($route['cleanBodyParameters'], JSON_PRETTY_PRINT) !!}
 @endif
 
@@ -32,7 +39,7 @@ fetch(url, {
 @if(count($route['headers']))
     headers: headers,
 @endif
-@if(count($route['bodyParameters']))
+@if(count($route['fileParameters']) || count($route['cleanBodyParameters']))
     body: body
 @endif
 })

+ 18 - 1
resources/views/partials/example-requests/php.blade.php

@@ -11,7 +11,24 @@ $response = $client->{{ strtolower($route['methods'][0]) }}(
 @if(!empty($route['cleanQueryParameters']))
         'query' => {!! \Knuckles\Scribe\Tools\WritingUtils::printQueryParamsAsKeyValue($route['cleanQueryParameters'], "'", "=>", 12, "[]", 8) !!},
 @endif
-@if(!empty($route['cleanBodyParameters']))
+@if(count($route['fileParameters']))
+        'multipart' => [
+@foreach($route['cleanBodyParameters'] as $parameter => $value)
+@foreach( \Knuckles\Scribe\Tools\WritingUtils::getParameterNamesAndValuesForFormData($parameter,$value) as $key => $actualValue)
+            [
+                'name' => '{!! $key !!}',
+                'contents' => '{!! $actualValue !!}'
+            ],
+@endforeach
+@endforeach
+@foreach($route['fileParameters'] as $parameter => $file)
+            [
+                'name' => '{!!  $parameter !!}',
+                'contents' => fopen('{!! $file->path() !!}', 'r')
+            ],
+@endforeach
+        ],
+@elseif(!empty($route['cleanBodyParameters']))
         'json' => {!! \Knuckles\Scribe\Tools\WritingUtils::printPhpArray($route['cleanBodyParameters'], 8) !!},
 @endif
     ]

+ 11 - 1
src/Tools/ErrorHandlingUtils.php

@@ -12,7 +12,17 @@ class ErrorHandlingUtils
         if (Flags::$shouldBeVerbose) {
             self::dumpException($e);
         } else {
-            ConsoleOutputUtils::warn(get_class($e) . ': ' . $e->getMessage());
+            [$firstFrame, $secondFrame] = $e->getTrace();
+
+            try {
+                ['file' => $file, 'line' => $line] = $firstFrame;
+            } catch (\Exception $_) {
+                ['file' => $file, 'line' => $line] = $secondFrame;
+            }
+            $exceptionType = get_class($e);
+            $message = $e->getMessage();
+            $message = "$exceptionType in $file at line $line: $message";
+            ConsoleOutputUtils::warn($message);
             ConsoleOutputUtils::warn('Run again with --verbose for a full stacktrace');
         }
 

+ 54 - 18
src/Tools/WritingUtils.php

@@ -6,6 +6,16 @@ use Symfony\Component\VarExporter\VarExporter;
 
 class WritingUtils
 {
+
+    public static $httpMethodToCssColour = [
+        'GET' => 'green',
+        'HEAD' => 'darkgreen',
+        'POST' => 'black',
+        'PUT' => 'darkblue',
+        'PATCH' => 'purple',
+        'DELETE' => 'red',
+    ];
+
     /**
      * @param array $value
      * @param int $indentationLevel
@@ -14,7 +24,7 @@ class WritingUtils
      * @throws \Symfony\Component\VarExporter\Exception\ExceptionInterface
      *
      */
-    public static function printPhpArray(array $value, int $indentationLevel = 0): string
+    public static function printPhpArray($value, int $indentationLevel = 0): string
     {
         $output = VarExporter::export($value);
         // Padding with x spaces so they align
@@ -54,50 +64,76 @@ class WritingUtils
 
     public static function printQueryParamsAsKeyValue(
         array $cleanQueryParams,
-        string $quote = "\"",
+        string $quote = '"',
         string $delimiter = ":",
         int $spacesIndentation = 4,
         string $braces = "{}",
-        int $closingBraceIndentation = 0
-    ): string {
-        $output = "{$braces[0]}\n";
+        int $closingBraceIndentation = 0,
+        string $startLinesWith = '',
+        string $endLinesWith = ','
+    ): string
+    {
+        $output = isset($braces[0]) ? "{$braces[0]}\n" : '';
         foreach ($cleanQueryParams as $parameter => $value) {
             if (!is_array($value)) {
                 $output .= str_repeat(" ", $spacesIndentation);
-                $output .= "$quote$parameter$quote$delimiter $quote$value$quote,\n";
+                $output .= "$startLinesWith$quote$parameter$quote$delimiter $quote$value$quote$endLinesWith\n";
             } else {
                 if (array_keys($value)[0] === 0) {
                     // List query param (eg filter[]=haha should become "filter[]": "haha")
                     $output .= str_repeat(" ", $spacesIndentation);
-                    $output .= "$quote$parameter" . "[]$quote$delimiter $quote$value[0]$quote,\n";
+                    $output .= "$startLinesWith$quote$parameter" . "[]$quote$delimiter $quote$value[0]$quote$endLinesWith\n";
                 } else {
                     // Hash query param (eg filter[name]=john should become "filter[name]": "john")
                     foreach ($value as $item => $itemValue) {
                         $output .= str_repeat(" ", $spacesIndentation);
-                        $output .= "$quote$parameter" . "[$item]$quote$delimiter $quote$itemValue$quote,\n";
+                        $output .= "$startLinesWith$quote$parameter" . "[$item]$quote$delimiter $quote$itemValue$quote$endLinesWith\n";
                     }
                 }
             }
         }
 
-        return $output . str_repeat(" ", $closingBraceIndentation) . "{$braces[1]}";
+        $closing = isset($braces[1]) ? str_repeat(" ", $closingBraceIndentation) . "{$braces[1]}" : '';
+        return $output . $closing;
     }
 
-    public static $httpMethodToCssColour = [
-        'GET' => 'green',
-        'HEAD' => 'darkgreen',
-        'POST' => 'black',
-        'PUT' => 'darkblue',
-        'PATCH' => 'purple',
-        'DELETE' => 'red',
-    ];
+    /**
+     * Expand a request parameter into one or more parameters to be used when sending as form-data.
+     * A primitive value like ("name", "John") is returned as ["name" => "John"]
+     * Lists like ("filter", ["haha"]) becomes ["filter[]" => "haha"]
+     * Maps like ("filter", ["name" => "john", "age" => "12"]) become ["filter[name]" => "john", "filter[age]" => 12]
+     *
+     * @param string $parameter The name of the parameter
+     * @param mixed $value Value of the parameter
+     *
+     * @return array
+     */
+    public static function getParameterNamesAndValuesForFormData(string $parameter, $value): array
+    {
+        if (!is_array($value)) {
+            return [$parameter => $value];
+        }
+
+        if (array_keys($value)[0] === 0) {
+            // We assume it's a list if its first key is 0
+            return [$parameter . '[]' => $value[0]];
+        }
+
+        // Transform maps
+        $params = [];
+        foreach ($value as $item => $itemValue) {
+            $params[$parameter . "[$item]"] = $itemValue;
+        }
+        return $params;
+    }
 
     /**
      * Convert a list of possible values to a friendly string:
      * [1, 2, 3] -> "1, 2, or 3"
      * [1, 2] -> "1 or 2"
      * [1] -> "1"
-     * Each value is wrapped in HTML <code> tags, so you actually get "<code>1</code>, <code>2</code>, or <code>3</code>"
+     * Each value is wrapped in HTML <code> tags, so you actually get "<code>1</code>, <code>2</code>, or
+     * <code>3</code>"
      *
      * @param array $list
      *

+ 0 - 5
src/Writing/Writer.php

@@ -123,11 +123,6 @@ class Writer
     {
         $routesWithOutput = $parsedRoutes->map(function (Collection $routeGroup) use ($settings) {
             return $routeGroup->map(function (array $route) use ($settings) {
-                if (count($route['cleanBodyParameters']) && !isset($route['headers']['Content-Type'])) {
-                    // Set content type if the user forgot to set it
-                    $route['headers']['Content-Type'] = 'application/json';
-                }
-
                 $hasRequestOptions = !empty($route['headers'])
                     || !empty($route['cleanQueryParameters'])
                     || !empty($route['cleanBodyParameters']);

+ 33 - 34
tests/Strategies/BodyParameters/GetFromFormRequestTest.php

@@ -137,115 +137,114 @@ class GetFromFormRequestTest extends TestCase
         $description = 'A description';
         return [
             'required' => [
-                ['required' => 'required'],
-                ['required' => ['description' => $description]],
+                ['required_param' => 'required'],
+                ['required_param' => ['description' => $description]],
                 [
                     'required' => true,
                 ],
             ],
             'string' => [
-                ['string' => 'string|required'],
-                ['string' => ['description' => $description]],
+                ['string_param' => 'string|required'],
+                ['string_param' => ['description' => $description]],
                 [
                     'type' => 'string',
                 ],
             ],
             'boolean' => [
-                ['boolean' => 'boolean|required'],
-                ['boolean' => ['description' => $description]],
+                ['boolean_param' => 'boolean|required'],
+                ['boolean_param' => ['description' => $description]],
                 [
                     'type' => 'boolean',
                 ],
             ],
             'integer' => [
-                ['integer' => 'integer|required'],
-                ['integer' => ['description' => $description]],
+                ['integer_param' => 'integer|required'],
+                ['integer_param' => ['description' => $description]],
                 [
                     'type' => 'integer',
                 ],
             ],
             'numeric' => [
-                ['numeric' => 'numeric|required'],
-                ['numeric' => ['description' => $description]],
+                ['numeric_param' => 'numeric|required'],
+                ['numeric_param' => ['description' => $description]],
                 [
                     'type' => 'number',
                 ],
             ],
             'array' => [
-                ['array' => 'array|required'],
-                ['array' => ['description' => $description]],
+                ['array_param' => 'array|required'],
+                ['array_param' => ['description' => $description]],
                 [
                     'type' => 'array',
                 ],
             ],
-
-            /* Ignore file fo now until we figure out how to support it
             'file' => [
-                ['file' => 'file|required'],
-['file' => ['description' => $description]],
+                ['file_param' => 'file|required'],
+                ['file_param' => ['description' => $description]],
                 [
+                    'description' => 'The value must be a file.',
                     'type' => 'file',
-                ]
-            ],*/
+                ],
+            ],
             'timezone' => [
-                ['timezone' => 'timezone|required'],
-                ['timezone' => ['description' => $description]],
+                ['timezone_param' => 'timezone|required'],
+                ['timezone_param' => ['description' => $description]],
                 [
                     'description' => 'The value must be a valid time zone, such as <code>Africa/Accra</code>.',
                     'type' => 'string',
                 ],
             ],
             'email' => [
-                ['email' => 'email|required'],
-                ['email' => ['description' => $description]],
+                ['email_param' => 'email|required'],
+                ['email_param' => ['description' => $description]],
                 [
                     'description' => 'The value must be a valid email address.',
                     'type' => 'string',
                 ],
             ],
             'url' => [
-                ['url' => 'url|required'],
-                ['url' => ['description' => $description]],
+                ['url_param' => 'url|required'],
+                ['url_param' => ['description' => $description]],
                 [
                     'description' => 'The value must be a valid URL.',
                     'type' => 'string',
                 ],
             ],
             'ip' => [
-                ['ip' => 'ip|required'],
-                ['ip' => ['description' => $description]],
+                ['ip_param' => 'ip|required'],
+                ['ip_param' => ['description' => $description]],
                 [
                     'description' => 'The value must be a valid IP address.',
                     'type' => 'string',
                 ],
             ],
             'json' => [
-                ['json' => 'json|required'],
-                ['json' => ['description' => $description]],
+                ['json_param' => 'json|required'],
+                ['json_param' => ['description' => $description]],
                 [
                     'description' => 'The value must be a valid JSON string.',
                     'type' => 'string',
                 ],
             ],
             'date' => [
-                ['date' => 'date|required'],
-                ['date' => ['description' => $description]],
+                ['date_param' => 'date|required'],
+                ['date_param' => ['description' => $description]],
                 [
                     'description' => 'The value must be a valid date.',
                     'type' => 'string',
                 ],
             ],
             'date_format' => [
-                ['date_format' => 'date_format:Y-m-d|required'],
-                ['date_format' => ['description' => $description]],
+                ['date_format_param' => 'date_format:Y-m-d|required'],
+                ['date_format_param' => ['description' => $description]],
                 [
                     'description' => 'The value must be a valid date in the format Y-m-d.',
                     'type' => 'string',
                 ],
             ],
             'in' => [
-                ['in' => 'in:3,5,6|required'],
-                ['in' => ['description' => $description]],
+                ['in_param' => 'in:3,5,6|required'],
+                ['in_param' => ['description' => $description]],
                 [
                     'description' => 'The value must be one of <code>3</code>, <code>5</code>, or <code>6</code>.',
                     'type' => 'string',

+ 3 - 1
todo.md

@@ -11,7 +11,9 @@
 # Release blocker
 - Port recent changes from old repo
 
+# Improvements
+- Speed
+
 # Features
-- File upload input: see https://github.com/mpociot/laravel-apidoc-generator/issues/735 . The primitive type `file` has already been added to FormRequest support, but with no example value
 - Possible feature: https://github.com/mpociot/laravel-apidoc-generator/issues/731