Problem/Motivation

There are a few existing issues (added as related) dealing with core/json:api module's assumption that entity references are, basically, always "core" entity references which can only target a single entity type. The denormalization performed in the top-level JSON:API normalizer does what amounts to early denormalization of this data and deprives field normalizers (or Extras field enhancers) sufficient information about the reference to accomodate for other entity type targets. E.g., Dynamic Entity Reference needs to set entity_type in addition to target_id.

Interestingly, we are dangerously close to actually supporting this, in so far as the target entity type is identifiable by its public json:api name, which is how these entities actually pass initial validation and are loaded up to get their target IDs.

You can post DER data by specifying the target_type in meta, however this is unnecessary as it effectively duplicates the type member of the relationship identifier object and requires the client to know about this drupalism (and potentially expose your internals.) See #2888553: Handle denormalizing meta payload in relationships where this pattern was introduced.

Note that there is #3027430: Dynamic Entity Reference normalization support and others which address the normalization/querying side, but AFAICT simple reporting of DER field data "works", and there may be some quirks with querying (particularly as it releates to included data and filtering by relationships) but there are no open issues for the create/update denormalization side of the coin.

Steps to reproduce

Try to post just a relationship with type and id to a DER field.

Proposed resolution

Load up what I'm referring to as the "early denormalized" value array for entity references with the entity type; this should be a no-op for the core ER field but will provide DER with what it needs (and any other field doing something similar.)

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3277553

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

bradjones1 created an issue. See original summary.

bradjones1’s picture

Status: Active » Needs review
bradjones1’s picture

Another reason to change the value set from target_id to entity is that this would allow the entity reference field to validate the entity type (and, if applicable, bundle) of the entity matches the field's settings. At the moment, there's nothing stopping a client from setting an entity of a different type to a field expecting an entirely different entity type. This is because the entity is loaded in the top-level document normalizer, but so long as it's valid, its ID is set unquestionably to target_id. If the same target_id exists on the correct type, you would end up with a totally different entity being referenced. And because entity IDs are serial (as opposed to UUIDs) this is a non-trivial risk of inadvertant data corruption.

ericchew’s picture

I am building a decoupled app and ran into this issue with DER when trying to POST to an entity with a DER field. The error I received was:
InvalidArgumentException: No entity type was provided, value is not a valid entity. in Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceItem->setValue(). I applied the patch and was able to create the entity with no errors.

Below is the shape of the POST request I am sending. I have the DER field setup as multi-value but at this time only require referencing one entity, so that is all I have been able to test. I also have not tested PATCH requests.

{
    "data": {
        "type": "custom-entity--custom-entity",
        "attributes": {
            "example_text_field": {
                "value": "<p>test</p>",
                "format": "html"
            },
            "example_boolean": true
        },
        "relationships": {
            "uid": {
                "data": {
                    "type": "user--user",
                    "id": "some-uuid"
                }
            },
            "example_der_field": {
                "data": [
                    {
                        "type": "custom-entity--custom-entity",
                        "id": "some-uuid"
                    }
                ]
            }
        }
    }
}
bradjones1’s picture

Looks like the test in #2998662: Saving a tagged article in staged workspace causes fatal exception ::testNewEntitiesAllowedInDefaultWorkspace()was incorrect but passing due to the incorrect type checking when setting entity reference values by passing only the entity property. Fixing, but it would be nice to get some other eyes on this to ensure the spirit of the test is preserved/correct.

bradjones1’s picture

Title: json:api module does not support POSTing dynamic entity references with only `type`, `id` » json:api module does not support POSTing dynamic entity references with only `type`, `id`/entity reference field does not validate type when setting with entity property
bradjones1’s picture

bradjones1’s picture

@bbrala should also receive credit for sprinting on this during DrupalCon.

larowlan credited bbrala.

larowlan’s picture

bbrala’s picture

Status: Needs review » Reviewed & tested by the community

I've reviewed the submitted code. Great improvement imo that led to a fix in entityrelations :)

bbrala’s picture

ericchew should be credited for finding this issue and talking through it with @bradjones1

bradjones1’s picture

There are some transient errors in Drupal 9.4 HEAD but this is green, now, after poking DrupalCI.

Thank you for the RTBC.

bradjones1’s picture

It would be great to finish up #3057545: ResourceTypeRepository wrongly assumes that all entity reference fields have the setting "target_type" along with this; the one does not require the other, but particularly when generating (or trying to...) OpenAPI specs for DER fields, we need to continue to improve the core ER field handling to make fewer assumptions about its operation.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record

Left some comments, needs work for a change notice

bradjones1’s picture

Status: Needs work » Needs review

Back to NR. Added two CRs, one as requested for the ER field and another documenting the change in json:api's exception translation. This should provide additional context to your other MR review question.

Also forked off #3279103: Test cleanup: Remove dead code from JsonApiFunctionalTest for the dead test code cleanup.

bradjones1’s picture

Issue tags: -Needs change record
bradjones1’s picture

I added an additional CR to notify site owners/client implementators that they can drop the unnecessary meta information if the field type supports this. I thought this might be a CR on DER but the change is in core and at least theoretically, this affects other modules with a custom ER field type. I'm only really aware of DER though.

https://www.drupal.org/node/3279367

bradjones1’s picture

