Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate documentation for non eloquent resources without extra configuration #18

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"ext-json": "*",
"friendsofphp/php-cs-fixer": "^3.14",
"laravel-json-api/laravel": "^2.0|^3.0|^4.0",
"laravel-json-api/non-eloquent": "^2.0|^3.0|^v4.0",
"nesbot/carbon": "^2.63|^3.0",
"orchestra/testbench": "^6.25|^7.21|^8.0|^9.0",
"phpunit/phpunit": "^9.5"
Expand Down
10 changes: 10 additions & 0 deletions sites.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"example": {
"domain": "johnsmith.com",
"name": "Johns site"
},
"foo": {
"domain": "foo.bar",
"name": "Foo Bar"
}
}
61 changes: 39 additions & 22 deletions src/Descriptors/Schema/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,26 @@
use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema as OASchema;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use LaravelJsonApi\Contracts\Schema\Attribute as AttributeContract;
use LaravelJsonApi\Contracts\Schema\Field;
use LaravelJsonApi\Contracts\Schema\Filter;
use LaravelJsonApi\Contracts\Schema\PolymorphicRelation;
use LaravelJsonApi\Contracts\Schema\Relation as RelationContract;
use LaravelJsonApi\Contracts\Schema\Schema as JASchema;
use LaravelJsonApi\Contracts\Schema\Sortable;
use LaravelJsonApi\Core\Resources\JsonApiResource;
use LaravelJsonApi\Core\Support\Str;
use LaravelJsonApi\Eloquent;
use LaravelJsonApi\Eloquent\Fields\ArrayHash;
use LaravelJsonApi\Eloquent\Fields\ArrayList;
use LaravelJsonApi\Eloquent\Fields\Attribute;
use LaravelJsonApi\Eloquent\Fields\Attribute as EloquentAttribute;
use LaravelJsonApi\Eloquent\Fields\Boolean;
use LaravelJsonApi\Eloquent\Fields\ID;
use LaravelJsonApi\Eloquent\Fields\Map;
use LaravelJsonApi\Eloquent\Fields\Number;
use LaravelJsonApi\Eloquent\Fields\Relations\Relation;
use LaravelJsonApi\Eloquent\Pagination\CursorPagination;
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
use LaravelJsonApi\NonEloquent\Fields\Attribute as NonEloquentAttribute;
use LaravelJsonApi\OpenApiSpec\Builders\Paths\Operation\SchemaBuilder;
use LaravelJsonApi\OpenApiSpec\Contracts\Descriptors\Schema\PaginationDescriptor;
use LaravelJsonApi\OpenApiSpec\Contracts\Descriptors\Schema\SortablesDescriptor;
Expand Down Expand Up @@ -324,7 +327,7 @@ public function pagination(Route $route): array
public function filters($route): array
{
return collect($route->schema()->filters())
->map(function (Eloquent\Contracts\Filter $filterInstance) use ($route
->map(function (Filter $filterInstance) use ($route
) {
$descriptor = $this->getDescriptor($filterInstance);

Expand All @@ -347,10 +350,10 @@ protected function fields(
return collect($fields)
->mapToGroups(function (Field $field) {
switch (true) {
case $field instanceof Attribute:
case $field instanceof AttributeContract:
$key = 'attributes';
break;
case $field instanceof Relation:
case $field instanceof RelationContract:
$key = 'relationships';
break;
default:
Expand Down Expand Up @@ -402,12 +405,24 @@ protected function attributes(

$schema = $fieldDataType->title($field->name());

$column = $field instanceof Attribute ? $field->column() : $field->name();
if (isset($example[$column])) {
$schema = $schema->example($example[$column]);
}
if ($field->isReadOnly(null)) {
$schema = $schema->readOnly(true);
try {
$column = $field instanceof EloquentAttribute ? $field->column() : $field->name();

if ($field instanceof NonEloquentAttribute) {
$attributes = $example->attributes(null);
if (isset($attributes[$column])) {
$schema = $schema->example($attributes[$column]);
}
} else {
if (isset($example[$column])) {
$schema = $schema->example($example[$column]);
}
if (method_exists($field, 'isReadOnly') && $field->isReadOnly(null)) {
$schema = $schema->readOnly(true);
}
}
} catch (\Throwable $e) {
throw $e;
}

return $schema;
Expand All @@ -427,22 +442,22 @@ protected function relationships(
JsonApiResource $example,
): array {
return $relationships
->map(function (Relation $relation) use ($example) {
->map(function (RelationContract $relation) use ($example) {
return $this->relationship($relation, $example);
})->toArray();
}

/**
* @param Relation $relation
* @param JsonApiResource $example
* @param bool $includeData
* @param RelationContract $relation
* @param JsonApiResource $example
* @param bool $includeData
*
* @throws \GoldSpecDigital\ObjectOrientedOAS\Exceptions\InvalidArgumentException
*
* @return OASchema
*/
protected function relationship(
Relation $relation,
RelationContract $relation,
JsonApiResource $example,
bool $includeData = false,
): OASchema {
Expand All @@ -469,16 +484,16 @@ protected function relationship(
}

/**
* @param Relation $relation
* @param JsonApiResource $example
* @param string $type
* @param RelationContract $relation
* @param JsonApiResource $example
* @param string $type
*
* @throws \GoldSpecDigital\ObjectOrientedOAS\Exceptions\InvalidArgumentException
*
* @return OASchema
*/
protected function relationshipData(
Relation $relation,
RelationContract $relation,
JsonApiResource $example,
string $type,
): OASchema {
Expand Down Expand Up @@ -524,7 +539,9 @@ public function relationshipLinks(
string $type,
): OASchema {
$name = Str::dasherize(
Str::plural($relation->relationName())
Str::plural(method_exists($relation, 'relationName')
? $relation->relationName()
: Str::camel($relation->name()))
);

/*
Expand Down Expand Up @@ -578,7 +595,7 @@ protected function links(Route $route, JsonApiResource $resource): array
/**
* @todo Get descriptors from Attributes
*/
protected function getDescriptor(Eloquent\Contracts\Filter $filter): string
protected function getDescriptor(Filter $filter): string
{
foreach ($this->filterDescriptors as $filterClass => $descriptor) {
if ($filter instanceof $filterClass) {
Expand Down
16 changes: 15 additions & 1 deletion src/ResourceContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use LaravelJsonApi\Contracts\Schema\Schema;
use LaravelJsonApi\Contracts\Server\Server;
use LaravelJsonApi\Contracts\Store\QueriesAll;
use LaravelJsonApi\Core\Resources\JsonApiResource;

class ResourceContainer
Expand All @@ -26,7 +27,7 @@ public function __construct(Server $server)
public function resource($model): JsonApiResource
{
$fqn = $this->getFQN($model);
if (!isset($this->resource[$fqn])) {
if (!isset($this->resources[$fqn])) {
$this->loadResources($fqn);
}

Expand Down Expand Up @@ -77,6 +78,19 @@ protected function getFQN($model): string
*/
protected function loadResources(string $model)
{
$schema = $this->server->schemas()->schemaForModel($model);
$repository = $schema->repository();

if ($repository instanceof QueriesAll) {
$this->resources[$model] = collect($repository->queryAll()->get())
->map(function ($model) {
return $this->server->resources()->create($model);
})
->take(3);

return;
}

if (method_exists($model, 'all')) {
$resources = $model::all()->map(function ($model) {
return $this->server->resources()->create($model);
Expand Down
6 changes: 6 additions & 0 deletions tests/Feature/OpenApiSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ public function testItCreatesAnEmptyDescriptionIfASchemaDoesNotImplementTheDescr
{
$this->assertEquals('', $this->spec['paths']['/videos']['get']['description']);
}

public function testItDescribesNonEloquentResources(): void
{
$this->assertEquals('Get all sites', $this->spec['paths']['/sites']['get']['summary']);
$this->assertEquals('object', $this->spec['components']['schemas']['resources.sites.resource.fetch']['type']);
}
}
13 changes: 13 additions & 0 deletions tests/Support/Controllers/Api/V1/SiteController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace LaravelJsonApi\OpenApiSpec\Tests\Support\Controllers\Api\V1;

use LaravelJsonApi\Laravel\Http\Controllers\Actions;
use LaravelJsonApi\OpenApiSpec\Tests\Support\Controllers\Controller;

class SiteController extends Controller
{
use Actions\FetchMany;
use Actions\FetchOne;
use Actions\Store;
}
67 changes: 67 additions & 0 deletions tests/Support/Entities/Site.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace LaravelJsonApi\OpenApiSpec\Tests\Support\Entities;

use Illuminate\Contracts\Support\Arrayable;

class Site implements Arrayable
{
private string $slug;

private ?string $domain;

private ?string $name;

public static function fromArray(string $slug, array $values): self
{
$site = new self($slug);
$site->setDomain($values['domain'] ?? null);
$site->setName($values['name'] ?? null);

return $site;
}

public function __construct(string $slug)
{
$this->slug = $slug;
}

public function getSlug(): string
{
return $this->slug;
}

public function getDomain(): ?string
{
return $this->domain;
}

public function setDomain(?string $domain): Site
{
$this->domain = $domain;

return $this;
}

public function getName(): ?string
{
return $this->name;
}

public function setName(?string $name): Site
{
$this->name = $name;

return $this;
}

public function toArray(): array
{
return [
$this->getDomain(),
$this->getName(),
];
}
}
63 changes: 63 additions & 0 deletions tests/Support/Entities/SiteStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace LaravelJsonApi\OpenApiSpec\Tests\Support\Entities;

use Illuminate\Filesystem\Filesystem;

class SiteStorage
{
protected Filesystem $files;

/**
* @var array<int, array<string, mixed>
*/
protected array $sites;

public function __construct(Filesystem $files)
{
$this->files = $files;
$this->sites = json_decode($files->get('sites.json'), true);
}

public function find(string $slug): ?Site
{
if (!isset($this->sites[$slug])) {
return null;
}

return Site::fromArray($slug, $this->sites[$slug]);
}

public function cursor(): \Generator
{
foreach ($this->sites as $slug => $values) {
yield $slug => Site::fromArray($slug, $values);
}
}

public function all(): array
{
return iterator_to_array($this->cursor());
}

public function store(Site $site): void
{
$this->sites[$site->getSlug()] = $site->toArray();

$this->write();
}

public function remove(Site $site): void
{
unset($this->sites[$site->getSlug()]);

$this->write();
}

public function write(): void
{
$this->files->put('sites.json', json_encode($this->sites));
}
}
1 change: 1 addition & 0 deletions tests/Support/JsonApi/V1/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ protected function allSchemas(): array
Tags\TagSchema::class,
Users\UserSchema::class,
Videos\VideoSchema::class,
Sites\SiteSchema::class,
];
}

Expand Down
Loading
Loading