BinarCode/laravel-restify

Store/Update requests aren't working with JSONAPI payload format

maicol07 opened this issue ยท 3 comments

Hi,

if I try to send to the POST endpoint (store) the following payload (compliant with JSONAPI specs):

{
  "type": "users",
  "attributes": {
    "username": "test",
    "email": "test@test.tl"
  },
  "relationships": {}
}

I get the following error:

{
  "message": "username is required. (and 1 more error)",
  "errors": {
    "username": [
      "username is required."
    ],
    "email": [
      "email is required."
    ]
  }
}

I've found out Restify only accepts a plain payload of type [attribute => value], like this:

{
    "username": "test",
    "email": "test@test.tl"
}

By the way, this works but due to this Restify isn't fully compliant with JSONAPI (the docs home page says "JSON:API consistency" under features).

Can you support the JSONAPI payload schema?

Thanks

As a workaround, I've added methods on my repository to support JSON API payloads, translating them into Restify ones:

/**
     * @psalm-suppress MissingParamType
     */
    public function allowToStore(RestifyRequest $request, $payload = null): RestifyRepository
    {
        $this->adaptJsonApiRequest($request);

        return parent::allowToStore($request, $payload);
    }

    /**
     * @psalm-suppress MissingParamType
     */
    public function allowToPatch(RestifyRequest $request, $payload = null): RestifyRepository
    {
        $this->adaptJsonApiRequest($request, true);

        return parent::allowToPatch($request, $payload);
    }

    public function getStoringRules(RestifyRequest $request): array
    {
        return $this->collectFields($request)->mapWithKeys(static fn (Field $k) => [
            ($k->label ?? $k->attribute) => $k->getStoringRules(),
        ])->toArray();
    }

    public function collectFields(RestifyRequest $request): FieldCollection
    {
        $fields = parent::collectFields($request);
        if ($request->isUpdateRequest()) {
            return $fields->map(static function (Field $field) {
                if (!($field instanceof BelongsTo)) {
                    // Fix to allow updating fields with custom labels
                    $field->label = $field->attribute;
                }

                return $field;
            });
        }

        return $fields;
    }

    /**
     * Adapt JSON:API request to Restify request.
     */
    protected function adaptJsonApiRequest(RestifyRequest $request, bool $snake_attributes = false): void
    {
        /** @var array<string, mixed> $attributes */
        $attributes = $request->input('data.attributes') ?? [];
        // Convert all keys to snake_case using Collections
        if ($snake_attributes) {
            $attributes = collect($attributes)
                ->mapWithKeys(static fn ($value, $key) => [Str::snake($key) => $value])
                ->toArray();
        }
        $relationships = $request->input('data.relationships') ?? [];

        // Get relationships in form of "relationshipName" โ†’ relationship_id
        $relationships = array_map(static fn (array $relationship): int => Arr::get($relationship, 'data.id'), $relationships);

        $request->replace([
            ...$attributes,
            ...$relationships,
        ]);
    }

Actually, it supports:

  • Post (Store)
  • Patch/update
  • Labels
  • Different attribute casing
  • One to one/HasOne relationships (TBC)

That's a good idea to include support. Thanks @maicol07 , will make sure we prioritize this.

That's a good idea to include support. Thanks @maicol07 , will make sure we prioritize this.

By the way, we'll also how to figure out where to put pivot fields (I've read something on discuss.jsonapi.org where they indicate meta inside the the relationship)