cloudcreativity / json-api Goto Github PK
View Code? Open in Web Editor NEWFramework agnostic JSON API serialisation and deserialisation
License: Apache License 2.0
Framework agnostic JSON API serialisation and deserialisation
License: Apache License 2.0
An empty relationships member should be accepted by ResourceObjectValidator
even if there is no relationships validator set. I.e. the validator has been set up not to accept relationships, and although the client has included the member, it is empty so is acceptable.
The same applies to attributes.
ThrowableError
isn't really useful because Exception::getCode()
is final so cannot be overridden to provide an actual JSON API code.
Solution: deprecate ThrowableError
and instead allow the first argument in ErrorException
to be an array that is cast to an ErrorObject
instance.
Hi! I found nothing in the documentation about my question. How I can add custom action to resource controller? For instance: I have PostController, this controller extends JsonApiController, inherits index, create, update, etc. And I want to add 'share' action.
Could it be possible to have the toArray() method (in CloudCreativity\JsonApi\Error\ErrorObject
line 332) return only not null values?
I'll try to explain my scenario. I wanna be able to use this class to build application logic errors (as opposed to an Exception which handles "exceptional" situations) that I can then use to respond a client with using the following syntax:
$error = new ErrorObject([
'status' => '418',
'code' => '1',
'title' => "I'm an error object",
'detail' => 'error object',
'meta' => ['render' => 'form'],
]);
return $this->reply()->respond('418', ['errors' => [$error->toArray()]]);
Currently if I do it like that, I get the following response:
{
"errors": [
{
"id": null,
"links": null,
"status": "418",
"code": "1",
"title": "I'm an error object",
"detail": "error object",
"source": null,
"meta": {
"render": "form"
}
}
]
}
I'd rather not have all the null value keys returned from toArray(). Would implement such behavour break in some way the functionality of the class?
Instead of 422 responses with validation error messages. I always get this.
CloudCreativity\LaravelJsonApi\Exceptions\ValidationException: JSON API error in file /Library/WebServer/Documents/omega_mcs_api/vendor/cloudcreativity/laravel-json-api/src/Exceptions/ValidationException.php on line 49
Expecting this:
'''
{
"errors": [
{
"status": "422",
"title": "Unprocessable Entity",
"detail": "The content field is required.",
"source": {
"pointer": "/data"
}
}
]
}
'''
Add a date type validator. This should by default accept ISO8601 date formats, but the format(s) accept should be configurable per instance.
If a client submits a relationship with a resource type that the store does not recognise, a 500
error is caused by the following:
[2016-09-17 12:04:35] testing.ERROR: exception 'CloudCreativity\JsonApi\Exceptions\RuntimeException' with message 'No adapter for resource type: order' in [...]/vendor/cloudcreativity/json-api/src/Store/Store.php:144
The following:
{
"errors": [
{
"status": "422",
"code": "invalid-attributes",
"title": "Invalid Attributes",
"detail": "The provided attributes are invalid.",
"source": {
"pointer": "/data/attributes"
}
},
{
"status": "422",
"code": "invalid-attribute",
"title": "Invalid Attribute",
"detail": "The title must be at least 2 characters.",
"source": {
"pointer": "/data/attributes/title"
}
}
]
}
had a HTTP status code of 400
. Check what ErrorCollection::getStatus()
returns in the above scenario.
Need to add this method so that there is a default implementation for the interface method, if the trait is applied to a class.
[2017-05-17 06:10:53] testing.ERROR: exception 'CloudCreativity\JsonApi\Exceptions\RuntimeException' with message 'Id member is not a string or integer.' in /vagrant/vendor/cloudcreativity/json-api/src/Object/Helpers/IdentifiableTrait.php:86
Stack trace:
#0 /vagrant/vendor/cloudcreativity/json-api/src/Object/ResourceIdentifier.php(105): CloudCreativity\JsonApi\Object\ResourceIdentifier->getId()
#1 /vagrant/vendor/cloudcreativity/json-api/src/Store/IdentityMap.php(102): CloudCreativity\JsonApi\Object\ResourceIdentifier->toString()
#2 /vagrant/vendor/cloudcreativity/json-api/src/Store/IdentityMap.php(73): CloudCreativity\JsonApi\Store\IdentityMap->lookup(Object(CloudCreativity\JsonApi\Object\ResourceIdentifier))
#3 /vagrant/vendor/cloudcreativity/json-api/src/Store/Store.php(73): CloudCreativity\JsonApi\Store\IdentityMap->exists(Object(CloudCreativity\JsonApi\Object\ResourceIdentifier))
#4 /vagrant/vendor/cloudcreativity/json-api/src/Validators/AbstractRelationshipValidator.php(103): CloudCreativity\JsonApi\Store\Store->exists(Object(CloudCreativity\JsonApi\Object\ResourceIdentifier))
#5 /vagrant/vendor/cloudcreativity/json-api/src/Validators/AbstractRelationshipValidator.php(311): CloudCreativity\JsonApi\Validators\AbstractRelationshipValidator->doesExist(Object(CloudCreativity\JsonApi\Object\ResourceIdentifier))
#6 /vagrant/vendor/cloudcreativity/json-api/src/Validators/AbstractRelationshipValidator.php(192): CloudCreativity\JsonApi\Validators\AbstractRelationshipValidator->validateExists(Object(CloudCreativity\JsonApi\Object\ResourceIdentifier), 'ticket-type')
#7 /vagrant/vendor/cloudcreativity/json-api/src/Validators/RelationshipValidator.php(48): CloudCreativity\JsonApi\Validators\AbstractRelationshipValidator->validateHasOne(Object(CloudCreativity\JsonApi\Object\Relationship), NULL, 'ticket-type', Object(CloudCreativity\JsonApi\Object\Resource))
#8 /vagrant/vendor/cloudcreativity/json-api/src/Validators/RelationshipsValidator.php(211): CloudCreativity\JsonApi\Validators\RelationshipValidator->isValid(Object(CloudCreativity\JsonApi\Object\Relationship), NULL, 'ticket-type', Object(CloudCreativity\JsonApi\Object\Resource))
#9 /vagrant/vendor/cloudcreativity/json-api/src/Validators/RelationshipsValidator.php(157): CloudCreativity\JsonApi\Validators\RelationshipsValidator->validateRelationship('ticket-type', Object(CloudCreativity\JsonApi\Object\Relationships), Object(CloudCreativity\JsonApi\Object\Resource), NULL)
#10 /vagrant/vendor/cloudcreativity/json-api/src/Validators/ResourceValidator.php(234): CloudCreativity\JsonApi\Validators\RelationshipsValidator->isValid(Object(CloudCreativity\JsonApi\Object\Resource), NULL)
#11 /vagrant/vendor/cloudcreativity/json-api/src/Validators/ResourceValidator.php(111): CloudCreativity\JsonApi\Validators\ResourceValidator->validateRelationships(Object(CloudCreativity\JsonApi\Object\Resource), NULL)
#12 /vagrant/vendor/cloudcreativity/json-api/src/Validators/ResourceDocumentValidator.php(79): CloudCreativity\JsonApi\Validators\ResourceValidator->isValid(Object(CloudCreativity\JsonApi\Object\Resource), NULL)
#13 /vagrant/vendor/cloudcreativity/json-api/src/Http/Requests/RequestFactory.php(140): CloudCreativity\JsonApi\Validators\ResourceDocumentValidator->isValid(Object(CloudCreativity\JsonApi\Object\Document))
#14 /vagrant/vendor/cloudcreativity/json-api/src/Http/Requests/RequestFactory.php(127): CloudCreativity\JsonApi\Http\Requests\RequestFactory->validateDocument(Object(CloudCreativity\JsonApi\Object\Document), Object(CloudCreativity\LaravelJsonApi\Http\Requests\RequestInterpreter))
Believe the data submitted was a has-one relationship with data object that had correct type but id was a null value.
Add id validators that can check for existence and format of a client generated id, as per the JSON-API spec for client generated ids.
When authorising update requests from the client, the authoriser requires access to what the client has sent (i.e. what the client is attempting to change). E.g. when authorising an update of a resource, the authorisation might be dependent on which resource attributes are being changed, as per this issue:
cloudcreativity/laravel-json-api#14
Two modifications are required to the AuthorizerInterface
:
canUpdate
needs to receive a ResourceInterface
object as its second parameter.canModifyRelationship
needs to receive a RelationshipInterface
object as its third parameter.This is a breaking change so will have to be in v0.6
The following error message from a HasOneValidator
is not adding the relationship name to the detail
member:
{
"status": "400",
"code": "required",
"title": "Required Relationship",
"detail": "Missing required relationship \"%s\".",
"source": {
"pointer": "\/data\/relationships\/application"
}
}
In the User/Posts example:
Imagine I want to present the User all his Posts.
I could return $user->posts
, but there are lots of posts for each user, so I want to paginate them.
From my research, there is nothing like this available in laravel. (If there is, please let me know!)
I think it would be a really good feature if it's not planned yet.
Differently to what is done in AttributesValidator::checkKey()
, the same method in RelationshipsValidator
class doesn't check if the key is allowed.
I whould fix the checkKey()
as follow in the RelationshipsValidator
:
protected function checkKey($key)
{
if (!$this->hasValidator($key) || !$this->isAllowedKey($key)) {
$this->error(static::ERROR_UNRECOGNISED_RELATIONSHIP)
->source()
->setPointer(sprintf('/%s', $key));
}
return $this;
}
We have the need to add configuration to an API definition from multiple packages rather than a single configuration file. This is because we are composing APIs from separate Composer packages, that each need to add their configuration to a specific API's definition. This isn't a major change or too much added complexity - the interfaces just need to be updated to allow partial config to be added.
At the same time it would be good to rename Repositories
to Factories
. This is because the neomerx/json-api
package uses the name Factory
so it would be more consistent to follow the same pattern.
To do:
CodecMatcherRepository
to CodecMatcherFactory
. Change the method signature to createCodecMatcher($apiNamespace, $schemas, $urlPrefix)
for consistency. Move it to the Codec
namespace as that is where it is in neomerx/json-api
.defaults
set, i.e. bring it inline with the schemas repository.SchemasRepository
to SchemaContainerFactory
and change method signature to createSchemaContainer($apiNamespace)
. Move to the Schema
namespace as that is where it is in neomerx/json-api
.defaults
for adapters to apply to all stores, and then adapters per store. Factory should be in the Store
namespace for consistency.The iterator on StandardObject
does not recognise stdClass
(as it is not a traversable).
This means whenever iterating over a standard object, it is always turned into an array. The problem here is that this means any nested objects are also converted into arrays, so the iteration value is not as expected.
See here:
https://github.com/cloudcreativity/json-api/blob/master/src/Object/StandardObject.php#L89-L96
It would be better to iterate, probably using a generator, and any values that are objects should be wrapped in a standard object. I.e. keep it consistent that objects are cast to the standard object interface.
This fix will however be breaking as it could have unintended consequences on existing code.
The following passes validation:
{
"data": { "type": "foo" }
}
i.e. no id member, so this identifier is invalid.
Create a parameters checker that allows an ValidatorInterface
class to be set to validate unrecognised parameters.
Hi,
I created a client which is receiving a response similar to this:
{
"data": [
{
"type": "ENTITY",
"id": "identifier",
"attributes": {
"name": "My name 1",
"number": 1,
},
"relationships": {
"relation": {
"data": {
"type": "type",
"id": "identifier2"
}
},
},
"links": {
"self": "link1"
}
},
{
"type": "ENTITY",
"id": "identifier2",
"attributes": {
"name": "My name 2",
"number": 2,
},
"relationships": {
"relation": {
"data": {
"type": "type",
"id": "identifier2"
}
},
},
"links": {
"self": "link1"
}
},
{
"type": "ENTITY",
"id": "identifier",
"attributes": {
"name": "My name 3",
"number": 3,
},
"relationships": {
"relation": {
"data": {
"type": "type",
"id": "identifier3"
}
},
},
"links": {
"self": "link1"
}
},
]
}
As you can see, inside data I receive an array of Entities, I don't know how I can iterate in this entities I receive in the array.
It would be great to have an example on how to do that!
Thanks in advance,
Julio
I'm trying to save the result of any query inside the key in the cache and add middle ware where I can check if
the key already exist inside the cache and if it is return it otherwise go further to the api
At the moment the http_contains_body
helper method does not correctly detect message body on a response. This is because the rules for a response having body are different from the rules for a request. For a response:
For response messages, whether or not a message-body is included with a message is dependent on both the request method and the response status code (section 6.1.1). All responses to the HEAD request method MUST NOT include a message-body, even though the presence of entity- header fields might lead one to believe they do. All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST NOT include a message-body. All other responses do include a message-body, although it MAY be of zero length.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
At the moment, the helper only applies the logic for a request - need to also add logic for a response.
There is a need to introduce a search interface to encapsulate logic around searching resources and searching resources that are related to a primary resource.
The JSON API spec allows for resources to be retrieved via a GET request to the resource route. E.g. for a posts
resource, the request can be to GET /posts
. This route may support filtering, pagination, sorting, etc. The response may be a resource collection (or an empty resource collection) or a singular resource (or null
if no matching singular resource).
The JSON API spec allows for the retrieval of resources that are related to a primary resource. The related resources may be a has-one or has-many relationship.
For example, a posts
resource with a has-one author
relationship to a users
resource. GET /posts/1/author
gets the related users
resource. GET /posts/1/relationships/author
gets the resource identifier of the related users
resource.
For example, a posts
resource with a has-many comments
relationship. GET /posts/1/comments
gets the related comments
resources. GET /posts/1/relationships/comments
gets a resource identifier collection of the related comments
resources.
For these has-many relationships the server may support filtering, pagination, sorting etc. E.g. if the comments
relationship was to support a since
filter, then GET /posts/1/comments?filter[since]=2017-01-01
would return all comments related to post 1
that were created since the supplied date.
It cannot be assumed that the paginating, filtering, sorting etc strategies for a resource type and its relationships are the same. For example, the filter parameters allowed for searching posts
resources are likely to be different from those allowed when filtering the comments
relationship for a specific post. The same may equally be true for sorting, pagination, allowed include paths etc.
At the moment, the "unit" that contains the logic as to what request parameters a server supports for a specific resource type is the RequestHandlerInterface
class. At the moment this is only designed for processing requests for the primary resource (e.g. the posts
resource) and does not allow variation of allowed parameters based on whether the request is for the primary resource or one of its relationships.
An additional problem is that the paging strategy is defined at the API level. However, paging strategies may vary across resource types, as well as within relationships. For instance, the posts
resource collection might be paginated using page-based number
and size
parameters, but the comments
relationship might be paginated using cursor
based pagination.
The current pagination solution assumes that there are two pagination parameters, but this may not be the case. It does not allow rejection of a paging request if for instance the client asks for more than the maximum number of resources per page.
The logic for searching resources is complex and is linked directly to the resource type that owns the endpoints. To follow this package's pattern of encapsulating complex logic into isolated units, it is proposed that a SearchInterface
is created, with the implementing class containing the logic for searching a specific resource type and its relationships. This would have two methods on it:
searchAll(EncodingParametersInterface $params)
- to search the primary resource (e.g. posts
)searchRelated($record, $relationship, EncodingParametersInterface $params)
- e.g. to search the comments
relationship of a specific posts
record.The following additional changes are proposed:
The ChecksQueryParameters
trait must be modified so that the request handler can validate inbound parameters based on whether the primary resource or one of its relationships is being searched.
Validation of sort
, filter
, page
should only occur if the request is to a search endpoint, rather than on all endpoints as is the current case.
The filterValidator
method on the validator provider needs an additional argument of the relationship being searched (or null
if searching the primary resource) so that it can vary its rules based on whether a relationship is being searched or not.
Remove the PagingStrategyInterface
and PaginatorInterface
as logic around pagination can be held within the search class.
Add a pageValidator
method to the validator provider, following same pattern as filterValidator
. This would allow the page params to be validated, which in frameworks such as Laravel means actual validation rules could be used. Add a PageValidatorInterface
along the same lines as the FilterValidatorInterface
.
At the moment, the HTTP client only allows sending resource requests, and not resource relationship requests. These need to be added in.
If a ResourceObjectValidator
has required relationships, but has not been set to expect relationships, then it will validate true if no relationships
member is provided in input.
This is incorrect because if there are required relationships, there must be expected input. Need to fix this so that if there are required relationships then input must always be expected.
The same scenario will also exist for attributes - required vs expected.
If the type
member of the has-one identifier is invalid, the callback to validate the identifier is still being called.
Is there any fix for the deprecated warning?
If it's a simple answer, please excuse me, im still a junior developer.
It will be much better to add badges (code coverage, code climate, dependencies) to this repo.
I think it will help to promote it and shows people stability of this lib.
I am also sure laravel-json-api should have such badges
I've been playing around with this package using both the CloudCreativity\JsonApi\Error\ThrowableError
and the CloudCreativity\JsonApi\Error\ErrorObject
classes to try implementing JWT authentication exception and error responses but I noticed a radical difference between both of em.
ThrowableError doesn't seem to use a provided "code" key in the array sent to its constructor's first parameter (CloudCreativity\JsonApi\Error\ThrowableError line: 76
), I can use a separate $code parameter but it allows only null|integer values, which goes against JSON API standard as stated on their specification: code: an application-specific error code, expressed as a string value.
I believe ThrowableError should not use the same code that's being used for the exception code and allow for a different application's specific code to be added which should be treated as a string and rendered on the response.
What do you think?
Currently RelationshipsValidator
does not use isAllowedKey
in the protected checkKey
method. This is a bug because the decision of whether a key should be accepted should be down to whether it is allowed, not whether there is a validator for it (the current situation).
Camel case the key, prefix with hydrate
and suffix with Relationship
. So e.g. relationship called foo-bar
would return hydrateFooBarRelationship
Hello!
While using getResourceObject
from the JsonApiController I haven't been successful to validate JSON object attribute values whereas with Arrays ones it's working without any issue. I'll show you a couple of examples.
Array value validation:
// Validation rules
protected $rules = [
"numbers" => ["array"],
"numbers.0" => ["integer"],
"numbers.1" => ["integer"],
];
// Request content
{
"data": {
"type": "users",
"attributes": {
"numbers": ["123456789","987654321"]
}
}
}
// Everything works like a charm
JSON object value validation:
// Validation rules
protected $rules = [
//"objects" => ["array"],
"objects.attr1" => ["integer"],
"objects.attr2" => ["integer"],
];
// Request content
{
"data": {
"type": "users",
"attributes": {
"objects": {
"attr1": "123456789",
"attr2": "987654321"
}
}
}
}
// Makes me a sad panda
In fact, in the second example if I uncomment this line: //"objects" => ["array"],
I'll get an error on the objects attribute value as it's not an array and I'm not able to access the attr1 and attr2 with dot notation because of the same reason.
Is there a specific way to declare the rules to validate JSON objects attribute values or is there any other way to go about this? maybe consider transforming every object position inside "attributes" into arrays so the default dot notation validation way works?
AbstractValidator
should implement ConfigurableInterface
and allow templates to be completely replaced or allow template config to be merged.
Getting this:
TypeError: Argument 1 passed to Neomerx\JsonApi\Exceptions\JsonApiException::addError() must implement interface Neomerx\JsonApi\Contracts\Document\ErrorInterface, null given, called in /home/cgammie/Development/cgammie/radio-poc/vendor/neomerx/json-api/src/Exceptions/JsonApiException.php on line 77
After using the guzzle client to submit a request when the URL is invalid (the base_uri
option incorrectly set).
Add a StandardObjectInterface
.
ResourceObjectValidator::getKeyValidator()
assumes that both the attributes and relationships validators are keyed validators. But one or both might not be.
It needs to only attempt to get a keyed validator from either one if they are keyed validators. If not, it should ignore them.
Validation of content sent by a client actually needs to pass two questions:
At the moment the two are being mixed and it is not possible to check one, then the other. There are instances where it may be necessary to check (1) but not (2) - therefore there is a need to construct a validator that checks a document for (1).
This framework agnostic library should be able to do this because (1) is about validating against the spec. To do this:
ValidatorProviderInterface
needs to be return a validator for validating a resource document conforms to the JSON API spec.ValidatorFactoryInterface::resourceDocument
needs its parameter to be optional and if not provided, would use a validator that checks the resource conforms to the JSON API spec.ValidatorFactoryInterface::relationshipDocument
needs its parameter to be optional and if not provided, would use a validator that checks the relationship is either a valid has-one or has-many relationship.Amend condec matcher repository config structure to this:
Config::NAME => [
Config::CODEC_MATCHERS => [
Codec::DEFAULTS => [
Codec::ENCODERS => [
Codec::MEDIA_TYPES => [
'application/vnd.api+json' => null,
'application/vnd.api+json;charset=utf-8' => null,
'text/plain' => 'humanized',
],
Codec::SCHEMAS => null,
],
Codec::DECODERS => [
Codec::MEDIA_TYPES => [
'application/vnd.api+json' => null,
'application/vnd.api+json;charset=utf-8' => null,
],
],
],
'foo' => [
Codec::ENCODERS => [
Codec::SCHEMAS => 'foo',
],
],
],
Config::ENCODERS => [
Enc::OPTIONS => [
EncOpt::DEFAULTS => [
'options' => JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG,
],
'humanized' => [
'options' => JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES,
],
],
Enc::SCHEMAS => [
'foo' => [
// foo set of schemas here.
],
],
],
Config::DECODERS => [
'document' => DocumentDecoder::class,
'object' => ObjectDecoder::class,
'array' => ArrayDecoder::class,
],
],
As best practice is to create errors via an instance of the ErrorRepositoryInterface
, inject this into the AbstractAuthorizer
by default.
Add a new trait to use instead of ErrorsAwareTrait
that has an addError
method that accepts either an ErrorInterface
object or a string. If a string, will create the error from the repository. The method should return the error that was added so that methods such as setDetail
can then be chained off it.
The store should not make multiple calls to resolve a record, or to check that are record identifier exists. It therefore needs to use an Identity Map.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.