diff --git a/README.md b/README.md index 792fe51..c715304 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ It has support for generating & sending documents with: - resource collections - to-one and to-many relationships - errors (easily turning exceptions into jsonapi output) -- v1.1 extensions via profiles +- v1.1 extensions and profiles - v1.1 @-members for JSON-LD and others Also there's tools to help processing of incoming requests: @@ -189,9 +189,10 @@ Also there's tools to help processing of incoming requests: - parse request options (include paths, sparse fieldsets, sort fields, pagination, filtering) - parse request documents for creating, updating and deleting resources and relationships -Next to custom extensions, the following [official extensions](https://jsonapi.org/extensions/) are included: +Next to custom extensions/profiles, the following [official extensions/profiles](https://jsonapi.org/extensions/) are included: -- Cursor Pagination ([example code](/examples/cursor_pagination_profile.php), [specification](https://jsonapi.org/profiles/ethanresnick/cursor-pagination/)) +- Atomic Operations extension ([example code](/examples/atomic_operations_extension.php), [specification](https://jsonapi.org/ext/atomic/)) +- Cursor Pagination profile ([example code](/examples/cursor_pagination_profile.php), [specification](https://jsonapi.org/profiles/ethanresnick/cursor-pagination/)) Plans for the future include: diff --git a/examples/atomic_operations_extension.php b/examples/atomic_operations_extension.php new file mode 100644 index 0000000..4e97c6d --- /dev/null +++ b/examples/atomic_operations_extension.php @@ -0,0 +1,33 @@ +add('name', 'Ford'); +$user2->add('name', 'Arthur'); +$user42->add('name', 'Zaphod'); + +$document->addResults($user1); +$document->addResults($user2); +$document->addResults($user42); + +/** + * get the json + */ + +$options = [ + 'prettyPrint' => true, +]; +echo '
'.$document->toJson($options); diff --git a/examples/bootstrap_examples.php b/examples/bootstrap_examples.php index 2832970..fc4793d 100644 --- a/examples/bootstrap_examples.php +++ b/examples/bootstrap_examples.php @@ -2,7 +2,7 @@ use alsvanzelf\jsonapi\Document; use alsvanzelf\jsonapi\ResourceDocument; -use alsvanzelf\jsonapi\helpers\ProfileAliasManager; +use alsvanzelf\jsonapi\interfaces\ExtensionInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; @@ -102,29 +102,59 @@ function getCurrentLocation() { } } -class ExampleVersionProfile extends ProfileAliasManager implements ProfileInterface { +class ExampleVersionExtension implements ExtensionInterface { /** - * the required methods (next to extending ProfileAliasManager) + * the required method */ public function getOfficialLink() { - return 'https://jsonapi.org/format/1.1/#profile-keywords-and-aliases'; + return 'https://jsonapi.org/format/1.1/#extension-rules'; } - public function getOfficialKeywords() { - return ['version']; + public function getNamespace() { + return 'version'; } /** - * optionally helpers for the specific profile + * optionally helpers for the specific extension */ public function setVersion(ResourceInterface $resource, $version) { if ($resource instanceof ResourceDocument) { - $resource->addMeta($this->getKeyword('version'), $version, $level=Document::LEVEL_RESOURCE); + $resource->getResource()->addExtensionMember($this, 'id', $version); } else { - $resource->addMeta($this->getKeyword('version'), $version); + $resource->addExtensionMember($this, 'id', $version); } } } + +class ExampleTimestampsProfile implements ProfileInterface { + /** + * the required method + */ + + public function getOfficialLink() { + return 'https://jsonapi.org/recommendations/#authoring-profiles'; + } + + /** + * optionally helpers for the specific profile + */ + + public function setTimestamps(ResourceInterface $resource, \DateTimeInterface $created=null, \DateTimeInterface $updated=null) { + if ($resource instanceof ResourceIdentifierObject) { + throw new Exception('cannot add attributes to identifier objects'); + } + + $timestamps = []; + if ($created !== null) { + $timestamps['created'] = $created->format(\DateTime::ISO8601); + } + if ($updated !== null) { + $timestamps['updated'] = $updated->format(\DateTime::ISO8601); + } + + $resource->add('timestamps', $timestamps); + } +} diff --git a/examples/errors_all_options.php b/examples/errors_all_options.php index 5be9ab7..4f628cc 100644 --- a/examples/errors_all_options.php +++ b/examples/errors_all_options.php @@ -38,7 +38,7 @@ $errorSpecApi->setHumanTitle($genericTitle='Too much options'); $errorSpecApi->setHumanDetails($specificDetails='Please, choose a bit less. Consult your ...'); $errorSpecApi->setAboutLink($specificAboutLink='https://www.example.com/explanation.html', ['foo'=>'bar']); -$errorSpecApi->appendTypeLink($genericTypeLink='https://www.example.com/documentation.html', ['foo'=>'bar']); +$errorSpecApi->setTypeLink($genericTypeLink='https://www.example.com/documentation.html', ['foo'=>'bar']); /** * prepare multiple error objects for the errors response diff --git a/examples/example_profile.php b/examples/example_profile.php deleted file mode 100644 index 66cef95..0000000 --- a/examples/example_profile.php +++ /dev/null @@ -1,32 +0,0 @@ - 'ref']); - -$document = new ResourceDocument('user', 42); -$document->applyProfile($profile); - -/** - * you can apply the rules of the profile manually - * or use methods of the profile if provided - */ - -$profile->setVersion($document, '2019'); - -/** - * get the json - */ - -$options = [ - 'prettyPrint' => true, -]; -echo ''.$document->toJson($options); diff --git a/examples/extension.php b/examples/extension.php new file mode 100644 index 0000000..74738bf --- /dev/null +++ b/examples/extension.php @@ -0,0 +1,37 @@ +applyExtension($extension); + +$document->add('foo', 'bar'); + +/** + * you can apply the rules of the extension manually + * or use methods of the extension if provided + */ + +$extension->setVersion($document, '2019'); + +/** + * get the json + */ + +$contentType = Converter::prepareContentType(Document::CONTENT_TYPE_OFFICIAL, [$extension], []); +echo 'Content-Type: '.$contentType.'
'.PHP_EOL; + +$options = [ + 'prettyPrint' => true, +]; +echo ''.$document->toJson($options); diff --git a/examples/index.html b/examples/index.html index 8c1963c..1687b28 100644 --- a/examples/index.html +++ b/examples/index.html @@ -49,7 +49,9 @@Misc
Content-Type: '.$contentType.'
'.PHP_EOL;
+
+$options = [
+ 'prettyPrint' => true,
+];
+echo ''.$document->toJson($options); diff --git a/src/Document.php b/src/Document.php index 2967cd2..ddb75d9 100644 --- a/src/Document.php +++ b/src/Document.php @@ -2,26 +2,30 @@ namespace alsvanzelf\jsonapi; +use alsvanzelf\jsonapi\exceptions\DuplicateException; use alsvanzelf\jsonapi\exceptions\Exception; use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\helpers\AtMemberManager; use alsvanzelf\jsonapi\helpers\Converter; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; use alsvanzelf\jsonapi\helpers\HttpStatusCodeManager; use alsvanzelf\jsonapi\helpers\LinksManager; use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\DocumentInterface; +use alsvanzelf\jsonapi\interfaces\ExtensionInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\objects\JsonapiObject; use alsvanzelf\jsonapi\objects\LinkObject; use alsvanzelf\jsonapi\objects\LinksObject; use alsvanzelf\jsonapi\objects\MetaObject; -use alsvanzelf\jsonapi\objects\ProfileLinkObject; /** * @see ResourceDocument, CollectionDocument, ErrorsDocument or MetaDocument */ abstract class Document implements DocumentInterface, \JsonSerializable { - use AtMemberManager, HttpStatusCodeManager, LinksManager; + use AtMemberManager, ExtensionMemberManager, HttpStatusCodeManager, LinksManager { + LinksManager::addLink as linkManagerAddLink; + } const JSONAPI_VERSION_1_0 = '1.0'; const JSONAPI_VERSION_1_1 = '1.1'; @@ -39,6 +43,8 @@ abstract class Document implements DocumentInterface, \JsonSerializable { protected $meta; /** @var JsonapiObject */ protected $jsonapi; + /** @var ExtensionInterface[] */ + protected $extensions = []; /** @var ProfileInterface[] */ protected $profiles = []; /** @var array */ @@ -94,11 +100,7 @@ public function __construct() { */ public function addLink($key, $href, array $meta=[], $level=Document::LEVEL_ROOT) { if ($level === Document::LEVEL_ROOT) { - if ($this->links === null) { - $this->setLinksObject(new LinksObject()); - } - - $this->links->add($key, $href, $meta); + $this->linkManagerAddLink($key, $href, $meta); } elseif ($level === Document::LEVEL_JSONAPI) { throw new InputException('level "jsonapi" can not be used for links'); @@ -114,12 +116,38 @@ public function addLink($key, $href, array $meta=[], $level=Document::LEVEL_ROOT /** * set the self link on the document * + * @note a LinkObject is added when extensions or profiles are applied + * * @param string $href * @param array $meta optional, if given a LinkObject is added, otherwise a link string is added * @param string $level one of the Document::LEVEL_* constants, optional, defaults to Document::LEVEL_ROOT */ public function setSelfLink($href, array $meta=[], $level=Document::LEVEL_ROOT) { - $this->addLink('self', $href, $meta, $level); + if ($level === Document::LEVEL_ROOT && ($this->extensions !== [] || $this->profiles !== [])) { + $contentType = Converter::prepareContentType(Document::CONTENT_TYPE_OFFICIAL, $this->extensions, $this->profiles); + + $linkObject = new LinkObject($href, $meta); + $linkObject->setMediaType($contentType); + + $this->addLinkObject('self', $linkObject); + } + else { + $this->addLink('self', $href, $meta, $level); + } + } + + /** + * set a link describing the current document + * + * for example this could link to an OpenAPI or JSON Schema document + * + * @note according to the spec, this can only be set to Document::LEVEL_ROOT + * + * @param string $href + * @param array $meta optional, if given a LinkObject is added, otherwise a link string is added + */ + public function setDescribedByLink($href, array $meta=[]) { + $this->addLink('describedby', $href, $meta, $level=Document::LEVEL_ROOT); } /** @@ -178,6 +206,36 @@ public function unsetJsonapiObject() { $this->jsonapi = null; } + /** + * apply a extension which adds the link and sets a correct content-type + * + * note that the rules from the extension are not automatically enforced + * applying the rules, and applying them correctly, is manual + * however the $extension could have custom methods to help + * + * @see https://jsonapi.org/extensions/#extensions + * + * @param ExtensionInterface $extension + * + * @throws Exception if namespace uses illegal characters + * @throws DuplicateException if namespace conflicts with another applied extension + */ + public function applyExtension(ExtensionInterface $extension) { + $namespace = $extension->getNamespace(); + if (strlen($namespace) < 1 || preg_match('{[^a-zA-Z0-9]}', $namespace) === 1) { + throw new Exception('invalid namespace "'.$namespace.'"'); + } + if (isset($this->extensions[$namespace])) { + throw new DuplicateException('an extension with namespace "'.$namespace.'" is already applied'); + } + + $this->extensions[$namespace] = $extension; + + if ($this->jsonapi !== null) { + $this->jsonapi->addExtension($extension); + } + } + /** * apply a profile which adds the link and sets a correct content-type * @@ -185,23 +243,15 @@ public function unsetJsonapiObject() { * applying the rules, and applying them correctly, is manual * however the $profile could have custom methods to help * - * @see https://jsonapi.org/format/1.1/#profiles + * @see https://jsonapi.org/extensions/#profiles * * @param ProfileInterface $profile */ public function applyProfile(ProfileInterface $profile) { $this->profiles[] = $profile; - if ($this->links === null) { - $this->setLinksObject(new LinksObject()); - } - - $link = $profile->getAliasedLink(); - if ($link instanceof LinkObject) { - $this->links->appendLinkObject('profile', $link); - } - else { - $this->links->append('profile', $link); + if ($this->jsonapi !== null) { + $this->jsonapi->addProfile($profile); } } @@ -213,7 +263,14 @@ public function applyProfile(ProfileInterface $profile) { * @inheritDoc */ public function toArray() { - $array = $this->getAtMembers(); + $array = []; + + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } if ($this->jsonapi !== null && $this->jsonapi->isEmpty() === false) { $array['jsonapi'] = $this->jsonapi->toArray(); @@ -267,7 +324,7 @@ public function sendResponse(array $options=[]) { http_response_code($this->httpStatusCode); - $contentType = Converter::mergeProfilesInContentType($options['contentType'], $this->profiles); + $contentType = Converter::prepareContentType($options['contentType'], $this->extensions, $this->profiles); header('Content-Type: '.$contentType); echo $json; diff --git a/src/ResourceDocument.php b/src/ResourceDocument.php index a94c16e..8db9d09 100644 --- a/src/ResourceDocument.php +++ b/src/ResourceDocument.php @@ -179,6 +179,13 @@ public function setId($id) { $this->resource->setId($id); } + /** + * @param string|int $localId will be casted to a string + */ + public function setLocalId($localId) { + $this->resource->setLocalId($localId); + } + /** * @param AttributesObject $attributesObject * @param array $options optional {@see ResourceObject::$defaults} diff --git a/src/extensions/AtomicOperationsDocument.php b/src/extensions/AtomicOperationsDocument.php new file mode 100644 index 0000000..1599fd8 --- /dev/null +++ b/src/extensions/AtomicOperationsDocument.php @@ -0,0 +1,57 @@ +extension = new AtomicOperationsExtension(); + $this->applyExtension($this->extension); + } + + /** + * add resources as results of the operations + * + * @param ResourceInterface[] ...$resources + */ + public function addResults(ResourceInterface ...$resources) { + $this->results = array_merge($this->results, $resources); + } + + /** + * DocumentInterface + */ + + /** + * @inheritDoc + */ + public function toArray() { + $results = []; + foreach ($this->results as $result) { + $results[] = [ + 'data' => $result->getResource()->toArray(), + ]; + } + + $this->addExtensionMember($this->extension, 'results', $results); + + return parent::toArray(); + } +} diff --git a/src/extensions/AtomicOperationsExtension.php b/src/extensions/AtomicOperationsExtension.php new file mode 100644 index 0000000..b94cfe6 --- /dev/null +++ b/src/extensions/AtomicOperationsExtension.php @@ -0,0 +1,32 @@ +getOfficialLink(); + } + $extensionLinks = implode(' ', $extensionLinks); + + $contentType .= '; ext="'.$extensionLinks.'"'; } - $profileLinks = []; - foreach ($profiles as $profile) { - $link = $profile->getAliasedLink(); - $profileLinks[] = ($link instanceof LinkObject) ? $link->toArray()['href'] : $link; + if ($profiles !== []) { + $profileLinks = []; + foreach ($profiles as $profile) { + $profileLinks[] = $profile->getOfficialLink(); + } + $profileLinks = implode(' ', $profileLinks); + + $contentType .= '; profile="'.$profileLinks.'"'; } - $profileLinks = implode(' ', $profileLinks); - return $contentType.';profile="'.$profileLinks.'", '.$contentType; + return $contentType; + } + + /** + * @deprecated {@see prepareContentType()} + */ + public static function mergeProfilesInContentType($contentType, array $profiles) { + return self::prepareContentType($contentType, $extensions=[], $profiles); } } diff --git a/src/helpers/ExtensionMemberManager.php b/src/helpers/ExtensionMemberManager.php new file mode 100644 index 0000000..4261628 --- /dev/null +++ b/src/helpers/ExtensionMemberManager.php @@ -0,0 +1,63 @@ +getNamespace(); + + if (strpos($key, $namespace.':') === 0) { + $key = substr($key, strlen($namespace.':')); + } + + Validator::checkMemberName($key); + + if (is_object($value)) { + $value = Converter::objectToArray($value); + } + + $this->extensionMembers[$namespace.':'.$key] = $value; + } + + /** + * internal api + */ + + /** + * @internal + * + * @return boolean + */ + public function hasExtensionMembers() { + return ($this->extensionMembers !== []); + } + + /** + * @internal + * + * @return array + */ + public function getExtensionMembers() { + return $this->extensionMembers; + } +} diff --git a/src/helpers/LinksManager.php b/src/helpers/LinksManager.php index 97c1c7f..ab234a4 100644 --- a/src/helpers/LinksManager.php +++ b/src/helpers/LinksManager.php @@ -29,6 +29,8 @@ public function addLink($key, $href, array $meta=[]) { /** * append a link to a key with an array of links * + * @deprecated array links are not supported anymore {@see ->addLink()} + * * @param string $key * @param string $href * @param array $meta optional, if given a LinkObject is added, otherwise a link string is added @@ -56,6 +58,8 @@ public function addLinkObject($key, LinkObject $linkObject) { /** * set a key containing a LinksArray * + * @deprecated array links are not supported anymore {@see ->addLinkObject()} + * * @param string $key * @param LinksArray $linksArray */ @@ -67,6 +71,8 @@ public function addLinksArray($key, LinksArray $linksArray) { /** * append a LinkObject to a key with a LinksArray * + * @deprecated array links are not supported anymore {@see ->addLinkObject()} + * * @param string $key * @param LinkObject $linkObject */ diff --git a/src/helpers/ProfileAliasManager.php b/src/helpers/ProfileAliasManager.php deleted file mode 100644 index 1ef0075..0000000 --- a/src/helpers/ProfileAliasManager.php +++ /dev/null @@ -1,80 +0,0 @@ -getOfficialKeywords(); - if ($officialKeywords === []) { - return; - } - - $this->keywordMapping = array_combine($officialKeywords, $officialKeywords); - if ($aliases === []) { - return; - } - - foreach ($aliases as $keyword => $alias) { - if ($alias === $keyword) { - throw new InputException('an alias should be different from its keyword'); - } - if (in_array($keyword, $officialKeywords, $strict=true) === false) { - throw new InputException('unknown keyword "'.$keyword.'" to alias'); - } - Validator::checkMemberName($alias); - - $this->keywordMapping[$keyword] = $alias; - } - - $this->aliasMapping = $aliases; - } - - /** - * @inheritDoc - */ - public function getKeyword($keyword) { - if (isset($this->keywordMapping[$keyword]) === false) { - throw new InputException('unknown keyword "'.$keyword.'"'); - } - - return $this->keywordMapping[$keyword]; - } - - /** - * @inheritDoc - */ - abstract public function getOfficialKeywords(); - - /** - * @inheritDoc - */ - abstract public function getOfficialLink(); - - /** - * @inheritDoc - */ - public function getAliasedLink() { - if ($this->aliasMapping === []) { - return $this->getOfficialLink(); - } - - return new ProfileLinkObject($this->getOfficialLink(), $this->aliasMapping); - } -} diff --git a/src/helpers/RequestParser.php b/src/helpers/RequestParser.php index 5ae8f25..5209fda 100644 --- a/src/helpers/RequestParser.php +++ b/src/helpers/RequestParser.php @@ -248,6 +248,20 @@ public function getFilter() { return $this->queryParameters['filter']; } + /** + * @return boolean + */ + public function hasLocalId() { + return (isset($this->document['data']['lid'])); + } + + /** + * @return string + */ + public function getLocalId() { + return $this->document['data']['lid']; + } + /** * @param string $attributeName * @return boolean diff --git a/src/helpers/Validator.php b/src/helpers/Validator.php index 6e814d7..692d473 100644 --- a/src/helpers/Validator.php +++ b/src/helpers/Validator.php @@ -12,6 +12,7 @@ class Validator { const OBJECT_CONTAINER_TYPE = 'type'; const OBJECT_CONTAINER_ID = 'id'; + const OBJECT_CONTAINER_LID = 'lid'; const OBJECT_CONTAINER_ATTRIBUTES = 'attributes'; const OBJECT_CONTAINER_RELATIONSHIPS = 'relationships'; @@ -84,7 +85,7 @@ public function clearUsedFields($objectContainerToClear) { */ public function claimUsedResourceIdentifier(ResourceInterface $resource) { if ($resource->getResource()->hasIdentification() === false) { - throw new InputException('can not validate resource without identifier, set type and id first'); + throw new InputException('can not validate resource without identifier, set type and id/lid first'); } $resourceKey = $resource->getResource()->getIdentificationKey(); diff --git a/src/interfaces/ExtensionInterface.php b/src/interfaces/ExtensionInterface.php new file mode 100644 index 0000000..3c07497 --- /dev/null +++ b/src/interfaces/ExtensionInterface.php @@ -0,0 +1,21 @@ +alias('version', 'v') was called before, this would return 'v' - * - * @param string $keyword - * @return string - * - * @throws InputException if the keyword is not known to the profile - */ - public function getKeyword($keyword); - - /** - * returns an array of official keywords this profile defines - * - * @internal - * - * @return string[] - */ - public function getOfficialKeywords(); - /** * the unique link identifying and describing the profile * @@ -52,15 +11,4 @@ public function getOfficialKeywords(); * @return string */ public function getOfficialLink(); - - /** - * get the official link, or a LinkObject with the link and its aliases - * - * optionally also contains the aliases applied - * - * @internal - * - * @return LinkObject|string - */ - public function getAliasedLink(); } diff --git a/src/objects/AttributesObject.php b/src/objects/AttributesObject.php index 5cbc5b1..867c45a 100644 --- a/src/objects/AttributesObject.php +++ b/src/objects/AttributesObject.php @@ -4,11 +4,12 @@ use alsvanzelf\jsonapi\helpers\AtMemberManager; use alsvanzelf\jsonapi\helpers\Converter; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\ObjectInterface; class AttributesObject implements ObjectInterface { - use AtMemberManager; + use AtMemberManager, ExtensionMemberManager; /** @var array */ protected $attributes = []; @@ -85,13 +86,32 @@ public function getKeys() { * @inheritDoc */ public function isEmpty() { - return ($this->attributes === [] && $this->hasAtMembers() === false); + if ($this->attributes !== []) { + return false; + } + if ($this->hasAtMembers()) { + return false; + } + if ($this->hasExtensionMembers()) { + return false; + } + + return true; } /** * @inheritDoc */ public function toArray() { - return array_merge($this->getAtMembers(), $this->attributes); + $array = []; + + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } + + return array_merge($array, $this->attributes); } } diff --git a/src/objects/ErrorObject.php b/src/objects/ErrorObject.php index 1473b04..3d40d78 100644 --- a/src/objects/ErrorObject.php +++ b/src/objects/ErrorObject.php @@ -6,13 +6,14 @@ use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\helpers\AtMemberManager; use alsvanzelf\jsonapi\helpers\Converter; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; use alsvanzelf\jsonapi\helpers\HttpStatusCodeManager; use alsvanzelf\jsonapi\helpers\LinksManager; use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\ObjectInterface; class ErrorObject implements ObjectInterface { - use AtMemberManager, HttpStatusCodeManager, LinksManager; + use AtMemberManager, ExtensionMemberManager, HttpStatusCodeManager, LinksManager; /** @var string */ protected $id; @@ -137,7 +138,7 @@ public function setHumanExplanation($genericTitle, $specificDetails=null, $speci $this->setAboutLink($specificAboutLink); } if ($genericTypeLink !== null) { - $this->appendTypeLink($genericTypeLink); + $this->setTypeLink($genericTypeLink); } } @@ -151,9 +152,21 @@ public function setAboutLink($href, array $meta=[]) { $this->addLink('about', $href, $meta); } + /** + * set the link of the generic type of this error, explained in a human-friendly way + * + * @param string $href + * @param array $meta optional, if given a LinkObject is added, otherwise a link string is added + */ + public function setTypeLink($href, array $meta=[]) { + $this->addLink('type', $href, $meta); + } + /** * append a link of the generic type of this error, explained in a human-friendly way * + * @deprecated array links are not supported anymore {@see ->setTypeLink()} + * * @param string $href * @param array $meta optional, if given a LinkObject is added, otherwise a link string is added */ @@ -288,6 +301,9 @@ public function isEmpty() { if ($this->hasAtMembers()) { return false; } + if ($this->hasExtensionMembers()) { + return false; + } return true; } @@ -296,8 +312,14 @@ public function isEmpty() { * @inheritDoc */ public function toArray() { - $array = $this->getAtMembers(); + $array = []; + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } if ($this->id !== null) { $array['id'] = $this->id; } diff --git a/src/objects/JsonapiObject.php b/src/objects/JsonapiObject.php index ad04da2..980b0b2 100644 --- a/src/objects/JsonapiObject.php +++ b/src/objects/JsonapiObject.php @@ -4,14 +4,21 @@ use alsvanzelf\jsonapi\Document; use alsvanzelf\jsonapi\helpers\AtMemberManager; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; +use alsvanzelf\jsonapi\interfaces\ExtensionInterface; use alsvanzelf\jsonapi\interfaces\ObjectInterface; +use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\objects\MetaObject; class JsonapiObject implements ObjectInterface { - use AtMemberManager; + use AtMemberManager, ExtensionMemberManager; /** @var string */ protected $version; + /** @var ExtensionInterface[] */ + protected $extensions = []; + /** @var ProfileInterface */ + protected $profiles = []; /** @var MetaObject */ protected $meta; @@ -51,6 +58,20 @@ public function setVersion($version) { $this->version = $version; } + /** + * @param ExtensionInterface $extension + */ + public function addExtension(ExtensionInterface $extension) { + $this->extensions[] = $extension; + } + + /** + * @param ProfileInterface $profile + */ + public function addProfile(ProfileInterface $profile) { + $this->profiles[] = $profile; + } + /** * @param MetaObject $metaObject */ @@ -69,12 +90,21 @@ public function isEmpty() { if ($this->version !== null) { return false; } + if ($this->extensions !== []) { + return false; + } + if ($this->profiles !== []) { + return false; + } if ($this->meta !== null && $this->meta->isEmpty() === false) { return false; } if ($this->hasAtMembers()) { return false; } + if ($this->hasExtensionMembers()) { + return false; + } return true; } @@ -83,11 +113,29 @@ public function isEmpty() { * @inheritDoc */ public function toArray() { - $array = $this->getAtMembers(); + $array = []; + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } if ($this->version !== null) { $array['version'] = $this->version; } + if ($this->extensions !== []) { + $array['ext'] = []; + foreach ($this->extensions as $extension) { + $array['ext'][] = $extension->getOfficialLink(); + } + } + if ($this->profiles !== []) { + $array['profile'] = []; + foreach ($this->profiles as $profile) { + $array['profile'][] = $profile->getOfficialLink(); + } + } if ($this->meta !== null && $this->meta->isEmpty() === false) { $array['meta'] = $this->meta->toArray(); } diff --git a/src/objects/LinkObject.php b/src/objects/LinkObject.php index 1eab36d..244d103 100644 --- a/src/objects/LinkObject.php +++ b/src/objects/LinkObject.php @@ -3,14 +3,25 @@ namespace alsvanzelf\jsonapi\objects; use alsvanzelf\jsonapi\helpers\AtMemberManager; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; use alsvanzelf\jsonapi\interfaces\ObjectInterface; use alsvanzelf\jsonapi\objects\MetaObject; class LinkObject implements ObjectInterface { - use AtMemberManager; + use AtMemberManager, ExtensionMemberManager; /** @var string */ protected $href; + /** @var string */ + protected $rel; + /** @var LinkObject */ + protected $describedby; + /** @var string */ + protected $title; + /** @var string */ + protected $type; + /** @var string[] */ + protected $hreflang = []; /** @var MetaObject */ protected $meta; @@ -31,6 +42,25 @@ public function __construct($href=null, array $meta=[]) { * human api */ + /** + * @param string $href + */ + public function setDescribedBy($href) { + $this->setDescribedByLinkObject(new LinkObject($href)); + } + + /** + * @param string $language + */ + public function addLanguage($language) { + if ($this->hreflang === []) { + $this->setHreflang($language); + } + else { + $this->setHreflang(...array_merge($this->hreflang, [$language])); + } + } + /** * @param string $key * @param mixed $value @@ -54,6 +84,45 @@ public function setHref($href) { $this->href = $href; } + /** + * @todo validate according to https://tools.ietf.org/html/rfc8288#section-2.1 + * + * @param string $relationType + */ + public function setRelationType($relationType) { + $this->rel = $relationType; + } + + /** + * @param LinkObject $describedBy + */ + public function setDescribedByLinkObject(LinkObject $describedBy) { + $this->describedby = $describedBy; + } + + /** + * @param string $friendlyTitle + */ + public function setHumanTitle($humanTitle) { + $this->title = $humanTitle; + } + + /** + * @param string $mediaType + */ + public function setMediaType($mediaType) { + $this->type = $mediaType; + } + + /** + * @todo validate according to https://tools.ietf.org/html/rfc5646 + * + * @param string ...$hreflang + */ + public function setHreflang(...$hreflang) { + $this->hreflang = $hreflang; + } + /** * @param MetaObject $metaObject */ @@ -72,12 +141,30 @@ public function isEmpty() { if ($this->href !== null) { return false; } + if ($this->rel !== null) { + return false; + } + if ($this->title !== null) { + return false; + } + if ($this->type !== null) { + return false; + } + if ($this->hreflang !== []) { + return false; + } + if ($this->describedby !== null && $this->describedby->isEmpty() === false) { + return false; + } if ($this->meta !== null && $this->meta->isEmpty() === false) { return false; } if ($this->hasAtMembers()) { return false; } + if ($this->hasExtensionMembers()) { + return false; + } return true; } @@ -86,10 +173,37 @@ public function isEmpty() { * @inheritDoc */ public function toArray() { - $array = $this->getAtMembers(); + $array = []; + + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } $array['href'] = $this->href; + if ($this->rel !== null) { + $array['rel'] = $this->rel; + } + if ($this->title !== null) { + $array['title'] = $this->title; + } + if ($this->type !== null) { + $array['type'] = $this->type; + } + if ($this->hreflang !== []) { + if (count($this->hreflang) === 1) { + $array['hreflang'] = $this->hreflang[0]; + } + else { + $array['hreflang'] = $this->hreflang; + } + } + if ($this->describedby !== null && $this->describedby->isEmpty() === false) { + $array['describedby'] = $this->describedby->toArray(); + } if ($this->meta !== null && $this->meta->isEmpty() === false) { $array['meta'] = $this->meta->toArray(); } diff --git a/src/objects/LinksArray.php b/src/objects/LinksArray.php index d390e01..d4ae042 100644 --- a/src/objects/LinksArray.php +++ b/src/objects/LinksArray.php @@ -9,7 +9,6 @@ /** * an array of links (strings and LinkObjects), used for: * - type links in an ErrorObject - * - profile links at root level */ class LinksArray implements ObjectInterface { /** @var array with string|LinkObject */ diff --git a/src/objects/LinksObject.php b/src/objects/LinksObject.php index 9e6347b..1e759ee 100644 --- a/src/objects/LinksObject.php +++ b/src/objects/LinksObject.php @@ -5,13 +5,14 @@ use alsvanzelf\jsonapi\exceptions\DuplicateException; use alsvanzelf\jsonapi\helpers\AtMemberManager; use alsvanzelf\jsonapi\helpers\Converter; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\ObjectInterface; use alsvanzelf\jsonapi\objects\LinkObject; use alsvanzelf\jsonapi\objects\LinksArray; class LinksObject implements ObjectInterface { - use AtMemberManager; + use AtMemberManager, ExtensionMemberManager; /** @var array with string|LinkObject */ protected $links = []; @@ -63,6 +64,8 @@ public function add($key, $href, array $meta=[]) { * * @see LinksArray for use cases * + * @deprecated array links are not supported anymore {@see ->add()} + * * @param string $key * @param string $href * @param array $meta optional, if given a LinkObject is added, otherwise a link string is added @@ -119,6 +122,8 @@ public function addLinkObject($key, LinkObject $linkObject) { } /** + * @deprecated array links are not supported anymore {@see ->addLinkObject()} + * * @param string $key * @param LinksArray $linksArray * @@ -135,6 +140,8 @@ public function addLinksArray($key, LinksArray $linksArray) { } /** + * @deprecated array links are not supported anymore {@see ->addLinkObject()} + * * @param string $key * @param LinkObject $linkObject * @@ -161,14 +168,31 @@ public function appendLinkObject($key, LinkObject $linkObject) { * @inheritDoc */ public function isEmpty() { - return ($this->links === [] && $this->hasAtMembers() === false); + if ($this->links !== []) { + return false; + } + if ($this->hasAtMembers()) { + return false; + } + if ($this->hasExtensionMembers()) { + return false; + } + + return true; } /** * @inheritDoc */ public function toArray() { - $array = $this->getAtMembers(); + $array = []; + + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } foreach ($this->links as $key => $link) { if ($link instanceof LinkObject && $link->isEmpty() === false) { diff --git a/src/objects/MetaObject.php b/src/objects/MetaObject.php index f31eb28..03e9e1e 100644 --- a/src/objects/MetaObject.php +++ b/src/objects/MetaObject.php @@ -4,11 +4,12 @@ use alsvanzelf\jsonapi\helpers\AtMemberManager; use alsvanzelf\jsonapi\helpers\Converter; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\ObjectInterface; class MetaObject implements ObjectInterface { - use AtMemberManager; + use AtMemberManager, ExtensionMemberManager; /** @var array */ protected $meta = []; @@ -67,13 +68,32 @@ public function add($key, $value) { * @inheritDoc */ public function isEmpty() { - return ($this->meta === [] && $this->hasAtMembers() === false); + if ($this->meta !== []) { + return false; + } + if ($this->hasAtMembers()) { + return false; + } + if ($this->hasExtensionMembers()) { + return false; + } + + return true; } /** * @inheritDoc */ public function toArray() { - return array_merge($this->getAtMembers(), $this->meta); + $array = []; + + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } + + return array_merge($array, $this->meta); } } diff --git a/src/objects/ProfileLinkObject.php b/src/objects/ProfileLinkObject.php deleted file mode 100644 index 4652a53..0000000 --- a/src/objects/ProfileLinkObject.php +++ /dev/null @@ -1,47 +0,0 @@ -aliases = $aliases; - } - - /** - * human api - */ - - /** - * spec api - */ - - /** - * ObjectInterface - */ - - /** - * @inheritDoc - */ - public function toArray() { - $array = parent::toArray(); - - if ($this->aliases !== []) { - $array['aliases'] = $this->aliases; - } - - return $array; - } -} diff --git a/src/objects/RelationshipObject.php b/src/objects/RelationshipObject.php index ea62561..7644cd5 100644 --- a/src/objects/RelationshipObject.php +++ b/src/objects/RelationshipObject.php @@ -5,6 +5,7 @@ use alsvanzelf\jsonapi\CollectionDocument; use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\helpers\AtMemberManager; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; use alsvanzelf\jsonapi\helpers\LinksManager; use alsvanzelf\jsonapi\interfaces\ObjectInterface; use alsvanzelf\jsonapi\interfaces\PaginableInterface; @@ -15,7 +16,7 @@ use alsvanzelf\jsonapi\objects\ResourceObject; class RelationshipObject implements ObjectInterface, PaginableInterface, RecursiveResourceContainerInterface { - use AtMemberManager, LinksManager; + use AtMemberManager, ExtensionMemberManager, LinksManager; const TO_ONE = 'one'; const TO_MANY = 'many'; @@ -273,6 +274,9 @@ public function isEmpty() { if ($this->hasAtMembers()) { return false; } + if ($this->hasExtensionMembers()) { + return false; + } return true; } @@ -281,7 +285,14 @@ public function isEmpty() { * @inheritDoc */ public function toArray() { - $array = $this->getAtMembers(); + $array = []; + + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } if ($this->links !== null && $this->links->isEmpty() === false) { $array['links'] = $this->links->toArray(); diff --git a/src/objects/RelationshipsObject.php b/src/objects/RelationshipsObject.php index 6764aa3..c0bf3e0 100644 --- a/src/objects/RelationshipsObject.php +++ b/src/objects/RelationshipsObject.php @@ -4,6 +4,7 @@ use alsvanzelf\jsonapi\exceptions\DuplicateException; use alsvanzelf\jsonapi\helpers\AtMemberManager; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\ObjectInterface; use alsvanzelf\jsonapi\interfaces\RecursiveResourceContainerInterface; @@ -12,7 +13,7 @@ use alsvanzelf\jsonapi\objects\ResourceObject; class RelationshipsObject implements ObjectInterface, RecursiveResourceContainerInterface { - use AtMemberManager; + use AtMemberManager, ExtensionMemberManager; /** @var RelationshipObject[] */ protected $relationships = []; @@ -77,14 +78,31 @@ public function getKeys() { * @inheritDoc */ public function isEmpty() { - return ($this->relationships === [] && $this->hasAtMembers() === false); + if ($this->relationships !== []) { + return false; + } + if ($this->hasAtMembers()) { + return false; + } + if ($this->hasExtensionMembers()) { + return false; + } + + return true; } /** * @inheritDoc */ public function toArray() { - $array = $this->getAtMembers(); + $array = []; + + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } foreach ($this->relationships as $key => $relationshipObject) { $array[$key] = $relationshipObject->toArray(); diff --git a/src/objects/ResourceIdentifierObject.php b/src/objects/ResourceIdentifierObject.php index fe32d91..05aaa6b 100644 --- a/src/objects/ResourceIdentifierObject.php +++ b/src/objects/ResourceIdentifierObject.php @@ -3,19 +3,23 @@ namespace alsvanzelf\jsonapi\objects; use alsvanzelf\jsonapi\exceptions\Exception; +use alsvanzelf\jsonapi\exceptions\DuplicateException; use alsvanzelf\jsonapi\helpers\AtMemberManager; +use alsvanzelf\jsonapi\helpers\ExtensionMemberManager; use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\ObjectInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; use alsvanzelf\jsonapi\objects\MetaObject; class ResourceIdentifierObject implements ObjectInterface, ResourceInterface { - use AtMemberManager; + use AtMemberManager, ExtensionMemberManager; /** @var string */ protected $type; /** @var string */ protected $id; + /** @var string */ + protected $lid; /** @var MetaObject */ protected $meta; /** @var Validator */ @@ -42,6 +46,7 @@ public function __construct($type=null, $id=null) { // always mark as used, as these keys are reserved $this->validator->claimUsedFields($fieldNames=['type'], Validator::OBJECT_CONTAINER_TYPE); $this->validator->claimUsedFields($fieldNames=['id'], Validator::OBJECT_CONTAINER_ID); + $this->validator->claimUsedFields($fieldNames=['lid'], Validator::OBJECT_CONTAINER_LID); } /** @@ -73,11 +78,34 @@ public function setType($type) { /** * @param string|int $id will be casted to a string + * + * @throws DuplicateException if localId is already set */ public function setId($id) { + if ($this->lid !== null) { + throw new DuplicateException('id is not allowed when localId is already set'); + } + $this->id = (string) $id; } + /** + * set a local id to connect resources to each other when created on the client + * + * @note this should not be used to send back from the server to the client + * + * @param string|int $localId will be casted to a string + * + * @throws DuplicateException if normal id is already set + */ + public function setLocalId($localId) { + if ($this->id !== null) { + throw new DuplicateException('localId is not allowed when id is already set'); + } + + $this->lid = (string) $localId; + } + /** * @param MetaObject $metaObject */ @@ -96,7 +124,7 @@ public function setMetaObject(MetaObject $metaObject) { * @return ResourceIdentifierObject */ public static function fromResourceObject(ResourceObject $resourceObject) { - $resourceIdentifierObject = new self($resourceObject->type, $resourceObject->id); + $resourceIdentifierObject = new self($resourceObject->type, $resourceObject->primaryId()); if ($resourceObject->meta !== null) { $resourceIdentifierObject->setMetaObject($resourceObject->meta); @@ -127,7 +155,7 @@ public function equals(ResourceInterface $resource) { * @return boolean */ public function hasIdentification() { - return ($this->type !== null && $this->id !== null); + return ($this->type !== null && $this->primaryId() !== null); } /** @@ -144,7 +172,7 @@ public function getIdentificationKey() { throw new Exception('resource has no identification yet'); } - return $this->type.'|'.$this->id; + return $this->type.'|'.$this->primaryId(); } /** @@ -155,7 +183,7 @@ public function getIdentificationKey() { * @inheritDoc */ public function isEmpty() { - if ($this->type !== null || $this->id !== null) { + if ($this->type !== null || $this->primaryId() !== null) { return false; } if ($this->meta !== null && $this->meta->isEmpty() === false) { @@ -164,6 +192,9 @@ public function isEmpty() { if ($this->hasAtMembers()) { return false; } + if ($this->hasExtensionMembers()) { + return false; + } return true; } @@ -172,13 +203,23 @@ public function isEmpty() { * @inheritDoc */ public function toArray() { - $array = $this->getAtMembers(); + $array = []; $array['type'] = $this->type; if ($this->id !== null) { $array['id'] = $this->id; } + elseif ($this->lid !== null) { + $array['lid'] = $this->lid; + } + + if ($this->hasAtMembers()) { + $array = array_merge($array, $this->getAtMembers()); + } + if ($this->hasExtensionMembers()) { + $array = array_merge($array, $this->getExtensionMembers()); + } if ($this->meta !== null && $this->meta->isEmpty() === false) { $array['meta'] = $this->meta->toArray(); @@ -197,4 +238,16 @@ public function toArray() { public function getResource($identifierOnly=false) { return $this; } + + /** + * @internal + */ + + private function primaryId() { + if ($this->lid !== null) { + return $this->lid; + } + + return $this->id; + } } diff --git a/src/profiles/CursorPaginationProfile.php b/src/profiles/CursorPaginationProfile.php index 1700f92..e95f523 100644 --- a/src/profiles/CursorPaginationProfile.php +++ b/src/profiles/CursorPaginationProfile.php @@ -4,7 +4,6 @@ use alsvanzelf\jsonapi\Document; use alsvanzelf\jsonapi\ResourceDocument; -use alsvanzelf\jsonapi\helpers\ProfileAliasManager; use alsvanzelf\jsonapi\interfaces\PaginableInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; @@ -44,7 +43,7 @@ * - {@see get*ErrorObject} to generate ErrorObjects for specific error cases * - {@see generatePreviousLink} {@see generateNextLink} to apply the links manually */ -class CursorPaginationProfile extends ProfileAliasManager implements ProfileInterface { +class CursorPaginationProfile implements ProfileInterface { /** * human api */ @@ -115,7 +114,7 @@ public function setCount(PaginableInterface $paginable, $exactTotal=null, $bestG * @return string */ public function generatePreviousLink($baseOrCurrentUrl, $beforeCursor) { - return $this->setQueryParameter($baseOrCurrentUrl, $this->getKeyword('page').'[before]', $beforeCursor); + return $this->setQueryParameter($baseOrCurrentUrl, 'page[before]', $beforeCursor); } /** @@ -126,7 +125,7 @@ public function generatePreviousLink($baseOrCurrentUrl, $beforeCursor) { * @return string */ public function generateNextLink($baseOrCurrentUrl, $afterCursor) { - return $this->setQueryParameter($baseOrCurrentUrl, $this->getKeyword('page').'[after]', $afterCursor); + return $this->setQueryParameter($baseOrCurrentUrl, 'page[after]', $afterCursor); } /** @@ -192,10 +191,10 @@ public function setItemMeta(ResourceInterface $resource, $cursor) { ]; if ($resource instanceof ResourceDocument) { - $resource->addMeta($this->getKeyword('page'), $metadata, $level=Document::LEVEL_RESOURCE); + $resource->addMeta('page', $metadata, $level=Document::LEVEL_RESOURCE); } else { - $resource->addMeta($this->getKeyword('page'), $metadata); + $resource->addMeta('page', $metadata); } } @@ -229,7 +228,7 @@ public function setPaginationMeta(PaginableInterface $paginable, $exactTotal=nul $metadata['rangeTruncated'] = $rangeIsTruncated; } - $paginable->addMeta($this->getKeyword('page'), $metadata); + $paginable->addMeta('page', $metadata); } /** @@ -249,7 +248,7 @@ public function setPaginationMeta(PaginableInterface $paginable, $exactTotal=nul */ public function getUnsupportedSortErrorObject($genericTitle=null, $specificDetails=null) { $errorObject = new ErrorObject('Unsupported sort'); - $errorObject->appendTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort'); + $errorObject->setTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort'); $errorObject->blameQueryParameter('sort'); $errorObject->setHttpStatusCode(400); @@ -279,10 +278,10 @@ public function getUnsupportedSortErrorObject($genericTitle=null, $specificDetai */ public function getMaxPageSizeExceededErrorObject($maxSize, $genericTitle=null, $specificDetails=null) { $errorObject = new ErrorObject('Max page size exceeded'); - $errorObject->appendTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded'); - $errorObject->blameQueryParameter($this->getKeyword('page').'[size]'); + $errorObject->setTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded'); + $errorObject->blameQueryParameter('page[size]'); $errorObject->setHttpStatusCode(400); - $errorObject->addMeta($this->getKeyword('page'), $value=['maxSize' => $maxSize]); + $errorObject->addMeta('page', $value=['maxSize' => $maxSize]); if ($genericTitle !== null) { $errorObject->setHumanExplanation($genericTitle, $specificDetails); @@ -302,7 +301,7 @@ public function getMaxPageSizeExceededErrorObject($maxSize, $genericTitle=null, * - /errors/0/title optional * - /errors/0/detail optional * - * @param int $queryParameter e.g. 'sort' or 'page[size]', aliasing should already be done using {@see getKeyword} + * @param int $queryParameter e.g. 'sort' or 'page[size]' * @param string $typeLink optional * @param string $genericTitle optional, e.g. 'Invalid Parameter.' * @param string $specificDetails optional, e.g. 'page[size] must be a positive integer; got 0' @@ -314,7 +313,7 @@ public function getInvalidParameterValueErrorObject($queryParameter, $typeLink=n $errorObject->setHttpStatusCode(400); if ($typeLink !== null) { - $errorObject->appendTypeLink($typeLink); + $errorObject->setTypeLink($typeLink); } if ($genericTitle !== null) { @@ -338,7 +337,7 @@ public function getInvalidParameterValueErrorObject($queryParameter, $typeLink=n */ public function getRangePaginationNotSupportedErrorObject($genericTitle=null, $specificDetails=null) { $errorObject = new ErrorObject('Range pagination not supported'); - $errorObject->appendTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported'); + $errorObject->setTypeLink('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported'); $errorObject->setHttpStatusCode(400); if ($genericTitle !== null) { @@ -394,9 +393,14 @@ public function getOfficialLink() { } /** - * @inheritDoc + * returns the keyword without aliasing + * + * @deprecated since aliasing was removed from the profiles spec + * + * @param string $keyword + * @return string */ - public function getOfficialKeywords() { - return ['page']; + public function getKeyword($keyword) { + return $keyword; } } diff --git a/tests/ConverterTest.php b/tests/ConverterTest.php index ad33478..f5c7d51 100644 --- a/tests/ConverterTest.php +++ b/tests/ConverterTest.php @@ -4,7 +4,7 @@ use alsvanzelf\jsonapi\helpers\Converter; use alsvanzelf\jsonapi\objects\AttributesObject; -use alsvanzelf\jsonapi\objects\LinkObject; +use alsvanzelf\jsonapiTests\extensions\TestExtension; use alsvanzelf\jsonapiTests\profiles\TestProfile; use PHPUnit\Framework\TestCase; @@ -64,32 +64,63 @@ public function dataProviderCamelCaseToWords_HappyPath() { ]; } - public function testMergeProfilesInContentType_HappyPath() { - $this->assertSame('foo', Converter::mergeProfilesInContentType('foo', [])); + /** + * @group Extensions + * @group Profiles + */ + public function testPrepareContentType_HappyPath() { + $this->assertSame('foo', Converter::prepareContentType('foo', [], [])); } - public function testMergeProfilesInContentType_WithProfileStringLink() { - $profile = new TestProfile(); - $profile->setAliasedLink('bar'); + /** + * @group Extensions + */ + public function testPrepareContentType_WithExtensionStringLink() { + $extension = new TestExtension(); + $extension->setOfficialLink('bar'); - $this->assertSame('foo;profile="bar", foo', Converter::mergeProfilesInContentType('foo', [$profile])); + $this->assertSame('foo; ext="bar"', Converter::prepareContentType('foo', [$extension], [])); } - public function testMergeProfilesInContentType_WithProfileObjectLink() { + /** + * @group Profiles + */ + public function testPrepareContentType_WithProfileStringLink() { $profile = new TestProfile(); - $profile->setAliasedLink(new LinkObject('bar')); + $profile->setOfficialLink('bar'); - $this->assertSame('foo;profile="bar", foo', Converter::mergeProfilesInContentType('foo', [$profile])); + $this->assertSame('foo; profile="bar"', Converter::prepareContentType('foo', [], [$profile])); } - public function testMergeProfilesInContentType_WithMultipleProfiles() { + /** + * @group Extensions + * @group Profiles + */ + public function testPrepareContentType_WithMultipleExtensionsAndProfiles() { + $extension1 = new TestExtension(); + $extension1->setOfficialLink('bar'); + + $extension2 = new TestExtension(); + $extension2->setOfficialLink('baz'); + $profile1 = new TestProfile(); - $profile1->setAliasedLink('bar'); + $profile1->setOfficialLink('bar'); $profile2 = new TestProfile(); - $profile2->setAliasedLink(new LinkObject('baz')); + $profile2->setOfficialLink('baz'); + + $this->assertSame('foo; ext="bar baz"; profile="bar baz"', Converter::prepareContentType('foo', [$extension1, $extension2], [$profile1, $profile2])); + } + + /** + * test method while it is part of the interface + * @group Profiles + */ + public function testMergeProfilesInContentType_HappyPath() { + $profile = new TestProfile(); + $profile->setOfficialLink('bar'); - $this->assertSame('foo;profile="bar baz", foo', Converter::mergeProfilesInContentType('foo', [$profile1, $profile2])); + $this->assertSame('foo; profile="bar"', Converter::mergeProfilesInContentType('foo', [$profile])); } } diff --git a/tests/DocumentTest.php b/tests/DocumentTest.php index 2cd0916..020a70a 100644 --- a/tests/DocumentTest.php +++ b/tests/DocumentTest.php @@ -2,11 +2,12 @@ namespace alsvanzelf\jsonapiTests; +use alsvanzelf\jsonapi\exceptions\DuplicateException; use alsvanzelf\jsonapi\exceptions\Exception; use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\objects\LinkObject; -use alsvanzelf\jsonapi\objects\ProfileLinkObject; use alsvanzelf\jsonapiTests\TestableNonAbstractDocument as Document; +use alsvanzelf\jsonapiTests\extensions\TestExtension; use alsvanzelf\jsonapiTests\profiles\TestProfile; use PHPUnit\Framework\TestCase; @@ -98,6 +99,58 @@ public function testAddLink_BlocksUnknownLevel() { $document->addLink('foo', 'https://jsonapi.org', $meta=[], $level='foo'); } + public function testSetSelfLink_HappyPath() { + $document = new Document(); + + $array = $document->toArray(); + $this->assertArrayNotHasKey('links', $array); + + $document->setSelfLink('https://jsonapi.org/foo'); + + $array = $document->toArray(); + $this->assertArrayHasKey('links', $array); + $this->assertCount(1, $array['links']); + $this->assertArrayHasKey('self', $array['links']); + $this->assertSame('https://jsonapi.org/foo', $array['links']['self']); + } + + public function testSetDescribedByLink_HappyPath() { + $document = new Document(); + $document->setDescribedByLink('https://jsonapi.org/format', ['version' => '1.1']); + + $array = $document->toArray(); + + $this->assertCount(1, $array['links']); + if (method_exists($this, 'assertIsArray')) { + $this->assertIsArray($array['links']['describedby']); + } + else { + $this->assertInternalType('array', $array['links']['describedby']); + } + $this->assertCount(2, $array['links']['describedby']); + $this->assertArrayHasKey('href', $array['links']['describedby']); + $this->assertArrayHasKey('meta', $array['links']['describedby']); + $this->assertSame('https://jsonapi.org/format', $array['links']['describedby']['href']); + $this->assertCount(1, $array['links']['describedby']['meta']); + $this->assertArrayHasKey('version', $array['links']['describedby']['meta']); + $this->assertSame('1.1', $array['links']['describedby']['meta']['version']); + } + + public function testSetDescribedByLink_WithMeta() { + $document = new Document(); + + $array = $document->toArray(); + $this->assertArrayNotHasKey('links', $array); + + $document->setDescribedByLink('https://jsonapi.org/format'); + + $array = $document->toArray(); + $this->assertArrayHasKey('links', $array); + $this->assertCount(1, $array['links']); + $this->assertArrayHasKey('describedby', $array['links']); + $this->assertSame('https://jsonapi.org/format', $array['links']['describedby']); + } + public function testAddMeta_HappyPath() { $document = new Document(); @@ -178,48 +231,107 @@ public function testAddLinkObject_HappyPath() { $this->assertSame('https://jsonapi.org', $array['links']['foo']['href']); } - public function testApplyProfile_HappyPath() { - $profile = new TestProfile(); - $profile->setAliasedLink('https://jsonapi.org'); + /** + * @group Extensions + */ + public function testApplyExtension_HappyPath() { + $extension = new TestExtension(); + $extension->setNamespace('test'); + $extension->setOfficialLink('https://jsonapi.org/extension'); $document = new Document(); - $document->applyProfile($profile); + $document->applyExtension($extension); + $document->addExtensionMember($extension, 'foo', 'bar'); + $document->setSelfLink('https://jsonapi.org/foo'); $array = $document->toArray(); + $this->assertCount(3, $array); + $this->assertArrayHasKey('jsonapi', $array); + $this->assertCount(2, $array['jsonapi']); + $this->assertSame('1.1', $array['jsonapi']['version']); + $this->assertArrayHasKey('ext', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['ext']); + $this->assertArrayHasKey(0, $array['jsonapi']['ext']); + $this->assertSame('https://jsonapi.org/extension', $array['jsonapi']['ext'][0]); + $this->assertArrayHasKey('test:foo', $array); + $this->assertSame('bar', $array['test:foo']); $this->assertArrayHasKey('links', $array); $this->assertCount(1, $array['links']); - $this->assertArrayHasKey('profile', $array['links']); - $this->assertCount(1, $array['links']['profile']); - $this->assertArrayHasKey(0, $array['links']['profile']); - $this->assertSame('https://jsonapi.org', $array['links']['profile'][0]); + $this->assertArrayHasKey('self', $array['links']); + $this->assertCount(2, $array['links']['self']); + $this->assertArrayHasKey('href', $array['links']['self']); + $this->assertArrayHasKey('type', $array['links']['self']); + $this->assertSame('https://jsonapi.org/foo', $array['links']['self']['href']); + $this->assertSame('application/vnd.api+json; ext="https://jsonapi.org/extension"', $array['links']['self']['type']); + } + + /** + * @group Extensions + */ + public function testApplyExtension_InvalidNamespace() { + $document = new Document(); + $extension = new TestExtension(); + $extension->setNamespace('foo-bar'); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('invalid namespace "foo-bar"'); + + $document->applyExtension($extension); } - public function testApplyProfile_WithLinkObject() { + /** + * @group Extensions + */ + public function testApplyExtension_ConflictingNamespace() { + $document = new Document(); + + $extension1 = new TestExtension(); + $extension1->setNamespace('foo'); + $document->applyExtension($extension1); + + $extension2 = new TestExtension(); + $extension2->setNamespace('bar'); + $document->applyExtension($extension2); + + $extension3 = new TestExtension(); + $extension3->setNamespace('foo'); + + $this->expectException(DuplicateException::class); + $this->expectExceptionMessage('an extension with namespace "foo" is already applied'); + + $document->applyExtension($extension3); + } + + /** + * @group Profiles + */ + public function testApplyProfile_HappyPath() { $profile = new TestProfile(); - $profile->setAliasedLink(new ProfileLinkObject('https://jsonapi.org', $aliases=['foo' => 'bar'], $meta=['baz' => 'baf'])); + $profile->setOfficialLink('https://jsonapi.org/profile'); $document = new Document(); $document->applyProfile($profile); + $document->setSelfLink('https://jsonapi.org/foo'); $array = $document->toArray(); + $this->assertCount(2, $array); + $this->assertArrayHasKey('jsonapi', $array); + $this->assertCount(2, $array['jsonapi']); + $this->assertSame('1.1', $array['jsonapi']['version']); + $this->assertArrayHasKey('profile', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['profile']); + $this->assertArrayHasKey(0, $array['jsonapi']['profile']); + $this->assertSame('https://jsonapi.org/profile', $array['jsonapi']['profile'][0]); $this->assertArrayHasKey('links', $array); $this->assertCount(1, $array['links']); - $this->assertArrayHasKey('profile', $array['links']); - $this->assertCount(1, $array['links']['profile']); - $this->assertArrayHasKey(0, $array['links']['profile']); - $this->assertCount(3, $array['links']['profile'][0]); - $this->assertArrayHasKey('href', $array['links']['profile'][0]); - $this->assertArrayHasKey('aliases', $array['links']['profile'][0]); - $this->assertArrayHasKey('meta', $array['links']['profile'][0]); - $this->assertSame('https://jsonapi.org', $array['links']['profile'][0]['href']); - $this->assertCount(1, $array['links']['profile'][0]['aliases']); - $this->assertArrayHasKey('foo', $array['links']['profile'][0]['aliases']); - $this->assertSame('bar', $array['links']['profile'][0]['aliases']['foo']); - $this->assertCount(1, $array['links']['profile'][0]['meta']); - $this->assertArrayHasKey('baz', $array['links']['profile'][0]['meta']); - $this->assertSame('baf', $array['links']['profile'][0]['meta']['baz']); + $this->assertArrayHasKey('self', $array['links']); + $this->assertCount(2, $array['links']['self']); + $this->assertArrayHasKey('href', $array['links']['self']); + $this->assertArrayHasKey('type', $array['links']['self']); + $this->assertSame('https://jsonapi.org/foo', $array['links']['self']['href']); + $this->assertSame('application/vnd.api+json; profile="https://jsonapi.org/profile"', $array['links']['self']['type']); } public function testToJson_HappyPath() { diff --git a/tests/ResourceDocumentTest.php b/tests/ResourceDocumentTest.php index 8c352f2..36bcf7e 100644 --- a/tests/ResourceDocumentTest.php +++ b/tests/ResourceDocumentTest.php @@ -132,6 +132,19 @@ public function testAddMeta_RecreateJsonapiObject() { $this->assertSame('jsonapi', $array['jsonapi']['meta']['baz']); } + public function testSetLocalId_HappyPath() { + $document = new ResourceDocument(); + $document->setType('user'); + $document->setLocalId('42'); + + $array = $document->toArray(); + + $this->assertArrayHasKey('data', $array); + $this->assertArrayHasKey('lid', $array['data']); + $this->assertArrayNotHasKey('id', $array['data']); + $this->assertSame('42', $array['data']['lid']); + } + public function testAddRelationshipObject_WithIncluded() { $resourceObject = new ResourceObject('user', 42); $resourceObject->add('foo', 'bar'); diff --git a/tests/SeparateProcessTest.php b/tests/SeparateProcessTest.php index 0582826..ec00ccd 100644 --- a/tests/SeparateProcessTest.php +++ b/tests/SeparateProcessTest.php @@ -2,8 +2,8 @@ namespace alsvanzelf\jsonapiTests; -use alsvanzelf\jsonapi\objects\ProfileLinkObject; use alsvanzelf\jsonapiTests\TestableNonAbstractDocument as Document; +use alsvanzelf\jsonapiTests\extensions\TestExtension; use alsvanzelf\jsonapiTests\profiles\TestProfile; use PHPUnit\Framework\TestCase; @@ -75,6 +75,39 @@ public function testSendResponse_ContentTypeHeader() { /** * @runInSeparateProcess + * @group Extensions + */ + public function testSendResponse_ContentTypeHeaderWithExtensions() { + if (extension_loaded('xdebug') === false) { + $this->markTestSkipped('can not run without xdebug'); + } + + $extension = new TestExtension(); + $extension->setNamespace('one'); + $extension->setOfficialLink('https://jsonapi.org'); + + $document = new Document(); + $document->applyExtension($extension); + + ob_start(); + $document->sendResponse(); + ob_end_clean(); + $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.'; ext="https://jsonapi.org"'], xdebug_get_headers()); + + $extension = new TestExtension(); + $extension->setNamespace('two'); + $extension->setOfficialLink('https://jsonapi.org/2'); + $document->applyExtension($extension); + + ob_start(); + $document->sendResponse(); + ob_end_clean(); + $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.'; ext="https://jsonapi.org https://jsonapi.org/2"'], xdebug_get_headers()); + } + + /** + * @runInSeparateProcess + * @group Profiles */ public function testSendResponse_ContentTypeHeaderWithProfiles() { if (extension_loaded('xdebug') === false) { @@ -82,7 +115,7 @@ public function testSendResponse_ContentTypeHeaderWithProfiles() { } $profile = new TestProfile(); - $profile->setAliasedLink('https://jsonapi.org'); + $profile->setOfficialLink('https://jsonapi.org'); $document = new Document(); $document->applyProfile($profile); @@ -90,16 +123,16 @@ public function testSendResponse_ContentTypeHeaderWithProfiles() { ob_start(); $document->sendResponse(); ob_end_clean(); - $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.';profile="https://jsonapi.org", '.Document::CONTENT_TYPE_OFFICIAL], xdebug_get_headers()); + $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.'; profile="https://jsonapi.org"'], xdebug_get_headers()); $profile = new TestProfile(); - $profile->setAliasedLink('https://jsonapi.org/2'); + $profile->setOfficialLink('https://jsonapi.org/2'); $document->applyProfile($profile); ob_start(); $document->sendResponse(); ob_end_clean(); - $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.';profile="https://jsonapi.org https://jsonapi.org/2", '.Document::CONTENT_TYPE_OFFICIAL], xdebug_get_headers()); + $this->assertSame(['Content-Type: '.Document::CONTENT_TYPE_OFFICIAL.'; profile="https://jsonapi.org https://jsonapi.org/2"'], xdebug_get_headers()); } /** diff --git a/tests/example_output/ExampleEverywhereExtension.php b/tests/example_output/ExampleEverywhereExtension.php new file mode 100644 index 0000000..885e26b --- /dev/null +++ b/tests/example_output/ExampleEverywhereExtension.php @@ -0,0 +1,15 @@ +format(\DateTime::ISO8601); + } + if ($updated !== null) { + $timestamps['updated'] = $updated->format(\DateTime::ISO8601); + } + + $resource->add('timestamps', $timestamps); + } +} diff --git a/tests/example_output/ExampleVersionExtension.php b/tests/example_output/ExampleVersionExtension.php new file mode 100644 index 0000000..bde454a --- /dev/null +++ b/tests/example_output/ExampleVersionExtension.php @@ -0,0 +1,26 @@ +getResource()->addExtensionMember($this, 'id', $version); + } + else { + $resource->addExtensionMember($this, 'id', $version); + } + } +} diff --git a/tests/example_output/ExampleVersionProfile.php b/tests/example_output/ExampleVersionProfile.php deleted file mode 100644 index 824a2e6..0000000 --- a/tests/example_output/ExampleVersionProfile.php +++ /dev/null @@ -1,36 +0,0 @@ -addMeta($this->getKeyword('version'), $version, $level=Document::LEVEL_RESOURCE); - } - else { - $resource->addMeta($this->getKeyword('version'), $version); - } - } -} diff --git a/tests/example_output/at_members_everywhere/at_members_everywhere.json b/tests/example_output/at_members_everywhere/at_members_everywhere.json index feb9aea..76638c7 100644 --- a/tests/example_output/at_members_everywhere/at_members_everywhere.json +++ b/tests/example_output/at_members_everywhere/at_members_everywhere.json @@ -21,9 +21,9 @@ "@context": "/meta/@context" }, "data": { - "@context": "/data/@context", "type": "user", "id": "42", + "@context": "/data/@context", "meta": { "@context": "/data/meta/@context" }, @@ -48,9 +48,9 @@ "bar": { "@context": "/data/relationships/bar/@context", "data": { - "@context": "/data/relationships/bar/data/@context", "type": "user", "id": "2", + "@context": "/data/relationships/bar/data/@context", "meta": { "@context": "/data/relationships/bar/data/meta/@context" } @@ -70,17 +70,17 @@ }, "included": [ { - "@context": "/included/0/@context", "type": "user", "id": "1", + "@context": "/included/0/@context", "attributes": { "@context": "/included/0/attributes/@context" } }, { - "@context": "/included/1/@context", "type": "user", "id": "3", + "@context": "/included/1/@context", "relationships": { "@context": "/included/1/relationships/@context" } diff --git a/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.json b/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.json index cb84fda..02b81ad 100644 --- a/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.json +++ b/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.json @@ -1,11 +1,11 @@ { "jsonapi": { - "version": "1.1" - }, - "links": { + "version": "1.1", "profile": [ "https://jsonapi.org/profiles/ethanresnick/cursor-pagination/" - ], + ] + }, + "links": { "prev": null, "next": { "href": "/users?sort=42&page[size]=10&page[after]=zaphod" diff --git a/tests/example_output/errors_all_options/errors_all_options.json b/tests/example_output/errors_all_options/errors_all_options.json index b0cd4a3..11c8e33 100644 --- a/tests/example_output/errors_all_options/errors_all_options.json +++ b/tests/example_output/errors_all_options/errors_all_options.json @@ -17,9 +17,7 @@ "detail": "Please, choose a bit less. Consult your ...", "links": { "about": "https://www.example.com/explanation.html", - "type": [ - "https://www.example.com/documentation.html" - ] + "type": "https://www.example.com/documentation.html" } }, { @@ -35,14 +33,12 @@ "foo": "bar" } }, - "type": [ - { - "href": "https://www.example.com/documentation.html", - "meta": { - "foo": "bar" - } + "type": { + "href": "https://www.example.com/documentation.html", + "meta": { + "foo": "bar" } - ] + } }, "source": { "pointer": "/data/attributes/title", diff --git a/tests/example_output/errors_all_options/errors_all_options.php b/tests/example_output/errors_all_options/errors_all_options.php index 7ca6e96..f0a4076 100644 --- a/tests/example_output/errors_all_options/errors_all_options.php +++ b/tests/example_output/errors_all_options/errors_all_options.php @@ -19,7 +19,7 @@ public static function createJsonapiDocument() { $errorSpecApi->setHumanTitle($genericTitle='Too much options'); $errorSpecApi->setHumanDetails($specificDetails='Please, choose a bit less. Consult your ...'); $errorSpecApi->setAboutLink($specificAboutLink='https://www.example.com/explanation.html', ['foo'=>'bar']); - $errorSpecApi->appendTypeLink($genericTypeLink='https://www.example.com/documentation.html', ['foo'=>'bar']); + $errorSpecApi->setTypeLink($genericTypeLink='https://www.example.com/documentation.html', ['foo'=>'bar']); $metaObject = new \stdClass(); $metaObject->property = 'value'; diff --git a/tests/example_output/example_profile/example_profile.json b/tests/example_output/example_profile/example_profile.json deleted file mode 100644 index e7ee9ff..0000000 --- a/tests/example_output/example_profile/example_profile.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "jsonapi": { - "version": "1.1" - }, - "links": { - "profile": [ - { - "href": "https://jsonapi.org/format/1.1/#profile-keywords-and-aliases", - "aliases": { - "version": "ref" - } - } - ] - }, - "data": { - "type": "user", - "id": "42", - "meta": { - "ref": "2019" - } - } -} diff --git a/tests/example_output/example_profile/example_profile.php b/tests/example_output/example_profile/example_profile.php deleted file mode 100644 index c881f25..0000000 --- a/tests/example_output/example_profile/example_profile.php +++ /dev/null @@ -1,19 +0,0 @@ - 'ref']); - - $document = new ResourceDocument('user', 42); - $document->applyProfile($profile); - - $profile->setVersion($document, '2019'); - - return $document; - } -} diff --git a/tests/example_output/extension/extension.json b/tests/example_output/extension/extension.json new file mode 100644 index 0000000..4b857c0 --- /dev/null +++ b/tests/example_output/extension/extension.json @@ -0,0 +1,13 @@ +{ + "jsonapi": { + "version": "1.1", + "ext": [ + "https://jsonapi.org/format/1.1/#extension-rules" + ] + }, + "data": { + "type": "user", + "id": "42", + "version:id": "2019" + } +} diff --git a/tests/example_output/extension/extension.php b/tests/example_output/extension/extension.php new file mode 100644 index 0000000..4535c63 --- /dev/null +++ b/tests/example_output/extension/extension.php @@ -0,0 +1,19 @@ +applyExtension($extension); + + $extension->setVersion($document, '2019'); + + return $document; + } +} diff --git a/tests/example_output/extension_members_everywhere/extension_members_everywhere.json b/tests/example_output/extension_members_everywhere/extension_members_everywhere.json new file mode 100644 index 0000000..f41faa3 --- /dev/null +++ b/tests/example_output/extension_members_everywhere/extension_members_everywhere.json @@ -0,0 +1,89 @@ +{ + "everywhere:key": "/key", + "jsonapi": { + "everywhere:key": "/jsonapi/key", + "version": "1.1", + "meta": { + "everywhere:key": "/jsonapi/meta/key" + } + }, + "links": { + "everywhere:key": "/links/key", + "foo": { + "everywhere:key": "/links/foo/key", + "href": "https://jsonapi.org", + "meta": { + "everywhere:key": "/links/foo/meta/key" + } + } + }, + "meta": { + "everywhere:key": "/meta/key" + }, + "data": { + "type": "user", + "id": "42", + "everywhere:key": "/data/key", + "meta": { + "everywhere:key": "/data/meta/key" + }, + "attributes": { + "everywhere:key": "/data/attributes/key" + }, + "relationships": { + "everywhere:key": "/data/relationships/key", + "foo": { + "everywhere:key": "/data/relationships/foo/key", + "links": { + "everywhere:key": "/data/relationships/foo/links/key" + }, + "data": { + "type": "user", + "id": "1" + }, + "meta": { + "everywhere:key": "/data/relationships/foo/meta/key" + } + }, + "bar": { + "everywhere:key": "/data/relationships/bar/key", + "data": { + "type": "user", + "id": "2", + "everywhere:key": "/data/relationships/bar/data/key", + "meta": { + "everywhere:key": "/data/relationships/bar/data/meta/key" + } + } + } + }, + "links": { + "everywhere:key": "/data/links/key", + "foo": { + "everywhere:key": "/data/links/foo/key", + "href": "https://jsonapi.org", + "meta": { + "everywhere:key": "/data/links/foo/meta/key" + } + } + } + }, + "included": [ + { + "type": "user", + "id": "1", + "everywhere:key": "/included/0/key", + "attributes": { + "everywhere:key": "/included/0/attributes/key" + } + }, + { + "type": "user", + "id": "3", + "everywhere:key": "/included/1/key", + "relationships": { + "everywhere:key": "/included/1/relationships/key" + } + } + ] +} diff --git a/tests/example_output/extension_members_everywhere/extension_members_everywhere.php b/tests/example_output/extension_members_everywhere/extension_members_everywhere.php new file mode 100644 index 0000000..f7747f2 --- /dev/null +++ b/tests/example_output/extension_members_everywhere/extension_members_everywhere.php @@ -0,0 +1,173 @@ +applyExtension($extension); + + /** + * root + */ + + $document->addExtensionMember($extension, 'key', '/key'); + + /** + * jsonapi + */ + + $metaObject = new MetaObject(); + $metaObject->addExtensionMember($extension, 'key', '/jsonapi/meta/key'); + + $jsonapiObject = new JsonapiObject(); + $jsonapiObject->addExtensionMember($extension, 'key', '/jsonapi/key'); + $jsonapiObject->setMetaObject($metaObject); + $document->setJsonapiObject($jsonapiObject); + + /** + * links + */ + + $metaObject = new MetaObject(); + $metaObject->addExtensionMember($extension, 'key', '/links/foo/meta/key'); + + $linkObject = new LinkObject('https://jsonapi.org'); + $linkObject->addExtensionMember($extension, 'key', '/links/foo/key'); + $linkObject->setMetaObject($metaObject); + + $linksObject = new LinksObject(); + $linksObject->addExtensionMember($extension, 'key', '/links/key'); + $linksObject->addLinkObject('foo', $linkObject); + $document->setLinksObject($linksObject); + + /** + * meta + */ + + $metaObject = new MetaObject(); + $metaObject->addExtensionMember($extension, 'key', '/meta/key'); + $document->setMetaObject($metaObject); + + /** + * resource + */ + + /** + * resource - relationships + * + * @todo make it work to have extension members in both the identifier and the resource parts + * e.g. it is missing in the data of the first relationship (`data.relationships.foo.data.key`) + * whereas it does appear in the second relationship (`data.relationships.bar.data.key`) + * @see https://github.com/json-api/json-api/issues/1367 + */ + + $relationshipsObject = new RelationshipsObject(); + $relationshipsObject->addExtensionMember($extension, 'key', '/data/relationships/key'); + + $attributesObject = new AttributesObject(); + $attributesObject->addExtensionMember($extension, 'key', '/included/0/attributes/key'); + + $resourceObject = new ResourceObject('user', 1); + $resourceObject->addExtensionMember($extension, 'key', '/included/0/key'); + $resourceObject->setAttributesObject($attributesObject); + + $linksObject = new LinksObject(); + $linksObject->addExtensionMember($extension, 'key', '/data/relationships/foo/links/key'); + + $metaObject = new MetaObject(); + $metaObject->addExtensionMember($extension, 'key', '/data/relationships/foo/meta/key'); + + $relationshipObject = new RelationshipObject(RelationshipObject::TO_ONE); + $relationshipObject->addExtensionMember($extension, 'key', '/data/relationships/foo/key'); + $relationshipObject->setResource($resourceObject); + $relationshipObject->setLinksObject($linksObject); + $relationshipObject->setMetaObject($metaObject); + + $relationshipsObject->addRelationshipObject('foo', $relationshipObject); + + $metaObject = new MetaObject(); + $metaObject->addExtensionMember($extension, 'key', '/data/relationships/bar/data/meta/key'); + + $resourceIdentifierObject = new ResourceIdentifierObject('user', 2); + $resourceIdentifierObject->addExtensionMember($extension, 'key', '/data/relationships/bar/data/key'); + $resourceIdentifierObject->setMetaObject($metaObject); + + $relationshipObject = new RelationshipObject(RelationshipObject::TO_ONE); + $relationshipObject->addExtensionMember($extension, 'key', '/data/relationships/bar/key'); + $relationshipObject->setResource($resourceIdentifierObject); + + $relationshipsObject->addRelationshipObject('bar', $relationshipObject); + + /** + * resource - attributes + */ + + $attributesObject = new AttributesObject(); + $attributesObject->addExtensionMember($extension, 'key', '/data/attributes/key'); + + /** + * resource - links + */ + + $metaObject = new MetaObject(); + $metaObject->addExtensionMember($extension, 'key', '/data/links/foo/meta/key'); + + $linkObject = new LinkObject('https://jsonapi.org'); + $linkObject->addExtensionMember($extension, 'key', '/data/links/foo/key'); + $linkObject->setMetaObject($metaObject); + + $linksObject = new LinksObject(); + $linksObject->addExtensionMember($extension, 'key', '/data/links/key'); + $linksObject->addLinkObject('foo', $linkObject); + + /** + * resource - meta + */ + + $metaObject = new MetaObject(); + $metaObject->addExtensionMember($extension, 'key', '/data/meta/key'); + + /** + * resource - resource + */ + + $resourceObject = new ResourceObject('user', 42); + $resourceObject->addExtensionMember($extension, 'key', '/data/key'); + $resourceObject->setAttributesObject($attributesObject); + $resourceObject->setLinksObject($linksObject); + $resourceObject->setMetaObject($metaObject); + $resourceObject->setRelationshipsObject($relationshipsObject); + + $document->setPrimaryResource($resourceObject); + + /** + * included + */ + + $relationshipsObject = new RelationshipsObject(); + $relationshipsObject->addExtensionMember($extension, 'key', '/included/1/relationships/key'); + + $resourceObject = new ResourceObject('user', 3); + $resourceObject->addExtensionMember($extension, 'key', '/included/1/key'); + $resourceObject->setRelationshipsObject($relationshipsObject); + + $document->addIncludedResourceObject($resourceObject); + + return $document; + } +} diff --git a/tests/example_output/profile/profile.json b/tests/example_output/profile/profile.json new file mode 100644 index 0000000..49d5808 --- /dev/null +++ b/tests/example_output/profile/profile.json @@ -0,0 +1,18 @@ +{ + "jsonapi": { + "version": "1.1", + "profile": [ + "https://jsonapi.org/recommendations/#authoring-profiles" + ] + }, + "data": { + "type": "user", + "id": "42", + "attributes": { + "timestamps": { + "created": "2019-01-01T00:00:00+0000", + "updated": "2021-01-01T00:00:00+0000" + } + } + } +} diff --git a/tests/example_output/profile/profile.php b/tests/example_output/profile/profile.php new file mode 100644 index 0000000..16deba8 --- /dev/null +++ b/tests/example_output/profile/profile.php @@ -0,0 +1,19 @@ +applyProfile($profile); + + $profile->setTimestamps($document, new \DateTime('2019-01-01T00:00:00+0000'), new \DateTime('2021-01-01T00:00:00+0000')); + + return $document; + } +} diff --git a/tests/extensions/AtomicOperationsDocumentTest.php b/tests/extensions/AtomicOperationsDocumentTest.php new file mode 100644 index 0000000..e1bac4f --- /dev/null +++ b/tests/extensions/AtomicOperationsDocumentTest.php @@ -0,0 +1,59 @@ +add('name', 'Ford'); + $resource2->add('name', 'Arthur'); + $resource3->add('name', 'Zaphod'); + $document->addResults($resource1, $resource2, $resource3); + $document->setSelfLink('https://example.org/operations'); + + $array = $document->toArray(); + + $this->assertArrayHasKey('jsonapi', $array); + $this->assertArrayHasKey('ext', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['ext']); + $this->assertSame((new AtomicOperationsExtension())->getOfficialLink(), $array['jsonapi']['ext'][0]); + + $this->assertArrayHasKey('links', $array); + $this->assertArrayHasKey('self', $array['links']); + $this->assertArrayHasKey('href', $array['links']['self']); + $this->assertArrayHasKey('type', $array['links']['self']); + $this->assertSame('https://example.org/operations', $array['links']['self']['href']); + $this->assertSame('application/vnd.api+json; ext="'.(new AtomicOperationsExtension())->getOfficialLink().'"', $array['links']['self']['type']); + + $this->assertArrayHasKey('atomic:results', $array); + $this->assertCount(3, $array['atomic:results']); + $this->assertSame(['data' => $resource1->toArray()], $array['atomic:results'][0]); + $this->assertSame(['data' => $resource2->toArray()], $array['atomic:results'][1]); + $this->assertSame(['data' => $resource3->toArray()], $array['atomic:results'][2]); + } + + public function testSetResults_EmptySuccessResults() { + $document = new AtomicOperationsDocument(); + $array = $document->toArray(); + + $this->assertArrayHasKey('jsonapi', $array); + $this->assertArrayHasKey('ext', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['ext']); + $this->assertSame((new AtomicOperationsExtension())->getOfficialLink(), $array['jsonapi']['ext'][0]); + + $this->assertArrayHasKey('atomic:results', $array); + $this->assertCount(0, $array['atomic:results']); + } +} diff --git a/tests/extensions/TestExtension.php b/tests/extensions/TestExtension.php new file mode 100644 index 0000000..9b57f9e --- /dev/null +++ b/tests/extensions/TestExtension.php @@ -0,0 +1,26 @@ +namespace = $namespace; + } + + public function setOfficialLink($officialLink) { + $this->officialLink = $officialLink; + } + + public function getNamespace() { + return $this->namespace; + } + + public function getOfficialLink() { + return $this->officialLink; + } +} diff --git a/tests/helpers/ExtensionMemberManagerTest.php b/tests/helpers/ExtensionMemberManagerTest.php new file mode 100644 index 0000000..745cd64 --- /dev/null +++ b/tests/helpers/ExtensionMemberManagerTest.php @@ -0,0 +1,70 @@ +setNamespace('test'); + + $this->assertFalse($helper->hasExtensionMembers()); + $this->assertSame([], $helper->getExtensionMembers()); + + $helper->addExtensionMember($extension, 'foo', 'bar'); + + $array = $helper->getExtensionMembers(); + + $this->assertTrue($helper->hasExtensionMembers()); + $this->assertCount(1, $array); + $this->assertArrayHasKey('test:foo', $array); + $this->assertSame('bar', $array['test:foo']); + } + + public function testAddExtensionMember_WithNamespacePrefixed() { + $helper = new ExtensionMemberManager(); + $extension = new TestExtension(); + $extension->setNamespace('test'); + + $helper->addExtensionMember($extension, 'test:foo', 'bar'); + + $array = $helper->getExtensionMembers(); + + $this->assertArrayHasKey('test:foo', $array); + } + + public function testAddExtensionMember_WithObjectValue() { + $helper = new ExtensionMemberManager(); + $extension = new TestExtension(); + $extension->setNamespace('test'); + + $object = new \stdClass(); + $object->bar = 'baz'; + + $helper->addExtensionMember($extension, 'foo', $object); + + $array = $helper->getExtensionMembers(); + + $this->assertArrayHasKey('test:foo', $array); + $this->assertArrayHasKey('bar', $array['test:foo']); + $this->assertSame('baz', $array['test:foo']['bar']); + } + + public function testAddExtensionMember_InvalidNamespaceOrCharacter() { + $helper = new ExtensionMemberManager(); + $extension = new TestExtension(); + $extension->setNamespace('test'); + + $this->expectException(InputException::class); + + $helper->addExtensionMember($extension, 'foo:bar', 'baz'); + } +} diff --git a/tests/helpers/LinksManagerTest.php b/tests/helpers/LinksManagerTest.php index 750ae69..be87fa2 100644 --- a/tests/helpers/LinksManagerTest.php +++ b/tests/helpers/LinksManagerTest.php @@ -82,6 +82,9 @@ public function testAddLinkObject_HappyPath() { $this->assertSame('https://jsonapi.org', $array['foo']['href']); } + /** + * @deprecated array links are not supported anymore + */ public function testAddLinksArray_HappyPath() { $linksArray = new LinksArray(); $linksArray->add('https://jsonapi.org'); @@ -98,6 +101,9 @@ public function testAddLinksArray_HappyPath() { $this->assertSame('https://jsonapi.org', $array['foo'][0]); } + /** + * @deprecated array links are not supported anymore + */ public function testAppendLinkObject_HappyPath() { $linksManager = new LinksManager(); $linksManager->addLinksArray('foo', LinksArray::fromArray(['https://jsonapi.org/1'])); diff --git a/tests/helpers/ProfileAliasManagerTest.php b/tests/helpers/ProfileAliasManagerTest.php deleted file mode 100644 index fe086f3..0000000 --- a/tests/helpers/ProfileAliasManagerTest.php +++ /dev/null @@ -1,87 +0,0 @@ -assertSame([], $profileAliasManager->getAliasMapping()); - $this->assertSame(['foo' => 'foo', 'bar' => 'bar'], $profileAliasManager->getKeywordMapping()); - } - - public function testConstructor_WithAliases() { - $profileAliasManager = new ProfileAliasManager(['bar' => 'baz']); - - $this->assertSame(['bar' => 'baz'], $profileAliasManager->getAliasMapping()); - $this->assertSame(['foo' => 'foo', 'bar' => 'baz'], $profileAliasManager->getKeywordMapping()); - } - - public function testConstructor_WithoutOfficialKeywords() { - $profileAliasManager = new ProfileAliasManager_WithoutKeywords(); - - $this->assertSame([], $profileAliasManager->getAliasMapping()); - $this->assertSame([], $profileAliasManager->getKeywordMapping()); - } - - public function testConstructor_NonAdjustedAliases() { - $this->expectException(InputException::class); - - new ProfileAliasManager(['foo' => 'foo']); - } - - public function testConstructor_NonExistingKeyword() { - $this->expectException(InputException::class); - - new ProfileAliasManager(['baz' => 'bar']); - } - - public function testGetKeyword_HappyPath() { - $profileAliasManager = new ProfileAliasManager(['bar' => 'baz']); - - $this->assertSame('foo', $profileAliasManager->getKeyword('foo')); - $this->assertSame('baz', $profileAliasManager->getKeyword('bar')); - } - - public function testGetKeyword_NonExistingKeyword() { - $profileAliasManager = new ProfileAliasManager(); - - $this->expectException(InputException::class); - - $profileAliasManager->getKeyword('baz'); - } - - public function testGetAliasedLink_HappyPath() { - $profileAliasManager = new ProfileAliasManager(); - - if (method_exists($this, 'assertIsString')) { - $this->assertIsString($profileAliasManager->getAliasedLink()); - } - else { - $this->assertInternalType('string', $profileAliasManager->getAliasedLink()); - } - $this->assertSame('https://jsonapi.org', $profileAliasManager->getAliasedLink()); - } - - public function testGetAliasedLink_ObjectWithAliases() { - $profileAliasManager = new ProfileAliasManager(['bar' => 'baz']); - - $this->assertInstanceOf(ProfileLinkObject::class, $profileAliasManager->getAliasedLink()); - - $array = $profileAliasManager->getAliasedLink()->toArray(); - - $this->assertArrayHasKey('href', $array); - $this->assertSame('https://jsonapi.org', $array['href']); - - $this->assertArrayHasKey('aliases', $array); - $this->assertCount(1, $array['aliases']); - $this->assertArrayHasKey('bar', $array['aliases']); - $this->assertSame('baz', $array['aliases']['bar']); - } -} diff --git a/tests/helpers/RequestParserTest.php b/tests/helpers/RequestParserTest.php index 6410c78..fd17061 100644 --- a/tests/helpers/RequestParserTest.php +++ b/tests/helpers/RequestParserTest.php @@ -336,6 +336,31 @@ public function testGetFilter() { $this->assertSame(['foo' => 'bar'], $requestParser->getFilter()); } + public function testHasLocalId() { + $document = [ + 'data' => [ + 'id' => 'foo', + ], + ]; + $requestParser = new RequestParser($selfLink='', $quaryParameters=[], $document); + + $this->assertArrayHasKey('data', $requestParser->getDocument()); + $this->assertArrayHasKey('id', $requestParser->getDocument()['data']); + $this->assertFalse($requestParser->hasLocalId()); + + $document = [ + 'data' => [ + 'lid' => 'foo', + ], + ]; + $requestParser = new RequestParser($selfLink='', $quaryParameters=[], $document); + + $this->assertArrayHasKey('data', $requestParser->getDocument()); + $this->assertArrayNotHasKey('id', $requestParser->getDocument()['data']); + $this->assertTrue($requestParser->hasLocalId()); + $this->assertSame('foo', $requestParser->getLocalId()); + } + public function testHasAttribute() { $requestParser = new RequestParser(); $this->assertFalse($requestParser->hasAttribute('foo')); diff --git a/tests/helpers/TestableNonAbstractProfileAliasManager.php b/tests/helpers/TestableNonAbstractProfileAliasManager.php deleted file mode 100644 index e73d7dd..0000000 --- a/tests/helpers/TestableNonAbstractProfileAliasManager.php +++ /dev/null @@ -1,39 +0,0 @@ -setAccessible(true); - - return $aliasMapping->getValue($this); - } - - public function getKeywordMapping() { - $keywordMapping = new \ReflectionProperty(ProfileAliasManager::class, 'keywordMapping'); - $keywordMapping->setAccessible(true); - - return $keywordMapping->getValue($this); - } - - public function getOfficialKeywords() { - return ['foo', 'bar']; - } - - public function getOfficialLink() { - return 'https://jsonapi.org'; - } -} - -class TestableNonAbstractProfileAliasManager_WithoutKeywords extends TestableNonAbstractProfileAliasManager { - public function getOfficialKeywords() { - return []; - } -} diff --git a/tests/helpers/TestableNonTraitExtensionMemberManager.php b/tests/helpers/TestableNonTraitExtensionMemberManager.php new file mode 100644 index 0000000..516f103 --- /dev/null +++ b/tests/helpers/TestableNonTraitExtensionMemberManager.php @@ -0,0 +1,12 @@ +assertTrue($errorObject->isEmpty()); + + $errorObject->appendTypeLink('https://jsonapi.org'); + + $this->assertFalse($errorObject->isEmpty()); + + $array = $errorObject->toArray(); + + $this->assertArrayHasKey('links', $array); + $this->assertArrayHasKey('type', $array['links']); + $this->assertSame(['https://jsonapi.org'], $array['links']['type']); + } + public function testIsEmpty_All() { $errorObject = new ErrorObject(); $this->assertTrue($errorObject->isEmpty()); @@ -152,6 +171,28 @@ public function testIsEmpty_All() { $errorObject = new ErrorObject(); $errorObject->addAtMember('context', 'test'); $this->assertFalse($errorObject->isEmpty()); + + $errorObject = new ErrorObject(); + $errorObject->addExtensionMember(new TestExtension(), 'foo', 'bar'); + $this->assertFalse($errorObject->isEmpty()); + } + + /** + * @group Extensions + */ + public function testToArray_WithExtensionMembers() { + $errorObject = new ErrorObject(); + $extension = new TestExtension(); + $extension->setNamespace('test'); + + $this->assertSame([], $errorObject->toArray()); + + $errorObject->addExtensionMember($extension, 'foo', 'bar'); + + $array = $errorObject->toArray(); + + $this->assertArrayHasKey('test:foo', $array); + $this->assertSame('bar', $array['test:foo']); } } diff --git a/tests/objects/JsonapiObjectTest.php b/tests/objects/JsonapiObjectTest.php index 0807a19..882c708 100644 --- a/tests/objects/JsonapiObjectTest.php +++ b/tests/objects/JsonapiObjectTest.php @@ -3,6 +3,8 @@ namespace alsvanzelf\jsonapiTests\objects; use alsvanzelf\jsonapi\objects\JsonapiObject; +use alsvanzelf\jsonapiTests\extensions\TestExtension; +use alsvanzelf\jsonapiTests\profiles\TestProfile; use PHPUnit\Framework\TestCase; class JsonapiObjectTest extends TestCase { @@ -31,4 +33,43 @@ public function testIsEmpty_WithAtMembers() { $this->assertFalse($jsonapiObject->isEmpty()); } + + /** + * @group Extensions + */ + public function testIsEmpty_WithExtensionLink() { + $jsonapiObject = new JsonapiObject($version=null); + + $this->assertTrue($jsonapiObject->isEmpty()); + + $jsonapiObject->addExtension(new TestExtension()); + + $this->assertFalse($jsonapiObject->isEmpty()); + } + + /** + * @group Profiles + */ + public function testIsEmpty_WithProfileLink() { + $jsonapiObject = new JsonapiObject($version=null); + + $this->assertTrue($jsonapiObject->isEmpty()); + + $jsonapiObject->addProfile(new TestProfile()); + + $this->assertFalse($jsonapiObject->isEmpty()); + } + + /** + * @group Extensions + */ + public function testIsEmpty_WithExtensionMembers() { + $jsonapiObject = new JsonapiObject($version=null); + + $this->assertTrue($jsonapiObject->isEmpty()); + + $jsonapiObject->addExtensionMember(new TestExtension(), 'foo', 'bar'); + + $this->assertFalse($jsonapiObject->isEmpty()); + } } diff --git a/tests/objects/LinkObjectTest.php b/tests/objects/LinkObjectTest.php index bdf7e89..fd356c9 100644 --- a/tests/objects/LinkObjectTest.php +++ b/tests/objects/LinkObjectTest.php @@ -3,9 +3,53 @@ namespace alsvanzelf\jsonapiTests\objects; use alsvanzelf\jsonapi\objects\LinkObject; +use alsvanzelf\jsonapiTests\extensions\TestExtension; use PHPUnit\Framework\TestCase; class LinkObjectTest extends TestCase { + public function testSetDescribedBy_HappyPath() { + $linkObject = new LinkObject(); + + $this->assertTrue($linkObject->isEmpty()); + + $linkObject->setDescribedBy('https://jsonapi.org'); + + $this->assertFalse($linkObject->isEmpty()); + + $array = $linkObject->toArray(); + + $this->assertArrayHasKey('describedby', $array); + $this->assertArrayHasKey('href', $array['describedby']); + $this->assertSame('https://jsonapi.org', $array['describedby']['href']); + } + + public function testAddLanguage_HappyPath() { + $linkObject = new LinkObject(); + + $this->assertTrue($linkObject->isEmpty()); + + $linkObject->addLanguage('nl-NL'); + + $this->assertFalse($linkObject->isEmpty()); + + $array = $linkObject->toArray(); + + $this->assertArrayHasKey('hreflang', $array); + $this->assertSame('nl-NL', $array['hreflang']); + } + + public function testAddLanguage_Multiple() { + $linkObject = new LinkObject(); + + $linkObject->addLanguage('nl-NL'); + $array = $linkObject->toArray(); + $this->assertSame('nl-NL', $array['hreflang']); + + $linkObject->addLanguage('en-US'); + $array = $linkObject->toArray(); + $this->assertSame(['nl-NL', 'en-US'], $array['hreflang']); + } + public function testAddMeta_HappyPath() { $linkObject = new LinkObject(); @@ -22,6 +66,83 @@ public function testAddMeta_HappyPath() { $this->assertSame('bar', $array['meta']['foo']); } + public function testSetRelationType_HappyPath() { + $linkObject = new LinkObject(); + + $this->assertTrue($linkObject->isEmpty()); + + $linkObject->setRelationType('external'); + + $this->assertFalse($linkObject->isEmpty()); + + $array = $linkObject->toArray(); + + $this->assertArrayHasKey('rel', $array); + $this->assertSame('external', $array['rel']); + } + + public function testSetDescribedByLinkObject_HappyPath() { + $linkObject = new LinkObject(); + + $this->assertTrue($linkObject->isEmpty()); + + $describedBy = new LinkObject('https://jsonapi.org'); + $linkObject->setDescribedByLinkObject($describedBy); + + $this->assertFalse($linkObject->isEmpty()); + + $array = $linkObject->toArray(); + + $this->assertArrayHasKey('describedby', $array); + $this->assertArrayHasKey('href', $array['describedby']); + $this->assertSame('https://jsonapi.org', $array['describedby']['href']); + } + + public function testSetHumanTitle_HappyPath() { + $linkObject = new LinkObject(); + + $this->assertTrue($linkObject->isEmpty()); + + $linkObject->setHumanTitle('A link'); + + $this->assertFalse($linkObject->isEmpty()); + + $array = $linkObject->toArray(); + + $this->assertArrayHasKey('title', $array); + $this->assertSame('A link', $array['title']); + } + + public function testSetMediaType_HappyPath() { + $linkObject = new LinkObject(); + + $this->assertTrue($linkObject->isEmpty()); + + $linkObject->setMediaType('text/html'); + + $this->assertFalse($linkObject->isEmpty()); + + $array = $linkObject->toArray(); + + $this->assertArrayHasKey('type', $array); + $this->assertSame('text/html', $array['type']); + } + + public function testSetHreflang_HappyPath() { + $linkObject = new LinkObject(); + + $this->assertTrue($linkObject->isEmpty()); + + $linkObject->setHreflang('nl-NL', 'en-US'); + + $this->assertFalse($linkObject->isEmpty()); + + $array = $linkObject->toArray(); + + $this->assertArrayHasKey('hreflang', $array); + $this->assertSame(['nl-NL', 'en-US'], $array['hreflang']); + } + public function testIsEmpty_WithAtMembers() { $linkObject = new LinkObject(); @@ -31,4 +152,17 @@ public function testIsEmpty_WithAtMembers() { $this->assertFalse($linkObject->isEmpty()); } + + /** + * @group Extensions + */ + public function testIsEmpty_WithExtensionMembers() { + $linkObject = new LinkObject(); + + $this->assertTrue($linkObject->isEmpty()); + + $linkObject->addExtensionMember(new TestExtension(), 'foo', 'bar'); + + $this->assertFalse($linkObject->isEmpty()); + } } diff --git a/tests/objects/LinksArrayTest.php b/tests/objects/LinksArrayTest.php index 5b9945c..f0d0c7d 100644 --- a/tests/objects/LinksArrayTest.php +++ b/tests/objects/LinksArrayTest.php @@ -5,6 +5,9 @@ use alsvanzelf\jsonapi\objects\LinksArray; use PHPUnit\Framework\TestCase; +/** + * @deprecated array links are not supported anymore + */ class LinksArrayTest extends TestCase { public function testFromObject_HappyPath() { $object = new \stdClass(); diff --git a/tests/objects/LinksObjectTest.php b/tests/objects/LinksObjectTest.php index ac7b166..ac2995f 100644 --- a/tests/objects/LinksObjectTest.php +++ b/tests/objects/LinksObjectTest.php @@ -109,6 +109,9 @@ public function testAddLinkObject_ExistingKey() { $linksObject->addLinkObject($key='foo', $linkObject); } + /** + * @deprecated array links are not supported anymore + */ public function testAddLinksArray_HappyPath() { $linksObject = new LinksObject(); $linksObject->addLinksArray('foo', LinksArray::fromArray(['https://jsonapi.org'])); @@ -122,6 +125,9 @@ public function testAddLinksArray_HappyPath() { $this->assertSame('https://jsonapi.org', $array['foo'][0]); } + /** + * @deprecated array links are not supported anymore + */ public function testAddLinksArray_BlocksReusingNonArray() { $linksObject = new LinksObject(); $linksObject->add('foo', 'https://jsonapi.org'); diff --git a/tests/objects/MetaObjectTest.php b/tests/objects/MetaObjectTest.php index 5014060..fc4cb6a 100644 --- a/tests/objects/MetaObjectTest.php +++ b/tests/objects/MetaObjectTest.php @@ -3,6 +3,7 @@ namespace alsvanzelf\jsonapiTests\objects; use alsvanzelf\jsonapi\objects\MetaObject; +use alsvanzelf\jsonapiTests\extensions\TestExtension; use PHPUnit\Framework\TestCase; class MetaObjectTest extends TestCase { @@ -18,4 +19,27 @@ public function testFromObject_HappyPath() { $this->assertArrayHasKey('foo', $array); $this->assertSame('bar', $array['foo']); } + + public function testIsEmpty_WithAtMembers() { + $metaObject = new MetaObject(); + + $this->assertTrue($metaObject->isEmpty()); + + $metaObject->addAtMember('context', 'test'); + + $this->assertFalse($metaObject->isEmpty()); + } + + /** + * @group Extensions + */ + public function testIsEmpty_WithExtensionMembers() { + $metaObject = new MetaObject(); + + $this->assertTrue($metaObject->isEmpty()); + + $metaObject->addExtensionMember(new TestExtension(), 'foo', 'bar'); + + $this->assertFalse($metaObject->isEmpty()); + } } diff --git a/tests/objects/RelationshipObjectTest.php b/tests/objects/RelationshipObjectTest.php index 346480a..e5a99fb 100644 --- a/tests/objects/RelationshipObjectTest.php +++ b/tests/objects/RelationshipObjectTest.php @@ -9,6 +9,7 @@ use alsvanzelf\jsonapi\objects\RelationshipObject; use alsvanzelf\jsonapi\objects\ResourceIdentifierObject; use alsvanzelf\jsonapi\objects\ResourceObject; +use alsvanzelf\jsonapiTests\extensions\TestExtension; use PHPUnit\Framework\TestCase; class RelationshipObjectTest extends TestCase { @@ -315,6 +316,19 @@ public function testIsEmpty_WithAtMembers() { $this->assertFalse($relationshipObject->isEmpty()); } + /** + * @group Extensions + */ + public function testIsEmpty_WithExtensionMembers() { + $relationshipObject = new RelationshipObject(RelationshipObject::TO_ONE); + + $this->assertTrue($relationshipObject->isEmpty()); + + $relationshipObject->addExtensionMember(new TestExtension(), 'foo', 'bar'); + + $this->assertFalse($relationshipObject->isEmpty()); + } + private function validateToOneRelationshipArray(array $array) { $this->assertNotEmpty($array); $this->assertArrayHasKey('data', $array); diff --git a/tests/objects/ResourceIdentifierObjectTest.php b/tests/objects/ResourceIdentifierObjectTest.php index 2508479..b7eb1ea 100644 --- a/tests/objects/ResourceIdentifierObjectTest.php +++ b/tests/objects/ResourceIdentifierObjectTest.php @@ -3,10 +3,56 @@ namespace alsvanzelf\jsonapiTests\objects; use alsvanzelf\jsonapi\exceptions\Exception; +use alsvanzelf\jsonapi\exceptions\DuplicateException; use alsvanzelf\jsonapi\objects\ResourceIdentifierObject; +use alsvanzelf\jsonapiTests\extensions\TestExtension; use PHPUnit\Framework\TestCase; class ResourceIdentifierObjectTest extends TestCase { + public function testSetId_HappyPath() { + $resourceIdentifierObject = new ResourceIdentifierObject(); + $resourceIdentifierObject->setType('test'); + $resourceIdentifierObject->setId('1'); + + $array = $resourceIdentifierObject->toArray(); + + $this->assertArrayHasKey('id', $array); + $this->assertArrayNotHasKey('lid', $array); + $this->assertSame('1', $array['id']); + } + + public function testSetId_WithLocalIdAlreadySet() { + $resourceIdentifierObject = new ResourceIdentifierObject(); + $resourceIdentifierObject->setType('test'); + $resourceIdentifierObject->setLocalId('uuid-1'); + + $this->expectException(DuplicateException::class); + + $resourceIdentifierObject->setId('1'); + } + + public function testSetLocalId_HappyPath() { + $resourceIdentifierObject = new ResourceIdentifierObject(); + $resourceIdentifierObject->setType('test'); + $resourceIdentifierObject->setLocalId('uuid-1'); + + $array = $resourceIdentifierObject->toArray(); + + $this->assertArrayHasKey('lid', $array); + $this->assertArrayNotHasKey('id', $array); + $this->assertSame('uuid-1', $array['lid']); + } + + public function testSetLocalId_WithIdAlreadySet() { + $resourceIdentifierObject = new ResourceIdentifierObject(); + $resourceIdentifierObject->setType('test'); + $resourceIdentifierObject->setId('1'); + + $this->expectException(DuplicateException::class); + + $resourceIdentifierObject->setLocalId('uuid-1'); + } + public function testEquals_HappyPath() { $one = new ResourceIdentifierObject('test', 1); $two = new ResourceIdentifierObject('test', 2); @@ -25,6 +71,19 @@ public function testEquals_WithoutIdentification() { $one->equals($two); } + public function testEquals_WithLocalId() { + $one = new ResourceIdentifierObject('test'); + $two = new ResourceIdentifierObject('test'); + $new = new ResourceIdentifierObject('test'); + + $one->setLocalId('uuid-1'); + $two->setLocalId('uuid-2'); + $new->setLocalId('uuid-1'); + + $this->assertFalse($one->equals($two)); + $this->assertTrue($one->equals($new)); + } + public function testGetIdentificationKey_HappyPath() { $resourceIdentifierObject = new ResourceIdentifierObject('user', 42); @@ -32,6 +91,7 @@ public function testGetIdentificationKey_HappyPath() { $this->assertArrayHasKey('type', $array); $this->assertArrayHasKey('id', $array); + $this->assertArrayNotHasKey('lid', $array); $this->assertSame('user', $array['type']); $this->assertSame('42', $array['id']); $this->assertTrue($resourceIdentifierObject->hasIdentification()); @@ -59,6 +119,23 @@ public function testGetIdentificationKey_SetAfterwards() { $this->assertSame('user|42', $resourceIdentifierObject->getIdentificationKey()); } + public function testGetIdentificationKey_WithLocalId() { + $resourceIdentifierObject = new ResourceIdentifierObject(); + + $resourceIdentifierObject->setType('user'); + $resourceIdentifierObject->setLocalId('uuid-42'); + + $array = $resourceIdentifierObject->toArray(); + + $this->assertArrayHasKey('type', $array); + $this->assertArrayHasKey('lid', $array); + $this->assertArrayNotHasKey('id', $array); + $this->assertSame('user', $array['type']); + $this->assertSame('uuid-42', $array['lid']); + $this->assertTrue($resourceIdentifierObject->hasIdentification()); + $this->assertSame('user|uuid-42', $resourceIdentifierObject->getIdentificationKey()); + } + public function testGetIdentificationKey_NoIdentification() { $resourceIdentifierObject = new ResourceIdentifierObject(); @@ -98,4 +175,17 @@ public function testIsEmpty_WithAtMembers() { $this->assertFalse($resourceIdentifierObject->isEmpty()); } + + /** + * @group Extensions + */ + public function testIsEmpty_WithExtensionMembers() { + $resourceIdentifierObject = new ResourceIdentifierObject(); + + $this->assertTrue($resourceIdentifierObject->isEmpty()); + + $resourceIdentifierObject->addExtensionMember(new TestExtension(), 'foo', 'bar'); + + $this->assertFalse($resourceIdentifierObject->isEmpty()); + } } diff --git a/tests/profiles/CursorPaginationProfileTest.php b/tests/profiles/CursorPaginationProfileTest.php index 183df74..97704d3 100644 --- a/tests/profiles/CursorPaginationProfileTest.php +++ b/tests/profiles/CursorPaginationProfileTest.php @@ -14,29 +14,36 @@ */ class CursorPaginationProfileTest extends TestCase { public function testSetLinks_HappyPath() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $collection = new CollectionDocument(); - $baseOrCurrentUrl = '/people?'.$profile->getKeyword('page').'[size]=10'; + $baseOrCurrentUrl = '/people?page[size]=10'; $firstCursor = 'bar'; $lastCursor = 'foo'; + $collection->applyProfile($profile); $profile->setLinks($collection, $baseOrCurrentUrl, $firstCursor, $lastCursor); $array = $collection->toArray(); + $this->assertArrayHasKey('jsonapi', $array); + $this->assertArrayHasKey('profile', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['profile']); + $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]); + $this->assertArrayHasKey('links', $array); $this->assertCount(2, $array['links']); $this->assertArrayHasKey('prev', $array['links']); $this->assertArrayHasKey('next', $array['links']); $this->assertArrayHasKey('href', $array['links']['prev']); $this->assertArrayHasKey('href', $array['links']['next']); - $this->assertSame('/people?'.$profile->getKeyword('page').'[size]=10&'.$profile->getKeyword('page').'[before]='.$firstCursor, $array['links']['prev']['href']); - $this->assertSame('/people?'.$profile->getKeyword('page').'[size]=10&'.$profile->getKeyword('page').'[after]='.$lastCursor, $array['links']['next']['href']); + $this->assertSame('/people?page[size]=10&page[before]='.$firstCursor, $array['links']['prev']['href']); + $this->assertSame('/people?page[size]=10&page[after]='.$lastCursor, $array['links']['next']['href']); } public function test_WithRelationship() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $document = new ResourceDocument('test', 1); + $document->applyProfile($profile); $person1 = new ResourceObject('person', 1); $person2 = new ResourceObject('person', 2); @@ -45,7 +52,7 @@ public function test_WithRelationship() { $profile->setCursor($person2, 'arthur'); $profile->setCursor($person42, 'zaphod'); - $baseOrCurrentUrl = '/people?'.$profile->getKeyword('page').'[size]=10'; + $baseOrCurrentUrl = '/people?page[size]=10'; $firstCursor = 'ford'; $lastCursor = 'zaphod'; $exactTotal = 3; @@ -59,6 +66,11 @@ public function test_WithRelationship() { $array = $document->toArray(); + $this->assertArrayHasKey('jsonapi', $array); + $this->assertArrayHasKey('profile', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['profile']); + $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]); + $this->assertArrayHasKey('data', $array); $this->assertArrayHasKey('relationships', $array['data']); $this->assertArrayHasKey('people', $array['data']['relationships']); @@ -67,79 +79,103 @@ public function test_WithRelationship() { $this->assertArrayHasKey('meta', $array['data']['relationships']['people']); $this->assertArrayHasKey('prev', $array['data']['relationships']['people']['links']); $this->assertArrayHasKey('next', $array['data']['relationships']['people']['links']); - $this->assertArrayHasKey('pagination', $array['data']['relationships']['people']['meta']); + $this->assertArrayHasKey('page', $array['data']['relationships']['people']['meta']); $this->assertArrayHasKey('href', $array['data']['relationships']['people']['links']['prev']); $this->assertArrayHasKey('href', $array['data']['relationships']['people']['links']['next']); - $this->assertArrayHasKey('total', $array['data']['relationships']['people']['meta']['pagination']); - $this->assertArrayHasKey('estimatedTotal', $array['data']['relationships']['people']['meta']['pagination']); - $this->assertArrayHasKey('bestGuess', $array['data']['relationships']['people']['meta']['pagination']['estimatedTotal']); + $this->assertArrayHasKey('total', $array['data']['relationships']['people']['meta']['page']); + $this->assertArrayHasKey('estimatedTotal', $array['data']['relationships']['people']['meta']['page']); + $this->assertArrayHasKey('bestGuess', $array['data']['relationships']['people']['meta']['page']['estimatedTotal']); $this->assertCount(3, $array['data']['relationships']['people']['data']); $this->assertArrayHasKey('meta', $array['data']['relationships']['people']['data'][0]); - $this->assertArrayHasKey('pagination', $array['data']['relationships']['people']['data'][0]['meta']); - $this->assertArrayHasKey('cursor', $array['data']['relationships']['people']['data'][0]['meta']['pagination']); + $this->assertArrayHasKey('page', $array['data']['relationships']['people']['data'][0]['meta']); + $this->assertArrayHasKey('cursor', $array['data']['relationships']['people']['data'][0]['meta']['page']); } public function testSetLinksFirstPage_HappyPath() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $collection = new CollectionDocument(); - $baseOrCurrentUrl = '/people?'.$profile->getKeyword('page').'[size]=10'; + $baseOrCurrentUrl = '/people?page[size]=10'; $lastCursor = 'foo'; + $collection->applyProfile($profile); $profile->setLinksFirstPage($collection, $baseOrCurrentUrl, $lastCursor); $array = $collection->toArray(); + $this->assertArrayHasKey('jsonapi', $array); + $this->assertArrayHasKey('profile', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['profile']); + $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]); + $this->assertArrayHasKey('links', $array); $this->assertCount(2, $array['links']); $this->assertArrayHasKey('prev', $array['links']); $this->assertArrayHasKey('next', $array['links']); $this->assertNull($array['links']['prev']); $this->assertArrayHasKey('href', $array['links']['next']); - $this->assertSame('/people?'.$profile->getKeyword('page').'[size]=10&'.$profile->getKeyword('page').'[after]='.$lastCursor, $array['links']['next']['href']); + $this->assertSame('/people?page[size]=10&page[after]='.$lastCursor, $array['links']['next']['href']); } public function testSetLinksLastPage_HappyPath() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $collection = new CollectionDocument(); - $baseOrCurrentUrl = '/people?'.$profile->getKeyword('page').'[size]=10'; + $baseOrCurrentUrl = '/people?page[size]=10'; $firstCursor = 'bar'; + $collection->applyProfile($profile); $profile->setLinksLastPage($collection, $baseOrCurrentUrl, $firstCursor); $array = $collection->toArray(); + $this->assertArrayHasKey('jsonapi', $array); + $this->assertArrayHasKey('profile', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['profile']); + $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]); + $this->assertArrayHasKey('links', $array); $this->assertCount(2, $array['links']); $this->assertArrayHasKey('prev', $array['links']); $this->assertArrayHasKey('next', $array['links']); $this->assertArrayHasKey('href', $array['links']['prev']); $this->assertNull($array['links']['next']); - $this->assertSame('/people?'.$profile->getKeyword('page').'[size]=10&'.$profile->getKeyword('page').'[before]='.$firstCursor, $array['links']['prev']['href']); + $this->assertSame('/people?page[size]=10&page[before]='.$firstCursor, $array['links']['prev']['href']); } public function testSetCursor() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $resourceDocument = new ResourceDocument('user', 42); + $resourceDocument->applyProfile($profile); $profile->setCursor($resourceDocument, 'foo'); $array = $resourceDocument->toArray(); + $this->assertArrayHasKey('jsonapi', $array); + $this->assertArrayHasKey('profile', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['profile']); + $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]); + $this->assertArrayHasKey('data', $array); $this->assertArrayHasKey('meta', $array['data']); - $this->assertArrayHasKey('pagination', $array['data']['meta']); - $this->assertArrayHasKey('cursor', $array['data']['meta']['pagination']); - $this->assertSame('foo', $array['data']['meta']['pagination']['cursor']); + $this->assertArrayHasKey('page', $array['data']['meta']); + $this->assertArrayHasKey('cursor', $array['data']['meta']['page']); + $this->assertSame('foo', $array['data']['meta']['page']['cursor']); } public function testSetPaginationLinkObjectsExplicitlyEmpty_HapptPath() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $collection = new CollectionDocument(); + $collection->applyProfile($profile); $profile->setPaginationLinkObjectsExplicitlyEmpty($collection); $array = $collection->toArray(); + $this->assertArrayHasKey('jsonapi', $array); + $this->assertArrayHasKey('profile', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['profile']); + $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]); + $this->assertArrayHasKey('links', $array); $this->assertCount(2, $array['links']); $this->assertArrayHasKey('prev', $array['links']); @@ -149,29 +185,35 @@ public function testSetPaginationLinkObjectsExplicitlyEmpty_HapptPath() { } public function testSetPaginationMeta() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $collection = new CollectionDocument(); $exactTotal = 42; $bestGuessTotal = 100; $rangeIsTruncated = true; + $collection->applyProfile($profile); $profile->setPaginationMeta($collection, $exactTotal, $bestGuessTotal, $rangeIsTruncated); $array = $collection->toArray(); + $this->assertArrayHasKey('jsonapi', $array); + $this->assertArrayHasKey('profile', $array['jsonapi']); + $this->assertCount(1, $array['jsonapi']['profile']); + $this->assertSame($profile->getOfficialLink(), $array['jsonapi']['profile'][0]); + $this->assertArrayHasKey('meta', $array); - $this->assertArrayHasKey('pagination', $array['meta']); - $this->assertArrayHasKey('total', $array['meta']['pagination']); - $this->assertArrayHasKey('estimatedTotal', $array['meta']['pagination']); - $this->assertArrayHasKey('bestGuess', $array['meta']['pagination']['estimatedTotal']); - $this->assertArrayHasKey('rangeTruncated', $array['meta']['pagination']); - $this->assertSame(42, $array['meta']['pagination']['total']); - $this->assertSame(100, $array['meta']['pagination']['estimatedTotal']['bestGuess']); - $this->assertSame(true, $array['meta']['pagination']['rangeTruncated']); + $this->assertArrayHasKey('page', $array['meta']); + $this->assertArrayHasKey('total', $array['meta']['page']); + $this->assertArrayHasKey('estimatedTotal', $array['meta']['page']); + $this->assertArrayHasKey('bestGuess', $array['meta']['page']['estimatedTotal']); + $this->assertArrayHasKey('rangeTruncated', $array['meta']['page']); + $this->assertSame(42, $array['meta']['page']['total']); + $this->assertSame(100, $array['meta']['page']['estimatedTotal']['bestGuess']); + $this->assertSame(true, $array['meta']['page']['rangeTruncated']); } public function testGetUnsupportedSortErrorObject_HappyPath() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $genericTitle = 'foo'; $specificDetails = 'bar'; @@ -187,17 +229,16 @@ public function testGetUnsupportedSortErrorObject_HappyPath() { $this->assertArrayHasKey('type', $array['links']); $this->assertArrayHasKey('source', $array); $this->assertArrayHasKey('parameter', $array['source']); - $this->assertCount(1, $array['links']['type']); $this->assertSame('400', $array['status']); $this->assertSame('Unsupported sort', $array['code']); $this->assertSame($genericTitle, $array['title']); $this->assertSame($specificDetails, $array['detail']); - $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort', $array['links']['type'][0]); + $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort', $array['links']['type']); $this->assertSame('sort', $array['source']['parameter']); } public function testGetMaxPageSizeExceededErrorObject_HappyPath() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $maxSize = 42; $genericTitle = 'foo'; $specificDetails = 'bar'; @@ -215,21 +256,20 @@ public function testGetMaxPageSizeExceededErrorObject_HappyPath() { $this->assertArrayHasKey('source', $array); $this->assertArrayHasKey('parameter', $array['source']); $this->assertArrayHasKey('meta', $array); - $this->assertArrayHasKey('pagination', $array['meta']); - $this->assertArrayHasKey('maxSize', $array['meta']['pagination']); - $this->assertCount(1, $array['links']['type']); + $this->assertArrayHasKey('page', $array['meta']); + $this->assertArrayHasKey('maxSize', $array['meta']['page']); $this->assertSame('400', $array['status']); $this->assertSame('Max page size exceeded', $array['code']); $this->assertSame($genericTitle, $array['title']); $this->assertSame($specificDetails, $array['detail']); - $this->assertSame('pagination[size]', $array['source']['parameter']); - $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded', $array['links']['type'][0]); - $this->assertSame(42, $array['meta']['pagination']['maxSize']); + $this->assertSame('page[size]', $array['source']['parameter']); + $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded', $array['links']['type']); + $this->assertSame(42, $array['meta']['page']['maxSize']); } public function testGetInvalidParameterValueErrorObject_HappyPath() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); - $queryParameter = 'pagination[size]'; + $profile = new CursorPaginationProfile(); + $queryParameter = 'page[size]'; $typeLink = 'https://jsonapi.org'; $genericTitle = 'foo'; $specificDetails = 'bar'; @@ -246,17 +286,16 @@ public function testGetInvalidParameterValueErrorObject_HappyPath() { $this->assertArrayHasKey('type', $array['links']); $this->assertArrayHasKey('source', $array); $this->assertArrayHasKey('parameter', $array['source']); - $this->assertCount(1, $array['links']['type']); $this->assertSame('400', $array['status']); $this->assertSame('Invalid parameter value', $array['code']); $this->assertSame($genericTitle, $array['title']); $this->assertSame($specificDetails, $array['detail']); - $this->assertSame('pagination[size]', $array['source']['parameter']); - $this->assertSame('https://jsonapi.org', $array['links']['type'][0]); + $this->assertSame('page[size]', $array['source']['parameter']); + $this->assertSame('https://jsonapi.org', $array['links']['type']); } public function testGetRangePaginationNotSupportedErrorObject_HappyPath() { - $profile = new CursorPaginationProfile(['page' => 'pagination']); + $profile = new CursorPaginationProfile(); $genericTitle = 'foo'; $specificDetails = 'bar'; @@ -270,12 +309,11 @@ public function testGetRangePaginationNotSupportedErrorObject_HappyPath() { $this->assertArrayHasKey('detail', $array); $this->assertArrayHasKey('links', $array); $this->assertArrayHasKey('type', $array['links']); - $this->assertCount(1, $array['links']['type']); $this->assertSame('400', $array['status']); $this->assertSame('Range pagination not supported', $array['code']); $this->assertSame($genericTitle, $array['title']); $this->assertSame($specificDetails, $array['detail']); - $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported', $array['links']['type'][0]); + $this->assertSame('https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported', $array['links']['type']); } public function testSetQueryParameter_HappyPath() { @@ -305,4 +343,15 @@ public function testSetQueryParameter_EncodedUrl() { $this->assertSame('/people?sort=x&page%5Bsize%5D=10&page%5Bafter%5D=bar', $newUrl); } + + /** + * test method while it is part of the interface + */ + public function testGetKeyword_HappyPath() { + $profile = new CursorPaginationProfile(); + + $keyword = $profile->getKeyword('page'); + + $this->assertSame('page', $keyword); + } } diff --git a/tests/profiles/TestProfile.php b/tests/profiles/TestProfile.php index 91fe146..331e1a8 100644 --- a/tests/profiles/TestProfile.php +++ b/tests/profiles/TestProfile.php @@ -5,17 +5,13 @@ use alsvanzelf\jsonapi\interfaces\ProfileInterface; class TestProfile implements ProfileInterface { - private $aliasedLink; + private $officialLink; - public function setAliasedLink($aliasedLink) { - $this->aliasedLink = $aliasedLink; + public function setOfficialLink($officialLink) { + $this->officialLink = $officialLink; } - public function __construct(array $aliases=[]) {} - public function getKeyword($keyword) {} - public function getOfficialKeywords() {} - public function getOfficialLink() {} - public function getAliasedLink() { - return $this->aliasedLink; + public function getOfficialLink() { + return $this->officialLink; } }