├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── composer.json ├── docs ├── .editorconfig ├── .gitignore ├── Dockerfile ├── Makefile ├── _config │ ├── __init__.py │ └── cakephpbranch.py ├── additional-information │ ├── common-issues.rst │ ├── contributing.rst │ ├── customizing-output.rst │ └── examples.rst ├── api-usage-advanced │ ├── filtering-search.rst │ ├── filtering.rst │ ├── inclusion.rst │ ├── pagination.rst │ ├── sorting.rst │ └── sparse-fieldsets.rst ├── api-usage │ ├── creating-resources.rst │ ├── deleting-resources.rst │ ├── errors-exceptions.rst │ ├── fetching-collections.rst │ ├── fetching-resources.rst │ └── updating-resources.rst ├── conf.py ├── configuration │ └── debugging.rst ├── contents.rst ├── index.rst ├── listener-configuration │ ├── debugging.rst │ ├── listener-options.rst │ ├── pagination.rst │ └── schemas.rst ├── make.bat ├── preface │ ├── installation.rst │ └── setup.rst └── requirements.txt ├── phpstan-baseline.neon ├── phpstan.neon ├── phpunit.xml.dist ├── psalm.xml ├── src ├── Action │ ├── RelationshipsAction.php │ └── ViewAction.php ├── Error │ └── JsonApiExceptionRenderer.php ├── InflectTrait.php ├── Listener │ ├── JsonApi │ │ ├── DocumentRelationshipValidator.php │ │ └── DocumentValidator.php │ ├── JsonApiListener.php │ ├── PaginationListener.php │ └── SearchListener.php ├── Plugin.php ├── Route │ └── JsonApiRoutes.php ├── Schema │ └── JsonApi │ │ └── DynamicEntitySchema.php └── View │ └── JsonApiView.php └── tests ├── Fixture ├── CountriesFixture.php ├── CountriesLanguagesFixture.php ├── CulturesFixture.php ├── CurrenciesFixture.php ├── JsonApiDecoder │ ├── incoming-country-mixed-relationships.json │ └── incoming-country-no-relationships.json ├── JsonApiRequestBodies │ ├── CreatingResources │ │ ├── create-country-multiple-existing-belongsto-relationships.json │ │ ├── create-country-throw-side-posting-exception.json │ │ ├── create-currency-no-relationships.json │ │ ├── create-national-capital-no-relationships.json │ │ └── create-national-city-single-existing-belongsto-relationship.json │ ├── Relationships │ │ ├── add-culture-relationship.json │ │ ├── add-existing-culture-relationship.json │ │ ├── add-language-relationship.json │ │ ├── clear-culture-relationship.json │ │ ├── delete-culture-relationship.json │ │ ├── delete-language-relationship.json │ │ ├── delete-not-existing-culture-relationship.json │ │ ├── replace-culture-relationship.json │ │ ├── replace-currency-relationship.json │ │ └── replace-language-relationship.json │ └── UpdatingResources │ │ ├── patch-country.json │ │ ├── patch-national-capital.json │ │ ├── update-country-attributes-and-multiple-belongsto-relationships.json │ │ ├── update-country-multiple-belongsto-relationships-only.json │ │ ├── update-country-set-multiple-hasmany-relationships.json │ │ ├── update-country-set-single-hasmany-relationship.json │ │ ├── update-currency-attributes-only-all.json │ │ ├── update-currency-attributes-only-single.json │ │ ├── update-national-capital-attributes-only.json │ │ ├── update-national-city-attributes-and-single-belongsto-relationship.json │ │ └── update-national-city-single-belongsto-relationship-only.json ├── JsonApiResponseBodies │ ├── CreatingResources │ │ ├── created-country-multiple-existing-belongsto-relationships.json │ │ ├── created-currency-no-relationships.json │ │ ├── created-national-capital-no-relationships.json │ │ └── created-national-city-single-existing-belongsto-relationship.json │ ├── Errors │ │ ├── 404-error-for-collection-in-debug-mode.json │ │ ├── 404-error-for-collection-in-production-mode.json │ │ ├── 404-error-for-resource-in-debug-mode.json │ │ ├── 404-error-for-resource-in-production-mode.json │ │ └── validation-error-multiple-reasons.json │ ├── FetchingCollections │ │ ├── get-countries-with-pagination.json │ │ └── get-countries-without-pagination.json │ ├── FetchingResources │ │ ├── get-country-currency.json │ │ ├── get-country-languages.json │ │ ├── get-country-multiple-belongsto-relationships.json │ │ ├── get-country-no-relationships.json │ │ ├── get-currency-countries.json │ │ ├── get-currency-no-relationships.json │ │ ├── get-national-capital-no-relationships.json │ │ └── get-national-city-single-belongsto-relationship.json │ ├── Filtering │ │ ├── filter-single-field-partial.json │ │ └── filter-single-field.json │ ├── Inclusion │ │ ├── get-country-include-all-supported-associations.json │ │ ├── get-country-include-culture.json │ │ ├── get-country-include-currency-and-countries.json │ │ ├── get-country-include-currency-and-culture.json │ │ ├── get-country-include-currency.json │ │ ├── get-country-include-national-capital-and-national-cities.json │ │ ├── get-country-include-national-capital.json │ │ ├── get-country-include-national-cities.json │ │ └── get-country-include-nationalCities.json │ ├── MetaInformation │ │ └── meta-only.json │ ├── Relationships │ │ ├── delete-culture-relationship.json │ │ ├── delete-language-relationship.json │ │ ├── delete-not-existing-culture-relationship.json │ │ ├── get_culture_relationship_for_country.json │ │ ├── get_culture_relationship_for_country_with_none.json │ │ ├── get_currency_relationship_for_country.json │ │ ├── get_languages_relationship_for_country.json │ │ ├── patch-clear-culture-relationship.json │ │ ├── patch-replace-culture-relationship.json │ │ ├── patch-replace-currency-relationship.json │ │ ├── patch-replace-language-relationship.json │ │ ├── post-add-culture-relationship.json │ │ ├── post-add-existing-culture-relationship.json │ │ └── post-add-language-relationship.json │ ├── Sorting │ │ ├── get-countries-and-currencies-and-capitals-sort-by-code.json │ │ ├── get-countries-and-currencies-sort-by-code.json │ │ ├── get-currencies-and-countries-no-sort.json │ │ ├── get-currencies-no-sort.json │ │ ├── get-currencies-sort-by-code-desc.json │ │ ├── get-currency-and-countries-sort-by-code-desc.json │ │ ├── hasmany-index-with-multi-fields-sorting-with-direction.json │ │ ├── multi-fields-sorting-with-direction.json │ │ ├── multi-fields-sorting.json │ │ ├── national-cities-absolute-links.json │ │ ├── nationalCities-absolute-links.json │ │ ├── sort-primary-data-by-related-resource-single-field.json │ │ ├── sort-primary-field-in-dot-notation.json │ │ ├── sort-single-field-ascending.json │ │ ├── sorted-desc-with-include.json │ │ ├── sorted-with-include-page-2.json │ │ ├── sorted-with-include.json │ │ ├── sorted_desc_with_include.json │ │ ├── sorted_with_include.json │ │ └── sorted_with_include_page_2.json │ ├── SparseFieldsets │ │ ├── index-multi-field-sparse-for-included-data.json │ │ ├── index-multi-field-sparse-for-primary-and-included-data.json │ │ ├── index-multi-field-sparse.json │ │ ├── index-no-relationship.json │ │ ├── index-single-field-sparse-for-included-data.json │ │ ├── index-single-field-sparse-for-primary-and-included-data.json │ │ ├── index-single-field-sparse.json │ │ ├── index-with-include-and-sort-desc.json │ │ ├── index-with-include-and-sort.json │ │ ├── index-with-include.json │ │ ├── national-cities-absolute-links.json │ │ ├── nationalCities-absolute-links.json │ │ ├── view-multi-barrel-single-field-sparse.json │ │ ├── view-multi-field-sparse-for-included-data.json │ │ ├── view-multi-field-sparse-for-primary-and-included-data.json │ │ ├── view-multi-field-sparse.json │ │ ├── view-no-relationship.json │ │ ├── view-single-field-sparse-for-included-data.json │ │ ├── view-single-field-sparse-for-primary-and-included-data.json │ │ └── view-single-field-sparse.json │ ├── UpdatingResources │ │ ├── patch-country.json │ │ ├── patch-national-capital.json │ │ ├── updated-country-attributes-and-multiple-belongsto-relationships.json │ │ ├── updated-country-multiple-belongsto-relationships-only.json │ │ ├── updated-country-set-multiple-hasmany-relationships.json │ │ ├── updated-country-set-single-hasmany-relationship.json │ │ ├── updated-currency-attributes-only-all.json │ │ ├── updated-currency-attributes-only-single.json │ │ ├── updated-national-capital-attributes-only.json │ │ ├── updated-national-city-attributes-and-single-belongsto-relationship.json │ │ └── updated-national-city-single-belongsto-relationship-only.json │ ├── get_subcountry_with_supercountry.json │ └── get_supercountry_with_subcountries.json ├── LanguagesFixture.php ├── NationalCapitalsFixture.php └── NationalCitiesFixture.php ├── TestCase ├── Error │ └── JsonApiExceptionRendererTest.php ├── Integration │ ├── JsonApi │ │ ├── CreatingResourcesIntegrationTest.php │ │ ├── ErrorsIntegrationTest.php │ │ ├── FetchingCollectionsIntegrationTest.php │ │ ├── FetchingResourcesIntegrationTest.php │ │ ├── FilteringIntegrationTest.php │ │ ├── InclusionIntegrationTest.php │ │ ├── RelationshipsIntegrationTest.php │ │ ├── SelfReferencedAssociationIntegrationTest.php │ │ ├── SortingIntegrationTest.php │ │ ├── SparseFieldsetsIntegrationTest.php │ │ └── UpdatingResourcesIntegrationTest.php │ └── JsonApiBaseTestCase.php ├── Listener │ ├── JsonApi │ │ └── DocumentValidatorTest.php │ └── JsonApiListenerTest.php ├── Schema │ └── DynamicEntitySchemaTest.php └── View │ └── JsonApiViewTest.php ├── bootstrap.php └── test_app ├── config ├── bootstrap.php └── routes.php └── src ├── Application.php ├── Controller ├── CountriesController.php ├── CurrenciesController.php ├── LanguagesController.php ├── NationalCapitalsController.php └── NationalCitiesController.php └── Model ├── Entity ├── Country.php ├── Culture.php ├── Currency.php ├── Language.php ├── NationalCapital.php └── NationalCity.php └── Table ├── CountriesTable.php ├── CulturesTable.php ├── CurrenciesTable.php ├── LanguagesTable.php ├── NationalCapitalsTable.php └── NationalCitiesTable.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = false 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | charset = "utf-8" 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.neon] 19 | indent_style = tab 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | docs/_build 3 | phpunit.xml 4 | vendor/ 5 | composer.lock 6 | tmp 7 | .phpunit.result.cache 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Crud loves to welcome your contributions. There are several ways to help out: 4 | * Create a ticket in GitHub, if you have found a bug 5 | * Write testcases for open bug tickets 6 | * Write patches for open bug/feature tickets, preferably with testcases included 7 | * Contribute to the [documentation](https://github.com/friendsofcake/crud/tree/gh-pages) 8 | 9 | There are a few guidelines that we need contributors to follow so that we have a 10 | chance of keeping on top of things. 11 | 12 | ## Getting Started 13 | 14 | * Make sure you have a [GitHub account](https://github.com/signup/free) 15 | * Submit a ticket for your issue, assuming one does not already exist. 16 | * Clearly describe the issue including steps to reproduce when it is a bug. 17 | * Make sure you fill in the earliest version that you know has the issue. 18 | * Fork the repository on GitHub. 19 | 20 | ## Making Changes 21 | 22 | * Create a topic branch from where you want to base your work. 23 | * This is usually the develop branch 24 | * To quickly create a topic branch based on master; `git branch 25 | master/my_contribution master` then checkout the new branch with `git 26 | checkout master/my_contribution`. Better avoid working directly on the 27 | `master` branch, to avoid conflicts if you pull in updates from origin. 28 | * Make commits of logical units. 29 | * Check for unnecessary whitespace with `git diff --check` before committing. 30 | * Use descriptive commit messages and reference the #ticket number 31 | * Core testcases should continue to pass. You can run tests locally or enable 32 | [travis-ci](https://travis-ci.org/) for your fork, so all tests and codesniffs 33 | will be executed. 34 | * Your work should apply the CakePHP coding standards. 35 | 36 | ## Which branch to base the work 37 | 38 | * Bugfix branches will be based on develop branch. 39 | * New features that are backwards compatible will be based on develop branch 40 | * New features or other non-BC changes will go in the next major release branch. 41 | 42 | ## Submitting Changes 43 | 44 | * Push your changes to a topic branch in your fork of the repository. 45 | * Submit a pull request to the repository with the correct target branch. 46 | 47 | ## Testcases and codesniffer 48 | 49 | Crud tests requires [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html) 50 | 3.7.33 or higher. To run the testcases locally use the following command: 51 | 52 | phpunit 53 | 54 | To run the sniffs for CakePHP coding standards 55 | 56 | phpcs -n -p --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP --ignore=vendor --ignore=docs src/ tests/ 57 | 58 | Check the [cakephp-codesniffer](https://github.com/cakephp/cakephp-codesniffer) 59 | repository to setup the CakePHP standard. The README contains installation info 60 | for the sniff and phpcs. 61 | 62 | ## Documentation 63 | 64 | You can build the documentation using Docker via the following commands: 65 | 66 | # go to the docs dir 67 | cd docs 68 | 69 | # build the docs 70 | docker build . 71 | 72 | # make the html 73 | docker run -it --rm -v $(pwd)/docs:/data friendsofcake/crud make html 74 | 75 | # open the generated html docs in docs/_build/html 76 | 77 | # Additional Resources 78 | 79 | * [CakePHP coding standards](http://book.cakephp.org/2.0/en/contributing/cakephp-coding-conventions.html) 80 | * [Bug tracker](https://github.com/friendsofcake/crud/issues) 81 | * [General GitHub documentation](https://help.github.com/) 82 | * [GitHub pull request documentation](https://help.github.com/send-pull-requests/) 83 | * #cakephp IRC channel on freenode.org 84 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Christian "Jippi" Winther 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/FriendsOfCake/crud-json-api/master.svg?style=flat-square)](https://travis-ci.org/FriendsOfCake/crud-json-api) 2 | [![Coverage Status](https://img.shields.io/codecov/c/github/FriendsOfCake/crud-json-api.svg?style=flat-square)](https://codecov.io/github/FriendsOfCake/crud-json-api) 3 | [![PHPStan](https://img.shields.io/badge/PHPStan-enabled-brightgreen.svg?style=flat-square)](https://github.com/phpstan/phpstan) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/FriendsOfCake/crud-json-api.svg?style=flat-square)](https://packagist.org/packages/FriendsOfCake/crud-json-api) 5 | [![Latest Stable Version](https://img.shields.io/packagist/v/FriendsOfCake/crud-json-api.svg?style=flat-square)](https://packagist.org/packages/FriendsOfCake/crud-json-api) 6 | [![Documentation Status](https://readthedocs.org/projects/crud-json-api/badge?style=flat-square)](https://crud-json-api.readthedocs.org) 7 | 8 | # JSON API Crud Listener for CakePHP 9 | 10 | Build JSON API Servers with almost no code. Comes with advanced features like: 11 | 12 | - Compound Documents (Deeply Nested Includes) 13 | - Sparse Fieldsets 14 | - Multi-field Filtering (Search) 15 | - Multi-field Sorting 16 | - Multi-field Validation 17 | - Pagination 18 | 19 | ## How does it work? 20 | 21 | 1. Structure your data using the powerful CakePHP ORM 22 | 2. Create (near-empty) Controllers 23 | 3. Let crud-json-api worry about JSON API 24 | 25 | ## Documentation 26 | 27 | Fully documented at [https://crud-json-api.readthedocs.io/](https://crud-json-api.readthedocs.io/) 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"friendsofcake/crud-json-api", 3 | "description":"Listener for building CakePHP Crud APIs following the JSON API specification.", 4 | "type":"cakephp-plugin", 5 | "keywords":[ 6 | "cakephp", 7 | "crud", 8 | "api", 9 | "jsonapi", 10 | "json api" 11 | ], 12 | "homepage":"https://github.com/FriendsOfCake/crud-json-api", 13 | "license":"MIT", 14 | "authors":[ 15 | { 16 | "name":"Bravo-Kernel", 17 | "role":"Author", 18 | "homepage":"http://github.com/bravo-kernel" 19 | }, 20 | { 21 | "name":"Marlin Cremers", 22 | "role":"Contributor", 23 | "homepage":"https://github.com/Marlinc" 24 | }, 25 | { 26 | "name":"Walther Lalk", 27 | "role":"Contributor", 28 | "homepage":"https://github.com/dakota" 29 | } 30 | ], 31 | "require": { 32 | "php": ">=7.4.0", 33 | "cakephp/cakephp": "^4.4.1", 34 | "friendsofcake/crud": "^6.0", 35 | "laravel-json-api/neomerx-json-api": "^5.0" 36 | }, 37 | "require-dev": { 38 | "phpunit/phpunit": "~8.5 || ^9.3", 39 | "friendsofcake/cakephp-test-utilities": "^2.0.1", 40 | "friendsofcake/search": "^6.2.2", 41 | "cakephp/cakephp-codesniffer": "^4.0", 42 | "dms/phpunit-arraysubset-asserts": "^0.4.0" 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "CrudJsonApi\\": "src" 47 | } 48 | }, 49 | "autoload-dev": { 50 | "psr-4": { 51 | "CrudJsonApi\\Test\\": "tests", 52 | "CrudJsonApi\\Test\\App\\": "tests/test_app/src", 53 | "Cake\\Test\\Fixture\\": "./vendor/cakephp/cakephp/tests/Fixture" 54 | } 55 | }, 56 | "suggest": { 57 | "friendsofcake/search": "Provides search capabilities for the Crud plugin." 58 | }, 59 | "support":{ 60 | "source":"https://github.com/FriendsOfCake/crud-json-api", 61 | "issues":"https://github.com/FriendsOfCake/crud-json-api/issues", 62 | "wiki":"http://crud.readthedocs.org/en/latest/", 63 | "irc":"irc://irc.freenode.org/friendsofcake" 64 | }, 65 | "scripts": { 66 | "cs-check": "phpcs -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/", 67 | "cs-fix": "phpcbf --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/", 68 | "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:~1.8.0 psalm/phar:~4.26.0 && mv composer.backup composer.json", 69 | "phpstan": "phpstan analyse --memory-limit=3G src/", 70 | "psalm": "psalm.phar --show-info=false", 71 | "stan": [ 72 | "@phpstan", 73 | "@psalm" 74 | ], 75 | "test": "phpunit" 76 | }, 77 | "config": { 78 | "allow-plugins": { 79 | "dealerdirect/phpcodesniffer-composer-installer": true 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /docs/.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = false 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | charset = "utf-8" 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.js] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [Makefile] 23 | indent_style = tab 24 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | _build 3 | -------------------------------------------------------------------------------- /docs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | 5 | LABEL Description="This image is used to create an environment to contribute to the cakephp/docs" 6 | 7 | RUN apt-get update && apt-get install -y \ 8 | python-pip \ 9 | texlive-latex-recommended \ 10 | texlive-latex-extra \ 11 | texlive-fonts-recommended \ 12 | && apt-get clean \ 13 | && rm -rf /var/lib/apt/lists/* 14 | 15 | COPY requirements.txt /tmp/ 16 | RUN pip install -r /tmp/requirements.txt 17 | 18 | WORKDIR /data 19 | 20 | CMD ["/bin/bash"] 21 | -------------------------------------------------------------------------------- /docs/_config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfCake/crud-json-api/0d8856ea413694a92e3de521aff78585ed2cae54/docs/_config/__init__.py -------------------------------------------------------------------------------- /docs/_config/cakephpbranch.py: -------------------------------------------------------------------------------- 1 | """ 2 | CakePHP Git branch extension. 3 | 4 | A simple sphinx extension for adding 5 | the GitHub branch name of the docs' version. 6 | """ 7 | 8 | 9 | def setup(app): 10 | app.connect('html-page-context', append_template_ctx) 11 | app.add_config_value('branch', '', True) 12 | 13 | 14 | def append_template_ctx(app, pagename, templatename, ctx, event_arg): 15 | ctx['branch'] = app.config.branch 16 | -------------------------------------------------------------------------------- /docs/additional-information/common-issues.rst: -------------------------------------------------------------------------------- 1 | Common Issues 2 | ============= 3 | 4 | Missing template 5 | ^^^^^^^^^^^^^^^^ 6 | 7 | Crud-json-api does not require you to create templates so if you see the following error you are 8 | most likely not sending the correct ``application/vnd.api+json`` Accept Header with your requests: 9 | 10 | .. code-block:: php 11 | 12 | Error: Missing Template 13 | 14 | Missing routes 15 | ^^^^^^^^^^^^^^ 16 | 17 | Crud-json-api depends on CakePHP Routing to generate the correct links for all resources 18 | in your JSON API response. 19 | 20 | If you encounter errors like the one shown below, make sure that both your primary resource and all related 21 | resources are added to the ``API_RESOURCES`` constant found in your ``config/routes.php`` file. 22 | 23 | .. code-block:: php 24 | 25 | A route matching '' could not be found. 26 | 27 | Schema not registered 28 | ^^^^^^^^^^^^^^^^^^^^^ 29 | 30 | If you see the following error make sure that valid ``Table`` and ``Entity`` classes are 31 | present for both the primary resource and all related resources. 32 | 33 | .. code-block:: php 34 | 35 | Schema is not registered for a resource at path ''. 36 | 37 | Normal HTML is returned 38 | ^^^^^^^^^^^^^^^^^^^^^^^ 39 | 40 | If you are just getting back a standard page response, rather than a JSON response (and you have confirmed that you are sending the correct JSON API Request Headers) it is most likely because you already have a controller action defined for the resource you are trying to request. For CRUD to handle it, you must remove any existing controller actions that conflict with the routes you are trying to configure. 41 | -------------------------------------------------------------------------------- /docs/additional-information/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | There are many ways you can help improving this plugin: 5 | 6 | - submit a Pull Request, code optimizations are more than welcome 7 | - raise a Github issue if you detect missing functionality or behavior not according to the JSON API specification 8 | - suggest improvements to this documentation 9 | 10 | .. note:: 11 | 12 | We welcome and appreciate contributions on all levels. 13 | -------------------------------------------------------------------------------- /docs/additional-information/customizing-output.rst: -------------------------------------------------------------------------------- 1 | Customizing Output 2 | ================== 3 | 4 | Date Formats 5 | ^^^^^^^^^^^^ 6 | 7 | By default crud-json-api will return timestamps in the following format: 8 | 9 | .. code-block:: json 10 | 11 | "created-at": "2018-06-10T13:41:05+00:00" 12 | 13 | If you prefer a different format, either specify it in your ``bootstrap.php`` file or right before a 14 | specific action. E.g. 15 | 16 | .. code-block:: php 17 | 18 | \Cake\I18n\FrozenTime::setJsonEncodeFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); 19 | \Cake\I18n\FrozenDate::setJsonEncodeFormat('yyyy-MM-dd'); 20 | -------------------------------------------------------------------------------- /docs/additional-information/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | We realize the JSON API document structure can be complex and hard to memorize which is exactly why we have decided to use 5 | pure JSON API documents as fixtures for our integration tests. This not only assures crud-json-api will behave exactly as we 6 | expect, it also provides you with a reference directory you can use to lookup fully-functional examples of: 7 | 8 | - `JsonApiRequestBodies `_:, the JSON API bodies used when Creating or Updating Resources 9 | - `JsonApiResponseBodies `_:, the JSON API responses crud-json-api will produce 10 | 11 | .. note:: 12 | 13 | Please submit a PR if you are missing a use case. 14 | -------------------------------------------------------------------------------- /docs/api-usage-advanced/filtering-search.rst: -------------------------------------------------------------------------------- 1 | Filtering 2 | ========= 3 | 4 | `JSON API Filtering `_ 5 | allow searching your API and requires: 6 | 7 | 1. Composer installing `friendsofcake/search` 8 | 2. Configuring the ``Crud SearchListener`` as `described here `_ 9 | 10 | Now create search aliases named ``filter`` in your tables like shown below: 11 | 12 | .. code-block:: phpinline 13 | 14 | // src/Model/Table/CountriesTable.php 15 | 16 | public function searchManager() 17 | { 18 | $searchManager = $this->behaviors()->Search->searchManager(); 19 | $searchManager->like('filter', [ 20 | 'before' => true, 21 | 'after' => true, 22 | 'field' => [$this->aliasField('name')] 23 | ]); 24 | 25 | return $searchManager; 26 | } 27 | 28 | Once that is done you will be able to search your API using URLs similar to: 29 | 30 | - ``/countries?filter=netherlands`` 31 | - ``/countries?filter=nether`` 32 | 33 | Please note that the following search requests would also be matched: 34 | 35 | - ``/countries?filter[id]=1`` 36 | - ``/countries?filter[id][]=1&filter[id][]=2`` 37 | -------------------------------------------------------------------------------- /docs/api-usage-advanced/filtering.rst: -------------------------------------------------------------------------------- 1 | Filtering 2 | ========= 3 | 4 | `JSON API Filtering `_ 5 | allow searching your API and requires: 6 | 7 | 1. Composer installing `friendsofcake/search` 8 | 2. Configuring the ``Crud SearchListener`` as `described here `_ 9 | 10 | Now create search aliases named ``filter`` in your tables like shown below: 11 | 12 | .. code-block:: phpinline 13 | 14 | // src/Model/Table/CountriesTable.php 15 | 16 | public function searchManager() 17 | { 18 | $searchManager = $this->behaviors()->Search->searchManager(); 19 | $searchManager->like('filter', [ 20 | 'before' => true, 21 | 'after' => true, 22 | 'field' => [$this->aliasField('name')], 23 | ]); 24 | 25 | return $searchManager; 26 | } 27 | 28 | Once that is done you will be able to search your API using URLs similar to: 29 | 30 | - ``/countries?filter=netherlands`` 31 | - ``/countries?filter=nether`` 32 | -------------------------------------------------------------------------------- /docs/api-usage-advanced/pagination.rst: -------------------------------------------------------------------------------- 1 | Pagination 2 | ========== 3 | 4 | This listener comes with an additional `Pagination` listener that, once enabled, 5 | wil add the ``meta`` and ``links`` nodes as per the JSON API specification. 6 | 7 | Attach the listener using the components array if you want to attach 8 | it to all controllers, application wide. 9 | 10 | .. code-block:: php 11 | 12 | loadComponent('RequestHandler'); 18 | $this->loadComponent('Crud.Crud', [ 19 | 'actions' => [ 20 | 'Crud.Index', 21 | 'Crud.View' 22 | ], 23 | 'listeners' => [ 24 | 'CrudJsonApi.JsonApi', 25 | 'CrudJsonApi.Pagination', 26 | ] 27 | ]); 28 | } 29 | 30 | Alternatively, attach the listener to your controllers ``beforeFilter`` 31 | if you prefer attaching the listener to only specific controllers on the fly. 32 | 33 | .. code-block:: php 34 | 35 | Crud->addListener('CrudJsonApi.Pagination'); 41 | } 42 | } 43 | 44 | All ``GET`` requests to the index action will now add 45 | JSON API pagination information to the response as shown below. 46 | 47 | .. code-block:: json 48 | 49 | { 50 | "meta": { 51 | "record_count": 15, 52 | "page_count": 2, 53 | "page_limit": null 54 | }, 55 | "links": { 56 | "self": "/countries?page=2", 57 | "first": "/countries?page=1", 58 | "last": "/countries?page=2", 59 | "prev": "/countries?page=1", 60 | "next": null 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/api-usage-advanced/sorting.rst: -------------------------------------------------------------------------------- 1 | Sorting 2 | ======= 3 | 4 | `JSON API Sorting `_ 5 | allows you to sort the results produced by your API according to one 6 | by passing one or more criteria to your request using the ``sort`` parameter. 7 | 8 | Before continuing please note that the default sort order for each field is ascending 9 | UNLESS the field is prefixed with a hyphen (``-``) in which case the sort order will 10 | be descending. 11 | 12 | Single Field Sorting 13 | ^^^^^^^^^^^^^^^^^^^^ 14 | 15 | To sort by a single field using ascending order: 16 | 17 | ``/currencies?sort=code`` 18 | 19 | To sort descending: 20 | 21 | ``/currencies?sort=-code`` 22 | 23 | Multi Field Sorting 24 | ^^^^^^^^^^^^^^^^^^^ 25 | 26 | To sort by multiple fields simply pass comma-separated sort fields 27 | in the order you want them applied: 28 | 29 | - ``/currencies?sort=code,name`` 30 | - ``/currencies?sort=-code,name`` 31 | - ``/currencies?sort=-code,-name`` 32 | - ``/currencies?sort=name,code`` 33 | 34 | Sorting By Related Data 35 | ^^^^^^^^^^^^^^^^^^^^^^^ 36 | 37 | You can also sort your primary data using fields in the related data. In this case 38 | all ``currencies`` (the primary data) would be sorted using the ascending order of the 39 | ``code`` field in ``countries`` (the associated data). 40 | 41 | - ``/currencies?include=countries&sort=countries.code`` 42 | - ``/currencies?include=countries&sort=-countries.code`` 43 | 44 | Combined Sorts 45 | ^^^^^^^^^^^^^^ 46 | 47 | CrudJsonApi supports any combination of the above sorts. E.g. 48 | 49 | - ``/currencies?include=countries&sort=name,countries.code`` 50 | - ``/currencies?include=countries&sort=name,-countries.code`` 51 | -------------------------------------------------------------------------------- /docs/api-usage-advanced/sparse-fieldsets.rst: -------------------------------------------------------------------------------- 1 | Sparse Fieldsets 2 | ================ 3 | 4 | `JSON API Sparse Fieldsets `_ 5 | allow you to limit the fields returned by your API by passing the ``fields`` parameter 6 | in your request. 7 | 8 | To select all countries but only retrieve their `code` field: 9 | 10 | ``/countries?fields[countries]=code`` 11 | 12 | To select a single country and only retrieve its `name` field: 13 | 14 | ``/countries/1?fields[countries]=name`` 15 | 16 | Limiting Associated Data 17 | ^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | It is also possible to limit the fields of associated data. The following example will 20 | return all fields for ``countries`` (the primary data) but will limit the fields returned 21 | for ``currencies`` (the associated data) to ``id`` and ``name``. 22 | 23 | ``/countries?include=currencies&fields[currencies]=id,name`` 24 | 25 | Please note that you MUST include the associated data in the fields args, eg: 26 | 27 | - ``/countries?fields[countries]=name&include=currencies&fields[currencies]=id,code`` will NOT work 28 | - ``/countries?fields[countries]=name,currency&include=currencies&fields[currencies]=id,code`` does WORK 29 | 30 | Combinations 31 | ^^^^^^^^^^^^ 32 | 33 | You may also use any combination of the above. In this case we are limiting the fields for both the primary 34 | resource and the associated data. 35 | 36 | ``/countries/1?fields[countries]=name,currency&include=currencies&fields[currencies]=id,name`` 37 | -------------------------------------------------------------------------------- /docs/api-usage/creating-resources.rst: -------------------------------------------------------------------------------- 1 | Creating Resources 2 | ================== 3 | 4 | Creating a new JSON API Resource is done by calling the ``add`` action of your API with: 5 | 6 | - the ``HTTP POST`` request type 7 | - an ``Accept`` header set to ``application/vnd.api+json`` 8 | - a ``Content-Type`` header set to ``application/vnd.api+json`` 9 | - request data in valid JSON API document format 10 | 11 | A successful request will respond with HTTP response code ``201`` 12 | and a JSON API response body presenting the newly created Resource 13 | along with ``id``, ``attributes`` and ``belongsTo`` relationships. 14 | 15 | Request Data 16 | ^^^^^^^^^^^^ 17 | 18 | All data posted to the listener is transformed from JSON API format to 19 | standard CakePHP format so it can be processed "as usual" once the data 20 | is accepted. 21 | 22 | To make sure posted data complies with the JSON API 23 | specification it is first validated by the listener's DocumentValidator which 24 | will throw a (422) ValidationException if it does not comply along 25 | with a pointer to the cause. 26 | 27 | A valid JSON API request body for creating a new Country would look similar to: 28 | 29 | .. code-block:: json 30 | 31 | { 32 | "data": { 33 | "type": "countries", 34 | "attributes": { 35 | "code": "NL", 36 | "name": "The Netherlands" 37 | } 38 | } 39 | } 40 | 41 | The same rules apply when you create a new Resource and want to set its ``belongsTo`` relationships. 42 | For example, the JSON API request body for creating a new Country with ``currency_id=1`` would like: 43 | 44 | .. code-block:: json 45 | 46 | { 47 | "data": { 48 | "type": "countries", 49 | "attributes": { 50 | "code": "NL", 51 | "name": "The Netherlands" 52 | }, 53 | "relationships": { 54 | "currency": { 55 | "data": { 56 | "type": "currencies", 57 | "id": "1" 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | .. note:: 65 | 66 | See this link for more examples of 67 | `valid JsonApiRequestBodies `_. 68 | 69 | Side-Posting 70 | ^^^^^^^^^^^^ 71 | 72 | Side-posting is an often requested feature which would allow creating multiple Resources (and/or relationships) using a single POST request. 73 | 74 | However, this functionality is NOT supported by version 1.0 of the JSON API specification and is therefore NOT supported by crud-json-api. 75 | 76 | In practice this means: 77 | 78 | - you will only be able to create Resources with ``belongsTo`` relationships pointing to EXISTING foreign keys 79 | - crud-json-api will throw a ``BadRequestException`` when it detects attempts to side-post ``hasMany`` relationships 80 | 81 | .. note:: 82 | 83 | Side-posting might land in version 1.1 of the JSON API specification, more information available in 84 | `this Pull Request `_. 85 | -------------------------------------------------------------------------------- /docs/api-usage/deleting-resources.rst: -------------------------------------------------------------------------------- 1 | Deleting Resources 2 | ================== 3 | 4 | Deleting an existing JSON API Resource is done by calling the ``delete`` action of your API with: 5 | 6 | - the ``HTTP DELETE`` request type 7 | - an ``Accept`` header set to ``application/vnd.api+json`` 8 | - a ``Content-Type`` header set to ``application/vnd.api+json`` 9 | - request data in valid JSON API document format 10 | - request data containing the ``id`` of the resource to delete 11 | 12 | A successful request will return HTTP response code ``204`` (No Content) 13 | and empty response body. Failed requests will return HTTP response 14 | code ``400`` with empty response body. 15 | 16 | An valid JSON API document structure for deleting a Country 17 | with ``id`` 10 would look similar to: 18 | 19 | .. code-block:: json 20 | 21 | { 22 | "data": { 23 | "type": "countries", 24 | "id": "10" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/api-usage/errors-exceptions.rst: -------------------------------------------------------------------------------- 1 | Errors and Exceptions 2 | ===================== 3 | 4 | Default Errors 5 | ^^^^^^^^^^^^^^ 6 | 7 | The listener will produce error responses in the following 8 | JSON API format for all standard errors and all non-validation 9 | exceptions: 10 | 11 | .. code-block:: json 12 | 13 | { 14 | "errors": [ 15 | { 16 | "code": "501", 17 | "title": "Not Implemented" 18 | } 19 | ], 20 | "debug": { 21 | "class": "Cake\\Network\\Exception\\NotImplementedException", 22 | "trace": [] 23 | } 24 | } 25 | 26 | .. note:: 27 | 28 | Please note that the ``debug`` node with the stack trace will only be included if ``debug`` is true. 29 | 30 | Validation Errors 31 | ^^^^^^^^^^^^^^^^^ 32 | 33 | The listener will produce validation error (422) responses 34 | in the following JSON API format for all validation errors: 35 | 36 | .. code-block:: json 37 | 38 | { 39 | "errors": [ 40 | { 41 | "title": "_required", 42 | "detail": "Primary data does not contain member 'type'", 43 | "source": { 44 | "pointer": "/data" 45 | } 46 | } 47 | ] 48 | } 49 | 50 | Invalid Request Data 51 | ^^^^^^^^^^^^^^^^^^^^ 52 | 53 | Please be aware that the listener will also respond with (422) validation errors 54 | if request data is posted in a structure that does not comply with the 55 | JSON API specification. 56 | -------------------------------------------------------------------------------- /docs/api-usage/fetching-collections.rst: -------------------------------------------------------------------------------- 1 | Fetching Collections 2 | ==================== 3 | 4 | Fetching JSON API Resource Collections is done by calling the ``index`` action of your API with: 5 | 6 | - the ``HTTP GET`` request type 7 | - an ``Accept`` header set to ``application/vnd.api+json`` 8 | 9 | A successful request will respond with HTTP response code ``200`` 10 | and response body similar to this output produced by 11 | ``http://example.com/countries``: 12 | 13 | .. code-block:: json 14 | 15 | { 16 | "data": [ 17 | { 18 | "type": "countries", 19 | "id": "1", 20 | "attributes": { 21 | "code": "NL", 22 | "name": "The Netherlands" 23 | }, 24 | "links": { 25 | "self": "/countries/1" 26 | } 27 | }, 28 | { 29 | "type": "countries", 30 | "id": "2", 31 | "attributes": { 32 | "code": "BE", 33 | "name": "Belgium" 34 | }, 35 | "links": { 36 | "self": "/countries/2" 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /docs/api-usage/fetching-resources.rst: -------------------------------------------------------------------------------- 1 | Fetching Resources 2 | ================== 3 | 4 | Fetch a single JSON API Resource by calling the ``view`` action of your API with: 5 | 6 | - the ``HTTP GET`` request type 7 | - an ``Accept`` header set to ``application/vnd.api+json`` 8 | 9 | A successful request will respond with HTTP response code ``200`` 10 | and response body similar to this output produced by 11 | ``http://example.com/countries/1``: 12 | 13 | .. code-block:: json 14 | 15 | { 16 | "data": { 17 | "type": "countries", 18 | "id": "1", 19 | "attributes": { 20 | "code": "NL", 21 | "name": "The Netherlands", 22 | "dummy-counter": 11111 23 | }, 24 | "relationships": { 25 | "currency": { 26 | "data": { 27 | "type": "currencies", 28 | "id": "1" 29 | }, 30 | "links": { 31 | "self": "/currencies/1" 32 | } 33 | }, 34 | "national-capital": { 35 | "data": { 36 | "type": "national-capitals", 37 | "id": "1" 38 | }, 39 | "links": { 40 | "self": "/national-capitals/1" 41 | } 42 | } 43 | }, 44 | "links": { 45 | "self": "/countries/1" 46 | } 47 | } 48 | } 49 | 50 | .. note:: 51 | 52 | When retrieving a single Resource, crud-json-api will automatically generate ``relationships`` links for 53 | all ``belongsTo`` attributes in your model UNLESS you pass the ``include`` request parameter OR define 54 | a contain statement inside your Controller. 55 | -------------------------------------------------------------------------------- /docs/api-usage/updating-resources.rst: -------------------------------------------------------------------------------- 1 | 2 | Updating Resources 3 | ================== 4 | 5 | Updating an existing JSON API Resource is done by calling the ``edit`` action of your API with: 6 | 7 | - the ``HTTP PATCH`` request type 8 | - an ``Accept`` header set to ``application/vnd.api+json`` 9 | - a ``Content-Type`` header set to ``application/vnd.api+json`` 10 | - request data in valid JSON API document format 11 | - request data containing the ``id`` of the resource to update 12 | 13 | A successful request will respond with HTTP response code ``200`` 14 | and response body similar to the one produced by the ``view`` action. 15 | 16 | A valid JSON API document structure for updating the ``name`` field 17 | for a Country with ``id`` 10 would look similar to the following output 18 | produced by ``http://example.com/countries/1``: 19 | 20 | .. code-block:: json 21 | 22 | { 23 | "data": { 24 | "type": "countries", 25 | "id": "10", 26 | "attributes": { 27 | "name": "My new name" 28 | } 29 | } 30 | } 31 | 32 | Updating To-One Relationships 33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | 35 | When updating a primary JSON API Resource, you can use the same PATCH request to set one or multiple To-One 36 | (or ``belongsTo``) relationships but only as long as the following conditions are met: 37 | 38 | - the ``id`` of the related resource MUST correspond with an EXISTING foreign key 39 | - the related resource MUST belong to the primary resource being PATCHed 40 | 41 | For example, a valid JSON API document structure that would set a single related 42 | ``national-capital`` for a given ``country`` would look like: 43 | 44 | .. code-block:: json 45 | 46 | { 47 | "data": { 48 | "type": "countries", 49 | "id": "2", 50 | "relationships": { 51 | "national-capital": { 52 | "data": { 53 | "type": "national-capitals", 54 | "id": "4" 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | .. note:: 62 | 63 | Please note that JSON API does not support updating attributes for the related resource(s) and thus 64 | will simply ignore them if detected in the request body. 65 | 66 | Updating To-Many Relationships 67 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 68 | 69 | When updating a primary JSON API Resource, you can use the same PATCH request to set one or multiple To-Many 70 | (or ``hasMany``) relationships but only as long as the following conditions are met: 71 | 72 | - the ``id`` of the related resource MUST correspond with an EXISTING foreign key 73 | - the related resource MUST belong to the primary resource being PATCHed 74 | 75 | For example, a valid JSON API document structure that would set multiple related ``cultures`` 76 | for a given ``country`` would look like: 77 | 78 | .. code-block:: json 79 | 80 | { 81 | "data": { 82 | "type": "countries", 83 | "id": "2", 84 | "relationships": { 85 | "cultures": { 86 | "data": [ 87 | { 88 | "type": "cultures", 89 | "id": "2" 90 | }, 91 | { 92 | "type": "cultures", 93 | "id": "3" 94 | } 95 | ] 96 | } 97 | } 98 | } 99 | } 100 | 101 | .. note:: 102 | 103 | Please note that JSON API does not support updating attributes for the related resource(s) and thus 104 | will simply ignore them if detected in the request body. 105 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | import sys 4 | import os 5 | import cakephp_theme 6 | from sphinx.highlighting import lexers 7 | from pygments.lexers.php import PhpLexer 8 | 9 | sys.path.insert(0, os.path.abspath('.')) 10 | 11 | # Pull in all the configuration options defined in the global config file.. 12 | from _config import * 13 | 14 | ######################## 15 | # Begin Customizations # 16 | ######################## 17 | 18 | maintainer = u'FriendsOfCake' 19 | project = u'crud-json-api' 20 | project_pretty_name = u'Crud JsonApi' 21 | copyright = u'%d, Friends of Cake' % datetime.datetime.now().year 22 | version = '1.0' 23 | release = '1.0' 24 | html_title = 'Crud JsonApi' 25 | html_context = { 26 | 'maintainer': maintainer, 27 | 'project_pretty_name': project_pretty_name, 28 | 'projects': { 29 | 'Bootstrap UI': 'https://bootstrap-ui.readthedocs.io/', 30 | 'CakePDF': 'https://cakepdf.readthedocs.io/', 31 | 'Crud': 'https://crud.readthedocs.io/', 32 | 'Crud JsonApi': 'https://crud-json-api.readthedocs.io/', 33 | 'Crud Users': 'https://crud-users.readthedocs.io/', 34 | 'Crud View': 'https://crud-view.readthedocs.io/', 35 | 'CsvView': 'https://csvview.readthedocs.io/', 36 | 'Search': 'https://friendsofcake-search.readthedocs.io/', 37 | } 38 | } 39 | 40 | htmlhelp_basename = 'crud-json-api' 41 | latex_documents = [ 42 | ('index', 'crud-json-api.tex', u'crud-json-api', 43 | u'Friends Of Cake', 'manual'), 44 | ] 45 | man_pages = [ 46 | ('index', 'crud-json-api', u'Crud JsonApi Documentation', 47 | [u'Friends Of Cake'], 1) 48 | ] 49 | 50 | texinfo_documents = [ 51 | ('index', 'crud-json-api', u'Crud JsonApi Documentation', 52 | u'Friends Of Cake', 'crud-json-api', 'Crud listener for building APIs that follow the JSON API specification', 53 | 'Miscellaneous'), 54 | ] 55 | 56 | branch = 'master' 57 | 58 | ######################## 59 | # End Customizations # 60 | ######################## 61 | 62 | # -- General configuration ------------------------------------------------ 63 | 64 | extensions = [ 65 | 'sphinx.ext.todo', 66 | 'sphinxcontrib.phpdomain', 67 | '_config.cakephpbranch', 68 | ] 69 | 70 | templates_path = ['_templates'] 71 | source_suffix = '.rst' 72 | master_doc = 'contents' 73 | exclude_patterns = [ 74 | '_build', 75 | '_themes', 76 | '_partials', 77 | ] 78 | 79 | pygments_style = 'sphinx' 80 | highlight_language = 'php' 81 | 82 | # -- Options for HTML output ---------------------------------------------- 83 | 84 | html_theme = 'cakephp_theme' 85 | html_theme_path = [cakephp_theme.get_html_theme_path()] 86 | html_static_path = [] 87 | html_last_updated_fmt = '%b %d, %Y' 88 | html_sidebars = { 89 | '**': ['globaltoc.html', 'localtoc.html'] 90 | } 91 | 92 | # -- Options for LaTeX output --------------------------------------------- 93 | 94 | latex_elements = { 95 | } 96 | 97 | lexers['php'] = PhpLexer(startinline=True) 98 | lexers['phpinline'] = PhpLexer(startinline=True) 99 | lexers['php-annotations'] = PhpLexer(startinline=True) 100 | primary_domain = "php" 101 | -------------------------------------------------------------------------------- /docs/configuration/debugging.rst: -------------------------------------------------------------------------------- 1 | Debugging 2 | ========= 3 | 4 | This listener fully supports the Crud ``API Query Log`` listener and will, 5 | once enabled as `described here `_ 6 | , add a top-level ``query`` node to every response when debug mode is enabled. 7 | 8 | .. code-block:: json 9 | 10 | { 11 | "query": { 12 | "default": [ 13 | { 14 | "query": "SHOW FULL COLUMNS FROM `countries`", 15 | "took": 0, 16 | "params": [], 17 | "numRows": 10, 18 | "error": null 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/contents.rst: -------------------------------------------------------------------------------- 1 | Contents 2 | ######## 3 | 4 | .. toctree:: 5 | :hidden: 6 | 7 | .. _preface-docs: 8 | 9 | .. toctree:: 10 | :maxdepth: 3 11 | :caption: Preface 12 | 13 | Introduction 14 | Installation 15 | Setup 16 | 17 | .. _listener-configuration-docs: 18 | 19 | .. toctree:: 20 | :maxdepth: 3 21 | :caption: Listener Configuration 22 | 23 | Listener Options 24 | Debugging 25 | Pagination 26 | Schemas 27 | 28 | .. _api-usage-docs: 29 | 30 | .. toctree:: 31 | :maxdepth: 3 32 | :caption: API Usage 33 | 34 | Fetching Collections 35 | Fetching Resources 36 | Creating Resources 37 | Updating Resources 38 | Deleting Resources 39 | Errors and Exceptions 40 | 41 | .. _api-usage-advanced-docs: 42 | 43 | .. toctree:: 44 | :maxdepth: 3 45 | :caption: Advanced API Usage 46 | 47 | Inclusion 48 | Sparse Fieldsets 49 | Sorting 50 | Filtering 51 | 52 | .. _additional-information-docs: 53 | 54 | .. toctree:: 55 | :maxdepth: 3 56 | :caption: Additional Information 57 | 58 | JSON Examples 59 | Common Issues 60 | Customizing Output 61 | Contributing 62 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | Crud listener for building `JSON API `_ Servers with almost no code. 5 | 6 | Comes with advanced features like: 7 | 8 | - Compound Documents (Deeply Nested Includes) 9 | - Sparse Fieldsets 10 | - Multi-field Search (Filtering) 11 | - Multi-field Sorting 12 | - Multi-field Validation 13 | - Pagination 14 | -------------------------------------------------------------------------------- /docs/listener-configuration/debugging.rst: -------------------------------------------------------------------------------- 1 | Query Logs 2 | ========== 3 | 4 | This listener fully supports the Crud ``API Query Log`` listener and will, 5 | once enabled as `described here `_ 6 | , add a top-level ``query`` node to every response when debug mode is enabled. 7 | 8 | .. code-block:: json 9 | 10 | { 11 | "query": { 12 | "default": [ 13 | { 14 | "query": "SHOW FULL COLUMNS FROM `countries`", 15 | "took": 0, 16 | "params": [], 17 | "numRows": 10, 18 | "error": null 19 | } 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/listener-configuration/pagination.rst: -------------------------------------------------------------------------------- 1 | Pagination 2 | ========== 3 | 4 | This listener comes with an additional `Pagination` listener that, once enabled, 5 | wil add the ``meta`` and ``links`` nodes as per the JSON API specification. 6 | 7 | Attach the listener using the components array if you want to attach 8 | it to all controllers, application wide. 9 | 10 | .. code-block:: php 11 | 12 | class AppController extends Controller 13 | { 14 | public function initialize() 15 | { 16 | $this->loadComponent('RequestHandler'); 17 | $this->loadComponent('Crud.Crud', [ 18 | 'actions' => [ 19 | 'Crud.Index', 20 | 'Crud.View', 21 | ], 22 | 'listeners' => [ 23 | 'CrudJsonApi.JsonApi', 24 | 'CrudJsonApi.Pagination', 25 | ], 26 | ]); 27 | } 28 | } 29 | 30 | Alternatively, attach the listener to your controllers ``beforeFilter`` 31 | if you prefer attaching the listener to only specific controllers on the fly. 32 | 33 | .. code-block:: php 34 | 35 | class SamplesController extends AppController 36 | { 37 | public function beforeFilter(\Cake\Event\EventInterface $event) 38 | { 39 | parent::beforeFilter($event); 40 | $this->Crud->addListener('CrudJsonApi.Pagination'); 41 | } 42 | } 43 | 44 | All ``GET`` requests to the index action will now add 45 | JSON API pagination information to the response as shown below. 46 | 47 | .. code-block:: json 48 | 49 | { 50 | "meta": { 51 | "record_count": 15, 52 | "page_count": 2, 53 | "page_limit": null 54 | }, 55 | "links": { 56 | "self": "/countries?page=2", 57 | "first": "/countries?page=1", 58 | "last": "/countries?page=2", 59 | "prev": "/countries?page=1", 60 | "next": null 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/listener-configuration/schemas.rst: -------------------------------------------------------------------------------- 1 | Schemas 2 | ======= 3 | 4 | This listener makes use of `NeoMerx schemas `_ 5 | to handle the heavy lifting that is required for converting CakePHP entities to JSON API format. 6 | 7 | By default all entities in the ``_entities`` viewVar will be passed to the 8 | Listener's ``DynamicEntitySchema`` for conversion. This dynamic schema extends 9 | ``Neomerx\JsonApi\Schema\SchemaProvider`` and is, amongst other things, used to 10 | override NeoMerx methods so we can generate CakePHP specific output (like links). 11 | 12 | Even though the dynamic entity schema provided by Crud should cater to the 13 | needs of most users, creating your own custom schemas is also supported. When 14 | using custom schemas please note that the listener will use the first matching 15 | schema, following this order: 16 | 17 | 1. Custom entity schema 18 | 2. Custom dynamic schema 19 | 3. Crud's dynamic schema 20 | 21 | Custom entity schema 22 | ^^^^^^^^^^^^^^^^^^^^ 23 | 24 | Use a custom entity schema in situations where you need to alter the 25 | generated JSON API but only for a specific controller/entity. 26 | 27 | An example would be overriding the NeoMerx ``getSelfSubUrl`` method used 28 | to prefix all ``self`` links in the generated json for a ``Countries`` 29 | controller. This would require creating a ``src/Schema/JsonApi/CountrySchema.php`` 30 | file looking similar to: 31 | 32 | .. code-block:: phpinline 33 | 34 | namespace App\Schema\JsonApi; 35 | 36 | use CrudJsonApi\Schema\JsonApi\DynamicEntitySchema; 37 | 38 | class CountrySchema extends DynamicEntitySchema 39 | { 40 | public function getSelfSubUrl($entity = null) 41 | { 42 | return 'https://countryies.example.com/controller/self-links/'; 43 | } 44 | } 45 | 46 | Custom dynamic schema 47 | ^^^^^^^^^^^^^^^^^^^^^ 48 | 49 | Use a custom dynamic schema if you need to alter the generated JSON API for all 50 | controllers, application wide. 51 | 52 | An example of a custom dynamic schema would require creating 53 | a ``src/Schema/JsonApi/DynamicEntitySchema.php`` file looking similar to: 54 | 55 | .. code-block:: phpinline 56 | 57 | namespace App\Schema\JsonApi; 58 | 59 | use CrudJsonApi\Schema\JsonApi\DynamicEntitySchema as CrudDynamicEntitySchema; 60 | 61 | class DynamicEntitySchema extends CrudDynamicEntitySchema 62 | { 63 | public function getSelfSubUrl($entity = null) 64 | { 65 | return 'https://api.example.com/controller/self-links/'; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docs/preface/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Install the JsonApi Listener by running the following command inside your project folder: 5 | 6 | .. code-block:: bash 7 | 8 | composer require friendsofcake/crud-json-api 9 | 10 | It is highly recommended that you install the ``Search`` plugin as well: 11 | 12 | .. code-block:: bash 13 | 14 | composer require friendsofcake/search 15 | 16 | Loading Crud 17 | ------------ 18 | 19 | Only run the following command if your application does not yet use Crud: 20 | 21 | .. code-block:: bash 22 | 23 | bin/cake plugin load Crud 24 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | Babel==2.9.1 3 | certifi==2022.12.7 4 | chardet==3.0.4 5 | docutils==0.14 6 | idna==2.7 7 | imagesize==0.7.1 8 | Jinja2==2.11.3 9 | MarkupSafe==0.23 10 | Pygments==2.7.4 11 | pytz==2018.7 12 | requests==2.31.0 13 | six==1.11.0 14 | snowballstemmer==1.2.1 15 | Sphinx==1.5.1 16 | sphinx-rtd-theme==0.1.9 17 | sphinxcontrib-phpdomain==0.2.1 18 | urllib3==1.26.18 19 | cakephp-theme 20 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Dead catch \\- Cake\\\\Core\\\\Exception\\\\CakeException is never thrown in the try block\\.$#" 5 | count: 1 6 | path: src/Error/JsonApiExceptionRenderer.php 7 | 8 | - 9 | message: "#^Access to an undefined property Cake\\\\Controller\\\\Controller\\:\\:\\$Crud\\.$#" 10 | count: 1 11 | path: src/Listener/PaginationListener.php 12 | 13 | - 14 | message: "#^Call to an undefined method Cake\\\\Datasource\\\\EntityInterface\\:\\:visibleProperties\\(\\)\\.$#" 15 | count: 1 16 | path: src/Schema/JsonApi/DynamicEntitySchema.php 17 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 6 6 | paths: 7 | - src 8 | checkMissingIterableValueType: false 9 | checkGenericClassInNonGenericObjectType: false 10 | universalObjectCratesClasses: 11 | - Crud\Event\Subject 12 | bootstrapFiles: 13 | - vendor/cakephp/cakephp/src/Core/Exception/CakeException.php 14 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | ./tests/ 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ./src/ 24 | ./src/ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Action/ViewAction.php: -------------------------------------------------------------------------------- 1 | _request(); 23 | $from = $request->getParam('from'); 24 | $relationName = $request->getParam('type'); 25 | 26 | //For non-related links treat as always 27 | if (!$from || !$relationName) { 28 | parent::_handle($id); 29 | 30 | return; 31 | } 32 | 33 | $subject = $this->_subject(); 34 | $this->_findRecordViaRelated($subject); 35 | $this->_trigger('beforeRender', $subject); 36 | } 37 | 38 | /** 39 | * Find a record via related linkage 40 | * 41 | * @param \Crud\Event\Subject $subject Event subject 42 | * @return \Cake\Datasource\EntityInterface 43 | * @throws \Exception 44 | */ 45 | protected function _findRecordViaRelated(Subject $subject): EntityInterface 46 | { 47 | $repository = $this->_table(); 48 | 49 | [$finder, $options] = $this->_extractFinder(); 50 | $query = $repository->find($finder, $options); 51 | 52 | $subject->set( 53 | [ 54 | 'repository' => $repository, 55 | 'query' => $query, 56 | ] 57 | ); 58 | 59 | $this->_trigger('beforeFind', $subject); 60 | $entity = $subject->query->first(); 61 | 62 | if (!$entity) { 63 | $this->_notFound(null, $subject); 64 | } 65 | 66 | $subject->set(['entity' => $entity, 'success' => true]); 67 | $this->_trigger('afterFind', $subject); 68 | 69 | return $entity; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/InflectTrait.php: -------------------------------------------------------------------------------- 1 | getConfig('inflect', 'variable'); 22 | 23 | if (!$inflect) { 24 | return $input; 25 | } 26 | 27 | return Inflector::$inflect($input); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Listener/JsonApi/DocumentRelationshipValidator.php: -------------------------------------------------------------------------------- 1 | _documentMustHavePrimaryData(); 28 | $this->_primaryDataMayBeNullEmptyArrayObjectOrArray(); 29 | if ($this->_errorCollection->count() === 0) { 30 | return; 31 | } 32 | 33 | throw new ValidationException($this->_getErrorCollectionEntity()); 34 | } 35 | 36 | /** 37 | * @return bool 38 | */ 39 | protected function _primaryDataMayBeNullEmptyArrayObjectOrArray(): bool 40 | { 41 | $dataProperty = $this->_getProperty('data'); 42 | if ($this->_relationshipDataIsNull('') || empty((array)$dataProperty)) { 43 | return true; 44 | } 45 | 46 | $about = ''; 47 | if (is_array($dataProperty)) { 48 | if (array_key_exists('type', $dataProperty) && array_key_exists('id', $dataProperty)) { 49 | return false; 50 | } 51 | 52 | $errors = false; 53 | foreach ($dataProperty as $val) { 54 | if (!is_array($val) || !array_key_exists('type', $val) || !array_key_exists('id', $val)) { 55 | $errors = true; 56 | break; 57 | } 58 | } 59 | if (!$errors) { 60 | return true; 61 | } 62 | $about = '#crud-updating-to-many-relationships'; 63 | } 64 | $this->_errorCollection->addDataAttributeError( 65 | $name = 'data', 66 | $title = '_required', 67 | $detail = "Related records are missing member 'type' or 'id'", 68 | $status = null, 69 | $idx = null, 70 | $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/' . $about) 71 | ); 72 | 73 | return false; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Listener/SearchListener.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'Crud.beforeLookup', 21 | 'Crud.beforePaginate', 22 | ], 23 | 'collection' => 'default', 24 | ]; 25 | 26 | /** 27 | * Returns a list of all events that will fire in the controller during its lifecycle. 28 | * You can override this function to add your own listener callbacks 29 | * 30 | * @return array 31 | */ 32 | public function implementedEvents(): array 33 | { 34 | return [ 35 | 'Crud.beforeLookup' => ['callable' => 'injectSearch'], 36 | 'Crud.beforePaginate' => ['callable' => 'injectSearch'], 37 | ]; 38 | } 39 | 40 | /** 41 | * Inject search conditions into the query object. 42 | * 43 | * @param \Cake\Event\EventInterface $event Event 44 | * @return void 45 | */ 46 | public function injectSearch(EventInterface $event): void 47 | { 48 | if (!in_array($event->getName(), $this->getConfig('enabled'))) { 49 | return; 50 | } 51 | 52 | $repository = $this->_table(); 53 | if ($repository instanceof Table && !$repository->behaviors()->has('Search')) { 54 | throw new RuntimeException( 55 | sprintf( 56 | 'Missing Search.Search behavior on %s', 57 | get_class($repository) 58 | ) 59 | ); 60 | } 61 | 62 | $filterParams = ['search' => $this->_request()->getQuery('filter', [])]; 63 | 64 | $filterParams['collection'] = $this->getConfig('collection'); 65 | $event->getSubject()->query->find('search', $filterParams); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Plugin.php: -------------------------------------------------------------------------------- 1 | ['type' => 'integer'], 10 | 'code' => ['type' => 'string', 'length' => 2, 'null' => false], 11 | 'name' => ['type' => 'string', 'length' => 255, 'null' => false], 12 | 'dummy_counter' => ['type' => 'integer'], 13 | 'currency_id' => ['type' => 'integer', 'null' => true], 14 | 'national_capital_id' => ['type' => 'integer', 'null' => true], 15 | 'supercountry_id' => ['type' => 'integer', 'null' => true], 16 | '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], 17 | ]; 18 | 19 | public $records = [ 20 | ['code' => 'NL', 'name' => 'The Netherlands', 'dummy_counter' => 11111, 'currency_id' => 1, 'national_capital_id' => 1], 21 | ['code' => 'BG', 'name' => 'Bulgaria', 'dummy_counter' => 22222, 'currency_id' => 1, 'national_capital_id' => 2], 22 | ['code' => 'IT', 'name' => 'Italy', 'dummy_counter' => 33333, 'currency_id' => 1, 'national_capital_id' => 4], 23 | ['code' => 'VT', 'name' => 'Vatican', 'dummy_counter' => 44444, 'currency_id' => 1, 'national_capital_id' => 5, 'supercountry_id' => 3], 24 | ['code' => 'US', 'name' => 'United States of America', 'dummy_counter' => 33333, 'currency_id' => 2, 'national_capital_id' => 6], 25 | ]; 26 | } 27 | -------------------------------------------------------------------------------- /tests/Fixture/CountriesLanguagesFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'integer'], 10 | 'country_id' => ['type' => 'integer', 'length' => 3, 'null' => false], 11 | 'language_id' => ['type' => 'integer', 'length' => 100, 'null' => false], 12 | '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], 13 | ]; 14 | 15 | public $records = [ 16 | ['country_id' => 1, 'language_id' => 1], 17 | ['country_id' => 1, 'language_id' => 2], 18 | ['country_id' => 2, 'language_id' => 4], 19 | ['country_id' => 3, 'language_id' => 3], 20 | ['country_id' => 4, 'language_id' => 4], 21 | ['country_id' => 5, 'language_id' => 1], 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /tests/Fixture/CulturesFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'integer'], 10 | 'code' => ['type' => 'string', 'length' => 5, 'null' => false], 11 | 'name' => ['type' => 'string', 'length' => 100, 'null' => false], 12 | 'another_dummy_counter' => ['type' => 'integer'], 13 | 'country_id' => ['type' => 'integer', 'null' => false], 14 | '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], 15 | ]; 16 | 17 | public $records = [ 18 | ['code' => 'nl-NL', 'name' => 'Dutch', 'another_dummy_counter' => 11111, 'country_id' => 1], 19 | ['code' => 'bg-BG', 'name' => 'Bulgarian', 'another_dummy_counter' => 22222, 'country_id' => 2], 20 | ['code' => 'tr-BG', 'name' => 'Turkish (Bulgarian)', 'another_dummy_counter' => 22222, 'country_id' => 2], 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /tests/Fixture/CurrenciesFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'integer'], 10 | 'code' => ['type' => 'string', 'length' => 3, 'null' => false], 11 | 'name' => ['type' => 'string', 'length' => 100, 'null' => false], 12 | '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], 13 | ]; 14 | 15 | public $records = [ 16 | ['code' => 'EUR', 'name' => 'Euro'], 17 | ['code' => 'USD', 'name' => 'US Dollar'], 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiDecoder/incoming-country-mixed-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "attributes": { 5 | "code": "NL", 6 | "name": "The Netherlands", 7 | "dummyCounter": 11111 8 | }, 9 | "relationships": { 10 | "cultures": { 11 | "data": [ 12 | { 13 | "type": "cultures", 14 | "id": "2", 15 | "attributes": { 16 | "name": "nl_NL", 17 | "language-name": "Dutch" 18 | } 19 | } 20 | ] 21 | }, 22 | "currency": { 23 | "data": { 24 | "type": "currencies", 25 | "id": "3" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiDecoder/incoming-country-no-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "attributes": { 5 | "code": "NL", 6 | "name": "The Netherlands", 7 | "dummyCounter": 11111 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/CreatingResources/create-country-multiple-existing-belongsto-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "attributes": { 5 | "code": "NZ", 6 | "name": "New Zealand", 7 | "dummyCounter": 66666 8 | }, 9 | "relationships": { 10 | "currency": { 11 | "data": { 12 | "type": "currencies", 13 | "id": "1" 14 | } 15 | }, 16 | "nationalCapital": { 17 | "data": { 18 | "type": "nationalCapitals", 19 | "id": "2" 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/CreatingResources/create-country-throw-side-posting-exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "attributes": { 5 | "code": "MC", 6 | "name": "Monaco", 7 | "dummyCounter": 66666 8 | }, 9 | "relationships": { 10 | "nationalCities": { 11 | "data": [ 12 | { 13 | "type": "nationalCities", 14 | "id": "4" 15 | } 16 | ] 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/CreatingResources/create-currency-no-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "currencies", 4 | "attributes": { 5 | "code": "ZAR", 6 | "name": "South African Rand" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/CreatingResources/create-national-capital-no-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCapitals", 4 | "attributes": { 5 | "name": "Havana", 6 | "description": "National capital of Cuba" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/CreatingResources/create-national-city-single-existing-belongsto-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCities", 4 | "attributes": { 5 | "name": "Rotterdam" 6 | }, 7 | "relationships": { 8 | "country": { 9 | "data": { 10 | "type": "countries", 11 | "id": "1" 12 | } 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/add-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "cultures", 5 | "id": "1" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/add-existing-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "cultures", 5 | "id": "2" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/add-language-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "languages", 5 | "id": "3" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/clear-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [] 3 | } 4 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/delete-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "cultures", 5 | "id": "2" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/delete-language-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "languages", 5 | "id": "2" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/delete-not-existing-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "cultures", 5 | "id": "1" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/replace-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "cultures", 5 | "id": "1" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/replace-currency-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "currencies", 4 | "id": "2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/Relationships/replace-language-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "languages", 5 | "id": "3" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/patch-country.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "name": "Updated Netherlands" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/patch-national-capital.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCapitals", 4 | "id": "1", 5 | "attributes": { 6 | "name": "Patched Amsterdam" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/update-country-attributes-and-multiple-belongsto-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "2", 5 | "attributes": { 6 | "code": "JM", 7 | "name": "Jamaica", 8 | "dummyCounter": 12345 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "data": { 13 | "type": "currencies", 14 | "id": "2" 15 | } 16 | }, 17 | "nationalCapital": { 18 | "data": { 19 | "type": "nationalCapitals", 20 | "id": "5" 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/update-country-multiple-belongsto-relationships-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "2", 5 | "relationships": { 6 | "currency": { 7 | "data": { 8 | "type": "currencies", 9 | "id": "2" 10 | } 11 | }, 12 | "nationalCapital": { 13 | "data": { 14 | "type": "nationalCapitals", 15 | "id": "4" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/update-country-set-multiple-hasmany-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "2", 5 | "relationships": { 6 | "cultures": { 7 | "data": [ 8 | { 9 | "type": "cultures", 10 | "id": "2" 11 | }, 12 | { 13 | "type": "cultures", 14 | "id": "3" 15 | } 16 | ] 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/update-country-set-single-hasmany-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "2", 5 | "relationships": { 6 | "cultures": { 7 | "data": [ 8 | { 9 | "type": "cultures", 10 | "id": "3" 11 | } 12 | ] 13 | } 14 | }, 15 | "links": { 16 | "self": "\/countries\/2" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/update-currency-attributes-only-all.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "currencies", 4 | "id": "2", 5 | "attributes": { 6 | "code": "RUB", 7 | "name": "Russian Ruble" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/update-currency-attributes-only-single.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "currencies", 4 | "id": "2", 5 | "attributes": { 6 | "name": "Russian Ruble" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/update-national-capital-attributes-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCapitals", 4 | "id": "6", 5 | "attributes": { 6 | "name": "Hollywood", 7 | "description": "National capital of the cinematic world" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/update-national-city-attributes-and-single-belongsto-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCities", 4 | "id": "2", 5 | "attributes": { 6 | "name": "Milan" 7 | }, 8 | "relationships": { 9 | "country": { 10 | "data": { 11 | "type": "countries", 12 | "id": "3" 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiRequestBodies/UpdatingResources/update-national-city-single-belongsto-relationship-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCities", 4 | "id": "2", 5 | "relationships": { 6 | "country": { 7 | "data": { 8 | "type": "countries", 9 | "id": "3" 10 | } 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/CreatingResources/created-country-multiple-existing-belongsto-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "6", 5 | "attributes": { 6 | "code": "NZ", 7 | "dummyCounter": 66666, 8 | "name": "New Zealand" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/6\/relationships\/currency", 14 | "related": "\/countries\/6\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/6\/relationships\/nationalCapital", 24 | "related": "\/countries\/6\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "2" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/6\/relationships\/cultures", 34 | "related": "\/countries\/6\/cultures" 35 | } 36 | }, 37 | "nationalCities": { 38 | "links": { 39 | "self": "\/countries\/6\/relationships\/nationalCities", 40 | "related": "\/countries\/6\/nationalCities" 41 | } 42 | }, 43 | "subcountries": { 44 | "links": { 45 | "self": "\/countries\/6\/relationships\/subcountries", 46 | "related": "\/countries\/6\/subcountries" 47 | } 48 | }, 49 | "supercountry": { 50 | "links": { 51 | "self": "\/countries\/6\/relationships\/supercountry", 52 | "related": "\/countries\/6\/supercountry" 53 | } 54 | }, 55 | "languages": { 56 | "links": { 57 | "self": "\/countries\/6\/relationships\/languages", 58 | "related": "\/countries\/6\/languages" 59 | } 60 | } 61 | }, 62 | "links": { 63 | "self": "\/countries\/6" 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/CreatingResources/created-currency-no-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "currencies", 4 | "id": "3", 5 | "attributes": { 6 | "code": "ZAR", 7 | "name": "South African Rand" 8 | }, 9 | "relationships": { 10 | "countries": { 11 | "links": { 12 | "self": "\/currencies\/3\/relationships\/countries", 13 | "related": "\/currencies\/3\/countries" 14 | } 15 | } 16 | }, 17 | "links": { 18 | "self": "\/currencies\/3" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/CreatingResources/created-national-capital-no-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCapitals", 4 | "id": "7", 5 | "attributes": { 6 | "description": "National capital of Cuba", 7 | "name": "Havana" 8 | }, 9 | "relationships": { 10 | "countries": { 11 | "links": { 12 | "self": "\/nationalCapitals\/7\/relationships\/countries", 13 | "related": "\/nationalCapitals\/7\/countries" 14 | } 15 | } 16 | }, 17 | "links": { 18 | "self": "\/nationalCapitals\/7" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/CreatingResources/created-national-city-single-existing-belongsto-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCities", 4 | "id": "7", 5 | "attributes": { 6 | "name": "Rotterdam" 7 | }, 8 | "relationships": { 9 | "country": { 10 | "links": { 11 | "self": "\/nationalCities\/7\/relationships\/country", 12 | "related": "\/nationalCities\/7\/country" 13 | }, 14 | "data": { 15 | "type": "countries", 16 | "id": "1" 17 | } 18 | } 19 | }, 20 | "links": { 21 | "self": "\/nationalCities\/7" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Errors/404-error-for-collection-in-debug-mode.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors": [ 3 | { 4 | "status": "404", 5 | "title": "Not Found", 6 | "detail": "A route matching \"\/nonexistents\" could not be found." 7 | } 8 | ], 9 | "debug": {} 10 | } 11 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Errors/404-error-for-collection-in-production-mode.json: -------------------------------------------------------------------------------- 1 | {"errors":[{"status":"404","title":"Not Found"}]} -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Errors/404-error-for-resource-in-debug-mode.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors": [ 3 | { 4 | "status": "404", 5 | "title": "Not Found" 6 | } 7 | ], 8 | "debug": {} 9 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Errors/404-error-for-resource-in-production-mode.json: -------------------------------------------------------------------------------- 1 | {"errors":[{"status":"404","title":"Not Found"}]} -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Errors/validation-error-multiple-reasons.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors": [{ 3 | "title": "_required", 4 | "detail": "This field is required", 5 | "source": { 6 | "pointer": "/data/attributes/name" 7 | } 8 | }, { 9 | "title": "UPPERCASE_ONLY", 10 | "detail": "Field must be uppercase only", 11 | "source": { 12 | "pointer": "/data/attributes/code" 13 | } 14 | }] 15 | } 16 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/FetchingResources/get-country-currency.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "currencies", 4 | "id": "1", 5 | "attributes": { 6 | "code": "EUR", 7 | "name": "Euro" 8 | }, 9 | "relationships": { 10 | "countries": { 11 | "links": { 12 | "self": "\/currencies\/1\/relationships\/countries", 13 | "related": "\/currencies\/1\/countries" 14 | } 15 | } 16 | }, 17 | "links": { 18 | "self": "\/currencies\/1" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/FetchingResources/get-country-languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "languages", 5 | "id": "1", 6 | "attributes": { 7 | "code": "en", 8 | "name": "English" 9 | }, 10 | "relationships": { 11 | "countries": { 12 | "links": { 13 | "self": "\/languages\/1\/relationships\/countries", 14 | "related": "\/languages\/1\/countries" 15 | } 16 | } 17 | }, 18 | "links": { 19 | "self": "\/languages\/1" 20 | } 21 | }, 22 | { 23 | "type": "languages", 24 | "id": "2", 25 | "attributes": { 26 | "code": "nl", 27 | "name": "Dutch" 28 | }, 29 | "relationships": { 30 | "countries": { 31 | "links": { 32 | "self": "\/languages\/2\/relationships\/countries", 33 | "related": "\/languages\/2\/countries" 34 | } 35 | } 36 | }, 37 | "links": { 38 | "self": "\/languages\/2" 39 | } 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/FetchingResources/get-country-multiple-belongsto-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "dummyCounter": 11111, 8 | "name": "The Netherlands" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/1\/relationships\/currency", 14 | "related": "\/countries\/1\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/nationalCapital", 24 | "related": "\/countries\/1\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "1" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/1\/relationships\/cultures", 34 | "related": "\/countries\/1\/cultures" 35 | } 36 | }, 37 | "nationalCities": { 38 | "links": { 39 | "self": "\/countries\/1\/relationships\/nationalCities", 40 | "related": "\/countries\/1\/nationalCities" 41 | } 42 | }, 43 | "subcountries": { 44 | "links": { 45 | "self": "\/countries\/1\/relationships\/subcountries", 46 | "related": "\/countries\/1\/subcountries" 47 | } 48 | }, 49 | "supercountry": { 50 | "links": { 51 | "self": "\/countries\/1\/relationships\/supercountry", 52 | "related": "\/countries\/1\/supercountry" 53 | } 54 | }, 55 | "languages": { 56 | "links": { 57 | "self": "\/countries\/1\/relationships\/languages", 58 | "related": "\/countries\/1\/languages" 59 | } 60 | } 61 | }, 62 | "links": { 63 | "self": "\/countries\/1" 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/FetchingResources/get-country-no-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "dummyCounter": 11111, 8 | "name": "The Netherlands" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/1\/relationships\/currency", 14 | "related": "\/countries\/1\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/nationalCapital", 24 | "related": "\/countries\/1\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "1" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/1\/relationships\/cultures", 34 | "related": "\/countries\/1\/cultures" 35 | } 36 | }, 37 | "nationalCities": { 38 | "links": { 39 | "self": "\/countries\/1\/relationships\/nationalCities", 40 | "related": "\/countries\/1\/nationalCities" 41 | } 42 | }, 43 | "subcountries": { 44 | "links": { 45 | "self": "\/countries\/1\/relationships\/subcountries", 46 | "related": "\/countries\/1\/subcountries" 47 | } 48 | }, 49 | "supercountry": { 50 | "links": { 51 | "self": "\/countries\/1\/relationships\/supercountry", 52 | "related": "\/countries\/1\/supercountry" 53 | } 54 | }, 55 | "languages": { 56 | "links": { 57 | "self": "\/countries\/1\/relationships\/languages", 58 | "related": "\/countries\/1\/languages" 59 | } 60 | } 61 | }, 62 | "links": { 63 | "self": "\/countries\/1" 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/FetchingResources/get-currency-no-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "currencies", 4 | "id": "1", 5 | "attributes": { 6 | "code": "EUR", 7 | "name": "Euro" 8 | }, 9 | "relationships": { 10 | "countries": { 11 | "links": { 12 | "self": "\/currencies\/1\/relationships\/countries", 13 | "related": "\/currencies\/1\/countries" 14 | } 15 | } 16 | }, 17 | "links": { 18 | "self": "\/currencies\/1" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/FetchingResources/get-national-capital-no-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCapitals", 4 | "id": "1", 5 | "attributes": { 6 | "description": "National capital of the Netherlands", 7 | "name": "Amsterdam" 8 | }, 9 | "relationships": { 10 | "countries": { 11 | "links": { 12 | "self": "\/nationalCapitals\/1\/relationships\/countries", 13 | "related": "\/nationalCapitals\/1\/countries" 14 | } 15 | } 16 | }, 17 | "links": { 18 | "self": "\/nationalCapitals\/1" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/FetchingResources/get-national-city-single-belongsto-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCities", 4 | "id": "1", 5 | "attributes": { 6 | "name": "Amsterdam" 7 | }, 8 | "relationships": { 9 | "country": { 10 | "links": { 11 | "self": "\/nationalCities\/1\/relationships\/country", 12 | "related": "\/nationalCities\/1\/country" 13 | }, 14 | "data": { 15 | "type": "countries", 16 | "id": "1" 17 | } 18 | } 19 | }, 20 | "links": { 21 | "self": "\/nationalCities\/1" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Filtering/filter-single-field-partial.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 1, 4 | "page_count": 1, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&filter=Nether", 9 | "first": "\/countries?page=1&filter=Nether", 10 | "last": "\/countries?page=1&filter=Nether" 11 | }, 12 | "data": [ 13 | { 14 | "type": "countries", 15 | "id": "1", 16 | "attributes": { 17 | "code": "NL", 18 | "dummyCounter": 11111, 19 | "name": "The Netherlands" 20 | }, 21 | "relationships": { 22 | "currency": { 23 | "links": { 24 | "self": "\/countries\/1\/relationships\/currency", 25 | "related": "\/countries\/1\/currency" 26 | }, 27 | "data": { 28 | "type": "currencies", 29 | "id": "1" 30 | } 31 | }, 32 | "nationalCapital": { 33 | "links": { 34 | "self": "\/countries\/1\/relationships\/nationalCapital", 35 | "related": "\/countries\/1\/nationalCapital" 36 | }, 37 | "data": { 38 | "type": "nationalCapitals", 39 | "id": "1" 40 | } 41 | }, 42 | "cultures": { 43 | "links": { 44 | "self": "\/countries\/1\/relationships\/cultures", 45 | "related": "\/countries\/1\/cultures" 46 | } 47 | }, 48 | "nationalCities": { 49 | "links": { 50 | "self": "\/countries\/1\/relationships\/nationalCities", 51 | "related": "\/countries\/1\/nationalCities" 52 | } 53 | }, 54 | "subcountries": { 55 | "links": { 56 | "self": "\/countries\/1\/relationships\/subcountries", 57 | "related": "\/countries\/1\/subcountries" 58 | } 59 | }, 60 | "supercountry": { 61 | "links": { 62 | "self": "\/countries\/1\/relationships\/supercountry", 63 | "related": "\/countries\/1\/supercountry" 64 | } 65 | }, 66 | "languages": { 67 | "links": { 68 | "self": "\/countries\/1\/relationships\/languages", 69 | "related": "\/countries\/1\/languages" 70 | } 71 | } 72 | }, 73 | "links": { 74 | "self": "\/countries\/1" 75 | } 76 | } 77 | ] 78 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Filtering/filter-single-field.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 1, 4 | "page_count": 1, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&filter=Netherlands", 9 | "first": "\/countries?page=1&filter=Netherlands", 10 | "last": "\/countries?page=1&filter=Netherlands" 11 | }, 12 | "data": [ 13 | { 14 | "type": "countries", 15 | "id": "1", 16 | "attributes": { 17 | "code": "NL", 18 | "dummyCounter": 11111, 19 | "name": "The Netherlands" 20 | }, 21 | "relationships": { 22 | "currency": { 23 | "links": { 24 | "self": "\/countries\/1\/relationships\/currency", 25 | "related": "\/countries\/1\/currency" 26 | }, 27 | "data": { 28 | "type": "currencies", 29 | "id": "1" 30 | } 31 | }, 32 | "nationalCapital": { 33 | "links": { 34 | "self": "\/countries\/1\/relationships\/nationalCapital", 35 | "related": "\/countries\/1\/nationalCapital" 36 | }, 37 | "data": { 38 | "type": "nationalCapitals", 39 | "id": "1" 40 | } 41 | }, 42 | "cultures": { 43 | "links": { 44 | "self": "\/countries\/1\/relationships\/cultures", 45 | "related": "\/countries\/1\/cultures" 46 | } 47 | }, 48 | "nationalCities": { 49 | "links": { 50 | "self": "\/countries\/1\/relationships\/nationalCities", 51 | "related": "\/countries\/1\/nationalCities" 52 | } 53 | }, 54 | "subcountries": { 55 | "links": { 56 | "self": "\/countries\/1\/relationships\/subcountries", 57 | "related": "\/countries\/1\/subcountries" 58 | } 59 | }, 60 | "supercountry": { 61 | "links": { 62 | "self": "\/countries\/1\/relationships\/supercountry", 63 | "related": "\/countries\/1\/supercountry" 64 | } 65 | }, 66 | "languages": { 67 | "links": { 68 | "self": "\/countries\/1\/relationships\/languages", 69 | "related": "\/countries\/1\/languages" 70 | } 71 | } 72 | }, 73 | "links": { 74 | "self": "\/countries\/1" 75 | } 76 | } 77 | ] 78 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Inclusion/get-country-include-culture.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "dummyCounter": 11111, 8 | "name": "The Netherlands" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/1\/relationships\/currency", 14 | "related": "\/countries\/1\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/nationalCapital", 24 | "related": "\/countries\/1\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "1" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/1\/relationships\/cultures", 34 | "related": "\/countries\/1\/cultures" 35 | }, 36 | "data": [ 37 | { 38 | "type": "cultures", 39 | "id": "1" 40 | } 41 | ] 42 | }, 43 | "nationalCities": { 44 | "links": { 45 | "self": "\/countries\/1\/relationships\/nationalCities", 46 | "related": "\/countries\/1\/nationalCities" 47 | } 48 | }, 49 | "subcountries": { 50 | "links": { 51 | "self": "\/countries\/1\/relationships\/subcountries", 52 | "related": "\/countries\/1\/subcountries" 53 | } 54 | }, 55 | "supercountry": { 56 | "links": { 57 | "self": "\/countries\/1\/relationships\/supercountry", 58 | "related": "\/countries\/1\/supercountry" 59 | } 60 | }, 61 | "languages": { 62 | "links": { 63 | "self": "\/countries\/1\/relationships\/languages", 64 | "related": "\/countries\/1\/languages" 65 | } 66 | } 67 | }, 68 | "links": { 69 | "self": "\/countries\/1" 70 | } 71 | }, 72 | "included": [ 73 | { 74 | "type": "cultures", 75 | "id": "1", 76 | "attributes": { 77 | "anotherDummyCounter": 11111, 78 | "code": "nl-NL", 79 | "name": "Dutch" 80 | }, 81 | "relationships": { 82 | "country": { 83 | "links": { 84 | "self": "\/cultures\/1\/relationships\/country", 85 | "related": "\/cultures\/1\/country" 86 | }, 87 | "data": { 88 | "type": "countries", 89 | "id": "1" 90 | } 91 | } 92 | }, 93 | "links": { 94 | "self": "\/cultures\/1" 95 | } 96 | } 97 | ] 98 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Inclusion/get-country-include-currency-and-culture.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "dummyCounter": 11111, 8 | "name": "The Netherlands" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/1\/relationships\/currency", 14 | "related": "\/countries\/1\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/nationalCapital", 24 | "related": "\/countries\/1\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "1" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/1\/relationships\/cultures", 34 | "related": "\/countries\/1\/cultures" 35 | }, 36 | "data": [ 37 | { 38 | "type": "cultures", 39 | "id": "1" 40 | } 41 | ] 42 | }, 43 | "nationalCities": { 44 | "links": { 45 | "self": "\/countries\/1\/relationships\/nationalCities", 46 | "related": "\/countries\/1\/nationalCities" 47 | } 48 | }, 49 | "subcountries": { 50 | "links": { 51 | "self": "\/countries\/1\/relationships\/subcountries", 52 | "related": "\/countries\/1\/subcountries" 53 | } 54 | }, 55 | "supercountry": { 56 | "links": { 57 | "self": "\/countries\/1\/relationships\/supercountry", 58 | "related": "\/countries\/1\/supercountry" 59 | } 60 | }, 61 | "languages": { 62 | "links": { 63 | "self": "\/countries\/1\/relationships\/languages", 64 | "related": "\/countries\/1\/languages" 65 | } 66 | } 67 | }, 68 | "links": { 69 | "self": "\/countries\/1" 70 | } 71 | }, 72 | "included": [ 73 | { 74 | "type": "currencies", 75 | "id": "1", 76 | "attributes": { 77 | "code": "EUR", 78 | "name": "Euro" 79 | }, 80 | "relationships": { 81 | "countries": { 82 | "links": { 83 | "self": "\/currencies\/1\/relationships\/countries", 84 | "related": "\/currencies\/1\/countries" 85 | } 86 | } 87 | }, 88 | "links": { 89 | "self": "\/currencies\/1" 90 | } 91 | }, 92 | { 93 | "type": "cultures", 94 | "id": "1", 95 | "attributes": { 96 | "anotherDummyCounter": 11111, 97 | "code": "nl-NL", 98 | "name": "Dutch" 99 | }, 100 | "relationships": { 101 | "country": { 102 | "links": { 103 | "self": "\/cultures\/1\/relationships\/country", 104 | "related": "\/cultures\/1\/country" 105 | }, 106 | "data": { 107 | "type": "countries", 108 | "id": "1" 109 | } 110 | } 111 | }, 112 | "links": { 113 | "self": "\/cultures\/1" 114 | } 115 | } 116 | ] 117 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Inclusion/get-country-include-currency.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "dummyCounter": 11111, 8 | "name": "The Netherlands" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/1\/relationships\/currency", 14 | "related": "\/countries\/1\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/nationalCapital", 24 | "related": "\/countries\/1\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "1" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/1\/relationships\/cultures", 34 | "related": "\/countries\/1\/cultures" 35 | } 36 | }, 37 | "nationalCities": { 38 | "links": { 39 | "self": "\/countries\/1\/relationships\/nationalCities", 40 | "related": "\/countries\/1\/nationalCities" 41 | } 42 | }, 43 | "subcountries": { 44 | "links": { 45 | "self": "\/countries\/1\/relationships\/subcountries", 46 | "related": "\/countries\/1\/subcountries" 47 | } 48 | }, 49 | "supercountry": { 50 | "links": { 51 | "self": "\/countries\/1\/relationships\/supercountry", 52 | "related": "\/countries\/1\/supercountry" 53 | } 54 | }, 55 | "languages": { 56 | "links": { 57 | "self": "\/countries\/1\/relationships\/languages", 58 | "related": "\/countries\/1\/languages" 59 | } 60 | } 61 | }, 62 | "links": { 63 | "self": "\/countries\/1" 64 | } 65 | }, 66 | "included": [ 67 | { 68 | "type": "currencies", 69 | "id": "1", 70 | "attributes": { 71 | "code": "EUR", 72 | "name": "Euro" 73 | }, 74 | "relationships": { 75 | "countries": { 76 | "links": { 77 | "self": "\/currencies\/1\/relationships\/countries", 78 | "related": "\/currencies\/1\/countries" 79 | } 80 | } 81 | }, 82 | "links": { 83 | "self": "\/currencies\/1" 84 | } 85 | } 86 | ] 87 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Inclusion/get-country-include-national-capital-and-national-cities.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "name": "The Netherlands" 8 | }, 9 | "relationships": { 10 | "nationalCapital": { 11 | "links": { 12 | "self": "\/nationalCapitals\/1" 13 | }, 14 | "data": { 15 | "type": "nationalCapitals", 16 | "id": "1" 17 | } 18 | }, 19 | "nationalCities": { 20 | "links": { 21 | "related": "\/nationalCities?country_id=1" 22 | }, 23 | "data": [ 24 | { 25 | "type": "nationalCities", 26 | "id": "1" 27 | }, 28 | { 29 | "type": "nationalCities", 30 | "id": "2" 31 | } 32 | ] 33 | } 34 | }, 35 | "links": { 36 | "self": "\/countries\/1" 37 | } 38 | }, 39 | "included": [ 40 | { 41 | "type": "nationalCapitals", 42 | "id": "1", 43 | "attributes": { 44 | "name": "Amsterdam", 45 | "description": "National capital of the Netherlands" 46 | }, 47 | "links": { 48 | "self": "\/nationalCapitals\/1" 49 | } 50 | }, 51 | { 52 | "type": "nationalCities", 53 | "id": "1", 54 | "attributes": { 55 | "name": "Amsterdam" 56 | }, 57 | "links": { 58 | "self": "\/nationalCities\/1" 59 | } 60 | }, 61 | { 62 | "type": "nationalCities", 63 | "id": "2", 64 | "attributes": { 65 | "name": "Rotterdam" 66 | }, 67 | "links": { 68 | "self": "\/nationalCities\/2" 69 | } 70 | } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Inclusion/get-country-include-national-capital.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "dummyCounter": 11111, 8 | "name": "The Netherlands" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/1\/relationships\/currency", 14 | "related": "\/countries\/1\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/nationalCapital", 24 | "related": "\/countries\/1\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "1" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/1\/relationships\/cultures", 34 | "related": "\/countries\/1\/cultures" 35 | } 36 | }, 37 | "nationalCities": { 38 | "links": { 39 | "self": "\/countries\/1\/relationships\/nationalCities", 40 | "related": "\/countries\/1\/nationalCities" 41 | } 42 | }, 43 | "subcountries": { 44 | "links": { 45 | "self": "\/countries\/1\/relationships\/subcountries", 46 | "related": "\/countries\/1\/subcountries" 47 | } 48 | }, 49 | "supercountry": { 50 | "links": { 51 | "self": "\/countries\/1\/relationships\/supercountry", 52 | "related": "\/countries\/1\/supercountry" 53 | } 54 | }, 55 | "languages": { 56 | "links": { 57 | "self": "\/countries\/1\/relationships\/languages", 58 | "related": "\/countries\/1\/languages" 59 | } 60 | } 61 | }, 62 | "links": { 63 | "self": "\/countries\/1" 64 | } 65 | }, 66 | "included": [ 67 | { 68 | "type": "nationalCapitals", 69 | "id": "1", 70 | "attributes": { 71 | "description": "National capital of the Netherlands", 72 | "name": "Amsterdam" 73 | }, 74 | "relationships": { 75 | "countries": { 76 | "links": { 77 | "self": "\/nationalCapitals\/1\/relationships\/countries", 78 | "related": "\/nationalCapitals\/1\/countries" 79 | } 80 | } 81 | }, 82 | "links": { 83 | "self": "\/nationalCapitals\/1" 84 | } 85 | } 86 | ] 87 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/MetaInformation/meta-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "author": "bravo-kernel" 4 | } 5 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/delete-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/2\/relationships\/cultures", 4 | "related": "\/countries\/2\/cultures" 5 | }, 6 | "data": [ 7 | { 8 | "type": "cultures", 9 | "id": "3" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/delete-language-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/1\/relationships\/languages", 4 | "related": "\/countries\/1\/languages" 5 | }, 6 | "data": [ 7 | { 8 | "type": "languages", 9 | "id": "1" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/delete-not-existing-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/2\/relationships\/cultures", 4 | "related": "\/countries\/2\/cultures" 5 | }, 6 | "data": [ 7 | { 8 | "type": "cultures", 9 | "id": "2" 10 | }, 11 | { 12 | "type": "cultures", 13 | "id": "3" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/get_culture_relationship_for_country.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/2\/relationships\/cultures", 4 | "related": "\/countries\/2\/cultures" 5 | }, 6 | "data": [ 7 | { 8 | "type": "cultures", 9 | "id": "2" 10 | }, 11 | { 12 | "type": "cultures", 13 | "id": "3" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/get_culture_relationship_for_country_with_none.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/4\/relationships\/cultures", 4 | "related": "\/countries\/4\/cultures" 5 | }, 6 | "data": [] 7 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/get_currency_relationship_for_country.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/2\/relationships\/currency", 4 | "related": "\/countries\/2\/currency" 5 | }, 6 | "data": { 7 | "type": "currencies", 8 | "id": "1" 9 | } 10 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/get_languages_relationship_for_country.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/1\/relationships\/languages", 4 | "related": "\/countries\/1\/languages" 5 | }, 6 | "data": [ 7 | { 8 | "type": "languages", 9 | "id": "1" 10 | }, 11 | { 12 | "type": "languages", 13 | "id": "2" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/patch-clear-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/2\/relationships\/cultures", 4 | "related": "\/countries\/2\/cultures" 5 | }, 6 | "data": [] 7 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/patch-replace-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/2\/relationships\/cultures", 4 | "related": "\/countries\/2\/cultures" 5 | }, 6 | "data": [ 7 | { 8 | "type": "cultures", 9 | "id": "1" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/patch-replace-currency-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/1\/relationships\/currency", 4 | "related": "\/countries\/1\/currency" 5 | }, 6 | "data": { 7 | "type": "currencies", 8 | "id": "2" 9 | } 10 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/patch-replace-language-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/1\/relationships\/languages", 4 | "related": "\/countries\/1\/languages" 5 | }, 6 | "data": [ 7 | { 8 | "type": "languages", 9 | "id": "3" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/post-add-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/2\/relationships\/cultures", 4 | "related": "\/countries\/2\/cultures" 5 | }, 6 | "data": [ 7 | { 8 | "type": "cultures", 9 | "id": "1" 10 | }, 11 | { 12 | "type": "cultures", 13 | "id": "2" 14 | }, 15 | { 16 | "type": "cultures", 17 | "id": "3" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/post-add-existing-culture-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/2\/relationships\/cultures", 4 | "related": "\/countries\/2\/cultures" 5 | }, 6 | "data": [ 7 | { 8 | "type": "cultures", 9 | "id": "2" 10 | }, 11 | { 12 | "type": "cultures", 13 | "id": "3" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Relationships/post-add-language-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": "\/countries\/1\/relationships\/languages", 4 | "related": "\/countries\/1\/languages" 5 | }, 6 | "data": [ 7 | { 8 | "type": "languages", 9 | "id": "1" 10 | }, 11 | { 12 | "type": "languages", 13 | "id": "2" 14 | }, 15 | { 16 | "type": "languages", 17 | "id": "3" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Sorting/get-currencies-no-sort.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "currencies", 5 | "id": "1", 6 | "attributes": { 7 | "code": "EUR", 8 | "name": "Euro" 9 | }, 10 | "relationships": { 11 | "countries": { 12 | "links": { 13 | "self": "\/currencies\/1\/relationships\/countries", 14 | "related": "\/currencies\/1\/countries" 15 | } 16 | } 17 | }, 18 | "links": { 19 | "self": "\/currencies\/1" 20 | } 21 | }, 22 | { 23 | "type": "currencies", 24 | "id": "2", 25 | "attributes": { 26 | "code": "USD", 27 | "name": "US Dollar" 28 | }, 29 | "relationships": { 30 | "countries": { 31 | "links": { 32 | "self": "\/currencies\/2\/relationships\/countries", 33 | "related": "\/currencies\/2\/countries" 34 | } 35 | } 36 | }, 37 | "links": { 38 | "self": "\/currencies\/2" 39 | } 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Sorting/get-currencies-sort-by-code-desc.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "currencies", 5 | "id": "2", 6 | "attributes": { 7 | "code": "USD", 8 | "name": "US Dollar" 9 | }, 10 | "relationships": { 11 | "countries": { 12 | "links": { 13 | "self": "\/currencies\/2\/relationships\/countries", 14 | "related": "\/currencies\/2\/countries" 15 | } 16 | } 17 | }, 18 | "links": { 19 | "self": "\/currencies\/2" 20 | } 21 | }, 22 | { 23 | "type": "currencies", 24 | "id": "1", 25 | "attributes": { 26 | "code": "EUR", 27 | "name": "Euro" 28 | }, 29 | "relationships": { 30 | "countries": { 31 | "links": { 32 | "self": "\/currencies\/1\/relationships\/countries", 33 | "related": "\/currencies\/1\/countries" 34 | } 35 | } 36 | }, 37 | "links": { 38 | "self": "\/currencies\/1" 39 | } 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Sorting/national-cities-absolute-links.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 6, 4 | "page_count": 3, 5 | "page_limit": 2 6 | }, 7 | "links": { 8 | "self": "http:\/\/test-server\/nationalCities?page=2&limit=2", 9 | "first": "http:\/\/test-server\/nationalCities?page=1&limit=2", 10 | "last": "http:\/\/test-server\/nationalCities?page=3&limit=2", 11 | "prev": "http:\/\/test-server\/nationalCities?page=1&limit=2", 12 | "next": "http:\/\/test-server\/nationalCities?page=3&limit=2" 13 | }, 14 | "data": [ 15 | { 16 | "type": "nationalCities", 17 | "id": "3", 18 | "attributes": { 19 | "name": "Sofia" 20 | }, 21 | "relationships": { 22 | "country": { 23 | "links": { 24 | "self": "\/countries\/2" 25 | }, 26 | "data": { 27 | "type": "countries", 28 | "id": "2" 29 | } 30 | } 31 | }, 32 | "links": { 33 | "self": "\/nationalCities\/3" 34 | } 35 | }, 36 | { 37 | "type": "nationalCities", 38 | "id": "4", 39 | "attributes": { 40 | "name": "Plovdiv" 41 | }, 42 | "relationships": { 43 | "country": { 44 | "links": { 45 | "self": "\/countries\/2" 46 | }, 47 | "data": { 48 | "type": "countries", 49 | "id": "2" 50 | } 51 | } 52 | }, 53 | "links": { 54 | "self": "\/nationalCities\/4" 55 | } 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Sorting/nationalCities-absolute-links.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 6, 4 | "page_count": 3, 5 | "page_limit": 2 6 | }, 7 | "links": { 8 | "self": "http:\/\/test-server\/nationalCities?page=2&limit=2", 9 | "first": "http:\/\/test-server\/nationalCities?page=1&limit=2", 10 | "last": "http:\/\/test-server\/nationalCities?page=3&limit=2", 11 | "prev": "http:\/\/test-server\/nationalCities?page=1&limit=2", 12 | "next": "http:\/\/test-server\/nationalCities?page=3&limit=2" 13 | }, 14 | "data": [ 15 | { 16 | "type": "nationalCities", 17 | "id": "3", 18 | "attributes": { 19 | "name": "Sofia" 20 | }, 21 | "relationships": { 22 | "country": { 23 | "links": { 24 | "self": "\/nationalCities\/3\/relationships\/country", 25 | "related": "\/nationalCities\/3\/country" 26 | }, 27 | "data": { 28 | "type": "countries", 29 | "id": "2" 30 | } 31 | } 32 | }, 33 | "links": { 34 | "self": "\/nationalCities\/3" 35 | } 36 | }, 37 | { 38 | "type": "nationalCities", 39 | "id": "4", 40 | "attributes": { 41 | "name": "Plovdiv" 42 | }, 43 | "relationships": { 44 | "country": { 45 | "links": { 46 | "self": "\/nationalCities\/4\/relationships\/country", 47 | "related": "\/nationalCities\/4\/country" 48 | }, 49 | "data": { 50 | "type": "countries", 51 | "id": "2" 52 | } 53 | } 54 | }, 55 | "links": { 56 | "self": "\/nationalCities\/4" 57 | } 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Sorting/sorted_desc_with_include.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&sort=-code&include=currency", 9 | "first": "\/countries?page=1&sort=-code&include=currency", 10 | "last": "\/countries?page=2&sort=-code&include=currency", 11 | "prev": null, 12 | "next": "\/countries?page=2&sort=-code&include=currency" 13 | }, 14 | "data": [ 15 | { 16 | "type": "countries", 17 | "id": "4", 18 | "attributes": { 19 | "code": "VT", 20 | "name": "Vatican", 21 | "dummyCounter": 44444 22 | }, 23 | "relationships": { 24 | "currency": { 25 | "data": { 26 | "type": "currencies", 27 | "id": "1" 28 | }, 29 | "links": { 30 | "self": "\/currencies\/1" 31 | } 32 | } 33 | }, 34 | "links": { 35 | "self": "\/countries\/4" 36 | } 37 | }, 38 | { 39 | "type": "countries", 40 | "id": "5", 41 | "attributes": { 42 | "code": "US", 43 | "name": "United States of America", 44 | "dummyCounter": 33333 45 | }, 46 | "relationships": { 47 | "currency": { 48 | "data": { 49 | "type": "currencies", 50 | "id": "2" 51 | }, 52 | "links": { 53 | "self": "\/currencies\/2" 54 | } 55 | } 56 | }, 57 | "links": { 58 | "self": "\/countries\/5" 59 | } 60 | }, 61 | { 62 | "type": "countries", 63 | "id": "1", 64 | "attributes": { 65 | "code": "NL", 66 | "name": "The Netherlands", 67 | "dummyCounter": 11111 68 | }, 69 | "relationships": { 70 | "currency": { 71 | "data": { 72 | "type": "currencies", 73 | "id": "1" 74 | }, 75 | "links": { 76 | "self": "\/currencies\/1" 77 | } 78 | } 79 | }, 80 | "links": { 81 | "self": "\/countries\/1" 82 | } 83 | } 84 | ], 85 | "included": [ 86 | { 87 | "type": "currencies", 88 | "id": "1", 89 | "attributes": { 90 | "code": "EUR", 91 | "name": "Euro" 92 | }, 93 | "links": { 94 | "self": "\/currencies\/1" 95 | } 96 | }, 97 | { 98 | "type": "currencies", 99 | "id": "2", 100 | "attributes": { 101 | "code": "USD", 102 | "name": "US Dollar" 103 | }, 104 | "links": { 105 | "self": "\/currencies\/2" 106 | } 107 | } 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Sorting/sorted_with_include.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&sort=code&include=currency", 9 | "first": "\/countries?page=1&sort=code&include=currency", 10 | "last": "\/countries?page=2&sort=code&include=currency", 11 | "prev": null, 12 | "next": "\/countries?page=2&sort=code&include=currency" 13 | }, 14 | "data": [ 15 | { 16 | "type": "countries", 17 | "id": "2", 18 | "attributes": { 19 | "code": "BG", 20 | "name": "Bulgaria", 21 | "dummyCounter": 22222 22 | }, 23 | "relationships": { 24 | "currency": { 25 | "data": { 26 | "type": "currencies", 27 | "id": "1" 28 | }, 29 | "links": { 30 | "self": "\/currencies\/1" 31 | } 32 | } 33 | }, 34 | "links": { 35 | "self": "\/countries\/2" 36 | } 37 | }, 38 | { 39 | "type": "countries", 40 | "id": "3", 41 | "attributes": { 42 | "code": "IT", 43 | "name": "Italy", 44 | "dummyCounter": 33333 45 | }, 46 | "relationships": { 47 | "currency": { 48 | "data": { 49 | "type": "currencies", 50 | "id": "1" 51 | }, 52 | "links": { 53 | "self": "\/currencies\/1" 54 | } 55 | } 56 | }, 57 | "links": { 58 | "self": "\/countries\/3" 59 | } 60 | }, 61 | { 62 | "type": "countries", 63 | "id": "1", 64 | "attributes": { 65 | "code": "NL", 66 | "name": "The Netherlands", 67 | "dummyCounter": 11111 68 | }, 69 | "relationships": { 70 | "currency": { 71 | "data": { 72 | "type": "currencies", 73 | "id": "1" 74 | }, 75 | "links": { 76 | "self": "\/currencies\/1" 77 | } 78 | } 79 | }, 80 | "links": { 81 | "self": "\/countries\/1" 82 | } 83 | } 84 | ], 85 | "included": [ 86 | { 87 | "type": "currencies", 88 | "id": "1", 89 | "attributes": { 90 | "code": "EUR", 91 | "name": "Euro" 92 | }, 93 | "links": { 94 | "self": "\/currencies\/1" 95 | } 96 | } 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/Sorting/sorted_with_include_page_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=2&sort=code&include=currency", 9 | "first": "\/countries?page=1&sort=code&include=currency", 10 | "last": "\/countries?page=2&sort=code&include=currency", 11 | "prev": "\/countries?page=1&sort=code&include=currency", 12 | "next": null 13 | }, 14 | "data": [ 15 | { 16 | "type": "countries", 17 | "id": "5", 18 | "attributes": { 19 | "code": "US", 20 | "name": "United States of America", 21 | "dummyCounter": 33333 22 | }, 23 | "relationships": { 24 | "currency": { 25 | "data": { 26 | "type": "currencies", 27 | "id": "2" 28 | }, 29 | "links": { 30 | "self": "\/currencies\/2" 31 | } 32 | } 33 | }, 34 | "links": { 35 | "self": "\/countries\/5" 36 | } 37 | }, 38 | { 39 | "type": "countries", 40 | "id": "4", 41 | "attributes": { 42 | "code": "VT", 43 | "name": "Vatican", 44 | "dummyCounter": 44444 45 | }, 46 | "relationships": { 47 | "currency": { 48 | "data": { 49 | "type": "currencies", 50 | "id": "1" 51 | }, 52 | "links": { 53 | "self": "\/currencies\/1" 54 | } 55 | } 56 | }, 57 | "links": { 58 | "self": "\/countries\/4" 59 | } 60 | } 61 | ], 62 | "included": [ 63 | { 64 | "type": "currencies", 65 | "id": "2", 66 | "attributes": { 67 | "code": "USD", 68 | "name": "US Dollar" 69 | }, 70 | "links": { 71 | "self": "\/currencies\/2" 72 | } 73 | }, 74 | { 75 | "type": "currencies", 76 | "id": "1", 77 | "attributes": { 78 | "code": "EUR", 79 | "name": "Euro" 80 | }, 81 | "links": { 82 | "self": "\/currencies\/1" 83 | } 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/index-multi-field-sparse-for-primary-and-included-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&include=currencies&fields%5Bcountries%5D=code%2Cname%2Ccurrency&fields%5Bcurrencies%5D=id%2Ccode%2Cname", 9 | "first": "\/countries?page=1&include=currencies&fields%5Bcountries%5D=code%2Cname%2Ccurrency&fields%5Bcurrencies%5D=id%2Ccode%2Cname", 10 | "last": "\/countries?page=2&include=currencies&fields%5Bcountries%5D=code%2Cname%2Ccurrency&fields%5Bcurrencies%5D=id%2Ccode%2Cname", 11 | "next": "\/countries?page=2&include=currencies&fields%5Bcountries%5D=code%2Cname%2Ccurrency&fields%5Bcurrencies%5D=id%2Ccode%2Cname" 12 | }, 13 | "data": [ 14 | { 15 | "type": "countries", 16 | "id": "1", 17 | "attributes": { 18 | "code": "NL", 19 | "name": "The Netherlands" 20 | }, 21 | "relationships": { 22 | "currency": { 23 | "links": { 24 | "self": "\/countries\/1\/relationships\/currency", 25 | "related": "\/countries\/1\/currency" 26 | }, 27 | "data": { 28 | "type": "currencies", 29 | "id": "1" 30 | } 31 | } 32 | }, 33 | "links": { 34 | "self": "\/countries\/1" 35 | } 36 | }, 37 | { 38 | "type": "countries", 39 | "id": "2", 40 | "attributes": { 41 | "code": "BG", 42 | "name": "Bulgaria" 43 | }, 44 | "relationships": { 45 | "currency": { 46 | "links": { 47 | "self": "\/countries\/2\/relationships\/currency", 48 | "related": "\/countries\/2\/currency" 49 | }, 50 | "data": { 51 | "type": "currencies", 52 | "id": "1" 53 | } 54 | } 55 | }, 56 | "links": { 57 | "self": "\/countries\/2" 58 | } 59 | }, 60 | { 61 | "type": "countries", 62 | "id": "3", 63 | "attributes": { 64 | "code": "IT", 65 | "name": "Italy" 66 | }, 67 | "relationships": { 68 | "currency": { 69 | "links": { 70 | "self": "\/countries\/3\/relationships\/currency", 71 | "related": "\/countries\/3\/currency" 72 | }, 73 | "data": { 74 | "type": "currencies", 75 | "id": "1" 76 | } 77 | } 78 | }, 79 | "links": { 80 | "self": "\/countries\/3" 81 | } 82 | } 83 | ], 84 | "included": [ 85 | { 86 | "type": "currencies", 87 | "id": "1", 88 | "attributes": { 89 | "code": "EUR", 90 | "name": "Euro" 91 | }, 92 | "links": { 93 | "self": "\/currencies\/1" 94 | } 95 | } 96 | ] 97 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/index-multi-field-sparse.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&fields%5Bcountries%5D=name%2Ccode", 9 | "first": "\/countries?page=1&fields%5Bcountries%5D=name%2Ccode", 10 | "last": "\/countries?page=2&fields%5Bcountries%5D=name%2Ccode", 11 | "next": "\/countries?page=2&fields%5Bcountries%5D=name%2Ccode" 12 | }, 13 | "data": [ 14 | { 15 | "type": "countries", 16 | "id": "1", 17 | "attributes": { 18 | "code": "NL", 19 | "name": "The Netherlands" 20 | }, 21 | "links": { 22 | "self": "\/countries\/1" 23 | } 24 | }, 25 | { 26 | "type": "countries", 27 | "id": "2", 28 | "attributes": { 29 | "code": "BG", 30 | "name": "Bulgaria" 31 | }, 32 | "links": { 33 | "self": "\/countries\/2" 34 | } 35 | }, 36 | { 37 | "type": "countries", 38 | "id": "3", 39 | "attributes": { 40 | "code": "IT", 41 | "name": "Italy" 42 | }, 43 | "links": { 44 | "self": "\/countries\/3" 45 | } 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/index-no-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&fields%5Bcountries%5D=name", 9 | "first": "\/countries?page=1&fields%5Bcountries%5D=name", 10 | "last": "\/countries?page=2&fields%5Bcountries%5D=name", 11 | "next": "\/countries?page=2&fields%5Bcountries%5D=name" 12 | }, 13 | "data": [ 14 | { 15 | "type": "countries", 16 | "id": "1", 17 | "attributes": { 18 | "name": "The Netherlands" 19 | }, 20 | "links": { 21 | "self": "\/countries\/1" 22 | } 23 | }, 24 | { 25 | "type": "countries", 26 | "id": "2", 27 | "attributes": { 28 | "name": "Bulgaria" 29 | }, 30 | "links": { 31 | "self": "\/countries\/2" 32 | } 33 | }, 34 | { 35 | "type": "countries", 36 | "id": "3", 37 | "attributes": { 38 | "name": "Italy" 39 | }, 40 | "links": { 41 | "self": "\/countries\/3" 42 | } 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/index-single-field-sparse-for-primary-and-included-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Ccode", 9 | "first": "\/countries?page=1&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Ccode", 10 | "last": "\/countries?page=2&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Ccode", 11 | "next": "\/countries?page=2&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Ccode" 12 | }, 13 | "data": [ 14 | { 15 | "type": "countries", 16 | "id": "1", 17 | "attributes": { 18 | "name": "The Netherlands" 19 | }, 20 | "relationships": { 21 | "currency": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/currency", 24 | "related": "\/countries\/1\/currency" 25 | }, 26 | "data": { 27 | "type": "currencies", 28 | "id": "1" 29 | } 30 | } 31 | }, 32 | "links": { 33 | "self": "\/countries\/1" 34 | } 35 | }, 36 | { 37 | "type": "countries", 38 | "id": "2", 39 | "attributes": { 40 | "name": "Bulgaria" 41 | }, 42 | "relationships": { 43 | "currency": { 44 | "links": { 45 | "self": "\/countries\/2\/relationships\/currency", 46 | "related": "\/countries\/2\/currency" 47 | }, 48 | "data": { 49 | "type": "currencies", 50 | "id": "1" 51 | } 52 | } 53 | }, 54 | "links": { 55 | "self": "\/countries\/2" 56 | } 57 | }, 58 | { 59 | "type": "countries", 60 | "id": "3", 61 | "attributes": { 62 | "name": "Italy" 63 | }, 64 | "relationships": { 65 | "currency": { 66 | "links": { 67 | "self": "\/countries\/3\/relationships\/currency", 68 | "related": "\/countries\/3\/currency" 69 | }, 70 | "data": { 71 | "type": "currencies", 72 | "id": "1" 73 | } 74 | } 75 | }, 76 | "links": { 77 | "self": "\/countries\/3" 78 | } 79 | } 80 | ], 81 | "included": [ 82 | { 83 | "type": "currencies", 84 | "id": "1", 85 | "attributes": { 86 | "code": "EUR" 87 | }, 88 | "links": { 89 | "self": "\/currencies\/1" 90 | } 91 | } 92 | ] 93 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/index-single-field-sparse.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&fields%5Bcountries%5D=name", 9 | "first": "\/countries?page=1&fields%5Bcountries%5D=name", 10 | "last": "\/countries?page=2&fields%5Bcountries%5D=name", 11 | "next": "\/countries?page=2&fields%5Bcountries%5D=name" 12 | }, 13 | "data": [ 14 | { 15 | "type": "countries", 16 | "id": "1", 17 | "attributes": { 18 | "name": "The Netherlands" 19 | }, 20 | "links": { 21 | "self": "\/countries\/1" 22 | } 23 | }, 24 | { 25 | "type": "countries", 26 | "id": "2", 27 | "attributes": { 28 | "name": "Bulgaria" 29 | }, 30 | "links": { 31 | "self": "\/countries\/2" 32 | } 33 | }, 34 | { 35 | "type": "countries", 36 | "id": "3", 37 | "attributes": { 38 | "name": "Italy" 39 | }, 40 | "links": { 41 | "self": "\/countries\/3" 42 | } 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/index-with-include-and-sort-desc.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&sort=-name&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname", 9 | "first": "\/countries?page=1&sort=-name&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname", 10 | "last": "\/countries?page=2&sort=-name&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname", 11 | "next": "\/countries?page=2&sort=-name&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname" 12 | }, 13 | "data": [ 14 | { 15 | "type": "countries", 16 | "id": "4", 17 | "attributes": { 18 | "name": "Vatican" 19 | }, 20 | "relationships": { 21 | "currency": { 22 | "links": { 23 | "self": "\/countries\/4\/relationships\/currency", 24 | "related": "\/countries\/4\/currency" 25 | }, 26 | "data": { 27 | "type": "currencies", 28 | "id": "1" 29 | } 30 | } 31 | }, 32 | "links": { 33 | "self": "\/countries\/4" 34 | } 35 | }, 36 | { 37 | "type": "countries", 38 | "id": "5", 39 | "attributes": { 40 | "name": "United States of America" 41 | }, 42 | "relationships": { 43 | "currency": { 44 | "links": { 45 | "self": "\/countries\/5\/relationships\/currency", 46 | "related": "\/countries\/5\/currency" 47 | }, 48 | "data": { 49 | "type": "currencies", 50 | "id": "2" 51 | } 52 | } 53 | }, 54 | "links": { 55 | "self": "\/countries\/5" 56 | } 57 | }, 58 | { 59 | "type": "countries", 60 | "id": "1", 61 | "attributes": { 62 | "name": "The Netherlands" 63 | }, 64 | "relationships": { 65 | "currency": { 66 | "links": { 67 | "self": "\/countries\/1\/relationships\/currency", 68 | "related": "\/countries\/1\/currency" 69 | }, 70 | "data": { 71 | "type": "currencies", 72 | "id": "1" 73 | } 74 | } 75 | }, 76 | "links": { 77 | "self": "\/countries\/1" 78 | } 79 | } 80 | ], 81 | "included": [ 82 | { 83 | "type": "currencies", 84 | "id": "1", 85 | "attributes": { 86 | "name": "Euro" 87 | }, 88 | "links": { 89 | "self": "\/currencies\/1" 90 | } 91 | }, 92 | { 93 | "type": "currencies", 94 | "id": "2", 95 | "attributes": { 96 | "name": "US Dollar" 97 | }, 98 | "links": { 99 | "self": "\/currencies\/2" 100 | } 101 | } 102 | ] 103 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/index-with-include-and-sort.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&sort=name&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname", 9 | "first": "\/countries?page=1&sort=name&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname", 10 | "last": "\/countries?page=2&sort=name&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname", 11 | "next": "\/countries?page=2&sort=name&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname" 12 | }, 13 | "data": [ 14 | { 15 | "type": "countries", 16 | "id": "2", 17 | "attributes": { 18 | "name": "Bulgaria" 19 | }, 20 | "relationships": { 21 | "currency": { 22 | "links": { 23 | "self": "\/countries\/2\/relationships\/currency", 24 | "related": "\/countries\/2\/currency" 25 | }, 26 | "data": { 27 | "type": "currencies", 28 | "id": "1" 29 | } 30 | } 31 | }, 32 | "links": { 33 | "self": "\/countries\/2" 34 | } 35 | }, 36 | { 37 | "type": "countries", 38 | "id": "3", 39 | "attributes": { 40 | "name": "Italy" 41 | }, 42 | "relationships": { 43 | "currency": { 44 | "links": { 45 | "self": "\/countries\/3\/relationships\/currency", 46 | "related": "\/countries\/3\/currency" 47 | }, 48 | "data": { 49 | "type": "currencies", 50 | "id": "1" 51 | } 52 | } 53 | }, 54 | "links": { 55 | "self": "\/countries\/3" 56 | } 57 | }, 58 | { 59 | "type": "countries", 60 | "id": "1", 61 | "attributes": { 62 | "name": "The Netherlands" 63 | }, 64 | "relationships": { 65 | "currency": { 66 | "links": { 67 | "self": "\/countries\/1\/relationships\/currency", 68 | "related": "\/countries\/1\/currency" 69 | }, 70 | "data": { 71 | "type": "currencies", 72 | "id": "1" 73 | } 74 | } 75 | }, 76 | "links": { 77 | "self": "\/countries\/1" 78 | } 79 | } 80 | ], 81 | "included": [ 82 | { 83 | "type": "currencies", 84 | "id": "1", 85 | "attributes": { 86 | "name": "Euro" 87 | }, 88 | "links": { 89 | "self": "\/currencies\/1" 90 | } 91 | } 92 | ] 93 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/index-with-include.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 5, 4 | "page_count": 2, 5 | "page_limit": null 6 | }, 7 | "links": { 8 | "self": "\/countries?page=1&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname", 9 | "first": "\/countries?page=1&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname", 10 | "last": "\/countries?page=2&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname", 11 | "next": "\/countries?page=2&include=currencies&fields%5Bcountries%5D=name%2Ccurrency&fields%5Bcurrencies%5D=id%2Cname" 12 | }, 13 | "data": [ 14 | { 15 | "type": "countries", 16 | "id": "1", 17 | "attributes": { 18 | "name": "The Netherlands" 19 | }, 20 | "relationships": { 21 | "currency": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/currency", 24 | "related": "\/countries\/1\/currency" 25 | }, 26 | "data": { 27 | "type": "currencies", 28 | "id": "1" 29 | } 30 | } 31 | }, 32 | "links": { 33 | "self": "\/countries\/1" 34 | } 35 | }, 36 | { 37 | "type": "countries", 38 | "id": "2", 39 | "attributes": { 40 | "name": "Bulgaria" 41 | }, 42 | "relationships": { 43 | "currency": { 44 | "links": { 45 | "self": "\/countries\/2\/relationships\/currency", 46 | "related": "\/countries\/2\/currency" 47 | }, 48 | "data": { 49 | "type": "currencies", 50 | "id": "1" 51 | } 52 | } 53 | }, 54 | "links": { 55 | "self": "\/countries\/2" 56 | } 57 | }, 58 | { 59 | "type": "countries", 60 | "id": "3", 61 | "attributes": { 62 | "name": "Italy" 63 | }, 64 | "relationships": { 65 | "currency": { 66 | "links": { 67 | "self": "\/countries\/3\/relationships\/currency", 68 | "related": "\/countries\/3\/currency" 69 | }, 70 | "data": { 71 | "type": "currencies", 72 | "id": "1" 73 | } 74 | } 75 | }, 76 | "links": { 77 | "self": "\/countries\/3" 78 | } 79 | } 80 | ], 81 | "included": [ 82 | { 83 | "type": "currencies", 84 | "id": "1", 85 | "attributes": { 86 | "name": "Euro" 87 | }, 88 | "links": { 89 | "self": "\/currencies\/1" 90 | } 91 | } 92 | ] 93 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/national-cities-absolute-links.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 6, 4 | "page_count": 3, 5 | "page_limit": 2 6 | }, 7 | "links": { 8 | "self": "http:\/\/test-server\/nationalCities?page=2&limit=2", 9 | "first": "http:\/\/test-server\/nationalCities?page=1&limit=2", 10 | "last": "http:\/\/test-server\/nationalCities?page=3&limit=2", 11 | "prev": "http:\/\/test-server\/nationalCities?page=1&limit=2", 12 | "next": "http:\/\/test-server\/nationalCities?page=3&limit=2" 13 | }, 14 | "data": [ 15 | { 16 | "type": "nationalCities", 17 | "id": "3", 18 | "attributes": { 19 | "name": "Sofia" 20 | }, 21 | "relationships": { 22 | "country": { 23 | "links": { 24 | "self": "\/countries\/2" 25 | }, 26 | "data": { 27 | "type": "countries", 28 | "id": "2" 29 | } 30 | } 31 | }, 32 | "links": { 33 | "self": "\/nationalCities\/3" 34 | } 35 | }, 36 | { 37 | "type": "nationalCities", 38 | "id": "4", 39 | "attributes": { 40 | "name": "Plovdiv" 41 | }, 42 | "relationships": { 43 | "country": { 44 | "links": { 45 | "self": "\/countries\/2" 46 | }, 47 | "data": { 48 | "type": "countries", 49 | "id": "2" 50 | } 51 | } 52 | }, 53 | "links": { 54 | "self": "\/nationalCities\/4" 55 | } 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/nationalCities-absolute-links.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "record_count": 6, 4 | "page_count": 3, 5 | "page_limit": 2 6 | }, 7 | "links": { 8 | "self": "http:\/\/test-server\/nationalCities?page=2&limit=2", 9 | "first": "http:\/\/test-server\/nationalCities?page=1&limit=2", 10 | "last": "http:\/\/test-server\/nationalCities?page=3&limit=2", 11 | "prev": "http:\/\/test-server\/nationalCities?page=1&limit=2", 12 | "next": "http:\/\/test-server\/nationalCities?page=3&limit=2" 13 | }, 14 | "data": [ 15 | { 16 | "type": "nationalCities", 17 | "id": "3", 18 | "attributes": { 19 | "name": "Sofia" 20 | }, 21 | "relationships": { 22 | "country": { 23 | "links": { 24 | "self": "\/nationalCities\/3\/relationships\/country", 25 | "related": "\/nationalCities\/3\/country" 26 | }, 27 | "data": { 28 | "type": "countries", 29 | "id": "2" 30 | } 31 | } 32 | }, 33 | "links": { 34 | "self": "\/nationalCities\/3" 35 | } 36 | }, 37 | { 38 | "type": "nationalCities", 39 | "id": "4", 40 | "attributes": { 41 | "name": "Plovdiv" 42 | }, 43 | "relationships": { 44 | "country": { 45 | "links": { 46 | "self": "\/nationalCities\/4\/relationships\/country", 47 | "related": "\/nationalCities\/4\/country" 48 | }, 49 | "data": { 50 | "type": "countries", 51 | "id": "2" 52 | } 53 | } 54 | }, 55 | "links": { 56 | "self": "\/nationalCities\/4" 57 | } 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/view-multi-barrel-single-field-sparse.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "dummyCounter": 11111 7 | }, 8 | "links": { 9 | "self": "\/countries\/1" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/view-multi-field-sparse-for-included-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "dummyCounter": 11111, 8 | "name": "The Netherlands" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/1\/relationships\/currency", 14 | "related": "\/countries\/1\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/nationalCapital", 24 | "related": "\/countries\/1\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "1" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/1\/relationships\/cultures", 34 | "related": "\/countries\/1\/cultures" 35 | } 36 | }, 37 | "nationalCities": { 38 | "links": { 39 | "self": "\/countries\/1\/relationships\/nationalCities", 40 | "related": "\/countries\/1\/nationalCities" 41 | } 42 | }, 43 | "subcountries": { 44 | "links": { 45 | "self": "\/countries\/1\/relationships\/subcountries", 46 | "related": "\/countries\/1\/subcountries" 47 | } 48 | }, 49 | "supercountry": { 50 | "links": { 51 | "self": "\/countries\/1\/relationships\/supercountry", 52 | "related": "\/countries\/1\/supercountry" 53 | } 54 | }, 55 | "languages": { 56 | "links": { 57 | "self": "\/countries\/1\/relationships\/languages", 58 | "related": "\/countries\/1\/languages" 59 | } 60 | } 61 | }, 62 | "links": { 63 | "self": "\/countries\/1" 64 | } 65 | }, 66 | "included": [ 67 | { 68 | "type": "currencies", 69 | "id": "1", 70 | "attributes": { 71 | "code": "EUR", 72 | "name": "Euro" 73 | }, 74 | "links": { 75 | "self": "\/currencies\/1" 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/view-multi-field-sparse-for-primary-and-included-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "name": "The Netherlands" 8 | }, 9 | "relationships": { 10 | "currency": { 11 | "links": { 12 | "self": "\/countries\/1\/relationships\/currency", 13 | "related": "\/countries\/1\/currency" 14 | }, 15 | "data": { 16 | "type": "currencies", 17 | "id": "1" 18 | } 19 | } 20 | }, 21 | "links": { 22 | "self": "\/countries\/1" 23 | } 24 | }, 25 | "included": [ 26 | { 27 | "type": "currencies", 28 | "id": "1", 29 | "attributes": { 30 | "code": "EUR", 31 | "name": "Euro" 32 | }, 33 | "links": { 34 | "self": "\/currencies\/1" 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/view-multi-field-sparse.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "name": "The Netherlands" 8 | }, 9 | "links": { 10 | "self": "\/countries\/1" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/view-no-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "name": "The Netherlands" 7 | }, 8 | "links": { 9 | "self": "\/countries\/1" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/view-single-field-sparse-for-included-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "dummyCounter": 11111, 8 | "name": "The Netherlands" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/1\/relationships\/currency", 14 | "related": "\/countries\/1\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/1\/relationships\/nationalCapital", 24 | "related": "\/countries\/1\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "1" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/1\/relationships\/cultures", 34 | "related": "\/countries\/1\/cultures" 35 | } 36 | }, 37 | "nationalCities": { 38 | "links": { 39 | "self": "\/countries\/1\/relationships\/nationalCities", 40 | "related": "\/countries\/1\/nationalCities" 41 | } 42 | }, 43 | "subcountries": { 44 | "links": { 45 | "self": "\/countries\/1\/relationships\/subcountries", 46 | "related": "\/countries\/1\/subcountries" 47 | } 48 | }, 49 | "supercountry": { 50 | "links": { 51 | "self": "\/countries\/1\/relationships\/supercountry", 52 | "related": "\/countries\/1\/supercountry" 53 | } 54 | }, 55 | "languages": { 56 | "links": { 57 | "self": "\/countries\/1\/relationships\/languages", 58 | "related": "\/countries\/1\/languages" 59 | } 60 | } 61 | }, 62 | "links": { 63 | "self": "\/countries\/1" 64 | } 65 | }, 66 | "included": [ 67 | { 68 | "type": "currencies", 69 | "id": "1", 70 | "attributes": { 71 | "name": "Euro" 72 | }, 73 | "links": { 74 | "self": "\/currencies\/1" 75 | } 76 | } 77 | ] 78 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/view-single-field-sparse-for-primary-and-included-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "name": "The Netherlands" 7 | }, 8 | "relationships": { 9 | "currency": { 10 | "links": { 11 | "self": "\/countries\/1\/relationships\/currency", 12 | "related": "\/countries\/1\/currency" 13 | }, 14 | "data": { 15 | "type": "currencies", 16 | "id": "1" 17 | } 18 | } 19 | }, 20 | "links": { 21 | "self": "\/countries\/1" 22 | } 23 | }, 24 | "included": [ 25 | { 26 | "type": "currencies", 27 | "id": "1", 28 | "attributes": { 29 | "code": "EUR" 30 | }, 31 | "links": { 32 | "self": "\/currencies\/1" 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/SparseFieldsets/view-single-field-sparse.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "name": "The Netherlands" 7 | }, 8 | "links": { 9 | "self": "\/countries\/1" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/patch-country.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "1", 5 | "attributes": { 6 | "code": "NL", 7 | "name": "Updated Netherlands", 8 | "dummyCounter": 11111 9 | }, 10 | "links": { 11 | "self": "\/countries\/1" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/patch-national-capital.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCapitals", 4 | "id": "1", 5 | "attributes": { 6 | "name": "Patched Amsterdam", 7 | "description": "National capital of the Netherlands" 8 | }, 9 | "links": { 10 | "self": "\/nationalCapitals\/1" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/updated-country-attributes-and-multiple-belongsto-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "2", 5 | "attributes": { 6 | "code": "JM", 7 | "dummyCounter": 12345, 8 | "name": "Jamaica" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/2\/relationships\/currency", 14 | "related": "\/countries\/2\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "2" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/2\/relationships\/nationalCapital", 24 | "related": "\/countries\/2\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "5" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/2\/relationships\/cultures", 34 | "related": "\/countries\/2\/cultures" 35 | } 36 | }, 37 | "nationalCities": { 38 | "links": { 39 | "self": "\/countries\/2\/relationships\/nationalCities", 40 | "related": "\/countries\/2\/nationalCities" 41 | } 42 | }, 43 | "subcountries": { 44 | "links": { 45 | "self": "\/countries\/2\/relationships\/subcountries", 46 | "related": "\/countries\/2\/subcountries" 47 | } 48 | }, 49 | "supercountry": { 50 | "links": { 51 | "self": "\/countries\/2\/relationships\/supercountry", 52 | "related": "\/countries\/2\/supercountry" 53 | } 54 | }, 55 | "languages": { 56 | "links": { 57 | "self": "\/countries\/2\/relationships\/languages", 58 | "related": "\/countries\/2\/languages" 59 | } 60 | } 61 | }, 62 | "links": { 63 | "self": "\/countries\/2" 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/updated-country-multiple-belongsto-relationships-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "2", 5 | "attributes": { 6 | "code": "BG", 7 | "dummyCounter": 22222, 8 | "name": "Bulgaria" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/2\/relationships\/currency", 14 | "related": "\/countries\/2\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "2" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/2\/relationships\/nationalCapital", 24 | "related": "\/countries\/2\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "4" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/2\/relationships\/cultures", 34 | "related": "\/countries\/2\/cultures" 35 | } 36 | }, 37 | "nationalCities": { 38 | "links": { 39 | "self": "\/countries\/2\/relationships\/nationalCities", 40 | "related": "\/countries\/2\/nationalCities" 41 | } 42 | }, 43 | "subcountries": { 44 | "links": { 45 | "self": "\/countries\/2\/relationships\/subcountries", 46 | "related": "\/countries\/2\/subcountries" 47 | } 48 | }, 49 | "supercountry": { 50 | "links": { 51 | "self": "\/countries\/2\/relationships\/supercountry", 52 | "related": "\/countries\/2\/supercountry" 53 | } 54 | }, 55 | "languages": { 56 | "links": { 57 | "self": "\/countries\/2\/relationships\/languages", 58 | "related": "\/countries\/2\/languages" 59 | } 60 | } 61 | }, 62 | "links": { 63 | "self": "\/countries\/2" 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/updated-country-set-multiple-hasmany-relationships.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "2", 5 | "attributes": { 6 | "code": "BG", 7 | "dummyCounter": 22222, 8 | "name": "Bulgaria" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/2\/relationships\/currency", 14 | "related": "\/countries\/2\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/2\/relationships\/nationalCapital", 24 | "related": "\/countries\/2\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "2" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/2\/relationships\/cultures", 34 | "related": "\/countries\/2\/cultures" 35 | }, 36 | "data": [ 37 | { 38 | "type": "cultures", 39 | "id": "2" 40 | }, 41 | { 42 | "type": "cultures", 43 | "id": "3" 44 | } 45 | ] 46 | }, 47 | "nationalCities": { 48 | "links": { 49 | "self": "\/countries\/2\/relationships\/nationalCities", 50 | "related": "\/countries\/2\/nationalCities" 51 | } 52 | }, 53 | "subcountries": { 54 | "links": { 55 | "self": "\/countries\/2\/relationships\/subcountries", 56 | "related": "\/countries\/2\/subcountries" 57 | } 58 | }, 59 | "supercountry": { 60 | "links": { 61 | "self": "\/countries\/2\/relationships\/supercountry", 62 | "related": "\/countries\/2\/supercountry" 63 | } 64 | }, 65 | "languages": { 66 | "links": { 67 | "self": "\/countries\/2\/relationships\/languages", 68 | "related": "\/countries\/2\/languages" 69 | } 70 | } 71 | }, 72 | "links": { 73 | "self": "\/countries\/2" 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/updated-country-set-single-hasmany-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "countries", 4 | "id": "2", 5 | "attributes": { 6 | "code": "BG", 7 | "dummyCounter": 22222, 8 | "name": "Bulgaria" 9 | }, 10 | "relationships": { 11 | "currency": { 12 | "links": { 13 | "self": "\/countries\/2\/relationships\/currency", 14 | "related": "\/countries\/2\/currency" 15 | }, 16 | "data": { 17 | "type": "currencies", 18 | "id": "1" 19 | } 20 | }, 21 | "nationalCapital": { 22 | "links": { 23 | "self": "\/countries\/2\/relationships\/nationalCapital", 24 | "related": "\/countries\/2\/nationalCapital" 25 | }, 26 | "data": { 27 | "type": "nationalCapitals", 28 | "id": "2" 29 | } 30 | }, 31 | "cultures": { 32 | "links": { 33 | "self": "\/countries\/2\/relationships\/cultures", 34 | "related": "\/countries\/2\/cultures" 35 | }, 36 | "data": [ 37 | { 38 | "type": "cultures", 39 | "id": "3" 40 | } 41 | ] 42 | }, 43 | "nationalCities": { 44 | "links": { 45 | "self": "\/countries\/2\/relationships\/nationalCities", 46 | "related": "\/countries\/2\/nationalCities" 47 | } 48 | }, 49 | "subcountries": { 50 | "links": { 51 | "self": "\/countries\/2\/relationships\/subcountries", 52 | "related": "\/countries\/2\/subcountries" 53 | } 54 | }, 55 | "supercountry": { 56 | "links": { 57 | "self": "\/countries\/2\/relationships\/supercountry", 58 | "related": "\/countries\/2\/supercountry" 59 | } 60 | }, 61 | "languages": { 62 | "links": { 63 | "self": "\/countries\/2\/relationships\/languages", 64 | "related": "\/countries\/2\/languages" 65 | } 66 | } 67 | }, 68 | "links": { 69 | "self": "\/countries\/2" 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/updated-currency-attributes-only-all.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "currencies", 4 | "id": "2", 5 | "attributes": { 6 | "code": "RUB", 7 | "name": "Russian Ruble" 8 | }, 9 | "relationships": { 10 | "countries": { 11 | "links": { 12 | "self": "\/currencies\/2\/relationships\/countries", 13 | "related": "\/currencies\/2\/countries" 14 | } 15 | } 16 | }, 17 | "links": { 18 | "self": "\/currencies\/2" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/updated-currency-attributes-only-single.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "currencies", 4 | "id": "2", 5 | "attributes": { 6 | "code": "USD", 7 | "name": "Russian Ruble" 8 | }, 9 | "relationships": { 10 | "countries": { 11 | "links": { 12 | "self": "\/currencies\/2\/relationships\/countries", 13 | "related": "\/currencies\/2\/countries" 14 | } 15 | } 16 | }, 17 | "links": { 18 | "self": "\/currencies\/2" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/updated-national-capital-attributes-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCapitals", 4 | "id": "6", 5 | "attributes": { 6 | "description": "National capital of the cinematic world", 7 | "name": "Hollywood" 8 | }, 9 | "relationships": { 10 | "countries": { 11 | "links": { 12 | "self": "\/nationalCapitals\/6\/relationships\/countries", 13 | "related": "\/nationalCapitals\/6\/countries" 14 | } 15 | } 16 | }, 17 | "links": { 18 | "self": "\/nationalCapitals\/6" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/updated-national-city-attributes-and-single-belongsto-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCities", 4 | "id": "2", 5 | "attributes": { 6 | "name": "Milan" 7 | }, 8 | "relationships": { 9 | "country": { 10 | "links": { 11 | "self": "\/nationalCities\/2\/relationships\/country", 12 | "related": "\/nationalCities\/2\/country" 13 | }, 14 | "data": { 15 | "type": "countries", 16 | "id": "3" 17 | } 18 | } 19 | }, 20 | "links": { 21 | "self": "\/nationalCities\/2" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Fixture/JsonApiResponseBodies/UpdatingResources/updated-national-city-single-belongsto-relationship-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "nationalCities", 4 | "id": "2", 5 | "attributes": { 6 | "name": "Rotterdam" 7 | }, 8 | "relationships": { 9 | "country": { 10 | "links": { 11 | "self": "\/nationalCities\/2\/relationships\/country", 12 | "related": "\/nationalCities\/2\/country" 13 | }, 14 | "data": { 15 | "type": "countries", 16 | "id": "3" 17 | } 18 | } 19 | }, 20 | "links": { 21 | "self": "\/nationalCities\/2" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Fixture/LanguagesFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'integer'], 10 | 'code' => ['type' => 'string', 'length' => 2, 'null' => false], 11 | 'name' => ['type' => 'string', 'length' => 100, 'null' => false], 12 | '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], 13 | ]; 14 | 15 | public $records = [ 16 | ['id' => 1, 'code' => 'en', 'name' => 'English'], 17 | ['id' => 2, 'code' => 'nl', 'name' => 'Dutch'], 18 | ['id' => 3, 'code' => 'it', 'name' => 'Italian'], 19 | ['id' => 4, 'code' => 'bg', 'name' => 'Bulgarian'], 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /tests/Fixture/NationalCapitalsFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'integer'], 10 | 'name' => ['type' => 'string', 'length' => 100, 'null' => false], 11 | 'description' => ['type' => 'string', 'length' => 255, 'null' => false], 12 | '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], 13 | ]; 14 | 15 | public $records = [ 16 | ['name' => 'Amsterdam', 'description' => 'National capital of the Netherlands'], 17 | ['name' => 'Sofia', 'description' => 'National capital of Bulgaria'], 18 | ['name' => 'Wellington', 'description' => 'National capital of New Zealand'], 19 | ['name' => 'Rome', 'description' => 'National capital of Italy'], 20 | ['name' => 'Vatican City', 'description' => 'National capital of the Vatican'], 21 | ['name' => 'Washington', 'description' => 'National capital of the US'], 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /tests/Fixture/NationalCitiesFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'integer'], 10 | 'name' => ['type' => 'string', 'length' => 100, 'null' => false], 11 | 'country_id' => ['type' => 'integer', 'null' => false], 12 | '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], 13 | ]; 14 | 15 | public $records = [ 16 | ['name' => 'Amsterdam', 'country_id' => 1], 17 | ['name' => 'Rotterdam', 'country_id' => 1], 18 | ['name' => 'Sofia', 'country_id' => 2], 19 | ['name' => 'Plovdiv', 'country_id' => 2], 20 | ['name' => 'Eindhoven', 'country_id' => 1], 21 | ['name' => 'Nuenen', 'country_id' => 1], 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /tests/TestCase/Integration/JsonApi/ErrorsIntegrationTest.php: -------------------------------------------------------------------------------- 1 | get('/nonexistents'); 19 | $this->assertResponseCode(404); 20 | $this->_assertJsonApiResponseHeaders(); 21 | 22 | $actualResponseBody = $this->_getResponseWithEmptyDebugNode($this->_getBodyAsString() . "\n"); 23 | $this->assertResponseSameAsFile('Errors' . DS . '404-error-for-collection-in-debug-mode.json', $actualResponseBody); 24 | } 25 | 26 | /** 27 | * Make sure 404 errors for Collections respond properly in production mode. 28 | */ 29 | public function test404ForCollectionInProductionMode() 30 | { 31 | Configure::write('debug', false); 32 | 33 | $this->get('/nonexistents'); 34 | $this->assertResponseCode(404); 35 | $this->_assertJsonApiResponseHeaders(); 36 | 37 | $this->assertResponseSameAsFile('Errors' . DS . '404-error-for-collection-in-production-mode.json'); 38 | } 39 | 40 | /** 41 | * Make sure 404 errors for Resources respond properly in debug-mode. 42 | */ 43 | public function test404ForResourceInDebugMode() 44 | { 45 | Configure::write('debug', true); 46 | 47 | $this->get('/countries/666'); 48 | $this->assertResponseCode(404); 49 | $this->_assertJsonApiResponseHeaders(); 50 | 51 | $actualResponseBody = $this->_getResponseWithEmptyDebugNode($this->_getBodyAsString()); 52 | $this->assertResponseSameAsFile('Errors' . DS . '404-error-for-resource-in-debug-mode.json', $actualResponseBody); 53 | } 54 | 55 | /** 56 | * Make sure 404 errors for Resources respond properly in production-mode. 57 | */ 58 | public function test404ForResourceInProductionMode() 59 | { 60 | Configure::write('debug', false); 61 | 62 | $this->get('/countries/666'); 63 | $this->assertResponseCode(404); 64 | $this->_assertJsonApiResponseHeaders(); 65 | $this->assertResponseSameAsFile('Errors' . DS . '404-error-for-resource-in-production-mode.json'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/TestCase/Integration/JsonApi/FetchingCollectionsIntegrationTest.php: -------------------------------------------------------------------------------- 1 | [ 20 | '/countries', 21 | 'get-countries-with-pagination.json', 22 | ], 23 | ]; 24 | } 25 | 26 | /** 27 | * @param string $url The endpoint to hit 28 | * @param string $expectedResponseFile The file to find the expected jsonapi response in 29 | * @return void 30 | * @dataProvider getProvider 31 | */ 32 | public function testGet($url, $expectedResponseFile) 33 | { 34 | $this->configRequest([ 35 | 'headers' => [ 36 | 'Accept' => 'application/vnd.api+json', 37 | ], 38 | ]); 39 | 40 | # execute the GET request 41 | $this->get($url); 42 | $this->assertResponseCode(200); 43 | $this->_assertJsonApiResponseHeaders(); 44 | 45 | $this->assertResponseSameAsFile('FetchingCollections' . DS . $expectedResponseFile); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/TestCase/Integration/JsonApi/FetchingResourcesIntegrationTest.php: -------------------------------------------------------------------------------- 1 | [ 20 | '/currencies/1', 21 | 'get-currency-no-relationships.json', 22 | ], 23 | 24 | 'fetch-single-word-resource-with-multiple-belongsto-relationships' => [ 25 | '/countries/1', 26 | 'get-country-multiple-belongsto-relationships.json', 27 | ], 28 | 29 | 'fetch-multi-word-resource-with-no-relationships' => [ 30 | '/nationalCapitals/1', 31 | 'get-national-capital-no-relationships.json', 32 | ], 33 | 34 | 'fetch-multi-word-resource-with-single-belongsTo-relationship' => [ 35 | '/nationalCities/1', 36 | 'get-national-city-single-belongsto-relationship.json', 37 | ], 38 | ]; 39 | } 40 | 41 | /** 42 | * @param string $url The endpoint to hit 43 | * @param string $expectedResponseFile The file to find the expected jsonapi response in 44 | * @return void 45 | * @dataProvider fetchResourceProvider 46 | */ 47 | public function testFetchResource($url, $expectedResponseFile) 48 | { 49 | $this->configRequest([ 50 | 'headers' => [ 51 | 'Accept' => 'application/vnd.api+json', 52 | ], 53 | ]); 54 | 55 | # execute the GET request 56 | $this->get($url); 57 | 58 | # assert the response 59 | $this->assertResponseCode(200); 60 | $this->_assertJsonApiResponseHeaders(); 61 | $this->assertResponseSameAsFile('FetchingResources' . DS . $expectedResponseFile); 62 | } 63 | 64 | /** 65 | * PhpUnit Data Provider that will call `testFetchNestedResource()` for every array entry 66 | * so we can test multiple successful GET requests without repeating ourselves. 67 | * 68 | * @return array 69 | */ 70 | public function fetchNestedResourceProvider() 71 | { 72 | return [ 73 | 'fetch-one-to-many-relation' => [ 74 | '/currencies/1/countries', 75 | 'get-currency-countries.json', 76 | ], 77 | 78 | 'fetch-many-to-one-relation' => [ 79 | '/countries/1/currency', 80 | 'get-country-currency.json', 81 | ], 82 | 83 | 'fetch-many-to-many-relation' => [ 84 | '/countries/1/languages', 85 | 'get-country-languages.json', 86 | ], 87 | ]; 88 | } 89 | 90 | /** 91 | * @param string $url The endpoint to hit 92 | * @param string $expectedResponseFile The file to find the expected jsonapi response in 93 | * @return void 94 | * @dataProvider fetchNestedResourceProvider 95 | */ 96 | public function testFetchNestedResource($url, $expectedResponseFile) 97 | { 98 | $this->configRequest( 99 | [ 100 | 'headers' => [ 101 | 'Accept' => 'application/vnd.api+json', 102 | ], 103 | ] 104 | ); 105 | 106 | # execute the GET request 107 | $this->get($url); 108 | 109 | # assert the response 110 | $this->assertResponseCode(200); 111 | $this->_assertJsonApiResponseHeaders(); 112 | $this->assertResponseSameAsFile('FetchingResources' . DS . $expectedResponseFile); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/TestCase/Integration/JsonApi/FilteringIntegrationTest.php: -------------------------------------------------------------------------------- 1 | [ 19 | '/countries?filter=Netherlands', 20 | 'filter-single-field.json', 21 | ], 22 | 'single field partial search-key' => [ 23 | '/countries?filter=Nether', 24 | 'filter-single-field-partial.json', 25 | ], 26 | ]; 27 | } 28 | 29 | /** 30 | * @param string $url The endpoint to hit 31 | * @param string $expectedFile The file to find the expected result in 32 | * @return void 33 | * @dataProvider filterProvider 34 | */ 35 | public function testFilter($url, $expectedFile) 36 | { 37 | $this->get($url); 38 | 39 | $this->assertResponseSuccess(); 40 | $this->_assertJsonApiResponseHeaders(); 41 | $this->assertResponseSameAsFile('Filtering' . DS . $expectedFile); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/TestCase/Integration/JsonApi/SelfReferencedAssociationIntegrationTest.php: -------------------------------------------------------------------------------- 1 | [ 17 | '/countries/3?include=subCountries', 18 | 'get_supercountry_with_subcountries.json', 19 | ], 20 | 'get subcountry with supercountry' => [ 21 | '/countries/4?include=superCountries', 22 | 'get_subcountry_with_supercountry.json', 23 | ], 24 | ]; 25 | } 26 | 27 | /** 28 | * @param string $url The endpoint to hit 29 | * @param string $expectedFile The file to find the expected result in 30 | * @return void 31 | * @dataProvider viewProvider 32 | */ 33 | public function testView($url, $expectedFile) 34 | { 35 | $this->get($url); 36 | 37 | $this->assertResponseSuccess(); 38 | $this->_assertJsonApiResponseHeaders(); 39 | $this->assertResponseSameAsFile($expectedFile); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 'CrudJsonApi\Test\App', 49 | 'encoding' => 'UTF-8', 50 | 'fullBaseUrl' => 'http://localhost' 51 | ] 52 | ); 53 | Cake\Core\Configure::write('debug', true); 54 | 55 | $TMP = new \Cake\Filesystem\Folder(TMP); 56 | $TMP->create(TMP . 'cache/models', 0777); 57 | $TMP->create(TMP . 'cache/persistent', 0777); 58 | $TMP->create(TMP . 'cache/views', 0777); 59 | 60 | $cache = [ 61 | 'default' => [ 62 | 'engine' => 'File' 63 | ], 64 | '_cake_core_' => [ 65 | 'className' => 'File', 66 | 'prefix' => 'crud_myapp_cake_core_', 67 | 'path' => CACHE . 'persistent/', 68 | 'serialize' => true, 69 | 'duration' => '+10 seconds' 70 | ], 71 | '_cake_model_' => [ 72 | 'className' => 'File', 73 | 'prefix' => 'crud_my_app_cake_model_', 74 | 'path' => CACHE . 'models/', 75 | 'serialize' => 'File', 76 | 'duration' => '+10 seconds' 77 | ] 78 | ]; 79 | 80 | Cake\Cache\Cache::setConfig($cache); 81 | Cake\Core\Configure::write( 82 | 'Session', 83 | [ 84 | 'defaults' => 'php' 85 | ] 86 | ); 87 | 88 | // Ensure default test connection is defined 89 | if (!getenv('DB_URL')) { 90 | putenv('DB_URL=sqlite:///:memory:'); 91 | } 92 | 93 | Cake\Datasource\ConnectionManager::setConfig( 94 | 'test', 95 | [ 96 | 'url' => getenv('DB_URL'), 97 | 'timezone' => 'UTC' 98 | ] 99 | ); 100 | 101 | Plugin::getCollection()->add(new \Crud\Plugin()); 102 | Plugin::getCollection()->add(new \CrudJsonApi\Plugin()); 103 | 104 | Configure::write('Error.ignoredDeprecationPaths', [ 105 | 'vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php', 106 | ]); 107 | -------------------------------------------------------------------------------- /tests/test_app/config/bootstrap.php: -------------------------------------------------------------------------------- 1 | scope('/', function (RouteBuilder $routes) { 10 | $routes->setRouteClass(InflectedRoute::class); 11 | $routes->setExtensions(['json']); 12 | 13 | $routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'InflectedRoute']); 14 | $routes->connect('/:controller/:action/*', [], ['routeClass' => 'InflectedRoute']); 15 | 16 | JsonApiRoutes::mapModels([ 17 | 'Countries', 18 | 'Currencies', 19 | 'Cultures', 20 | ], $routes); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/test_app/src/Application.php: -------------------------------------------------------------------------------- 1 | addParser(['application/vnd.api+json'], function ($body) { 18 | return json_decode($body, true); 19 | }); 20 | $middlewareQueue 21 | // Handle plugin/theme assets like CakePHP normally does. 22 | ->add(AssetMiddleware::class) 23 | ->add($bodies) 24 | 25 | // Add routing middleware. 26 | ->add(new RoutingMiddleware($this)); 27 | 28 | return $middlewareQueue; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/test_app/src/Controller/CountriesController.php: -------------------------------------------------------------------------------- 1 | 3]; 17 | 18 | public function initialize(): void 19 | { 20 | parent::initialize(); 21 | 22 | $this->loadComponent('RequestHandler'); 23 | $this->loadComponent('Flash'); 24 | $this->loadComponent( 25 | 'Crud.Crud', 26 | [ 27 | 'actions' => [ 28 | 'Crud.Index', 29 | 'Crud.Add', 30 | 'Crud.Edit', 31 | 'CrudJsonApi.View', 32 | 'Crud.Delete', 33 | 'CrudJsonApi.Relationships', 34 | ], 35 | 'listeners' => [ 36 | 'CrudJsonApi.JsonApi', 37 | 'CrudJsonApi.Pagination', 38 | 'Crud.Search', 39 | ], 40 | ] 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/test_app/src/Controller/CurrenciesController.php: -------------------------------------------------------------------------------- 1 | 3]; 17 | 18 | public function initialize(): void 19 | { 20 | parent::initialize(); 21 | 22 | $this->loadComponent('RequestHandler'); 23 | $this->loadComponent('Flash'); 24 | $this->loadComponent( 25 | 'Crud.Crud', 26 | [ 27 | 'actions' => [ 28 | 'Crud.Index', 29 | 'Crud.Add', 30 | 'Crud.Edit', 31 | 'CrudJsonApi.View', 32 | 'Crud.Delete', 33 | 'CrudJsonApi.Relationships', 34 | ], 35 | 'listeners' => [ 36 | 'CrudJsonApi.JsonApi', 37 | ], 38 | ] 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/test_app/src/Controller/LanguagesController.php: -------------------------------------------------------------------------------- 1 | 3]; 17 | 18 | public function initialize(): void 19 | { 20 | parent::initialize(); 21 | 22 | $this->loadComponent('RequestHandler'); 23 | $this->loadComponent('Flash'); 24 | $this->loadComponent( 25 | 'Crud.Crud', 26 | [ 27 | 'actions' => [ 28 | 'Crud.Index', 29 | 'Crud.Add', 30 | 'Crud.Edit', 31 | 'CrudJsonApi.View', 32 | 'Crud.Delete', 33 | 'CrudJsonApi.Relationships', 34 | ], 35 | 'listeners' => [ 36 | 'CrudJsonApi.JsonApi', 37 | ], 38 | ] 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/test_app/src/Controller/NationalCapitalsController.php: -------------------------------------------------------------------------------- 1 | loadComponent('RequestHandler'); 21 | $this->loadComponent('Flash'); 22 | $this->loadComponent( 23 | 'Crud.Crud', 24 | [ 25 | 'actions' => [ 26 | 'Crud.Index', 27 | 'Crud.Add', 28 | 'Crud.Edit', 29 | 'CrudJsonApi.View', 30 | 'Crud.Delete', 31 | 'CrudJsonApi.Relationships', 32 | ], 33 | 'listeners' => [ 34 | 'CrudJsonApi.JsonApi', 35 | 'CrudJsonApi.Pagination', 36 | ], 37 | ] 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/test_app/src/Controller/NationalCitiesController.php: -------------------------------------------------------------------------------- 1 | loadComponent('RequestHandler'); 22 | $this->loadComponent('Flash'); 23 | $this->loadComponent( 24 | 'Crud.Crud', 25 | [ 26 | 'actions' => [ 27 | 'Crud.Index', 28 | 'Crud.Add', 29 | 'Crud.Edit', 30 | 'CrudJsonApi.View', 31 | 'Crud.Delete', 32 | 'CrudJsonApi.Relationships', 33 | ], 34 | 'listeners' => [ 35 | 'CrudJsonApi.JsonApi', 36 | 'CrudJsonApi.Pagination', 37 | ], 38 | ] 39 | ); 40 | } 41 | 42 | public function beforeFilter(EventInterface $event) 43 | { 44 | $this->Crud->setConfig('listeners.jsonApi.absoluteLinks', true); 45 | parent::beforeFilter($event); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/test_app/src/Model/Entity/Country.php: -------------------------------------------------------------------------------- 1 | addBehavior('Search.Search'); 14 | 15 | $this->belongsTo('Currencies'); 16 | $this->belongsTo('NationalCapitals'); 17 | 18 | $this->hasMany('Cultures'); 19 | $this->hasMany('NationalCities'); 20 | 21 | // e.g. Italy has Vatican 22 | $this->hasMany('SubCountries', [ 23 | 'className' => 'Countries', 24 | 'foreignKey' => 'supercountry_id', 25 | 'propertyName' => 'subcountries', //not-default to mess a little 26 | ]); 27 | 28 | // e.g. Vatican has Italy 29 | $this->belongsTo('SuperCountries', [ 30 | 'className' => 'Countries', 31 | 'foreignKey' => 'supercountry_id', 32 | 'propertyName' => 'supercountry', 33 | ]); 34 | 35 | $this->belongsToMany('Languages'); 36 | } 37 | 38 | public function validationDefault(Validator $validator): Validator 39 | { 40 | // used for testing built-in validation rules/messages 41 | $validator 42 | ->requirePresence('name', 'create') 43 | ->notEmptyString('name'); 44 | 45 | // used for testing user-defined rules/messages 46 | $validator 47 | ->notEmptyString('code') 48 | ->add('code', [ 49 | 'UPPERCASE_ONLY' => [ 50 | 'rule' => ['custom', '/^([A-Z]+)+$/'], 51 | 'message' => 'Field must be uppercase only', 52 | ], 53 | ]); 54 | 55 | return $validator; 56 | } 57 | 58 | // set up the search filter 59 | public function searchManager() 60 | { 61 | $searchManager = $this->behaviors()->Search->searchManager(); 62 | $searchManager->like('filter', [ 63 | 'before' => true, 64 | 'after' => true, 65 | 'fields' => [$this->aliasField('name')], 66 | ]); 67 | 68 | return $searchManager; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/test_app/src/Model/Table/CulturesTable.php: -------------------------------------------------------------------------------- 1 | belongsTo('Countries'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/test_app/src/Model/Table/CurrenciesTable.php: -------------------------------------------------------------------------------- 1 | hasMany('Countries'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/test_app/src/Model/Table/LanguagesTable.php: -------------------------------------------------------------------------------- 1 | belongsToMany('Countries'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/test_app/src/Model/Table/NationalCapitalsTable.php: -------------------------------------------------------------------------------- 1 | hasMany('Countries'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/test_app/src/Model/Table/NationalCitiesTable.php: -------------------------------------------------------------------------------- 1 | belongsTo('Countries'); 14 | } 15 | 16 | // prevent unintentionally creating hasMany records 17 | public function validationDefault(Validator $validator): Validator 18 | { 19 | $validator 20 | ->requirePresence('name', 'create') 21 | ->notEmptyString('name'); 22 | 23 | return $validator; 24 | } 25 | } 26 | --------------------------------------------------------------------------------