mirror of
https://github.com/ambieco/scribe.git
synced 2026-04-29 11:26:04 +08:00
Scaffold basic attribute support + refactor tests
This commit is contained in:
@@ -8,6 +8,12 @@ use Spatie\DataTransferObject\DataTransferObject;
|
||||
|
||||
class BaseDTO extends DataTransferObject implements Arrayable
|
||||
{
|
||||
/**
|
||||
* @var array $custom
|
||||
* Added so end-users can dynamically add additional properties for their own use.
|
||||
*/
|
||||
public array $custom = [];
|
||||
|
||||
/**
|
||||
* @param array|self $data
|
||||
*
|
||||
@@ -41,4 +47,4 @@ class BaseDTO extends DataTransferObject implements Arrayable
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,4 @@ class Metadata extends BaseDTO
|
||||
public ?string $description;
|
||||
|
||||
public bool $authenticated = false;
|
||||
|
||||
public array $custom = [];
|
||||
}
|
||||
|
||||
@@ -10,9 +10,8 @@ class Parameter extends BaseDTO
|
||||
public string $name;
|
||||
public ?string $description = null;
|
||||
public bool $required = false;
|
||||
public $example = null;
|
||||
public mixed $example = null;
|
||||
public string $type = 'string';
|
||||
public array $custom = [];
|
||||
|
||||
public function __construct(array $parameters = [])
|
||||
{
|
||||
|
||||
@@ -5,20 +5,8 @@ namespace Knuckles\Camel\Output;
|
||||
|
||||
class Parameter extends \Knuckles\Camel\Extraction\Parameter
|
||||
{
|
||||
public string $name;
|
||||
|
||||
public ?string $description = null;
|
||||
|
||||
public bool $required = false;
|
||||
|
||||
public $example = null;
|
||||
|
||||
public string $type = 'string';
|
||||
|
||||
public array $__fields = [];
|
||||
|
||||
public array $custom = [];
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
if (empty($this->exceptKeys)) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"php": ">=8.0",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-json": "*",
|
||||
"ext-pdo": "*",
|
||||
|
||||
10
src/Attributes/BodyParam.php
Normal file
10
src/Attributes/BodyParam.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Attributes;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
|
||||
class BodyParam extends GenericParam
|
||||
{
|
||||
}
|
||||
29
src/Attributes/GenericParam.php
Normal file
29
src/Attributes/GenericParam.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Attributes;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
|
||||
class GenericParam
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public ?string $type = 'string',
|
||||
public ?string $description = '',
|
||||
public ?bool $required = true,
|
||||
public mixed $example = null, /* Pass 'No-example' to omit the example */
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
"name" => $this->name,
|
||||
"description" => $this->description,
|
||||
"type" => $this->type,
|
||||
"required" => $this->required,
|
||||
"example" => $this->example,
|
||||
];
|
||||
}
|
||||
}
|
||||
15
src/Attributes/Group.php
Normal file
15
src/Attributes/Group.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Attributes;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute]
|
||||
class Group
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public ?string $description,
|
||||
){
|
||||
}
|
||||
}
|
||||
10
src/Attributes/QueryParam.php
Normal file
10
src/Attributes/QueryParam.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Attributes;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
|
||||
class QueryParam extends GenericParam
|
||||
{
|
||||
}
|
||||
10
src/Attributes/UrlParam.php
Normal file
10
src/Attributes/UrlParam.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Attributes;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
|
||||
class UrlParam extends GenericParam
|
||||
{
|
||||
}
|
||||
@@ -15,12 +15,7 @@ trait FindsFormRequestForMethod
|
||||
{
|
||||
foreach ($method->getParameters() as $argument) {
|
||||
$argType = $argument->getType();
|
||||
if ($argType === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (class_exists(ReflectionUnionType::class)
|
||||
&& $argType instanceof ReflectionUnionType) {
|
||||
if ($argType === null || $argType instanceof ReflectionUnionType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Extracting\Strategies\BodyParameters;
|
||||
|
||||
use Knuckles\Scribe\Attributes\BodyParam;
|
||||
use Knuckles\Scribe\Extracting\Strategies\GetParamsFromAttributeStrategy;
|
||||
|
||||
class GetFromBodyParamAttribute extends GetParamsFromAttributeStrategy
|
||||
{
|
||||
protected string $attributeName = BodyParam::class;
|
||||
}
|
||||
36
src/Extracting/Strategies/GetParamsFromAttributeStrategy.php
Normal file
36
src/Extracting/Strategies/GetParamsFromAttributeStrategy.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Extracting\Strategies;
|
||||
|
||||
use Knuckles\Scribe\Extracting\ParamHelpers;
|
||||
|
||||
class GetParamsFromAttributeStrategy extends PhpAttributeStrategy
|
||||
{
|
||||
use ParamHelpers;
|
||||
|
||||
protected function extractFromAttributes(array $attributesOnMethod, array $attributesOnController): ?array
|
||||
{
|
||||
$parameters = [];
|
||||
foreach ($attributesOnController as $attributeInstance) {
|
||||
$parameters[$attributeInstance->name] = $attributeInstance->toArray();
|
||||
}
|
||||
foreach ($attributesOnMethod as $attributeInstance) {
|
||||
$parameters[$attributeInstance->name] = $attributeInstance->toArray();
|
||||
}
|
||||
|
||||
return array_map([$this, 'normalizeParameterData'], $parameters);
|
||||
}
|
||||
|
||||
protected function normalizeParameterData(array $data): array
|
||||
{
|
||||
$data['type'] = $this->normalizeTypeName($data['type']);
|
||||
if (is_null($data['example'])) {
|
||||
$data['example'] = $this->generateDummyValue($data['type']);
|
||||
} else if ($data['example'] == 'No-example' || $data['example'] == 'No-example.') {
|
||||
$data['example'] = null;
|
||||
}
|
||||
|
||||
$data['description'] = trim($data['description'] ?? '');
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,7 @@ class GetFromHeaderTag extends Strategy
|
||||
{
|
||||
foreach ($endpointData->method->getParameters() as $param) {
|
||||
$paramType = $param->getType();
|
||||
if ($paramType === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (class_exists(ReflectionUnionType::class)
|
||||
&& $paramType instanceof ReflectionUnionType) {
|
||||
if ($paramType === null || $paramType instanceof ReflectionUnionType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,14 @@ class GetFromDocBlocks extends Strategy
|
||||
|
||||
public function getMetadataFromDocBlock(DocBlock $methodDocBlock, DocBlock $classDocBlock): array
|
||||
{
|
||||
[$routeGroupName, $routeGroupDescription, $routeTitle] = $this->getEndpointGroupDetails($methodDocBlock, $classDocBlock);
|
||||
[$groupName, $groupDescription, $title] = $this->getEndpointGroupAndTitleDetails($methodDocBlock, $classDocBlock);
|
||||
|
||||
return [
|
||||
'groupName' => $routeGroupName,
|
||||
'groupDescription' => $routeGroupDescription,
|
||||
'groupName' => $groupName,
|
||||
'groupDescription' => $groupDescription,
|
||||
'subgroup' => $this->getEndpointSubGroup($methodDocBlock, $classDocBlock),
|
||||
'subgroupDescription' => $this->getEndpointSubGroupDescription($methodDocBlock, $classDocBlock),
|
||||
'title' => $routeTitle ?: $methodDocBlock->getShortDescription(),
|
||||
'title' => $title ?: $methodDocBlock->getShortDescription(),
|
||||
'description' => $methodDocBlock->getLongDescription()->getContents(),
|
||||
'authenticated' => $this->getAuthStatusFromDocBlock($methodDocBlock, $classDocBlock),
|
||||
];
|
||||
@@ -51,9 +51,9 @@ class GetFromDocBlocks extends Strategy
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array The route group name, the group description, and the route title
|
||||
* @return array The endpoint's group name, the group description, and the endpoint title
|
||||
*/
|
||||
protected function getEndpointGroupDetails(DocBlock $methodDocBlock, DocBlock $controllerDocBlock)
|
||||
protected function getEndpointGroupAndTitleDetails(DocBlock $methodDocBlock, DocBlock $controllerDocBlock)
|
||||
{
|
||||
foreach ($methodDocBlock->getTags() as $tag) {
|
||||
if ($tag->getName() === 'group') {
|
||||
|
||||
57
src/Extracting/Strategies/PhpAttributeStrategy.php
Normal file
57
src/Extracting/Strategies/PhpAttributeStrategy.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Extracting\Strategies;
|
||||
|
||||
use Knuckles\Camel\Extraction\ExtractedEndpointData;
|
||||
use Knuckles\Scribe\Extracting\ParamHelpers;
|
||||
use ReflectionAttribute;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunctionAbstract;
|
||||
|
||||
/**
|
||||
* @template T of \ReflectionAttribute
|
||||
*/
|
||||
abstract class PhpAttributeStrategy extends Strategy
|
||||
{
|
||||
use ParamHelpers;
|
||||
|
||||
/**
|
||||
* @var class-string<T>
|
||||
*/
|
||||
protected string $attributeName;
|
||||
|
||||
public function __invoke(ExtractedEndpointData $endpointData, array $routeRules): array
|
||||
{
|
||||
[$attributesOnMethod, $attributesOnController] =
|
||||
$this->getAttributes($endpointData->method, $endpointData->controller);
|
||||
|
||||
return $this->extractFromAttributes($attributesOnMethod, $attributesOnController);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ReflectionFunctionAbstract $method
|
||||
* @param \ReflectionClass|null $class
|
||||
*
|
||||
* @return array{array<T>, array<T>}
|
||||
*/
|
||||
protected function getAttributes(ReflectionFunctionAbstract $method, ?ReflectionClass $class = null): array
|
||||
{
|
||||
$attributesOnMethod = array_map(
|
||||
fn(ReflectionAttribute $a) => $a->newInstance(), $method->getAttributes($this->attributeName)
|
||||
);
|
||||
|
||||
if ($class) {
|
||||
$attributesOnController = array_map(
|
||||
fn(ReflectionAttribute $a) => $a->newInstance(), $class->getAttributes($this->attributeName)
|
||||
);
|
||||
}
|
||||
|
||||
return [$attributesOnMethod, $attributesOnController ?? []];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<T> $attributesOnMethod
|
||||
* @param array<T> $attributesOnController
|
||||
*/
|
||||
abstract protected function extractFromAttributes(array $attributesOnMethod, array $attributesOnController): ?array;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Extracting\Strategies\QueryParameters;
|
||||
|
||||
use Knuckles\Scribe\Attributes\QueryParam;
|
||||
use Knuckles\Scribe\Extracting\Strategies\GetParamsFromAttributeStrategy;
|
||||
|
||||
class GetFromQueryParamAttribute extends GetParamsFromAttributeStrategy
|
||||
{
|
||||
protected string $attributeName = QueryParam::class;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Extracting\Strategies\UrlParameters;
|
||||
|
||||
use Knuckles\Scribe\Attributes\UrlParam;
|
||||
use Knuckles\Scribe\Extracting\Strategies\GetParamsFromAttributeStrategy;
|
||||
|
||||
class GetFromUrlParamAttribute extends GetParamsFromAttributeStrategy
|
||||
{
|
||||
protected string $attributeName = UrlParam::class;
|
||||
}
|
||||
@@ -23,12 +23,7 @@ class GetFromUrlParamTag extends Strategy
|
||||
{
|
||||
foreach ($endpointData->method->getParameters() as $param) {
|
||||
$paramType = $param->getType();
|
||||
if ($paramType === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (class_exists(ReflectionUnionType::class)
|
||||
&& $paramType instanceof ReflectionUnionType) {
|
||||
if ($paramType === null || $paramType instanceof ReflectionUnionType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -332,16 +332,14 @@ class OutputTest extends BaseLaravelTest
|
||||
/** @test */
|
||||
public function generates_correct_url_params_from_resource_routes_and_model_binding_with_binded_interfaces()
|
||||
{
|
||||
$this->app->bind(TestPostBindedInterface::class, function(){
|
||||
return new TestPost();
|
||||
});
|
||||
$this->app->bind(TestPostBindedInterface::class, fn() => new TestPost());
|
||||
|
||||
RouteFacade::resource('posts', TestPostBindedInterfaceController::class)->only('update');
|
||||
|
||||
config(['scribe.routes.0.match.prefixes' => ['*']]);
|
||||
config(['scribe.routes.0.apply.response_calls.methods' => []]);
|
||||
|
||||
$this->artisan('scribe:generate');
|
||||
$this->generate();
|
||||
|
||||
$group = Yaml::parseFile('.scribe/endpoints/00.yaml');
|
||||
$this->assertEquals('posts/{slug}', $group['endpoints'][0]['uri']);
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Tests\Strategies\BodyParameters;
|
||||
|
||||
use Closure;
|
||||
use Knuckles\Camel\Extraction\ExtractedEndpointData;
|
||||
use Knuckles\Scribe\Attributes\BodyParam;
|
||||
use Knuckles\Scribe\Extracting\Strategies\BodyParameters\GetFromBodyParamAttribute;
|
||||
use Knuckles\Scribe\Tools\DocumentationConfig;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
|
||||
use ReflectionClass;
|
||||
|
||||
class GetFromBodyParamAttributeTest extends TestCase
|
||||
{
|
||||
use ArraySubsetAsserts;
|
||||
|
||||
/** @test */
|
||||
public function can_fetch_from_bodyparam_attribute()
|
||||
{
|
||||
$endpoint = $this->endpoint(function (ExtractedEndpointData $e) {
|
||||
$e->controller = new ReflectionClass(BodyParamAttributeTestController::class);
|
||||
$e->method = $e->controller->getMethod('methodWithAttributes');
|
||||
});
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'location_id' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'The id of the location.',
|
||||
],
|
||||
'user_id' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'The id of the user.',
|
||||
'example' => 'me',
|
||||
],
|
||||
'page' => [
|
||||
'type' => 'integer',
|
||||
'required' => false,
|
||||
'description' => 'The page number.',
|
||||
'example' => 4,
|
||||
],
|
||||
'with_type' => [
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'description' => '',
|
||||
'example' => 13.0,
|
||||
],
|
||||
'with_list_type' => [
|
||||
'type' => 'integer[]',
|
||||
'required' => false,
|
||||
'description' => '',
|
||||
],
|
||||
'fields' => [
|
||||
'type' => 'string[]',
|
||||
'required' => false,
|
||||
'description' => 'The fields.',
|
||||
'example' => ['age', 'name']
|
||||
],
|
||||
'filters' => [
|
||||
'type' => 'object',
|
||||
'required' => false,
|
||||
'description' => 'The filters.',
|
||||
],
|
||||
'filters.class' => [
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'description' => 'Class.',
|
||||
'example' => 11.0
|
||||
],
|
||||
'filters.other' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'Other things.',
|
||||
],
|
||||
'noExampleNoDescription' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => '',
|
||||
'example' => null
|
||||
],
|
||||
'noExample' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'Something',
|
||||
'example' => null
|
||||
],
|
||||
], $results);
|
||||
}
|
||||
|
||||
protected function fetch($endpoint): array
|
||||
{
|
||||
$strategy = new GetFromBodyParamAttribute(new DocumentationConfig([]));
|
||||
return $strategy($endpoint, []);
|
||||
}
|
||||
|
||||
protected function endpoint(Closure $configure): ExtractedEndpointData
|
||||
{
|
||||
$endpoint = new class extends ExtractedEndpointData {
|
||||
public function __construct(array $parameters = []) {}
|
||||
};
|
||||
$configure($endpoint);
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[BodyParam("user_id", description: "Will be overriden.")]
|
||||
#[BodyParam("location_id", description: "The id of the location.")]
|
||||
class BodyParamAttributeTestController
|
||||
{
|
||||
#[BodyParam("user_id", description: "The id of the user.", example: "me")]
|
||||
#[BodyParam("page", 'integer', description: "The page number.", required: false, example: 4)]
|
||||
#[BodyParam("with_type", "number", example: 13.0, required: false)]
|
||||
#[BodyParam("with_list_type", type: "int[]", required: false)]
|
||||
#[BodyParam("fields", "string[]", "The fields.", required: false, example: ["age", "name"])]
|
||||
#[BodyParam("filters", "object", "The filters. ", required: false)]
|
||||
#[BodyParam("filters.class", "double", required: false, example: 11.0, description: "Class.")]
|
||||
#[BodyParam("filters.other", "string", description: "Other things.")]
|
||||
#[BodyParam("noExampleNoDescription", example: "No-example")]
|
||||
#[BodyParam("noExample", description: "Something", example: "No-example")]
|
||||
public function methodWithAttributes()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Tests\Strategies\QueryParameters;
|
||||
|
||||
use Closure;
|
||||
use Knuckles\Camel\Extraction\ExtractedEndpointData;
|
||||
use Knuckles\Scribe\Attributes\QueryParam;
|
||||
use Knuckles\Scribe\Extracting\Strategies\QueryParameters\GetFromQueryParamAttribute;
|
||||
use Knuckles\Scribe\Tools\DocumentationConfig;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
|
||||
use ReflectionClass;
|
||||
|
||||
class GetFromQueryParamAttributeTest extends TestCase
|
||||
{
|
||||
use ArraySubsetAsserts;
|
||||
|
||||
/** @test */
|
||||
public function can_fetch_from_queryparam_attribute()
|
||||
{
|
||||
$endpoint = $this->endpoint(function (ExtractedEndpointData $e) {
|
||||
$e->controller = new ReflectionClass(QueryParamAttributeTestController::class);
|
||||
$e->method = $e->controller->getMethod('methodWithAttributes');
|
||||
});
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'location_id' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'The id of the location.',
|
||||
],
|
||||
'user_id' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'The id of the user.',
|
||||
'example' => 'me',
|
||||
],
|
||||
'page' => [
|
||||
'type' => 'integer',
|
||||
'required' => false,
|
||||
'description' => 'The page number.',
|
||||
'example' => 4,
|
||||
],
|
||||
'with_type' => [
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'description' => '',
|
||||
'example' => 13.0,
|
||||
],
|
||||
'with_list_type' => [
|
||||
'type' => 'integer[]',
|
||||
'required' => false,
|
||||
'description' => '',
|
||||
],
|
||||
'fields' => [
|
||||
'type' => 'string[]',
|
||||
'required' => false,
|
||||
'description' => 'The fields.',
|
||||
'example' => ['age', 'name']
|
||||
],
|
||||
'filters' => [
|
||||
'type' => 'object',
|
||||
'required' => false,
|
||||
'description' => 'The filters.',
|
||||
],
|
||||
'filters.class' => [
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'description' => 'Class.',
|
||||
'example' => 11.0
|
||||
],
|
||||
'filters.other' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'Other things.',
|
||||
],
|
||||
'noExampleNoDescription' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => '',
|
||||
'example' => null
|
||||
],
|
||||
'noExample' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'Something',
|
||||
'example' => null
|
||||
],
|
||||
], $results);
|
||||
}
|
||||
|
||||
protected function fetch($endpoint): array
|
||||
{
|
||||
$strategy = new GetFromQueryParamAttribute(new DocumentationConfig([]));
|
||||
return $strategy($endpoint, []);
|
||||
}
|
||||
|
||||
protected function endpoint(Closure $configure): ExtractedEndpointData
|
||||
{
|
||||
$endpoint = new class extends ExtractedEndpointData {
|
||||
public function __construct(array $parameters = []) {}
|
||||
};
|
||||
$configure($endpoint);
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[QueryParam("user_id", description: "Will be overriden.")]
|
||||
#[QueryParam("location_id", description: "The id of the location.")]
|
||||
class QueryParamAttributeTestController
|
||||
{
|
||||
#[QueryParam("user_id", description: "The id of the user.", example: "me")]
|
||||
#[QueryParam("page", 'integer', description: "The page number.", required: false, example: 4)]
|
||||
#[QueryParam("with_type", "number", example: 13.0, required: false)]
|
||||
#[QueryParam("with_list_type", type: "int[]", required: false)]
|
||||
#[QueryParam("fields", "string[]", "The fields.", required: false, example: ["age", "name"])]
|
||||
#[QueryParam("filters", "object", "The filters. ", required: false)]
|
||||
#[QueryParam("filters.class", "double", required: false, example: 11.0, description: "Class.")]
|
||||
#[QueryParam("filters.other", "string", description: "Other things.")]
|
||||
#[QueryParam("noExampleNoDescription", example: "No-example")]
|
||||
#[QueryParam("noExample", description: "Something", example: "No-example")]
|
||||
public function methodWithAttributes()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Knuckles\Scribe\Tests\Strategies\UrlParameters;
|
||||
|
||||
use Illuminate\Routing\Route;
|
||||
use Closure;
|
||||
use Illuminate\Routing\Router;
|
||||
use Knuckles\Camel\Extraction\ExtractedEndpointData;
|
||||
use Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromLaravelAPI;
|
||||
@@ -18,17 +18,8 @@ class GetFromLaravelAPITest extends BaseLaravelTest
|
||||
/** @test */
|
||||
public function can_fetch_from_url()
|
||||
{
|
||||
$endpoint = new class extends ExtractedEndpointData {
|
||||
public function __construct(array $parameters = [])
|
||||
{
|
||||
$this->method = new \ReflectionMethod(TestController::class, 'withInjectedModel');
|
||||
$this->route = app(Router::class)->addRoute(['GET'], "users/{id}", ['uses' => [TestController::class, 'withInjectedModel']]);
|
||||
$this->uri = $this->route->uri;
|
||||
}
|
||||
};
|
||||
|
||||
$strategy = new GetFromLaravelAPI(new DocumentationConfig([]));
|
||||
$results = $strategy($endpoint, []);
|
||||
$endpoint = $this->endpointForRoute("users/{id}", TestController::class, 'withInjectedModel');
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
"name" => "id",
|
||||
@@ -42,17 +33,8 @@ class GetFromLaravelAPITest extends BaseLaravelTest
|
||||
/** @test */
|
||||
public function can_infer_description_from_url()
|
||||
{
|
||||
$endpoint = new class extends ExtractedEndpointData {
|
||||
public function __construct(array $parameters = [])
|
||||
{
|
||||
$this->method = new \ReflectionMethod(TestController::class, 'dummy');
|
||||
$this->route = app(Router::class)->addRoute(['GET'], "everything/{cat_id}", ['uses' => [TestController::class, 'dummy']]);
|
||||
$this->uri = $this->route->uri;
|
||||
}
|
||||
};
|
||||
|
||||
$strategy = new GetFromLaravelAPI(new DocumentationConfig([]));
|
||||
$results = $strategy($endpoint, []);
|
||||
$endpoint = $this->endpointForRoute("everything/{cat_id}", TestController::class, 'dummy');
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
"name" => "cat_id",
|
||||
@@ -63,7 +45,7 @@ class GetFromLaravelAPITest extends BaseLaravelTest
|
||||
|
||||
$endpoint->route = app(Router::class)->addRoute(['GET'], 'dogs/{id}', ['uses' => [TestController::class, 'dummy']]);;
|
||||
$endpoint->uri = $endpoint->route->uri;
|
||||
$results = $strategy($endpoint, []);
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
"name" => "id",
|
||||
@@ -76,20 +58,14 @@ class GetFromLaravelAPITest extends BaseLaravelTest
|
||||
/** @test */
|
||||
public function can_infer_example_from_wheres()
|
||||
{
|
||||
$endpoint = new class extends ExtractedEndpointData {
|
||||
public function __construct(array $parameters = [])
|
||||
{
|
||||
$this->method = new \ReflectionMethod(TestController::class, 'dummy');
|
||||
|
||||
$route = app(Router::class)->addRoute(['GET'], "everything/{cat_id}", ['uses' => [TestController::class, 'dummy']]);
|
||||
$this->regex = '/catz\d+-\d/';
|
||||
$this->route = $route->where('cat_id', $this->regex);
|
||||
$this->uri = $this->route->uri;
|
||||
}
|
||||
};
|
||||
|
||||
$strategy = new GetFromLaravelAPI(new DocumentationConfig([]));
|
||||
$results = $strategy($endpoint, []);
|
||||
$regex = '/catz\d+-\d/';
|
||||
$endpoint = $this->endpoint(function (ExtractedEndpointData $e) use ($regex) {
|
||||
$e->method = new \ReflectionMethod(TestController::class, 'dummy');
|
||||
$e->route = app(Router::class)->addRoute(['GET'], "everything/{cat_id}", ['uses' => [TestController::class, 'dummy']])
|
||||
->where('cat_id', $regex);
|
||||
$e->uri = $e->route->uri;
|
||||
});
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
"name" => "cat_id",
|
||||
@@ -97,26 +73,14 @@ class GetFromLaravelAPITest extends BaseLaravelTest
|
||||
"required" => true,
|
||||
"type" => "string",
|
||||
], $results['cat_id']);
|
||||
$this->assertMatchesRegularExpression($endpoint->regex, $results['cat_id']['example']);
|
||||
$this->assertMatchesRegularExpression($regex, $results['cat_id']['example']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function can_infer_data_from_field_bindings()
|
||||
{
|
||||
$strategy = new GetFromLaravelAPI(new DocumentationConfig([]));
|
||||
|
||||
$endpoint = new class extends ExtractedEndpointData {
|
||||
public function __construct(array $parameters = [])
|
||||
{
|
||||
$this->method = new \ReflectionMethod(TestController::class, 'dummy');
|
||||
|
||||
$route = app(Router::class)->addRoute(['GET'], "audio/{audio:slug}", ['uses' => [TestController::class, 'dummy']]);
|
||||
$this->route = $route;
|
||||
$this->uri = $route->uri;
|
||||
}
|
||||
};
|
||||
|
||||
$results = $strategy($endpoint, []);
|
||||
$endpoint = $this->endpointForRoute("audio/{audio:slug}", TestController::class, 'dummy');
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
"name" => "audio",
|
||||
@@ -125,18 +89,8 @@ class GetFromLaravelAPITest extends BaseLaravelTest
|
||||
"type" => "string",
|
||||
], $results['audio']);
|
||||
|
||||
$endpoint = new class extends ExtractedEndpointData {
|
||||
public function __construct(array $parameters = [])
|
||||
{
|
||||
$this->method = new \ReflectionMethod(TestController::class, 'withInjectedModel');
|
||||
|
||||
$route = app(Router::class)->addRoute(['GET'], "users/{user:id}", ['uses' => [TestController::class, 'withInjectedModel']]);
|
||||
$this->route = $route;
|
||||
$this->uri = $route->uri;
|
||||
}
|
||||
};
|
||||
|
||||
$results = $strategy($endpoint, []);
|
||||
$endpoint = $this->endpointForRoute("users/{user:id}", TestController::class, 'withInjectedModel');
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
"name" => "user",
|
||||
@@ -145,4 +99,31 @@ class GetFromLaravelAPITest extends BaseLaravelTest
|
||||
"type" => "integer",
|
||||
], $results['user']);
|
||||
}
|
||||
|
||||
protected function endpointForRoute($path, $controller, $method): ExtractedEndpointData
|
||||
{
|
||||
return $this->endpoint(function (ExtractedEndpointData $e) use ($path, $method, $controller) {
|
||||
$e->method = new \ReflectionMethod($controller, $method);
|
||||
$e->route = app(Router::class)->addRoute(['GET'], $path, ['uses' => [$controller, $method]]);
|
||||
$e->uri = $e->route->uri;
|
||||
});
|
||||
}
|
||||
|
||||
protected function endpoint(Closure $configure): ExtractedEndpointData
|
||||
{
|
||||
$endpoint = new class extends ExtractedEndpointData {
|
||||
public function __construct(array $parameters = [])
|
||||
{
|
||||
}
|
||||
};
|
||||
$configure($endpoint);
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
protected function fetch($endpoint): array
|
||||
{
|
||||
$strategy = new GetFromLaravelAPI(new DocumentationConfig([]));
|
||||
return $strategy($endpoint, []);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
152
tests/Strategies/UrlParameters/GetFromUrlParamAttributeTest.php
Normal file
152
tests/Strategies/UrlParameters/GetFromUrlParamAttributeTest.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace Knuckles\Scribe\Tests\Strategies\UrlParameters;
|
||||
|
||||
use Closure;
|
||||
use Knuckles\Camel\Extraction\ExtractedEndpointData;
|
||||
use Knuckles\Scribe\Attributes\UrlParam;
|
||||
use Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromUrlParamAttribute;
|
||||
use Knuckles\Scribe\Tools\DocumentationConfig;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
|
||||
class GetFromUrlParamAttributeTest extends TestCase
|
||||
{
|
||||
use ArraySubsetAsserts;
|
||||
|
||||
/** @test */
|
||||
public function can_fetch_from_urlparam_attribute()
|
||||
{
|
||||
$endpoint = $this->endpoint(function (ExtractedEndpointData $e) {
|
||||
$e->controller = new ReflectionClass(UrlParamAttributeTestController::class);
|
||||
$e->method = $e->controller->getMethod('methodWithAttributes');
|
||||
});
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'id' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'The id of the order.',
|
||||
],
|
||||
'lang' => [
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
'description' => 'Will be inherited.',
|
||||
],
|
||||
'withType' => [
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'description' => 'With type, maybe.',
|
||||
],
|
||||
'withTypeDefinitely' => [
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'description' => 'With type.',
|
||||
],
|
||||
'barebones' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => '',
|
||||
],
|
||||
'barebonesType' => [
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'description' => '',
|
||||
],
|
||||
'barebonesOptional' => [
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
'description' => '',
|
||||
],
|
||||
'withExampleOnly' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => '',
|
||||
'example' => '12',
|
||||
],
|
||||
'withExampleOnlyButTyped' => [
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'description' => '',
|
||||
'example' => 12
|
||||
],
|
||||
'noExampleNoDescription' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => '',
|
||||
'example' => null
|
||||
],
|
||||
'noExample' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => 'Something',
|
||||
'example' => null
|
||||
],
|
||||
], $results);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function can_fetch_from_urlparam_attribute_on_closure()
|
||||
{
|
||||
$endpoint = $this->endpoint(function (ExtractedEndpointData $e) {
|
||||
$e->controller = null;
|
||||
$e->method = new ReflectionFunction('\Knuckles\Scribe\Tests\Strategies\UrlParameters\functionWithAttributes');
|
||||
});
|
||||
$results = $this->fetch($endpoint);
|
||||
|
||||
$this->assertArraySubset([
|
||||
'a_parameter' => [
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
'description' => 'Described',
|
||||
'example' => 'en',
|
||||
],
|
||||
], $results);
|
||||
}
|
||||
|
||||
protected function fetch($endpoint): array
|
||||
{
|
||||
$strategy = new GetFromUrlParamAttribute(new DocumentationConfig([]));
|
||||
return $strategy($endpoint, []);
|
||||
}
|
||||
|
||||
protected function endpoint(Closure $configure): ExtractedEndpointData
|
||||
{
|
||||
$endpoint = new class extends ExtractedEndpointData {
|
||||
public function __construct(array $parameters = []) {}
|
||||
};
|
||||
$configure($endpoint);
|
||||
return $endpoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[UrlParam("id", description: "Will be overriden.")]
|
||||
#[UrlParam("lang", description: "Will be inherited.", required: false)]
|
||||
class UrlParamAttributeTestController
|
||||
{
|
||||
#[UrlParam("id", description: "The id of the order.")]
|
||||
#[UrlParam("withType", required: false, type: "number", description: "With type, maybe.")]
|
||||
#[UrlParam("withTypeDefinitely", "integer", "With type.")]
|
||||
#[UrlParam("barebones")]
|
||||
#[UrlParam("barebonesType", type: "number")]
|
||||
#[UrlParam("barebonesOptional", required: false)]
|
||||
#[UrlParam("withExampleOnly", example: "12")]
|
||||
#[UrlParam("withExampleOnlyButTyped", type: "int", example: 12)]
|
||||
#[UrlParam("noExampleNoDescription", example: "No-example.")]
|
||||
#[UrlParam("noExample", description: "Something", example: "No-example")]
|
||||
public function methodWithAttributes()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[UrlParam("a_parameter", required: false, type: "string", description: "Described", example: "en")]
|
||||
function functionWithAttributes()
|
||||
{
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user