Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
shalvah committed Dec 25, 2023
1 parent 57b55ec commit 4c832f0
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 254 deletions.
133 changes: 131 additions & 2 deletions camel/Output/OutputEndpointData.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

use Illuminate\Http\UploadedFile;
use Illuminate\Routing\Route;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Knuckles\Camel\BaseDTO;
use Knuckles\Camel\Extraction\Metadata;
use Knuckles\Camel\Extraction\Parameter;
use Knuckles\Camel\Extraction\ResponseCollection;
use Knuckles\Camel\Extraction\ResponseField;
use Knuckles\Scribe\Extracting\Extractor;
Expand Down Expand Up @@ -105,8 +107,8 @@ public function __construct(array $parameters = [])
$this->cleanBodyParameters = Extractor::cleanParams($this->bodyParameters);
$this->cleanQueryParameters = Extractor::cleanParams($this->queryParameters);
$this->cleanUrlParameters = Extractor::cleanParams($this->urlParameters);
$this->nestedBodyParameters = Extractor::nestArrayAndObjectFields($this->bodyParameters, $this->cleanBodyParameters);
$this->nestedResponseFields = Extractor::nestArrayAndObjectFields($this->responseFields);
$this->nestedBodyParameters = self::nestArrayAndObjectFields($this->bodyParameters, $this->cleanBodyParameters);
$this->nestedResponseFields = self::nestArrayAndObjectFields($this->responseFields);

$this->boundUri = u::getUrlWithBoundParameters($this->uri, $this->cleanUrlParameters);

Expand Down Expand Up @@ -142,6 +144,133 @@ public static function fromExtractedEndpointArray(array $endpoint): OutputEndpoi
return new self($endpoint);
}

/**
* Transform body parameters such that object fields have a `fields` property containing a list of all subfields
* Subfields will be removed from the main parameter map
* For instance, if $parameters is [
* 'dad' => new Parameter(...),
* 'dad.age' => new Parameter(...),
* 'dad.cars[]' => new Parameter(...),
* 'dad.cars[].model' => new Parameter(...),
* 'dad.cars[].price' => new Parameter(...),
* ],
* normalise this into [
* 'dad' => [
* ...,
* '__fields' => [
* 'dad.age' => [...],
* 'dad.cars' => [
* ...,
* '__fields' => [
* 'model' => [...],
* 'price' => [...],
* ],
* ],
* ],
* ]]
*
* @param array $parameters
*
* @return array
*/
public static function nestArrayAndObjectFields(array $parameters, array $cleanParameters = []): array
{
// First, we'll make sure all object fields have parent fields properly set
$normalisedParameters = [];
foreach ($parameters as $name => $parameter) {
if (Str::contains($name, '.')) {
// If the user didn't add a parent field, we'll helpfully add it for them
$ancestors = [];

$parts = explode('.', $name);
$fieldName = array_pop($parts);
$parentName = rtrim(join('.', $parts), '[]');

// When the body is an array, param names will be "[].paramname",
// so $parentName is empty. Let's fix that.
if (empty($parentName)) {
$parentName = '[]';
}

while ($parentName) {
if (!empty($normalisedParameters[$parentName])) {
break;
}

$details = [
"name" => $parentName,
"type" => $parentName === '[]' ? "object[]" : "object",
"description" => "",
"required" => false,
];

if ($parameter instanceof ResponseField) {
$ancestors[] = [$parentName, new ResponseField($details)];
} else {
$lastParentExample = $details["example"] =
[$fieldName => $lastParentExample ?? $parameter->example];
$ancestors[] = [$parentName, new Parameter($details)];
}

$fieldName = array_pop($parts);
$parentName = rtrim(join('.', $parts), '[]');
}

// We add ancestors in reverse so we can iterate over parents first in the next section
foreach (array_reverse($ancestors) as [$ancestorName, $ancestor]) {
$normalisedParameters[$ancestorName] = $ancestor;
}
}

$normalisedParameters[$name] = $parameter;
unset($lastParentExample);
}

$finalParameters = [];
foreach ($normalisedParameters as $name => $parameter) {
$parameter = $parameter->toArray();
if (Str::contains($name, '.')) { // An object field
// Get the various pieces of the name
$parts = explode('.', $name);
$fieldName = array_pop($parts);
$baseName = join('.__fields.', $parts);

// For subfields, the type is indicated in the source object
// eg test.items[].more and test.items.more would both have parent field with name `items` and containing __fields => more
// The difference would be in the parent field's `type` property (object[] vs object)
// So we can get rid of all [] to get the parent name
$dotPathToParent = str_replace('[]', '', $baseName);
// When the body is an array, param names will be "[].paramname",
// so $parts is ['[]']
if ($parts[0] == '[]') {
$dotPathToParent = '[]' . $dotPathToParent;
}

$dotPath = $dotPathToParent . '.__fields.' . $fieldName;
Arr::set($finalParameters, $dotPath, $parameter);
} else { // A regular field, not a subfield of anything
// Note: we're assuming any subfields of this field are listed *after* it,
// and will set __fields correctly when we iterate over them
// Hence why we create a new "normalisedParameters" array above and push the parent to that first
$parameter['__fields'] = [];
$finalParameters[$name] = $parameter;
}

}

// Finally, if the body is an array, remove any other items.
if (isset($finalParameters['[]'])) {
$finalParameters = ["[]" => $finalParameters['[]']];
// At this point, the examples are likely [[], []],
// but have been correctly set in clean parameters, so let's update them
if ($finalParameters["[]"]["example"][0] == [] && !empty($cleanParameters)) {
$finalParameters["[]"]["example"] = $cleanParameters;
}
}

return $finalParameters;
}

