Skip to content

Commit

Permalink
Add support for file upload input user examples and response calls
Browse files Browse the repository at this point in the history
  • Loading branch information
shalvah committed May 9, 2020
1 parent ffa811b commit 90c9cca
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 53 deletions.
2 changes: 1 addition & 1 deletion docs/whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
- [Closure routes can now be documented]()
- [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 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.
Expand Down
39 changes: 30 additions & 9 deletions src/Extracting/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Knuckles\Scribe\Extracting;

use Faker\Factory;
use Illuminate\Http\UploadedFile;
use Illuminate\Routing\Route;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
Expand Down Expand Up @@ -84,6 +85,18 @@ public function processRoute(Route $route, array $routeRules = [])
$bodyParameters = $this->fetchBodyParameters($controller, $method, $route, $routeRules, $parsedRoute);
$parsedRoute['bodyParameters'] = $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);
$parsedRoute['responses'] = $responses;
Expand Down Expand Up @@ -194,9 +207,8 @@ protected function iterateThroughStrategies(string $stage, array $context, array
$context[$stage][] = $item;
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
if (! in_array($context[$stage], [null, ''], true) && in_array($item, [null, ''], true)) {
Expand All @@ -214,29 +226,38 @@ protected function iterateThroughStrategies(string $stage, array $context, array
/**
* Create samples at index 0 for array parameters.
* 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
*
* @return array
*/
public static function cleanParams(array $params)
{
$values = [];
$cleanParams = [];

// Remove params which have no examples and are optional.
$params = array_filter($params, function ($details) {
return ! (is_null($details['value']) && $details['required'] === false);
});

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(
$paramName,
$details['value'],
$values
$cleanParams
);
}

return $values;
return $cleanParams;
}

/**
Expand All @@ -245,18 +266,18 @@ public static function cleanParams(array $params)
*
* @param string $paramName
* @param mixed $paramExample
* @param array $values The array that holds the result
* @param array $cleanParams The array that holds the result
*
* @return void
*/
protected static function generateConcreteSampleForArrayKeys($paramName, $paramExample, array &$values = [])
protected static function generateConcreteSampleForArrayKeys($paramName, $paramExample, array &$cleanParams = [])
{
if (Str::contains($paramName, '[')) {
// Replace usages of [] with dot notation
$paramName = str_replace(['][', '[', ']', '..'], ['.', '.', '', '.*.'], $paramName);
}
// 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)
Expand Down
10 changes: 8 additions & 2 deletions src/Extracting/ParamHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Knuckles\Scribe\Extracting;

use Faker\Factory;
use Illuminate\Http\UploadedFile;
use stdClass;

trait ParamHelpers
Expand Down Expand Up @@ -43,6 +44,10 @@ protected function generateDummyValue(string $type)
'object' => function () {
return new stdClass();
},
'file' => function () use ($faker) {
$file = UploadedFile::fake()->create('test.jpg')->size(10);
return $file;
},
];

$fakeFactory = $fakeFactories[$type] ?? $fakeFactories['string'];
Expand Down Expand Up @@ -89,7 +94,7 @@ protected function castToType($value, string $type)
];

// 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')) {
return false;
}
Expand All @@ -98,6 +103,7 @@ protected function castToType($value, string $type)
return $casts[$type]($value);
}

// Return the value unchanged if there's no applicable cast
return $value;
}

Expand Down Expand Up @@ -152,7 +158,7 @@ protected function parseParamDescription(string $description, string $type)
if (preg_match('/(.*)\bExample:\s*(.+)\s*/', $description, $content)) {
$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);
}

Expand Down
3 changes: 1 addition & 2 deletions src/Extracting/RouteDocBlocker.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ public static function getDocBlocksFromRoute(Route $route): array
protected static function normalizeClassName($classNameOrInstance): string
{
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);
}

Expand Down
Loading

0 comments on commit 90c9cca

Please sign in to comment.