At the risk of delaying or over-complicating this, I think we have an opportunity to make this even more awesome-er.

While testing the entity that brought me to this issue in the first place, I noticed that the response resource relationship object included a meta object with entity_type set. This makes sense, as it's essentially the echo-back of the data that you could then post to Drupal to indicate the required property for extending fields (in this case, DER.)

Except, we don't need to do that anymore! Of course, the goal here is to both get out of the way of contrib field types as well as reduce unnecessary Drupalisms in the API surface.

I took a look at the related code, and it turns out json:api was doing a little more work than I think it needs to in order to determine which properties to send in the meta object. Properties can be marked internal, and the normalizer was already taking that into account in filtering properties. So let's mark entity and target_id in the core field as internal, and then modules like DER can do the same with their properties that don't need to be sent to the client, either! A fluent API for the win.

We'll see if this screws up the tests, but I don't think it should?

I think this can be added to the existing CR I made about property values in meta.

I'll open a corresponding issue in DER to mark entity_type as internal. This would be a BC break, since there may be some clients that require it. I don't think this should be a huge issue though, since to POST new content, you'd already need to know the entity_type out of band, somehow. That would work fine if json:api were the only API shape out there, but e.g. REST would take the target_id and entity_type properties directly, b/c both are necessary to create the field value.

Adding a related issue regarding removing the current exposure of the internal entity_id for ER fields. I believe this should be easily opt-out.

Thoughts?

bradjones1’s picture

Well, I realized setting the target ID property as internal was a little over-aggressive; REST, etc. need to actually normalize that. But, I think this is an opportunity for us to better respect the properties' internal flag, and consult that to determine whether we set the drupal_internal__target_id value. That allows paranoid site owners like me to override the field item class and tweak the property definitions to set it as internal, and makes that part of the code more fluent. I also think cleaning up the inline comment to reflect this as more field-agnostic is good?

If all of this is too much of a sidetrack, we can revert to #17/f3af2d79.

bradjones1’s picture

Status: Needs review » Needs work

We should also respect the isInternal() flag when resolving the field query; e.g., if the data reference property in question; I'm going to hack on this a bit more but I think the code in FieldResolver::resolveInternalEntityQueryPath() needs some refinement in line with the changes we're making here. This is all rather similar to the work in #3057545: ResourceTypeRepository wrongly assumes that all entity reference fields have the setting "target_type" but I think it should be appropriate here in so far as we are addressing what properties of the ER field json:api is aware of, and how we handle those properties during normalization/denormalization.

bradjones1’s picture

Status: Needs work » Needs review

Rebased and added additional validation of known field properties on denormalization. To be extra cautious, if the field name isn't found, I'm just copying as-is for the moment, since we otherwise aren't doing field name validation at that point in the process.

I considered if being able to set read-only or internal properties through meta is a security issue, but I couldn't come up with any real situation that didn't involve myself tied in a knot where this would be feasible given the known universe of ER fields.

Back to NR.

bradjones1’s picture

Added another draft CR covering the value array property filtering.

bradjones1’s picture

One final note for the day if reviewers are curious why this additional validation should matter - There are two core field types which extend EntityReferenceItem: FileItem and ImageItem. Both contain additional properties which can be set in meta. We have test coverage for this.

Honestly, I'd be interested to hear thoughts about tightening this up even further and throwing an exception (unprocessable entity?) if there are additional, unknown properties encountered during filtering. (We could first exclude the drupal_internal__* values out, like currently.) I don't necessarily think that well-behaved clients should be sending any unexpected data here. I guess in theory, you could be really wacky and use non-property data in the value for a custom field type, but I don't think it's our responsibility to support that antipattern. This would continue to help the client responses be more meaningful to public clients that do not have understandings of Drupal internals.

bbrala’s picture

Title: json:api module does not support POSTing dynamic entity references with only `type`, `id`/entity reference field does not validate type when setting with entity property » JSON:API module does not support POSTing dynamic entity references with only `type`, `id`/entity reference field does not validate type when setting with entity property

First off, i've added some comments to the MR, mostly eather clarification I'd like, and a nit.

#25: To be honest, although it sounds interesting to tighten up even further, i have the feeling this might give us more problems down the road. Especially if we are to be a little more liberal in responding with mixed collections those kind of extra validation might result in complications.

We could discuss this more, but i think i would prefer to move that to a separate issue, this is complicated enough already and rather not muddy up this issue more.

#22: My first thought on this was; 'doesnt this mean we are closing off some filtering possibilities we are currently supplieing?', but some manual testing does seem it doesn't. Technically is kinda great to use that flag instead of hardcoding stuff.

bradjones1’s picture

Status: Needs review » Needs work

Thanks for the review; all easy stuff to address. I'll put NW for those nits.

Regarding your two specific points - agreed and I do not believe there's anything actionable on this issue. We can open a separate issue for the field parameter validation if we want. Agreed it's not in scope here.

bradjones1’s picture

Status: Needs work » Needs review
bbrala’s picture

Status: Needs review » Reviewed & tested by the community

Great work Brad!

I'm interested to see what an committer would say in the internal comment. I do see your point, but perhaps I'm overlooking something.

Anyways, let's do this. RTBC.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bradjones1’s picture

Status: Reviewed & tested by the community » Needs work

Two failing tests, need to at least re-base and re-test.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Wim Leers’s picture