mezzio / mezzio-hal

Hypertext Application Language implementation for PHP and PSR-7

Home Page:https://docs.mezzio.dev/mezzio-hal/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`HalResource` does not allow `_embed` data that is based on a `oneOf:` JSONSchema definition

Ocramius opened this issue · comments

Bug Report

Q A
Version(s) 2.3.1

Summary

This is a union of Foo and Tab in a JSONSchema resource:

{
  "components": {
    "SomeResource": {
      "oneOf": [
        {"$ref": "#/components/Foo"},
        {"$ref": "#/components/Tab"}
      ]
    },
    "Foo": {
      "type": "object",
      "required": ["bar"],
      "properties": {
        "bar": {"type": "string"}
      }
    },
    "Tab": {
      "type": "object",
      "required": ["taz"],
      "properties": {
        "taz": {"type": "string"}
      }
    }
  }
}

In practice, when rendering a list<SomeResource>, you will get an array of JSON objects with different properties:

[
  {"bar": "haha"},
  {"taz": "hehe"},
  {"bar": "hihi"}
]

Current behavior

HalResource#__construct() does some pretty aggressive validation of its JSON contents, specifically on $embedded data:

/**
* @throws InvalidArgumentException If $a and $b are not structurally equivalent.
*/
private function compareResources(self $a, self $b, string $name, string $context): bool
{
$structureA = array_keys($a->getElements());
$structureB = array_keys($b->getElements());
sort($structureA);
sort($structureB);
if ($structureA !== $structureB) {
throw new InvalidArgumentException(sprintf(
'%s detected structurally inequivalent resources for element %s',
$context,
$name
));
}
return true;
}

This precludes usage of well-documented resources of different types.

How to reproduce

  • configure a resource for class Foo { public string $bar = 'baz'; }
  • configure a resource for class Tab { public string $taz = 'tar'; }
  • render a (pseudo-code) new HalResource(..., ..., [new Foo(), new Tab()]) - it will crash due to Foo and Tab not sharing same properties

Expected behavior

A HalResource can be instantiated with $embedded data of different types:

* @param HalResource[][] $embedded
*/
public function __construct(array $data = [], array $links = [], array $embedded = [])

Suggested solution

Let's just drop the exception: it was seemingly designed to support developers that have difficulty with keeping their data consistent, but it hinders proper union type design.

My understanding of how embedded collections work in HAL is that a collection (an _embedded item with an array of resources) is expected to contain resources that are all of the same type.

Interestingly, on reviewing the current draft of the specification, there is no mention of this, which either means this has changed during draft evolution, or my understanding was based on participation in the HAL forums "back in the day".

As such, the only thing that is really required is that each item in the array is a resource (i.e., an object with a Link collection).

Totally willing and ready to review a patch that does this.

@Lucian-Olariu I can send this patch, FWIW 👍

@weierophinney even if they had the same type, a union type is indeed a valid type, heh 😁

Never thought I'd see the day you'd advocate for union types, @Ocramius . 😄