I bring up the issue here: Can't get the right target type when filtering on relationship with bundle-specific target entity type, It's about inlude fail on commerce_pricelist after issue 2953207 was commited.
Now I can reproduce the bug just though drupal core:
- Install drupal8.5.x and jsonapi:1.x-dev.
- make a comment type named "tcomment", and Target entity type set to "Taxonomy term".
- add a comment field to vocabulary "tags", and set Comment type to "tcomment".
- Add a entity_reference field on node-page, set machine name as field_comment and target type to comment.
- Create a article, then create a comment for it.
- Add a node-page and set field_comment field's value to last step's comment.
- GET /jsonapi/node/page?include=field_comment,field_comment.entity_id,field_comment.entity_id.uid
We will get error:
{
"errors": [
{
"title": "Bad Request",
"status": 400,
"detail": "Ambiguous path. Try one of the following: entity:node, entity:taxonomy_term, before the given path: uid"
But If I revert some change here: https://www.drupal.org/project/jsonapi/issues/2953207#comment-12607897
- $reference_property_names[] = $property_name;
+ $target_definition = $property_definition->getTargetDefinition();
+ assert($target_definition instanceof EntityDataDefinitionInterface, 'Entity reference fields should only be able to reference entities.');
+ $reference_property_names[] = $property_name . ':' . $target_definition->getEntityTypeId();
I can get the right result.
| Comment | File | Size | Author |
|---|---|---|---|
| #41 | 2973681-41.patch | 28.58 KB | gabesullice |
| #41 | interdiff-37-41.txt | 3.52 KB | gabesullice |
| #37 | 2973681-37.patch | 28.56 KB | gabesullice |
| #37 | interdiff-36-37.txt | 7.19 KB | gabesullice |
| #36 | 2973681-36.patch | 25.49 KB | gabesullice |
Comments
Comment #2
lawxen commentedComment #3
wim leersThanks for filing a separate issue! 🙏
Comment #4
wim leersAnd even better: you've included a failing test! And reproduced it with just core!
@caseylau++
@caseylau++
@caseylau++
In #2953207-49: Can't get the right target type when filtering on relationship with bundle-specific target entity type, you indicated this was a bug introduced by the commit in #2953207. So I tried to verify that. And I can confirm it: it is a regression.
Comment #5
gabesullice@caseylau++
Thanks for the excellent report!
This is the root of the issue.
The given path can reference two different bundles. Unlike most reference fields in Drupal, a comment's
entity_idfield can reference different entity types per bundle. Typically, a shared reference field between bundles always references the same entity type.What the error is saying is that the field resolver can expand
field_comment.entity_id.uidinto either:we should probably improve the error message in another issue, I think it could be more clear that this is what it is saying
This affects which tables the entity query API will
JOINagainst when the path is used in afilterquery param.However, since this is for an include, not a filter, this doesn't really matter.
The difference between
includeandfilterpaths has been a nagging issue in the back of my mind for a while.In the one case, we're only concerned with path validity, in the other, path expansion (which implies validity).
As long as any one expansion of a path is valid, the
includepath should be considered valid. IOW, when we hit an "ambiguous" part of the path, instead of throwing an error, the logic should just fork.Comment #6
gabesulliceFor those who may have come here because of the regression:
Until a patch lands, there is an easy workaround that you can do from your consumer with the workaround patch below.
If you are sending a request with an
includelike:And receiving a 400 error that reads something like:
Ambiguous path. Try one of the following: entity:node, entity:taxonomy_term, before the given path: uidThe workaround is to send a request using both suggestions:
Comment #7
gabesulliceQuick patch for the workaround.
This is not intended to resolve this issue.
Comment #8
lawxen commented@Wim Leers @gabesullice
What about add is_filter to getDataReferencePropertyName
Comment #9
lawxen commentedComment #11
lawxen commentedFix the fail test of 2973681-9.patch
Comment #12
lawxen commentedFix the fail test of FieldResolverTest
I was wondering whether we should change the patch's $is_include(TRUE/FALSE) to $query_parameter_type(include/filter/limit...).
Comment #13
lawxen commentedDo it.
Comment #14
lawxen commentedEnrich the test case data of \Drupal\Tests\jsonapi\Kernel\Context\FieldResolverTest->resolveInternalProvider()
Comment #15
gabesulliceWow! Thanks @caseylau. Great work.
Resolving the internal path currently applies to
sort,filter&include.sortandfilterboth get passed directly to the entity query API after resolution.includedoesn't deal with the entity query API and ends up in a completely different code path. Its format comes directly from the spec, while the filter and sort path format is just a convention established by this module.Because they're really such different beasts, I think we should probably separate the logic of validating includes out entirely from sort/filter, rather than switching behavior within the resolver method. IOW, they merely "look" the same, but in reality they're very different.
Supernit: don't need the comment anchor.
Supernit: Extra line.
Comment #16
gabesulliceWhoops, messed up the IS.
I'll make these changes and add a new patch.
Comment #17
gabesulliceComment #18
gabesulliceExplication...
Validation is a static method on the field resolver now. No need to inject the dependency. That's because the resource type already has all required information.
These are the operative lines of the patch.
We need to prepend the related field name to the path because
$context['resource_type']is the resource type of the base individual resource not the targeted resource type(s).For example, the resource type at
/jsonapi/node/article/{uuid}/field_tagsis nottaxonomy_term--tags, it isnode--article. Therefore, if one wishes to include the vocab entity, the path would bevid. However, we need to validate it asfield_tags.vidagainst thenode--articleresource type (seeJsonApiFunctionalTest:L99-112).This only applies to
relatedroutes, notrelationshipsroutes. That's becauserelationshiproutes require the full path in the query string (seeJsonApiFunctionalTest:L148-161and the include examples given by the JSON API specification).We need the actual resource type, with relationship data set, not a mock resource type because
setRelatableResourceTypesmust be called on the resource type before that information can be accessed by the validation logic (the repository does this for us automatically).Comment #19
gabesulliceCS violations.
Comment #20
e0ipsonit: single quotes.
Did we lose the ability to have field aliases? What is the reason for that?
Comment #21
gabesulliceNope, it's still there ^
Comment #22
wim leers#5:
This went over my head … I don't think I see sufficient documentation for this yet in the current patch.
#18
#20:
but does not return it.
Nit: missing trailing period. Fixed.
Also: missing explicit test coverage.
Missing explicit test coverage.
The point of regression tests is that they each set up their own environment as minimally as possible to reproduce it. Rather than sharing this in a
setUp()method that happens to work for the two regression tests we have so far, I'd much rather just repeat this in each.Fixed.
Comment #23
gabesulliceD'oh! 🤦♂️
Which ofc invalidates my comment in #5:
Because we are concerned with more than validity. We also care about resolution.
Right now,
FieldResolver::resolveInternal()does 3 things:node--article, which has a fielduid, which targets auser--userresource type, which has anamefield, thereforeuid.nameis a "valid" path).uid.namemust becomeuid.entity.nameand sometimesuid.entity:node.name).For
sortandfilter, all three are required. Forinclude, we do not want expansion and validation has a slightly different set of constraints. We also have an extra constraint: during validation, only entity reference fields are valid (e.g.uid.namewould not be valid becausenameis not an entity reference, it's nonsensical to "include" this).It's because
filterandincludeonly truly share resolution that I'm suggesting we separate out into an entirely new method rather than keep it all inresolveInternal.There's an additional validation constraint on
filterthat does not apply toinclude: a path relationship cannot reference more than one possible entity type. That's what's happening in this issue:field_comment.entity_id.uidapplies both to:The entity query system can't handle that, therefore it's invalid for
filter.But it's not invalid for
include. For include, if any of the paths are valid, the include path is valid. So, when something is ambiguous, we want to "fork" and keep the validation logic for each possibility.That's because we evaluate include paths at runtime, per entity. So, we effectively do:
$entity->get('field_comment')->get('entity')->get('entity_id')->get('entity')->get('uid')->get('entity');for each resource. In that case, it doesn't matter ifentity_idreferences two different entity types.Comment #24
gabesulliceBetter comments. Restores field de-aliasing. Still needs tests.
Comment #26
gabesulliceUnnecessary comma.
Undocumented param.
This could use a comment.
Second sentence is superfluous.
Comment #27
gabesulliceFixes a couple CS violations and fixes the errors in #24.
Comment #29
lawxen commentedI'll add test coverage for resolveInternalIncludePath()
Comment #30
lawxen commentedChange:
Besides:
I have test many extreme case of include and filter in our site, all of my test requests works.
Comment #31
lawxen commentedsince Impossible to filter using path specifier with entity type was commited, this issue's patch can't be applied, it needs reroll
Comment #32
lawxen commentedReroll of 2973681-30.patch
Comment #34
lawxen commentedComment #35
wim leersI think #23 deserves to be added to
jsonapi.api.php, because it's essential information about the JSON API module's code architecture! 😯👏Given that explanation, this totally makes sense!
Again such an eloquent, crystal clear explanation! 👏 Let's document it! Let's not keep it locked up in this one issue!
Thanks @gabesullice!
🙏 Removed tag! :)
🎉
Thanks @caseylau!
Review of #24's interdiff:
Übernit:
fieldmanagerwould be a very atypical field name, wouldn't it? That makes this comment less grokkable than it otherwise would be.Wait, is that even allowed? How can the same alias be used for multiple fields?! :O
Ahhhhh…
Can't we make
jsonapi_extrassmart enough to disallow this? Wouldn't that be the saner solution?I guess the idea is that each bundle (and hence resource type) is free to do whatever it wants on bundle-specific fields, and if some of those bundle-specific fields happen to have the same name, then there's nothing wrong with that.
Can you confirm that this is the rationale?
It is documented in a later iteration ✔️$depthis not yet documented.This means that we'll be exposing internal (actual) rather than external (public) field names.
OTOH, we have no way to get the external/public/alias for a field. So … this is actually the best we can do. Probably worth calling out in a comment though?
🙏 This comment will save much "WTFF!?" exclamations in the future :)
Wow, neat trick!
Review of #30's interdiff:
Nit: Let's make this service available as
$this->resourceRepositoryinsetUp().Can we also expect a message?
This one should fail because it's an invalid notation, right?
What's the difference between these?
What's the difference between these?
Nit: The comments should become the array keys.
Comment #36
gabesullice35.1: Ah, good point. I hadn't even considered it. I was thinking of "manager" as in "product manager". Regardless, I think I made the comments a lot better.
35.2/3:
I think it's a key DX part of JSON API Extras to allow aliasing different internal fields with the same external field name. That's important to maintain BC as data models evolve, but also to unify concepts, like if you you added an author field to taxonomy terms, you may want to alias
authortofield_authorfor terms, butuidfor nodes.Yes, each resource type can do whatever it likes. That is the rationale for the first part. I think you may have missed some reasoning in the second part though.
It doesn't really have anything to do with "bundle-specific"-ness. That comment is there because it's possible that you end up with resource types for different entity types (that's proved by the existence of this bug report).
How?
Imagine an entity type (like a node) has an entity reference field that can reference the entity type
commentand does not havehandler_settingslimiting it to a single bundle. We know different comment types (bundles) all have anentity_idfield. This field is an entity reference field. Unlike the first entity reference field though,entity_id'starget_typediffers per comment type (per bundle).This means one comment type's
entity_idfield might reference nodes while another comment type might reference taxonomy terms. Let's call thosencommentandtcommentrespectively (their resource type name would becomment--ncommentandcomment--tcomment).In code, you'd realize that
$entity->get('field_comments')->referencedEntities()would return bothncommenttcommentcomment entities at the same time. Go one step further and you'd realize that$entity->get('field_comments')->get('entity_id')->referencedEntities()can return either nodes or taxonomy terms at the same time too. That's the ambiguous part that the entity query API can't handle, butincludetotally can, because the pseudo-code I just showed would work just fine.35.4: 👍
35.5: I think
array_keys($resource_type->getRelatableResourceTypes())actually returns public field names becausesetRelatableResourceTypeshas this documentation:35.6: 👍
35.7: 🤘
I'll address that in my next comment/patch.
Comment #37
gabesullice#35.30.1-6: All done, plus a few CS violation fixes.
That one's been around for longer than I can remember. Technically, it just fails because of
host, not because of the anything else (like the!!chars)Comment #38
gabesulliceComment #39
wim leers#36 😍 👌
This comment is perfection.
The interdiff is too.
👏👏👏
Comment #40
wim leersActually, I found the smallest, stupidest nit possible in #36:
"that the uid" -> "that the uid field" or "that uid"
#37:
s/@encode/@endcode/
❓ s/nested fields/invalid path/
I'd fix it myself and commit right away, but the last point I'm not 100% certain about my last point. So, one last time to NW :)
Comment #41
gabesullice40.36.1: 🦅👁
40.37.1: whew man, I did that everywhere!
40.37.2: I just updated the input path to (hopefully) get rid of the confusion. There is not, nor has there ever been, character validation of the path (which is what I think you're asking about?).
Comment #42
wim leersThat's better :)
Comment #44
wim leers🚢
Comment #46
rpayanmMoving to Drupal core's issue queue.
I'm working on https://www.drupal.org/project/drupal/issues/3122113