public function endpointId(): string
{
return $this->httpMethods[0] . str_replace(['/', '?', '{', '}', ':', '\\', '+', '|', '.'], '-', $this->uri);
Expand Down
133 changes: 4 additions & 129 deletions src/Extracting/Extractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
use Knuckles\Camel\Extraction\ResponseCollection;
use Knuckles\Camel\Extraction\ResponseField;
use Knuckles\Camel\Output\OutputEndpointData;
use Knuckles\Scribe\Extracting\Strategies\Strategy;
use Knuckles\Scribe\Tools\DocumentationConfig;
use Knuckles\Scribe\Tools\RoutePatternMatcher;

class Extractor
{
Expand Down Expand Up @@ -199,7 +199,9 @@ protected function fetchRequestHeaders(ExtractedEndpointData $endpointData, arra
* @param callable $handler Function to run after each strategy returns its results (an array).
*
*/
protected function iterateThroughStrategies(string $stage, ExtractedEndpointData $endpointData, array $rulesToApply, callable $handler): void
protected function iterateThroughStrategies(
string $stage, ExtractedEndpointData $endpointData, array $rulesToApply, callable $handler
): void
{
$strategies = $this->config->get("strategies.$stage", []);

Expand Down Expand Up @@ -418,133 +420,6 @@ protected static function convertStringValueToUploadedFileInstance(string $fileP
return new File($fileName, fopen($filePath, 'r'));
}

/**
* Transform body parameters such that object fields have a `fields` property containing a list of all subfields
* Subfields will be removed from the main parameter map
* For instance, if $parameters is [
* 'dad' => new Parameter(...),
* 'dad.age' => new Parameter(...),
* 'dad.cars[]' => new Parameter(...),
* 'dad.cars[].model' => new Parameter(...),
* 'dad.cars[].price' => new Parameter(...),
* ],
* normalise this into [
* 'dad' => [
* ...,
* '__fields' => [
* 'dad.age' => [...],
* 'dad.cars' => [
* ...,
* '__fields' => [
* 'model' => [...],
* 'price' => [...],
* ],
* ],
* ],
* ]]
*
* @param array $parameters
*
* @return array
*/
public static function nestArrayAndObjectFields(array $parameters, array $cleanParameters = []): array
{
// First, we'll make sure all object fields have parent fields properly set
$normalisedParameters = [];
foreach ($parameters as $name => $parameter) {
if (Str::contains($name, '.')) {
// If the user didn't add a parent field, we'll helpfully add it for them
$ancestors = [];

$parts = explode('.', $name);
$fieldName = array_pop($parts);
$parentName = rtrim(join('.', $parts), '[]');

// When the body is an array, param names will be "[].paramname",
// so $parentName is empty. Let's fix that.
if (empty($parentName)) {
$parentName = '[]';
}

while ($parentName) {
if (!empty($normalisedParameters[$parentName])) {
break;
}

$details = [
"name" => $parentName,
"type" => $parentName === '[]' ? "object[]" : "object",
"description" => "",
"required" => false,
];

if ($parameter instanceof ResponseField) {
$ancestors[] = [$parentName, new ResponseField($details)];
} else {
$lastParentExample = $details["example"] =
[$fieldName => $lastParentExample ?? $parameter->example];
$ancestors[] = [$parentName, new Parameter($details)];
}

$fieldName = array_pop($parts);
$parentName = rtrim(join('.', $parts), '[]');
}

// We add ancestors in reverse so we can iterate over parents first in the next section
foreach (array_reverse($ancestors) as [$ancestorName, $ancestor]) {
$normalisedParameters[$ancestorName] = $ancestor;
}
}

$normalisedParameters[$name] = $parameter;
unset($lastParentExample);
}

$finalParameters = [];
foreach ($normalisedParameters as $name => $parameter) {
$parameter = $parameter->toArray();
if (Str::contains($name, '.')) { // An object field
// Get the various pieces of the name
$parts = explode('.', $name);
$fieldName = array_pop($parts);
$baseName = join('.__fields.', $parts);

// For subfields, the type is indicated in the source object
// eg test.items[].more and test.items.more would both have parent field with name `items` and containing __fields => more
// The difference would be in the parent field's `type` property (object[] vs object)
// So we can get rid of all [] to get the parent name
$dotPathToParent = str_replace('[]', '', $baseName);
// When the body is an array, param names will be "[].paramname",
// so $parts is ['[]']
if ($parts[0] == '[]') {
$dotPathToParent = '[]' . $dotPathToParent;
}

$dotPath = $dotPathToParent . '.__fields.' . $fieldName;
Arr::set($finalParameters, $dotPath, $parameter);
} else { // A regular field, not a subfield of anything
// Note: we're assuming any subfields of this field are listed *after* it,
// and will set __fields correctly when we iterate over them
// Hence why we create a new "normalisedParameters" array above and push the parent to that first
$parameter['__fields'] = [];
$finalParameters[$name] = $parameter;
}

}

// Finally, if the body is an array, remove any other items.
if (isset($finalParameters['[]'])) {
$finalParameters = ["[]" => $finalParameters['[]']];
// At this point, the examples are likely [[], []],
// but have been correctly set in clean parameters, so let's update them
if ($finalParameters["[]"]["example"][0] == [] && !empty($cleanParameters)) {
$finalParameters["[]"]["example"] = $cleanParameters;
}
}

return $finalParameters;
}

protected function mergeInheritedMethodsData(string $stage, ExtractedEndpointData $endpointData, array $inheritedDocsOverrides = []): void
{
$overrides = $inheritedDocsOverrides[$stage] ?? [];
Expand Down
10 changes: 10 additions & 0 deletions tests/BaseLaravelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

namespace Knuckles\Scribe\Tests;

use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use Knuckles\Scribe\ScribeServiceProvider;
use Orchestra\Testbench\TestCase;

class BaseLaravelTest extends TestCase
{
use TestHelpers;
use ArraySubsetAsserts;

protected function getEnvironmentSetUp($app)
{
Expand Down Expand Up @@ -41,4 +43,12 @@ protected function getPackageProviders($app)
}
return $providers;
}

protected function setConfig($configValues): void
{
foreach ($configValues as $key => $value) {
config(["scribe.$key" => $value]);
config(["scribe_new.$key" => $value]);
}
}
}
11 changes: 11 additions & 0 deletions tests/BaseUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Knuckles\Scribe\Tests;

use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use PHPUnit\Framework\TestCase;

class BaseUnitTest extends TestCase
{
use ArraySubsetAsserts;
}
4 changes: 2 additions & 2 deletions tests/Unit/AnnotationParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

namespace Knuckles\Scribe\Tests\Unit;

use Knuckles\Scribe\Tests\BaseUnitTest;
use Knuckles\Scribe\Tools\AnnotationParser;
use PHPUnit\Framework\TestCase;

class AnnotationParserTest extends TestCase
class AnnotationParserTest extends BaseUnitTest
{
/**
* @test
Expand Down
4 changes: 2 additions & 2 deletions tests/Unit/ConfigDifferTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

namespace Knuckles\Scribe\Tests\Unit;

use Knuckles\Scribe\Tests\BaseUnitTest;
use Knuckles\Scribe\Tools\ConfigDiffer;
use PHPUnit\Framework\TestCase;

class ConfigDifferTest extends TestCase
class ConfigDifferTest extends BaseUnitTest
{
/** @test */
public function returns_empty_when_there_are_no_changes()
Expand Down
Loading

0 comments on commit 4c832f0

Please sign in to comment.