Skip to content

Commit

Permalink
Merge pull request #18 from swisnl/non-eloquent-resources
Browse files Browse the repository at this point in the history
Generate documentation for non eloquent resources without extra configuration
  • Loading branch information
Rocksheep authored Nov 12, 2024
2 parents 3aaafaf + 2cb846b commit 56005b5
Show file tree
Hide file tree
Showing 15 changed files with 413 additions and 37 deletions.
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"
}
}
69 changes: 35 additions & 34 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,20 @@ 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);
$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 ($field instanceof EloquentAttribute && $field->isReadOnly(null)) {
$schema = $schema->readOnly(true);
}
}

return $schema;
Expand All @@ -427,30 +438,30 @@ 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 {
$fieldId = $relation->name();

$type = $relation->inverse();

$linkSchema = $this->relationshipLinks($relation, $example, $type);
$linkSchema = $this->relationshipLinks($relation, $example);

$dataSchema = $this->relationshipData($relation, $example, $type);

Expand All @@ -469,16 +480,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 @@ -511,21 +522,11 @@ protected function relationshipData(
return $dataSchema;
}

/**
* @param mixed $relation
* @param JsonApiResource $example
* @param string $type
*
* @return OASchema
*/
public function relationshipLinks(
$relation,
RelationContract $relation,
JsonApiResource $example,
string $type,
): OASchema {
$name = Str::dasherize(
Str::plural($relation->relationName())
);
$name = Str::dasherize(Str::plural(Str::camel($relation->name())));

/*
* @todo Create real links
Expand Down Expand Up @@ -578,7 +579,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

0 comments on commit 56005b5

Please sign in to comment.