diff --git a/README.md b/README.md
index 6eefa80..e5a8d3f 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
# JSON:API Resource for Laravel
-A lightweight JSON Resource for Laravel that helps you adhere to the JSON:API standards and also implements features such as sparse fieldsets and compound documents, whilst allowing you to extend the spec as needed for your project.
+A lightweight JSON Resource for Laravel that helps you adhere to the JSON:API standard and also implements features such as sparse fieldsets and compound documents.
-These docs are not designed to introduce you to the JSON:API spec and the associated concepts, instead you should [head over and read the spec](https:/jsonapi.org) if you are not familiar with it.
+These docs are not designed to introduce you to the JSON:API spec and the associated concepts, instead you should [head over and read the spec](https:/jsonapi.org) if you are not familiar with it. The documentation that follows only contains information on _how_ to implement the specification via the package.
# Version support
@@ -96,6 +96,9 @@ class UserResource extends JsonApiResource
'posts' => fn () => PostResource::collection($this->posts),
'subscription' => fn () => SubscriptionResource::make($this->subscription),
'profileImage' => fn () => optional($this->profileImage, fn (ProfileImage $profileImage) => ProfileImageResource::make($profileImage)),
+ // if the relationship has been loaded and is null, can we not just return the resource still and have a nice default? That way you never have to handle any of this
+ // optional noise?
+ // also is there a usecase for returning a resource linkage right from here and not a full resource?
];
}
}
@@ -131,12 +134,15 @@ To provide links for a resource, you can implement the `toLinks(Request $request
```php
route('users.show', $this->resource),
+ Link::self(route('users.show', $this->resource)),
+ 'related' => 'https://example.com/related'
];
}
}
@@ -330,21 +336,16 @@ Relationships can be resolved deeply and also multiple relationship paths can be
## Credits
- [Tim MacDonald](https://github.com/timacdonald)
+- [Jess Archer](https://github.com/jessarcher) for co-creating our initial in-house version and the brainstorming
- [All Contributors](../../contributors)
And a special (vegi) thanks to [Caneco](https://twitter.com/caneco) for the logo ✨
# Coming soon...
-- [ ] Top level links, jsonapi, etc.
-- [ ] Test assertions?
-- [ ] decide how to handle top level keys for single and collections (static? should collections have to be extended to specify the values? or can there be static methods on the single resource for the collection?)
- [ ] Handle loading relations on a already in memory object with Spatie Query builder (PR)
-- [ ] Resource identifier links and meta as a new concept different to normal resource links and relationships.
-- [ ] Ability to send the resource identifier "id" and "type" for a belongsTo relationship, even if not included?
-- [ ] Helper to define links
- [ ] Investigate collection count support
-- [ ] Transducers for all the looping?
+- [ ] a contract that other classes can implement to support the JSON:API spec as relationships? Can we have it work at a top level as well? Would that even make sense? Maybe be providing a toResponse implementation?
# To document
@@ -356,3 +357,12 @@ And a special (vegi) thanks to [Caneco](https://twitter.com/caneco) for the logo
- [ ] caching id and type
- [ ] caching includes and fields
- [ ] how it clears itself on toResponse
+ - [ ] that the goal is to have a consistent output at all levels, hence the maximal dataset for empty values
+ - [ ] Link object and meta
+
+# Not yet supported
+- [ ] Top level links & meta - how would you modify this for a collection? Top level links need to merge with pagination links
+ - [ ] decide how to handle top level keys for single and collections (static? should collections have to be extended to specify the values? or can there be static methods on the single resource for the collection?)
+- [ ] returning a resource as `null` as the Laravel resource does not support this. Is possible to support locally, but it might be unexpected. Perhaps a PR to Laravel is best?
+- [ ] Responses that contain only resource identifiers (related)
+- [ ] `400` when requesting relationships that are not present.
diff --git a/phpstan.neon b/phpstan.neon
index 9a23cb9..d59c469 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,10 +1,10 @@
parameters:
- level: 8
+ level: max
paths:
- src
checkInternalClassCaseSensitivity: true
checkTooWideReturnTypesInProtectedAndPublicMethods: true
checkUninitializedProperties: true
- checkMissingIterableValueType: false
+ checkMissingIterableValueType: true
includes:
- vendor-bin/linting/vendor/nunomaduro/larastan/extension.neon
diff --git a/phpunit.xml b/phpunit.xml
index 0b0d021..3173aab 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,23 +1,25 @@
-
+ bootstrap="vendor/autoload.php"
+ colors="true"
+>
tests/Unit
-
-
+
+
tests/Feature
-
- src
+
+ src
+
-
-
-
+
+
diff --git a/src/Concerns/Attributes.php b/src/Concerns/Attributes.php
index 1406225..715d45d 100644
--- a/src/Concerns/Attributes.php
+++ b/src/Concerns/Attributes.php
@@ -34,7 +34,7 @@ public static function maximalAttributes(): void
private function requestedAttributes(Request $request): Collection
{
return Collection::make($this->toAttributes($request))
- ->only($this->fields($request))
+ ->only(Fields::getInstance()->parse($request, $this->toType($request), self::$minimalAttributes))
->map(
/**
* @param mixed $value
@@ -49,20 +49,4 @@ private function requestedAttributes(Request $request): Collection
fn ($value): bool => $value instanceof PotentiallyMissing && $value->isMissing()
);
}
-
- /**
- * @internal
- */
- private function fields(Request $request): ?array
- {
- $fields = Fields::getInstance()->parse($request, $this->toType($request));
-
- if ($fields !== null) {
- return $fields;
- }
-
- return self::$minimalAttributes
- ? []
- : null;
- }
}
diff --git a/src/Concerns/Caching.php b/src/Concerns/Caching.php
new file mode 100644
index 0000000..f4ee857
--- /dev/null
+++ b/src/Concerns/Caching.php
@@ -0,0 +1,87 @@
+idCache = null;
+
+ $this->typeCache = null;
+
+ if ($this->requestedRelationshipsCache !== null) {
+ $this->requestedRelationshipsCache->each(
+ /**
+ * @param JsonApiResource|JsonApiResourceCollection|UnknownRelationship $resource
+ */
+ fn ($resource) => $resource->flush()
+ );
+ }
+
+ $this->requestedRelationshipsCache = null;
+ }
+
+ /**
+ * @internal
+ * @infection-ignore-all
+ */
+ private function rememberId(Closure $closure): string
+ {
+ return $this->idCache ??= $closure();
+ }
+
+
+ /**
+ * @internal
+ * @infection-ignore-all
+ */
+ private function rememberType(Closure $closure): string
+ {
+ return $this->typeCache ??= $closure();
+ }
+
+ /**
+ * @internal
+ * @infection-ignore-all
+ */
+ private function rememberRequestRelationships(Closure $closure): Collection
+ {
+ return $this->requestedRelationshipsCache ??= $closure();
+ }
+
+ /**
+ * @internal
+ */
+ public function requestedRelationshipsCache(): ?Collection
+ {
+ return $this->requestedRelationshipsCache;
+ }
+}
diff --git a/src/Concerns/Identification.php b/src/Concerns/Identification.php
index bea03e7..20b4f58 100644
--- a/src/Concerns/Identification.php
+++ b/src/Concerns/Identification.php
@@ -25,16 +25,6 @@ trait Identification
*/
private static ?Closure $typeResolver;
- /**
- * @internal
- */
- private ?string $idCache = null;
-
- /**
- * @internal
- */
- private ?string $typeCache = null;
-
/**
* @internal
*/
@@ -51,25 +41,12 @@ public static function resolveTypeNormally(): void
self::$typeResolver = null;
}
- /**
- * @internal
- */
- public function toResourceIdentifier(Request $request): array
- {
- return [
- 'data' => [
- 'id' => $this->resolveId($request),
- 'type' => $this->resolveType($request),
- ],
- ];
- }
-
/**
* @internal
*/
public function toUniqueResourceIdentifier(Request $request): string
{
- return "type:{$this->resolveType($request)} id:{$this->resolveId($request)}";
+ return "type:{$this->resolveType($request)};id:{$this->resolveId($request)};";
}
/**
@@ -88,24 +65,6 @@ private function resolveType(Request $request): string
return $this->rememberType(fn (): string => $this->toType($request));
}
- /**
- * @internal
- * @infection-ignore-all
- */
- private function rememberType(Closure $closure): string
- {
- return $this->typeCache ??= $closure();
- }
-
- /**
- * @internal
- * @infection-ignore-all
- */
- private function rememberId(Closure $closure): string
- {
- return $this->idCache ??= $closure();
- }
-
/**
* @internal
*/
@@ -116,6 +75,10 @@ private static function idResolver(): Closure
throw ResourceIdentificationException::attemptingToDetermineIdFor($resource);
}
+ /**
+ * @see https://github.com/timacdonald/json-api#customising-the-resource-id
+ * @phpstan-ignore-next-line
+ */
return (string) $resource->getKey();
};
}
diff --git a/src/Concerns/Implementation.php b/src/Concerns/Implementation.php
new file mode 100644
index 0000000..ab332e3
--- /dev/null
+++ b/src/Concerns/Implementation.php
@@ -0,0 +1,35 @@
+ new ServerImplementation('1.0');
+ }
+}
diff --git a/src/Concerns/Links.php b/src/Concerns/Links.php
new file mode 100644
index 0000000..a6aed76
--- /dev/null
+++ b/src/Concerns/Links.php
@@ -0,0 +1,34 @@
+
+ */
+ private function resolveLinks(Request $request): array
+ {
+ return Collection::make($this->toLinks($request))
+ ->mapWithKeys(
+ /**
+ * @param string|Link $value
+ * @param string|int $key
+ */
+ fn ($value, $key): array => $value instanceof Link
+ ? [$value->key() => $value]
+ : [$key => new Link($value)]
+ )
+ ->all();
+ }
+}
diff --git a/src/Concerns/Relationships.php b/src/Concerns/Relationships.php
index 8dd493a..1b75abc 100644
--- a/src/Concerns/Relationships.php
+++ b/src/Concerns/Relationships.php
@@ -11,7 +11,7 @@
use TiMacDonald\JsonApi\JsonApiResource;
use TiMacDonald\JsonApi\JsonApiResourceCollection;
use TiMacDonald\JsonApi\Support\Includes;
-use TiMacDonald\JsonApi\UnknownRelationship;
+use TiMacDonald\JsonApi\Support\UnknownRelationship;
/**
* @internal
@@ -23,19 +23,12 @@ trait Relationships
*/
private string $includePrefix = '';
- /**
- * @internal
- */
- private ?Collection $requestedRelationshipsCache = null;
-
/**
* @internal
*/
public function withIncludePrefix(string $prefix): self
{
- $this->includePrefix = "{$this->includePrefix}{$prefix}.";
-
- return $this;
+ return tap($this, fn (JsonApiResource $resource): string => $resource->includePrefix = "{$this->includePrefix}{$prefix}.");
}
/**
@@ -87,7 +80,7 @@ private function requestedRelationshipsAsIdentifiers(Request $request): Collecti
* @param JsonApiResource|JsonApiResourceCollection|UnknownRelationship $resource
* @return mixed
*/
- fn ($resource) => $resource->toResourceIdentifier($request)
+ fn ($resource) => $resource->toResourceLink($request)
);
}
@@ -102,15 +95,11 @@ private function requestedRelationships(Request $request): Collection
/**
* @return JsonApiResource|JsonApiResourceCollection|UnknownRelationship
*/
- function (Closure $value, string $key) use ($request) {
+ function (Closure $value, string $prefix) {
$resource = $value();
- if ($resource instanceof JsonApiResource) {
- return $resource->withIncludePrefix($key);
- }
-
- if ($resource instanceof JsonApiResourceCollection) {
- return $resource->filterDuplicates($request)->withIncludePrefix($key);
+ if ($resource instanceof JsonApiResource || $resource instanceof JsonApiResourceCollection) {
+ return $resource->withIncludePrefix($prefix);
}
return new UnknownRelationship($resource);
@@ -119,51 +108,10 @@ function (Closure $value, string $key) use ($request) {
/**
* @param JsonApiResource|JsonApiResourceCollection|UnknownRelationship $resource
*/
- fn ($resource) => $resource instanceof PotentiallyMissing && $resource->isMissing()
+ fn ($resource): bool => $resource instanceof PotentiallyMissing && $resource->isMissing()
));
}
- /**
- * @internal
- * @infection-ignore-all
- */
- public function flush(): void
- {
- if ($this->requestedRelationshipsCache !== null) {
- $this->requestedRelationshipsCache->each(
- /**
- * @param JsonApiResource|JsonApiResourceCollection|UnknownRelationship $resource
- */
- function ($resource): void {
- $resource->flush();
- }
- );
- }
-
- $this->requestedRelationshipsCache = null;
-
- $this->idCache = null;
-
- $this->typeCache = null;
- }
-
- /**
- * @internal
- * @infection-ignore-all
- */
- private function rememberRequestRelationships(Closure $closure): Collection
- {
- return $this->requestedRelationshipsCache ??= $closure();
- }
-
- /**
- * @internal
- */
- public function requestedRelationshipsCache(): ?Collection
- {
- return $this->requestedRelationshipsCache;
- }
-
/**
* @internal
*/
diff --git a/src/Contracts/Flushable.php b/src/Contracts/Flushable.php
new file mode 100644
index 0000000..64bfce1
--- /dev/null
+++ b/src/Contracts/Flushable.php
@@ -0,0 +1,10 @@
+
+ */
protected function toAttributes(Request $request): array
{
return [
+ // 'name' => $this->name,
//
+ // or with lazy evaluation...
+ //
+ // 'address' => fn () => new Address($this->address),
];
}
+ /**
+ * @see https://github.com/timacdonald/json-api#resource-relationships
+ * @see https://jsonapi.org/format/#document-resource-object-relationships
+ * @return array
+ */
protected function toRelationships(Request $request): array
{
return [
- //
+ // 'posts' => fn () => PostResource::collection($this->posts),
+ // 'avatar' => fn () => AvatarResource::make($this->avatar),
];
}
+ /**
+ * @see https://github.com/timacdonald/json-api#resource-links
+ * @see https://jsonapi.org/format/#document-resource-object-links
+ * @return array
+ */
protected function toLinks(Request $request): array
{
return [
- //
+ // Link::self(route('users.show'), $this->resource),
+ // Link::related(/** ... */),
+ // 'whatever' => 'Something'
+ // 'whateverElse' => new Link('whatever')
];
}
+ /**
+ * @see https://github.com/timacdonald/json-api#resource-meta
+ * @see https://jsonapi.org/format/#document-meta
+ * @return array
+ */
protected function toMeta(Request $request): array
{
return [
- //
+ // 'resourceDeprecated' => false,
];
}
+ /**
+ * @see https://github.com/timacdonald/json-api#customising-the-resource-id
+ * @see https://jsonapi.org/format/#document-resource-object-identification
+ */
protected function toId(Request $request): string
{
return self::idResolver()($this->resource, $request);
}
+ /**
+ * @see https://github.com/timacdonald/json-api#customising-the-resource-type
+ * @see https://jsonapi.org/format/#document-resource-object-identification
+ */
protected function toType(Request $request): string
{
return self::typeResolver()($this->resource, $request);
}
+ /**
+ * TODO: @see docs-link
+ * @see https://jsonapi.org/format/#document-resource-object-linkage
+ */
+ public function toResourceLink(Request $request): RelationshipLink
+ {
+ return new RelationshipLink(
+ new ResourceIdentifier($this->resolveId($request), $this->resolveType($request))
+ );
+ }
+
/**
* @param Request $request
+ * @return array{id: string, type: string, attributes: stdClass, relationships: stdClass, meta: stdClass, links: stdClass}
*/
public function toArray($request): array
{
@@ -81,22 +155,26 @@ public function toArray($request): array
'attributes' => (object) $this->requestedAttributes($request)->all(),
'relationships' => (object) $this->requestedRelationshipsAsIdentifiers($request)->all(),
'meta' => (object) $this->toMeta($request),
- 'links' => (object) $this->toLinks($request),
+ 'links' => (object) $this->resolveLinks($request),
];
}
/**
* @param Request $request
+ * @return array{included: Collection, jsonapi: JsonApiServerImplementation}
*/
public function with($request): array
{
return [
- 'included' => $this->included($request),
+ 'included' => $this->included($request)
+ ->uniqueStrict(fn (JsonApiResource $resource): string => $resource->toUniqueResourceIdentifier($request)),
+ 'jsonapi' => self::serverImplementationResolver()($request),
];
}
/**
* @param mixed $resource
+ * @return JsonApiResourceCollection
*/
public static function collection($resource): JsonApiResourceCollection
{
diff --git a/src/JsonApiResourceCollection.php b/src/JsonApiResourceCollection.php
index 9cf6bd5..71d6b5c 100644
--- a/src/JsonApiResourceCollection.php
+++ b/src/JsonApiResourceCollection.php
@@ -7,12 +7,14 @@
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Support\Collection;
+use TiMacDonald\JsonApi\Contracts\Flushable;
use TiMacDonald\JsonApi\Support\Cache;
-class JsonApiResourceCollection extends AnonymousResourceCollection
+class JsonApiResourceCollection extends AnonymousResourceCollection implements Flushable
{
/**
* @param Request $request
+ * @return array{included: Collection, jsonapi: JsonApiResource}
*/
public function with($request): array
{
@@ -20,8 +22,8 @@ public function with($request): array
'included' => $this->collection
->map(fn (JsonApiResource $resource): Collection => $resource->included($request))
->flatten()
- ->reject(fn (?JsonApiResource $resource): bool => $resource === null)
->uniqueStrict(fn (JsonApiResource $resource): string => $resource->toUniqueResourceIdentifier($request)),
+ 'jsonapi' => JsonApiResource::serverImplementationResolver()($request),
];
}
@@ -35,14 +37,14 @@ public function toResponse($request)
/**
* @internal
- * @return static
+ * @return JsonApiResourceCollection
*/
- public function withIncludePrefix(string $prefix)
+ public function withIncludePrefix(string $prefix): self
{
- /** @phpstan-ignore-next-line */
- $this->collection->each(fn (JsonApiResource $resource): JsonApiResource => $resource->withIncludePrefix($prefix));
-
- return $this;
+ return tap($this, function (JsonApiResourceCollection $resource) use ($prefix): void {
+ /** @phpstan-ignore-next-line */
+ $resource->collection->each(fn (JsonApiResource $resource): JsonApiResource => $resource->withIncludePrefix($prefix));
+ });
}
/**
@@ -56,9 +58,9 @@ public function included(Request $request): Collection
/**
* @internal
*/
- public function toResourceIdentifier(Request $request): array
+ public function toResourceLink(Request $request): Collection
{
- return $this->collection->map(fn (JsonApiResource $resource): array => $resource->toResourceIdentifier($request))->all();
+ return $this->collection->map(fn (JsonApiResource $resource): RelationshipLink => $resource->toResourceLink($request));
}
/**
@@ -69,17 +71,6 @@ public function includable(): Collection
return $this->collection;
}
- /**
- * @internal
- * @return static
- */
- public function filterDuplicates(Request $request)
- {
- $this->collection = $this->collection->uniqueStrict(fn (JsonApiResource $resource): string => $resource->toUniqueResourceIdentifier($request));
-
- return $this;
- }
-
/**
* @internal
* @infection-ignore-all
diff --git a/src/JsonApiServerImplementation.php b/src/JsonApiServerImplementation.php
new file mode 100644
index 0000000..7d90b3d
--- /dev/null
+++ b/src/JsonApiServerImplementation.php
@@ -0,0 +1,39 @@
+
+ */
+ private array $meta;
+
+ /**
+ * @param array $meta
+ */
+ public function __construct(string $version, array $meta = [])
+ {
+ $this->version = $version;
+
+ $this->meta = $meta;
+ }
+
+ /**
+ * @return array{version: string, meta: stdClass}
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'version' => $this->version,
+ 'meta' => (object) $this->meta,
+ ];
+ }
+}
diff --git a/src/Link.php b/src/Link.php
new file mode 100644
index 0000000..807b65f
--- /dev/null
+++ b/src/Link.php
@@ -0,0 +1,69 @@
+
+ */
+ private array $meta;
+
+ private string $key = 'unknown';
+
+ /**
+ * @param array $meta
+ */
+ public static function self(string $href, array $meta = []): self
+ {
+ return tap(new self($href, $meta), fn (self $instance): string => $instance->key = 'self');
+ }
+
+ /**
+ * @param array $meta
+ */
+ public static function related(string $href, array $meta = []): self
+ {
+ return tap(new self($href, $meta), fn (self $instance): string => $instance->key = 'related');
+ }
+
+ /**
+ * @internal
+ * @param array $meta
+ */
+ public function __construct(string $href, array $meta = [])
+ {
+ $this->href = $href;
+
+ $this->meta = $meta;
+ }
+
+ /**
+ * @return array{href: string, meta: stdClass}
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'href' => $this->href,
+ 'meta' => (object) $this->meta,
+ ];
+ }
+
+ /**
+ * @internal
+ */
+ public function key(): string
+ {
+ return $this->key;
+ }
+}
diff --git a/src/RelationshipLink.php b/src/RelationshipLink.php
new file mode 100644
index 0000000..435d802
--- /dev/null
+++ b/src/RelationshipLink.php
@@ -0,0 +1,48 @@
+
+ */
+ private array $links;
+
+ /**
+ * @var array
+ */
+ private array $meta;
+
+ /**
+ * @param array $links
+ * @param array $meta
+ */
+ public function __construct(ResourceIdentifier $data, array $links = [], array $meta = [])
+ {
+ $this->data = $data;
+
+ $this->links = $links;
+
+ $this->meta = $meta;
+ }
+
+ /**
+ * @return array{data: ResourceIdentifier, meta: stdClass, links: stdClass}
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'data' => $this->data,
+ 'meta' => (object) $this->meta,
+ 'links' => (object) $this->links,
+ ];
+ }
+}
diff --git a/src/ResourceIdentifier.php b/src/ResourceIdentifier.php
new file mode 100644
index 0000000..73edc0f
--- /dev/null
+++ b/src/ResourceIdentifier.php
@@ -0,0 +1,44 @@
+
+ */
+ private array $meta;
+
+ /**
+ * @param array $meta
+ */
+ public function __construct(string $id, string $type, array $meta = [])
+ {
+ $this->id = $id;
+
+ $this->type = $type;
+
+ $this->meta = $meta;
+ }
+
+ /**
+ * @return array{id: string, type: string, meta: stdClass}
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'id' => $this->id,
+ 'type' => $this->type,
+ 'meta' => (object) $this->meta,
+ ];
+ }
+}
diff --git a/src/Support/Cache.php b/src/Support/Cache.php
index 332e2b3..d3f427c 100644
--- a/src/Support/Cache.php
+++ b/src/Support/Cache.php
@@ -4,16 +4,15 @@
namespace TiMacDonald\JsonApi\Support;
-use TiMacDonald\JsonApi\JsonApiResource;
-use TiMacDonald\JsonApi\JsonApiResourceCollection;
+use TiMacDonald\JsonApi\Contracts\Flushable;
/**
* @internal
*/
-class Cache
+final class Cache
{
/**
- * @param JsonApiResource|JsonApiResourceCollection $resource
+ * @param Flushable $resource
*/
public static function flush($resource): void
{
diff --git a/src/Support/Fields.php b/src/Support/Fields.php
index ffcd159..ab63364 100644
--- a/src/Support/Fields.php
+++ b/src/Support/Fields.php
@@ -6,6 +6,8 @@
use Closure;
use Illuminate\Http\Request;
+use TiMacDonald\JsonApi\Contracts\Flushable;
+
use function array_key_exists;
use function explode;
use function is_string;
@@ -13,10 +15,13 @@
/**
* @internal
*/
-class Fields
+final class Fields implements Flushable
{
private static ?Fields $instance;
+ /**
+ * @var array|null>
+ */
private array $cache = [];
private function __construct()
@@ -29,17 +34,20 @@ public static function getInstance(): self
return self::$instance ??= new self();
}
- public function parse(Request $request, string $resourceType): ?array
+ /**
+ * @return array
+ */
+ public function parse(Request $request, string $resourceType, bool $minimalAttributes): ?array
{
- return $this->rememberResourceType($resourceType, function () use ($request, $resourceType): ?array {
+ return $this->rememberResourceType("type:{$resourceType};minimal:{$minimalAttributes};", function () use ($request, $resourceType, $minimalAttributes): ?array {
$typeFields = $request->query('fields') ?? [];
- if (is_string($typeFields)) {
- abort(400, 'The fields parameter must be an array of resource types.');
- }
+ abort_if(is_string($typeFields), 400, 'The fields parameter must be an array of resource types.');
if (! array_key_exists($resourceType, $typeFields)) {
- return null;
+ return $minimalAttributes
+ ? []
+ : null;
}
$fields = $typeFields[$resourceType];
@@ -48,9 +56,7 @@ public function parse(Request $request, string $resourceType): ?array
return [];
}
- if (! is_string($fields)) {
- abort(400, 'The fields parameter value must be a comma seperated list of attributes.');
- }
+ abort_if(! is_string($fields), 400, 'The fields parameter value must be a comma seperated list of attributes.');
return array_filter(explode(',', $fields), fn (string $value): bool => $value !== '');
});
@@ -58,6 +64,7 @@ public function parse(Request $request, string $resourceType): ?array
/**
* @infection-ignore-all
+ * @return array
*/
private function rememberResourceType(string $resourceType, Closure $callback): ?array
{
@@ -69,6 +76,9 @@ public function flush(): void
$this->cache = [];
}
+ /**
+ * @return array|null>
+ */
public function cache(): array
{
return $this->cache;
diff --git a/src/Support/Includes.php b/src/Support/Includes.php
index 331133c..95b4a0a 100644
--- a/src/Support/Includes.php
+++ b/src/Support/Includes.php
@@ -8,6 +8,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
+use TiMacDonald\JsonApi\Contracts\Flushable;
use function explode;
use function is_array;
@@ -15,10 +16,13 @@
/**
* @internal
*/
-class Includes
+final class Includes implements Flushable
{
private static ?Includes $instance;
+ /**
+ * @var array
+ */
private array $cache = [];
private function __construct()
@@ -36,15 +40,15 @@ public function parse(Request $request, string $prefix): Collection
return $this->rememberIncludes($prefix, function () use ($request, $prefix): Collection {
$includes = $request->query('include') ?? '';
- if (is_array($includes)) {
- abort(400, 'The include parameter must be a comma seperated list of relationship paths.');
- }
+ abort_if(is_array($includes), 400, 'The include parameter must be a comma seperated list of relationship paths.');
- return Collection::make(explode(',', $includes))
+ /** @var Collection */
+ $includes = Collection::make(explode(',', $includes))
->when($prefix !== '', function (Collection $includes) use ($prefix): Collection {
return $includes->filter(fn (string $include): bool => Str::startsWith($include, $prefix));
- })
- ->map(fn (string $include): string => Str::before(Str::after($include, $prefix), '.'))
+ });
+
+ return $includes->map(fn ($include): string => Str::before(Str::after($include, $prefix), '.'))
->uniqueStrict()
->filter(fn (string $include): bool => $include !== '');
});
@@ -63,6 +67,9 @@ public function flush(): void
$this->cache = [];
}
+ /**
+ * @return array
+ */
public function cache(): array
{
return $this->cache;
diff --git a/src/UnknownRelationship.php b/src/Support/UnknownRelationship.php
similarity index 78%
rename from src/UnknownRelationship.php
rename to src/Support/UnknownRelationship.php
index ed83113..7114e30 100644
--- a/src/UnknownRelationship.php
+++ b/src/Support/UnknownRelationship.php
@@ -2,15 +2,17 @@
declare(strict_types=1);
-namespace TiMacDonald\JsonApi;
+namespace TiMacDonald\JsonApi\Support;
+use Illuminate\Http\Request;
use Illuminate\Http\Resources\PotentiallyMissing;
use Illuminate\Support\Collection;
+use TiMacDonald\JsonApi\Contracts\Flushable;
/**
* @internal
*/
-class UnknownRelationship implements PotentiallyMissing
+final class UnknownRelationship implements PotentiallyMissing, Flushable
{
/**
* @var mixed
@@ -28,7 +30,7 @@ public function __construct($resource)
/**
* @return mixed
*/
- public function toResourceIdentifier()
+ public function toResourceLink(Request $request)
{
return $this->resource;
}
diff --git a/tests/Performance/FieldsTest.php b/tests/Performance/FieldsTest.php
index e6b90b1..8de9667 100644
--- a/tests/Performance/FieldsTest.php
+++ b/tests/Performance/FieldsTest.php
@@ -34,7 +34,7 @@
$start = microtime(true);
foreach ($resources as $resource) {
- Fields::getInstance()->parse($request, $resource);
+ Fields::getInstance()->parse($request, $resource, true);
}
$end = microtime(true);
diff --git a/tests/Unit/AttributesTest.php b/tests/Unit/AttributesTest.php
index 2cecab4..fb71067 100644
--- a/tests/Unit/AttributesTest.php
+++ b/tests/Unit/AttributesTest.php
@@ -46,6 +46,10 @@ protected function toAttributes(Request $request): array
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -84,6 +88,10 @@ protected function toAttributes(Request $request): array
'links' => [],
'meta' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -119,6 +127,10 @@ protected function toAttributes(Request $request): array
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -152,6 +164,10 @@ protected function toAttributes(Request $request): array
'links' => [],
'meta' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -184,6 +200,10 @@ protected function toAttributes(Request $request): array
'links' => [],
'meta' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -236,6 +256,10 @@ public function testItCanSpecifyMinimalAttributes(): void
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
@@ -275,19 +299,29 @@ public function testItCanUseSparseFieldsetsWithIncludedCollections(): void
'data' => [
'id' => 'post-id-1',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
[
'data' => [
'id' => 'post-id-2',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'post-id-1',
@@ -346,6 +380,10 @@ protected function toAttributes(Request $request): array
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -385,6 +423,10 @@ protected function toAttributes(Request $request): array
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
diff --git a/tests/Unit/FieldsTest.php b/tests/Unit/FieldsTest.php
index 82bb643..f3a66a7 100644
--- a/tests/Unit/FieldsTest.php
+++ b/tests/Unit/FieldsTest.php
@@ -23,8 +23,8 @@ public function testItHandlesMultipleRequests(): void
];
$fields = [
- Fields::getInstance()->parse($requests[0], 'a'),
- Fields::getInstance()->parse($requests[1], 'b'),
+ Fields::getInstance()->parse($requests[0], 'a', true),
+ Fields::getInstance()->parse($requests[1], 'b', true),
];
$this->assertSame(['a'], $fields[0]);
@@ -35,6 +35,13 @@ public function testItHandlesEmptyValues(): void
{
$request = Request::create('https://example.com?fields[a]=');
- $this->assertSame([], Fields::getInstance()->parse($request, 'a'));
+ $this->assertSame([], Fields::getInstance()->parse($request, 'a', true));
+ }
+
+ public function testItProvidesMinimalAttributesWhenNoFieldsAreSpecified(): void
+ {
+ $request = Request::create('https://example.com');
+
+ $this->assertSame([], Fields::getInstance()->parse($request, 'a', true));
}
}
diff --git a/tests/Unit/JsonApiTest.php b/tests/Unit/JsonApiTest.php
index c920a35..2334cb3 100644
--- a/tests/Unit/JsonApiTest.php
+++ b/tests/Unit/JsonApiTest.php
@@ -11,6 +11,10 @@
use Tests\Resources\UserResource;
use Tests\TestCase;
use TiMacDonald\JsonApi\JsonApiResource;
+use TiMacDonald\JsonApi\JsonApiServerImplementation;
+use TiMacDonald\JsonApi\Link;
+use TiMacDonald\JsonApi\RelationshipLink;
+use TiMacDonald\JsonApi\ResourceIdentifier;
use TiMacDonald\JsonApi\Support\Fields;
use TiMacDonald\JsonApi\Support\Includes;
use function get_class;
@@ -40,6 +44,10 @@ public function testItCanReturnASingleResource(): void
'links' => [],
],
'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
]);
}
@@ -84,6 +92,10 @@ public function testItCanReturnACollection(): void
],
],
'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
]);
}
@@ -122,10 +134,14 @@ protected function toMeta(Request $request): array
'links' => [],
],
'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
]);
}
- public function testItAddsLinksToIndividualResources(): void
+ public function testItAddsArbitraryLinksToIndividualResources(): void
{
Route::get('test-route', fn () => new class ((new BasicModel(['id' => 'expected-id']))) extends JsonApiResource {
protected function toLinks(Request $request): array
@@ -147,10 +163,63 @@ protected function toLinks(Request $request): array
'relationships' => [],
'meta' => [],
'links' => [
- 'links-key' => 'links-value',
+ 'links-key' => [
+ 'href' => 'links-value',
+ 'meta' => [],
+ ],
],
],
'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
+ ]);
+ }
+
+ public function testItHandlesSelfAndRelatedLinks(): void
+ {
+ Route::get('test-route', fn () => new class ((new BasicModel(['id' => 'expected-id']))) extends JsonApiResource {
+ protected function toLinks(Request $request): array
+ {
+ return [
+ Link::self('https://example.test/self'),
+ Link::related('https://example.test/related'),
+ 'home' => 'https://example.test',
+ ];
+ }
+ });
+
+ $response = $this->getJson('test-route');
+
+ $response->assertOk();
+ $response->assertExactJson([
+ 'data' => [
+ 'id' => 'expected-id',
+ 'type' => 'basicModels',
+ 'attributes' => [],
+ 'relationships' => [],
+ 'meta' => [],
+ 'links' => [
+ 'self' => [
+ 'href' => 'https://example.test/self',
+ 'meta' => [],
+ ],
+ 'related' => [
+ 'href' => 'https://example.test/related',
+ 'meta' => [],
+ ],
+ 'home' => [
+ 'href' => 'https://example.test',
+ 'meta' => [],
+ ],
+ ],
+ ],
+ 'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
]);
}
@@ -189,6 +258,10 @@ public function testItCanCustomiseTheTypeResolution(): void
'links' => [],
],
'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
]);
JsonApiResource::resolveTypeNormally();
@@ -211,6 +284,10 @@ public function testItCanCustomiseTheIdResolution(): void
'links' => [],
],
'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
]);
JsonApiResource::resolveIdNormally();
@@ -232,6 +309,10 @@ public function testItClearsTheHelperCachesAfterPreparingResponseForASingleResou
'links' => [],
],
'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
]);
$this->assertCount(0, Fields::getInstance()->cache());
$this->assertCount(0, Includes::getInstance()->cache());
@@ -255,8 +336,87 @@ public function testItClearsTheHelperCachesAfterPreparingResponseForACollectionO
],
],
'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
]);
$this->assertCount(0, Fields::getInstance()->cache());
$this->assertCount(0, Includes::getInstance()->cache());
}
+
+ public function testItCastsEmptyResourceIdentifierMetaToObject(): void
+ {
+ $relationship = new ResourceIdentifier('5', 'users');
+
+ $json = json_encode($relationship);
+
+ self::assertSame('{"id":"5","type":"users","meta":{}}', $json);
+ }
+
+ public function testItCastsEmptyLinksMetaToObject(): void
+ {
+ $link = Link::self('https://timacdonald.me', []);
+
+ $json = json_encode($link);
+
+ self::assertSame('{"href":"https:\/\/timacdonald.me","meta":{}}', $json);
+ }
+
+ public function testItCastsEmptyImplementationMetaToObject(): void
+ {
+ $implementation = new JsonApiServerImplementation('1.5', []);
+
+ $json = json_encode($implementation);
+
+ self::assertSame('{"version":"1.5","meta":{}}', $json);
+ }
+
+ public function testItCanSpecifyAnImplementation(): void
+ {
+ BasicJsonApiResource::resolveServerImplementationUsing(fn () => new JsonApiServerImplementation('1.4.3', [
+ 'secure' => true,
+ ]));
+ $user = new BasicModel([
+ 'id' => 'user-id',
+ 'name' => 'user-name',
+ ]);
+ Route::get('test-route', fn () => UserResource::make($user));
+
+ $response = $this->getJson('test-route');
+
+ $response->assertOk();
+ $response->assertExactJson([
+ 'data' => [
+ 'id' => 'user-id',
+ 'type' => 'basicModels',
+ 'attributes' => [
+ 'name' => 'user-name',
+ ],
+ 'relationships' => [],
+ 'meta' => [],
+ 'links' => [],
+ ],
+ 'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.4.3',
+ 'meta' => [
+ 'secure' => true,
+ ],
+ ],
+ ]);
+
+ BasicJsonApiResource::resolveServerImplementationNormally();
+ }
+
+ public function testItCastsEmptyRelationshipLinkMetaToJsonObject()
+ {
+ $resourceLink = new RelationshipLink(
+ new ResourceIdentifier('expected-id', 'expected-type')
+ );
+
+ $json = json_encode($resourceLink);
+
+ self::assertSame('{"data":{"id":"expected-id","type":"expected-type","meta":{}},"meta":{},"links":{}}', $json);
+ }
}
diff --git a/tests/Unit/RelationshipsTest.php b/tests/Unit/RelationshipsTest.php
index c68b46a..7b84b0e 100644
--- a/tests/Unit/RelationshipsTest.php
+++ b/tests/Unit/RelationshipsTest.php
@@ -64,6 +64,10 @@ protected function toRelationships(Request $request): array
'meta' => [],
],
'included' => [],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
]);
}
@@ -96,12 +100,19 @@ public function testItCanIncludeASingleToOneResourceForASingleResource(): void
'data' => [
'id' => 'author-id',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'links' => [],
'meta' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'author-id',
@@ -158,18 +169,28 @@ public function testItCanIncludeNestedToOneResourcesForASingleResource(): void
'data' => [
'id' => 'author-id',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
'featureImage' => [
'data' => [
'id' => 'feature-image-id',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'links' => [],
'meta' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'author-id',
@@ -182,13 +203,19 @@ public function testItCanIncludeNestedToOneResourcesForASingleResource(): void
'data' => [
'id' => 'avatar-id',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
'license' => [
'data' => [
'id' => 'license-id',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'links' => [],
@@ -266,12 +293,19 @@ public function toRelationships(Request $request): array
'data' => [
'id' => 'child-id-1',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'links' => [],
'meta' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'child-id-1',
@@ -282,7 +316,10 @@ public function toRelationships(Request $request): array
'data' => [
'id' => 'child-id-2',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'links' => [],
@@ -339,12 +376,19 @@ public function toRelationships(Request $request): array
'data' => [
'id' => 'child-id-1',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'child-id-1',
@@ -356,13 +400,19 @@ public function toRelationships(Request $request): array
'data' => [
'id' => 'child-id-2',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
[
'data' => [
'id' => 'child-id-3',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
@@ -428,7 +478,10 @@ public function testItCanIncludeToOneResourcesForACollectionOfResources(): void
'data' => [
'id' => 'author-id-1',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'meta' => [],
@@ -446,13 +499,20 @@ public function testItCanIncludeToOneResourcesForACollectionOfResources(): void
'data' => [
'id' => 'author-id-2',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'meta' => [],
'links' => [],
],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'author-id-1',
@@ -514,19 +574,29 @@ public function testItCanIncludeACollectionOfResourcesForASingleResource(): void
'data' => [
'id' => 'post-id-1',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
[
'data' => [
'id' => 'post-id-2',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'post-id-1',
@@ -634,13 +704,19 @@ public function testItCanIncludeAManyManyManyRelationship(): void
'data' => [
'id' => 'comment-id-1',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
[
'data' => [
'id' => 'comment-id-2',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
@@ -660,13 +736,19 @@ public function testItCanIncludeAManyManyManyRelationship(): void
'data' => [
'id' => 'comment-id-3',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
[
'data' => [
'id' => 'comment-id-4',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
@@ -674,6 +756,10 @@ public function testItCanIncludeAManyManyManyRelationship(): void
'links' => [],
],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'comment-id-1',
@@ -687,13 +773,19 @@ public function testItCanIncludeAManyManyManyRelationship(): void
'data' => [
'id' => 'like-id-1',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
[
'data' => [
'id' => 'like-id-2',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
@@ -712,13 +804,19 @@ public function testItCanIncludeAManyManyManyRelationship(): void
'data' => [
'id' => 'like-id-3',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
[
'data' => [
'id' => 'like-id-4',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
@@ -769,13 +867,19 @@ public function testItCanIncludeAManyManyManyRelationship(): void
'data' => [
'id' => 'like-id-5',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
[
'data' => [
'id' => 'like-id-6',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
@@ -794,13 +898,19 @@ public function testItCanIncludeAManyManyManyRelationship(): void
'data' => [
'id' => 'like-id-7',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
[
'data' => [
'id' => 'like-id-8',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
@@ -889,12 +999,19 @@ protected function toAttributes(Request $request): array
'data' => [
'id' => 'relation-id',
'type' => 'relation-type',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'relation-id',
@@ -946,7 +1063,10 @@ public function testItFiltersOutDuplicateIncludesForACollectionOfResources(): vo
'data' => [
'id' => 'avatar-id',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'meta' => [],
@@ -963,13 +1083,20 @@ public function testItFiltersOutDuplicateIncludesForACollectionOfResources(): vo
'data' => [
'id' => 'avatar-id',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'meta' => [],
'links' => [],
],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'avatar-id',
@@ -985,7 +1112,7 @@ public function testItFiltersOutDuplicateIncludesForACollectionOfResources(): vo
]);
}
- public function testItFiltersOutDuplicateIncludesForASingleResource(): void
+ public function testItFiltersOutDuplicateResourceObjectsIncludesForASingleResource(): void
{
$user = (new BasicModel([
'id' => 'user-id',
@@ -1020,13 +1147,29 @@ public function testItFiltersOutDuplicateIncludesForASingleResource(): void
'data' => [
'id' => 'post-id',
'type' => 'basicModels',
+ 'meta' => [],
+ ],
+ 'links' => [],
+ 'meta' => [],
+ ],
+ [
+ 'data' => [
+ 'id' => 'post-id',
+ 'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
],
'meta' => [],
'links' =>[],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => 'post-id',
@@ -1067,6 +1210,10 @@ public function testItHasIncludedArrayWhenIncludeParameterIsPresentForASingleRes
'meta' => [],
'links' =>[],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -1097,6 +1244,10 @@ public function testItHasIncludedArrayWhenIncludeParameterIsPresentForACollectio
'links' => [],
],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -1124,6 +1275,10 @@ public function testItCanReturnNullForEmptyToOneRelationships(): void
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -1151,6 +1306,10 @@ public function testItCanReturnAnEmptyArrayForEmptyToManyRelationships(): void
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -1209,12 +1368,19 @@ public function toRelationships(Request $request): array
'data' => [
'id' => '2',
'type' => 'basicModels',
+ 'meta' => [],
],
+ 'links' => [],
+ 'meta' => [],
],
],
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [
[
'id' => '2',
@@ -1262,6 +1428,10 @@ public function toRelationships(Request $request): array
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -1298,6 +1468,10 @@ public function toRelationships(Request $request): array
'meta' => [],
'links' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
diff --git a/tests/Unit/ResourceIdentificationTest.php b/tests/Unit/ResourceIdentificationTest.php
index 59429d3..c4c6722 100644
--- a/tests/Unit/ResourceIdentificationTest.php
+++ b/tests/Unit/ResourceIdentificationTest.php
@@ -33,6 +33,10 @@ public function testItResolvesTheIdAndTypeOfAModel(): void
'links' => [],
'meta' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}
@@ -58,6 +62,10 @@ public function testItCastsAModelsIntegerIdToAString(): void
'links' => [],
'meta' => [],
],
+ 'jsonapi' => [
+ 'version' => '1.0',
+ 'meta' => [],
+ ],
'included' => [],
]);
}