Skip to content

Commit

Permalink
Respect examples set on parent items for array/objects
Browse files Browse the repository at this point in the history
  • Loading branch information
shalvah committed Sep 21, 2021
1 parent e3b7dbe commit 12937e1
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 67 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Removals

## 3.11.0 (Tuesday, 21 September 2021)
### Added
- Introduced `beforeResponseCall()` ([25cbdc193f277c70d471a92b5019156c603255b7](https://github.com/knuckleswtf/scribe/commit/25cbdc193f277c70d471a92b5019156c603255b7))

### Fixed
- Parse multiline validation comments properly ([e3b7dbefc1cbb25ca773f7c74c84bbe8fe8740e5](https://github.com/knuckleswtf/scribe/commit/e3b7dbefc1cbb25ca773f7c74c84bbe8fe8740e5))

## 3.10.3 (Monday, 20 September 2021)
### Fixed
- Ignore user-specified values in upgrader (fixes #327) ([75b592724a8639583b4d660033549c8645a61b2b](https://github.com/knuckleswtf/scribe/commit/75b592724a8639583b4d660033549c8645a61b2b))
Expand Down
33 changes: 16 additions & 17 deletions src/Extracting/Extractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public static function cleanParams(array $parameters): array
if (Str::contains($paramName, '.')) { // Object field (or array of objects)
self::setObject($cleanParameters, $paramName, $details->example, $parameters, $details->required);
} else {
$cleanParameters[$paramName] = $details->example;
$cleanParameters[$paramName] = $details->example instanceof \stdClass ? $details->example : $details->example;
}
}

Expand Down Expand Up @@ -318,8 +318,16 @@ public static function setObject(array &$results, string $path, $value, array $s
$parentData = Arr::get($source, $baseNameInOriginalParams);
// Path we use for data_set
$dotPath = str_replace('[]', '.0', $path);

// Don't overwrite parent if there's already data there

if ($parentData->type === 'object') {
if (!Arr::has($results, $dotPath)) {
$parentPath = explode('.', $dotPath);
$property = array_pop($parentPath);
$parentPath = implode('.', $parentPath);

$exampleFromParent = Arr::get($results, $dotPath) ?? $parentData->example[$property] ?? null;
if (empty($exampleFromParent)) {
Arr::set($results, $dotPath, $value);
}
} else if ($parentData->type === 'object[]') {
Expand All @@ -329,24 +337,15 @@ public static function setObject(array &$results, string $path, $value, array $s
if (isset($results['[]'][0]) && !Arr::has($results['[]'][0], $valueDotPath)) {
Arr::set($results['[]'][0], $valueDotPath, $value);
}
// If there's a second item in the array, set for that too.
if ($value !== null && isset($results['[]'][1])) {
// If value is optional, flip a coin on whether to set or not
if ($isRequired || array_rand([true, false], 1)) {
Arr::set($results['[]'][1], $valueDotPath, $value);
}
}
} else {
if (!Arr::has($results, $dotPath)) {
$parentPath = explode('.', $dotPath);
$index = (int)array_pop($parentPath);
$parentPath = implode('.', $parentPath);

$exampleFromParent = Arr::get($results, $dotPath) ?? $parentData->example[$index] ?? null;
if (empty($exampleFromParent)) {
Arr::set($results, $dotPath, $value);
}
// If there's a second item in the array, set for that too.
if ($value !== null && Arr::has($results, Str::replaceLast('[]', '.1', $baseName))) {
// If value is optional, flip a coin on whether to set or not
if ($isRequired || array_rand([true, false], 1)) {
Arr::set($results, Str::replaceLast('.0', '.1', $dotPath), $value);
}
}
}
}
}
Expand Down
7 changes: 2 additions & 5 deletions src/Extracting/ParamHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,8 @@ protected function getDummyValueGenerator(string $type, int $size = null): \Clos
}

if ($isListType) {
// Return a two-array item for a list.
return fn() => array_map(
fn() => $this->generateDummyValue($baseType),
range(0, $size ? $size - 1 : 1)
);
// Return a one-array item for a list.
return fn() => [$this->generateDummyValue($baseType)];
}

$faker = $this->getFaker();
Expand Down
67 changes: 41 additions & 26 deletions src/Extracting/ParsesValidationRules.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ public function getParametersFromValidationRules(array $validationRules, array $
$parameterData['description'] .= '.';
}

$parameterData = $this->updateParameterExample($parameterData);
$parameters[$parameter] = $parameterData;
} catch (Throwable $e) {
if ($e instanceof ScribeException) {
Expand Down Expand Up @@ -484,26 +483,24 @@ protected function parseStringRuleIntoRuleAndArguments($rule): array
return [strtolower(trim($rule)), $ruleArguments];
}

protected function updateParameterExample(array $parameterData): array
protected function getParameterExample(array $parameterData)
{
// If no example was given by the user, set an autogenerated example.
// Each parsed rule returns a 'setter' function. We'll evaluate the last one.
if ($parameterData['example'] === self::$MISSING_VALUE && isset($parameterData['setter'])) {
$parameterData['example'] = $parameterData['setter']();
}

if ($parameterData['example'] === self::$MISSING_VALUE) {
$parameterData['example'] = $parameterData['required']
? $this->generateDummyValue($parameterData['type'])
: null;
}

if (!is_null($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE) {
if (isset($parameterData['setter'])) {
return $parameterData['setter']();
} else {
return $parameterData['required']
? $this->generateDummyValue($parameterData['type'])
: null;
}
} else if (!is_null($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE) {
// Casting again is important since values may have been cast to string in the validator
$parameterData['example'] = $this->castToType($parameterData['example'], $parameterData['type']);
return $this->castToType($parameterData['example'], $parameterData['type']);
}

return $parameterData;
return $parameterData['example'] === self::$MISSING_VALUE ? null : $parameterData['example'];
}

/**
Expand All @@ -521,18 +518,20 @@ protected function updateParameterExample(array $parameterData): array
public function normaliseArrayAndObjectParameters(array $parametersFromValidationRules): array
{
$results = [];
$originalParams = $parametersFromValidationRules;
foreach ($parametersFromValidationRules as $name => $details) {
if (isset($results[$name])) {
continue;
}
if ($details['type'] === 'array') {
// Generic array type. If a child item exists,
// This is the parent field, a generic array type. If a child item exists,
// this will be overwritten with the correct type (such as object or object[]) by the code below
$details['type'] = 'string[]';
}

if (Str::endsWith($name, '.*')) {
// Wrap array example properly
$details['example'] = $this->exampleOrDefault($details, $this->getParameterExample($details));
$needsWrapping = !is_array($details['example']);

$nestingLevel = 0;
Expand All @@ -541,14 +540,6 @@ public function normaliseArrayAndObjectParameters(array $parametersFromValidatio
$details['type'] .= '[]';
if ($needsWrapping) {
$details['example'] = [$details['example']];
// Make it two items in each array
if (isset($details['setter'])) {
$secondArrayItem = $secondExampleValue = $details['setter']();
for ($i = 0; $i < $nestingLevel; $i++) {
$secondArrayItem = [$secondExampleValue];
}
$details['example'][] = $secondArrayItem;
}
}
$name = substr($name, 0, -2);
$nestingLevel++;
Expand Down Expand Up @@ -580,27 +571,33 @@ public function normaliseArrayAndObjectParameters(array $parametersFromValidatio
// if the parent field already exists with a type 'array'
$parentDetails = $parametersFromValidationRules[$parentPath];
unset($parametersFromValidationRules[$parentPath]);

if (Str::endsWith($parentPath, '.*')) {
$parentPath = substr($parentPath, 0, -2);
$parentDetails['type'] = 'object[]';
// Set the example too. Very likely the example array was an array of strings or an empty array
if (empty($parentDetails['example']) || is_string($parentDetails['example'][0]) || is_string($parentDetails['example'][0][0])) {
if (!$this->examplePresent($parentDetails) || is_string($parentDetails['example'][0]) || is_string($parentDetails['example'][0][0])) {
$parentDetails['example'] = [[]];
}
} else {
$parentDetails['type'] = 'object';
if (empty($parentDetails['example']) || is_string($parentDetails['example'][0])) {
if (!$this->examplePresent($parentDetails) || is_string($parentDetails['example'][0])) {
$parentDetails['example'] = [];
}
}

$normalisedPath = str_replace('.*.', '[].', $parentPath);
$parentDetails['name'] = $normalisedPath;
$results[$normalisedPath] = $parentDetails;
}
}

$details['name'] = $name = str_replace('.*.', '[].', $name);
unset($details['setter']);

// If an example was specified on the parent, use that instead.
if (isset($originalParams[$details['name']]) && $this->examplePresent($originalParams[$details['name']])) {
$details['example'] = $originalParams[$details['name']]['example'];
}

// Change type 'array' to 'object' if there are subfields
if (
Expand All @@ -611,13 +608,31 @@ public function normaliseArrayAndObjectParameters(array $parametersFromValidatio
) {
$details['type'] = 'object';
}

$details['example'] = $this->getParameterExample($details);
unset($details['setter']);

$results[$name] = $details;

}

return $results;
}

private function exampleOrDefault(array $parameterData, $default)
{
if (!isset($parameterData['example']) || $parameterData['example'] === self::$MISSING_VALUE) {
return $default;
}

return $parameterData['example'];
}

private function examplePresent(array $parameterData)
{
return isset($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE;
}

protected function getDescription(string $rule, array $arguments = [], $baseType = 'string'): string
{
$description = trans("validation.{$rule}");
Expand Down
2 changes: 0 additions & 2 deletions src/Extracting/Strategies/Responses/ResponseCalls.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ public function makeResponseCall(ExtractedEndpointData $endpointData, array $rul

$request = $this->runPreRequestHook($request, $endpointData);

ray($request->headers);

try {
$response = $this->makeApiCall($request, $endpointData->route);
$response = [
Expand Down
2 changes: 1 addition & 1 deletion src/Tools/Globals.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class Globals
{
public const SCRIBE_VERSION = '3.10.3';
public const SCRIBE_VERSION = '3.11.0';

public static bool $shouldBeVerbose = false;

Expand Down
4 changes: 2 additions & 2 deletions tests/Fixtures/collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
],
"body": {
"mode": "raw",
"raw": "[{\"first_name\":\"John\",\"last_name\":\"Doe\",\"contacts\":[{\"first_name\":\"Janelle\",\"last_name\":\"Monáe\"}],\"roles\":[\"Admin\"]},{\"first_name\":\"John\",\"last_name\":\"Doe\",\"contacts\":[{\"first_name\":\"Janelle\",\"last_name\":\"Monáe\"}],\"roles\":[\"Admin\"]}]"
"raw": "[{\"first_name\":\"John\",\"last_name\":\"Doe\",\"contacts\":[{\"first_name\":\"Janelle\",\"last_name\":\"Monáe\"}],\"roles\":[\"Admin\"]}]"
},
"description": "",
"auth": {
Expand Down Expand Up @@ -130,7 +130,7 @@
],
"body": {
"mode": "raw",
"raw": "{\"user_id\":9,\"room_id\":\"consequatur\",\"forever\":false,\"another_one\":11613.31890586,\"yet_another_param\":{\"name\":\"consequatur\"},\"even_more_param\":[11613.31890586,11613.31890586],\"book\":{\"name\":\"consequatur\",\"author_id\":17,\"pages_count\":17},\"ids\":[17,17],\"users\":[{\"first_name\":\"John\",\"last_name\":\"Doe\"},{\"first_name\":\"John\",\"last_name\":\"Doe\"}]}"
"raw": "{\"user_id\":9,\"room_id\":\"consequatur\",\"forever\":false,\"another_one\":11613.31890586,\"yet_another_param\":{\"name\":\"consequatur\"},\"even_more_param\":[11613.31890586],\"book\":{\"name\":\"consequatur\",\"author_id\":17,\"pages_count\":17},\"ids\":[17],\"users\":[{\"first_name\":\"John\",\"last_name\":\"Doe\"}]}"
},
"description": "",
"auth": {
Expand Down
10 changes: 1 addition & 9 deletions tests/Fixtures/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -296,21 +296,13 @@ paths:
contacts:
- first_name: Janelle
last_name: Monáe
- {}
roles: [Admin]
- first_name: 'John'
last_name: 'Doe'
contacts:
- first_name: Janelle
last_name: Monáe
- {}
roles: [Admin]
items:
type: object
properties:
first_name: { type: string, description: 'The first name of the user.', example: John }
last_name: { type: string, description: 'The last name of the user.', example: Doe }
contacts: { type: array, description: 'Contact info', example: [ [ ], [ ] ], items: { type: object, properties: { first_name: { type: string, description: 'The first name of the contact.', example: Janelle }, last_name: { type: string, description: 'The last name of the contact.', example: Monáe } }, required: [ first_name, last_name ] } }
contacts: { type: array, description: 'Contact info', example: [ [ ] ], items: { type: object, properties: { first_name: { type: string, description: 'The first name of the contact.', example: Janelle }, last_name: { type: string, description: 'The last name of the contact.', example: Monáe } }, required: [ first_name, last_name ] } }
roles: { type: array, description: 'The name of the role.', example: [ Admin ], items: { type: string } }
required:
- first_name
Expand Down
6 changes: 1 addition & 5 deletions tests/Unit/ExtractorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public function clean_can_properly_parse_array_keys()
'list_of_objects' => [
'name' => 'list_of_objects',
'type' => 'object[]',
'example' => [[], []],
'example' => [[]],
],
'list_of_objects[].key1' => [
'name' => 'list_of_objects.key1',
Expand Down Expand Up @@ -131,10 +131,6 @@ public function clean_can_properly_parse_array_keys()
'key1' => 'John',
'key2' => false,
],
[
'key1' => 'John',
'key2' => false,
],
],
], $cleanBodyParameters);
}
Expand Down

0 comments on commit 12937e1

Please sign in to comment.