Skip to content

Commit

Permalink
Basic support for overriding docs for inherited methods
Browse files Browse the repository at this point in the history
  • Loading branch information
shalvah committed Aug 11, 2022
1 parent d29718c commit 9735fdf
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 4 deletions.
27 changes: 26 additions & 1 deletion camel/BaseDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Spatie\DataTransferObject\DataTransferObject;


class BaseDTO extends DataTransferObject implements Arrayable
class BaseDTO extends DataTransferObject implements Arrayable, \ArrayAccess
{
/**
* @var array $custom
Expand Down Expand Up @@ -48,4 +48,29 @@ protected function parseArray(array $array): array

return $array;
}

public static function make(array|self $data): static
{
return $data instanceof static ? $data : new static($data);
}

public function offsetExists(mixed $offset): bool
{
return isset($this->$offset);
}

public function offsetGet(mixed $offset): mixed
{
return $this->$offset;
}

public function offsetSet(mixed $offset, mixed $value): void
{
$this->$offset = $value;
}

public function offsetUnset(mixed $offset): void
{
unset($this->$offset);
}
}
55 changes: 52 additions & 3 deletions src/Extracting/Extractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Illuminate\Routing\Route;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Knuckles\Camel\Extraction\ResponseCollection;
use Knuckles\Camel\Extraction\ResponseField;
use Knuckles\Camel\Output\OutputEndpointData;
use Knuckles\Scribe\Extracting\Strategies\Strategy;
Expand Down Expand Up @@ -43,28 +44,39 @@ public static function getRouteBeingProcessed(): ?Route
* @param array $routeRules Rules to apply when generating documentation for this route
*
* @return ExtractedEndpointData
* @throws \ReflectionException
*
*/
public function processRoute(Route $route, array $routeRules = []): ExtractedEndpointData
{
self::$routeBeingProcessed = $route;

$endpointData = ExtractedEndpointData::fromRoute($route);

$inheritedDocsOverrides = [];
if ($endpointData?->controller->hasMethod('inheritedDocsOverrides')) {
$inheritedDocsOverrides = call_user_func([$endpointData->controller->getName(), 'inheritedDocsOverrides']);
$inheritedDocsOverrides = $inheritedDocsOverrides[$endpointData->method->getName()] ?? [];
}

$this->fetchMetadata($endpointData, $routeRules);
$this->mergeInheritedMethodsData('metadata', $endpointData, $inheritedDocsOverrides);

$this->fetchUrlParameters($endpointData, $routeRules);
$this->mergeInheritedMethodsData('urlParameters', $endpointData, $inheritedDocsOverrides);
$endpointData->cleanUrlParameters = self::cleanParams($endpointData->urlParameters);

$this->addAuthField($endpointData);

$this->fetchQueryParameters($endpointData, $routeRules);
$this->mergeInheritedMethodsData('queryParameters', $endpointData, $inheritedDocsOverrides);
$endpointData->cleanQueryParameters = self::cleanParams($endpointData->queryParameters);

$this->fetchRequestHeaders($endpointData, $routeRules);
$this->mergeInheritedMethodsData('headers', $endpointData, $inheritedDocsOverrides);

$this->fetchBodyParameters($endpointData, $routeRules);
$endpointData->cleanBodyParameters = self::cleanParams($endpointData->bodyParameters);
$this->mergeInheritedMethodsData('bodyParameters', $endpointData, $inheritedDocsOverrides);

if (count($endpointData->cleanBodyParameters) && !isset($endpointData->headers['Content-Type'])) {
// Set content type if the user forgot to set it
Expand All @@ -81,8 +93,10 @@ public function processRoute(Route $route, array $routeRules = []): ExtractedEnd
$endpointData->cleanBodyParameters = $regularParameters;

$this->fetchResponses($endpointData, $routeRules);
$this->mergeInheritedMethodsData('responses', $endpointData, $inheritedDocsOverrides);

$this->fetchResponseFields($endpointData, $routeRules);
$this->mergeInheritedMethodsData('responseFields', $endpointData, $inheritedDocsOverrides);

self::$routeBeingProcessed = null;

Expand All @@ -93,7 +107,7 @@ protected function fetchMetadata(ExtractedEndpointData $endpointData, array $rul
{
$endpointData->metadata = new Metadata([
'groupName' => $this->config->get('groups.default', ''),
"authenticated" => $this->config->get("auth.default", false)
"authenticated" => $this->config->get("auth.default", false),
]);

$this->iterateThroughStrategies('metadata', $endpointData, $rulesToApply, function ($results) use ($endpointData) {
Expand Down Expand Up @@ -444,7 +458,7 @@ public static function nestArrayAndObjectFields(array $parameters, array $cleanP
// When the body is an array, param names will be "[].paramname",
// so $parts is ['[]']
if ($parts[0] == '[]') {
$dotPathToParent = '[]'.$dotPathToParent;
$dotPathToParent = '[]' . $dotPathToParent;
}

$dotPath = $dotPathToParent . '.__fields.' . $fieldName;
Expand All @@ -471,4 +485,39 @@ public static function nestArrayAndObjectFields(array $parameters, array $cleanP

return $finalParameters;
}

protected function mergeInheritedMethodsData(string $stage, ExtractedEndpointData $endpointData, array $inheritedDocsOverrides = []): void
{
$overrides = $inheritedDocsOverrides[$stage] ?? [];
$normalizeparamData = fn($data, $key) => array_merge($data, ["name" => $key]);
if (is_array($overrides)) {
foreach ($overrides as $key => $item) {
switch ($stage) {
case "responses":
$endpointData->responses->concat($overrides);
$endpointData->responses->sortBy('status');
break;
case "urlParameters":
case "bodyParameters":
case "queryParameters":
$endpointData->$stage[$key] = Parameter::make($normalizeparamData($item, $key));
break;
case "responseFields":
$endpointData->$stage[$key] = ResponseField::make($normalizeparamData($item, $key));
break;
default:
$endpointData->$stage[$key] = $item;
}
}
} else if (is_callable($overrides)) {
$results = $overrides($endpointData);

$endpointData->$stage = match ($stage) {
"responses" => ResponseCollection::make($results),
"urlParameters", "bodyParameters", "queryParameters" => collect($results)->map(fn($param, $name) => Parameter::make($normalizeparamData($param, $name)))->all(),
"responseFields" => collect($results)->map(fn($field, $name) => ResponseField::make($normalizeparamData($field, $name)))->all(),
default => $results,
};
}
}
}
79 changes: 79 additions & 0 deletions tests/Unit/ExtractorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use Illuminate\Routing\Route;
use Knuckles\Camel\Extraction\ExtractedEndpointData;
use Knuckles\Camel\Extraction\Parameter;
use Knuckles\Scribe\Attributes\UrlParam;
use Knuckles\Scribe\Extracting\Extractor;
Expand Down Expand Up @@ -266,6 +267,32 @@ public function endpoint_metadata_supports_custom_declarations()
$this->assertSame('some custom metadata', $parsed->metadata->custom['myProperty']);
}

/** @test */
public function can_override_data_for_inherited_methods()
{
$route = $this->createRoute('POST', '/api/test', 'endpoint', TestParentController::class);
$parent = $this->extractor->processRoute($route);
$this->assertSame('Parent title', $parent->metadata->title);
$this->assertSame('Parent group name', $parent->metadata->groupName);
$this->assertSame('Parent description', $parent->metadata->description);
$this->assertCount(1, $parent->responses);
$this->assertCount(1, $parent->bodyParameters);
$this->assertArraySubset(["type" => "integer"], $parent->bodyParameters['thing']->toArray());
$this->assertEmpty($parent->queryParameters);

$inheritedRoute = $this->createRoute('POST', '/api/test', 'endpoint', TestInheritedController::class);
$inherited = $this->extractor->processRoute($inheritedRoute);
$this->assertSame('Overridden title', $inherited->metadata->title);
$this->assertSame('Overridden group name', $inherited->metadata->groupName);
$this->assertSame('Parent description', $inherited->metadata->description);
$this->assertCount(0, $inherited->responses);
$this->assertCount(2, $inherited->bodyParameters);
$this->assertArraySubset(["type" => "integer"], $inherited->bodyParameters['thing']->toArray());
$this->assertArraySubset(["type" => "string"], $inherited->bodyParameters["other_thing"]->toArray());
$this->assertCount(1, $inherited->queryParameters);
$this->assertArraySubset(["type" => "string"], $inherited->queryParameters["queryThing"]->toArray());
}

public function createRoute(string $httpMethod, string $path, string $controllerMethod, $class = TestController::class)
{
return new Route([$httpMethod], $path, ['uses' => [$class, $controllerMethod]]);
Expand Down Expand Up @@ -352,3 +379,55 @@ public function authRules()
];
}
}


class TestParentController
{
/**
* Parent title
*
* Parent description
*
* @group Parent group name
*
* @bodyParam thing integer
* @response {"hello":"there"}
*/
public function endpoint()
{

}
}

class TestInheritedController extends TestParentController
{
public static function inheritedDocsOverrides()
{
return [
"endpoint" => [
"metadata" => [
"title" => "Overridden title",
"groupName" => "Overridden group name",
],
"queryParameters" => function (ExtractedEndpointData $endpointData) {
// Overrides
return [
'queryThing' => [
'type' => 'string',
],
];
},
"bodyParameters" => [
// Merges
"other_thing" => [
"type" => "string",
],
],
"responses" => function (ExtractedEndpointData $endpointData) {
// Completely overrides responses
return [];
},
],
];
}
}

0 comments on commit 9735fdf

Please sign in to comment.