Closed (fixed)
Project:
JSON:API
Version:
8.x-2.x-dev
Component:
Code
Priority:
Major
Category:
Bug report
Assigned:
Unassigned
Reporter:
Created:
31 Oct 2018 at 00:25 UTC
Updated:
14 Dec 2018 at 11:34 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
peterdijk commentedI found out the right way to user ?filter. But I still can't filter by Vendor ID.
jsonapi/node/recipe?include=field_submitted_by&filter[user][condition][path]=field_submitted_by.title&filter[user][condition][value]=Happy Farm
This gives me the right result, but I want to filter not by name but by ID like d3f06dcd-59a9-44cb-a66b-3db6f4488727
Vendor node:
"data": {
"type": "node--vendor",
"id": "d3f06dcd-59a9-44cb-a66b-3db6f4488727",
So I can filter by attributes properties/ values, but not by the id property?
Comment #3
peterdijk commentedI found it. The right path to filter is field_submitted_by.uuid
Or you could use drupal_internal__nid. Does it matter which you use?
To get a single resource you use the uuid > jsonapi/node/vendor/d3f06dcd-59a9-44cb-a66b-3db6f4488727
So it makes sense to filter also on the uuid?
Comment #4
wim leersWelcome to Drupal.org! 😄
I think you make an excellent point. I think it's indeed reasonable to expect that doing
/jsonapi/node/article?filter[uid]=<UUID>works, that you shouldn't have to specify/jsonapi/node/article?filter[uid.uuid]=<UUID>.So I do think there's a case to be made for this:
/jsonapi/node/article?filter[uid.uuid]=<UUID of user>/jsonapi/node/article?filter[uid.name]=<name of user>/jsonapi/node/article?filter[uid]=<UUID of user>(alias of #1)What do my fellow maintainers think? If they don't think we should support this, then I think it's worth calling this out in https://www.drupal.org/docs/8/modules/json-api/filtering#shortcuts.
Comment #5
wim leersIf we're going to add this DX enhancement, it'll happen only in the 2.x branch. In any case, I encourage you to update to the 2.x branch, we released 2.0-RC1 a week ago: https://www.drupal.org/project/jsonapi/releases/8.x-2.0-rc1.
Comment #6
e0ipsoI think that the correct behavior should be:
The other cases should result in a cacheable 4XX response:
Comment #7
effulgentsia commentedI agree with #6, in as far as that is what matches the Fancy Filters spec as currently written, per:
From which follows that the last element may not be a relationship name.
And I don't think we should change the spec to allow the last element to be solely the relationship name (and interpret that as meaning the
idof the related resource), because the json:api spec does not consider theidalone to be the resource identifier; rather, the combination oftypeandidis. So I don't think json:api extensions should ever reduce the concept of a resource identifier to less than that, even if Drupal is choosing to use ER fields that are constrained to single entity types and UUIDs that are unique across entity types.Comment #8
gabesulliceI agree with #6 also.
Comment #9
wim leersI think that we then will also agree this is at least a major bug in the current RC that should be fixed in a next RC?
Comment #10
wim leersI mostly agree with #6.
There are two cases in #6 I'm not convinced of:
Citing the same sentence as #7:
idis not an attribute.idandtypeare top-level members of a resource object. Andattributesis also a top-level member.Therefore, I think it should be the exact opposite:
Looking forward to your thoughts :)
Comment #11
gabesulliceI still favor #6.
The spec just seems to be wrong IMHO. I imagine that as we make the FF spec ready to contribute on jsonapi.org, we'll find many inconsistencies and places where it can be refined and generalized. IOW, at this point I think the spec should match our implementation rather than use trying to match it.
For example, taken on its own, the above statement actually precludes chained relationships! Take
uid.role.name, according the above, it's forbidden becauseroleis not an attribute field, it's a relationship field.To me,
uidspecifies the relationship object. The addition of a dot (.) then "traverses" to filter on value of the target, which could be any of:id,typeor an attribute or relationship field name (which can't be in conflict thanks to #2977669: Spec Compliance: some entity types have an "id", "type" or "uuid" field, this violates the spec).Finally,
/jsonapi/node/article?filter[uid]={value}already has a meaningful behavior! It's the same as/jsonapi/node/article?filter[uid.target_id]={value}. That's because that's how Entity Query API handles this—it uses the main property name of the field item when one is not specified in the field path.Comment #12
wim leersBut that'd at least still be a
fieldin JSON API terminology.idis not a field.Right :( So even if my proposal were better (I'm not convinced it is!), then it'd still constitute a BC break.
Conclusion: #6++ 🙂
Thanks for your patience, @e0ipso and @gabesullice, in exploring all possible solutions.
Comment #13
effulgentsia commentedThis might be getting a bit off topic, and maybe I should open a new issue for it, but I must say that I don't like these kinds of hidden aliases. For what it's worth, my preference would be that our filters follow the resource's data structure as it is modeled in the json:api output. What I mean by this is that how Drupal internally models entity fields should not leak out to the json:api.
Examples:
attributesastitle: "My Article Title", not astitle: {value: "My Article Title"}. Therefore, when filtering by title,filter[title]=My Article Titleshould work (which it does), andfilter[title.value]=My Article Titleshould 4xx (as opposed to its current behavior of working). In other words, if we output the title in the resource document as a string, then the API of that resource type is that the title attribute is a string, and we should not support a filter query that bypasses that API contract with internal knowledge about how Drupal stores it.attributesasbody: {value: "My Article Body", ...}, not asbody: "My Article Body". Therefore, when filtering by body,filter[body.value]=My Article Bodyshould work (which it does), andfilter[body]=My Article Bodyshould 4xx (as opposed to its current behavior of working). In other words, if we output the body in the resource document as an object, then the API of that resource type is that the body attribute is an object, and we should not support a filter query that bypasses that API contract with internal knowledge about which key within that object is "the main one".target_idanduuiddo not appear as members/fields of a json:api resource, then including them in a filter path should 4xx (as opposed to its current behavior of working). Again, they're not part of the API of the resource, so we should not support a filter query that bypasses that API contract with internal knowledge about how Drupal stores entities and entity references.dataof a relationship is always an object with bothtypeandid. Therefore, the filter path should explicitly target down to the primitive.Like I said, I should probably open a new issue to discuss the above (since others could certainly disagree), but just wanted to share my thinking on it since it relates to #6.
Comment #14
wim leersVery good points, @effulgentsia. Makes perfect sense.
(I don't think it belongs in a separate issue, because this too is about confusingness/mismatches between how the filter system syntax works, what the JSON API resource objects look like and the correspondingly clearly leaky abstraction.)
Your last bullet is also the perfect justification for #6, and makes perfect logical sense. I wish I'd seen that, and made that argument :) 👌
Comment #15
gabesulliceUgh, @effulgentsia you're totally right about the leakiness of fancy filters. But... can I please put my head back in the sand now?
That sounds super hard to do and ripe with pitfalls. Despite its correctness.
Things we'd need to consider:
relationships.uid.attributes.body.value? Or, is it okay to eliderelationshipsandattributesbecause the field names are globally unique? One could argue that since they're unique, they're implied, so one would still writeuid.body.value?uid.entity:node.bodytoday in order to ensure that your filter only applies to nodes when it could apply to other entity types (take it as a matter of faith that this is necessary :P). We'll need to have some kind of special syntax to use the resource type name instead of the entity type name (which would open up a can of worms because resource types are bundle specific).alttext on an image field. One can filter by those values withfield_image.alt, but under your premise it would need to befield_image.meta.altI think.Truly getting rid of these leaky filter paths would seem to me to be a pretty extensive undertaking and very disruptive. I don't know if we should tackle that prior to 2.0 stable.
Maybe we could postpone the complete reckoning of this realization and later use a profile to negotiate the correct vs legacy behavior. We could then use a BC flag to determine the default.
Comment #16
effulgentsia commentedExactly.
I need to think about that more before I have a suggestion on how to handle that in terms of a Drupal-independent fancy filters spec.
Yes, I think that's correct, because at least in theory, the resource referenced by field_image could have an alt attribute that is different than what is in the meta of the relationship. In fact, it gets even more complicated since the resource referenced by field_image could have metadata with overlapping keys as the metadata of the relationship. In other words, should
field_image.meta.altfilter on the "alt" metadata of the field_image relationship or on the "alt" metadata of the referenced resource? We'll need to think about how to disambiguate those.+1.
Comment #17
effulgentsia commentedFor
field_node_reference.entity:node.body, could we express that as?filter[field_node_reference.meta.entity_type]=node&filter[field_node_reference.body]=...? And actually includeentity_typein themetaof ER relationships and/or themetaof entity resources?Or is there a reason why this needs to be expressed in a single filter rather than as a conjunction of two filters?
Comment #18
gabesullice😭 I had a nice reply all written, then my Mac restarted to install Mojave...
@effulgentsia, I'm very much on board with using a complementary filter to narrow down the entity type. That's very clean and intuitive, as opposed to inventing some in-path syntax or copying EFQ's.
However, I'm not very warm about the idea of adding
entity_typeto the meta object. That seems to just patching the leak in one spot and letting it leak out somewhere else.Instead, I think we should just have a smarter resolver. We could expand/compile this:
into something like:
That would mean
FieldResolverwill need to get smarter (yet again). That's fine though, it has to for all the other cases we've discussed already. Since getting to know all the ins and outs of this space, I'd like to take another stab atFieldResolveranyhow.Comment #19
effulgentsia commentedI like #18, but how would you limit the filter to
'node'without limiting it to'node--article'? Or do we not need to support that use-case, since with respect to json:api, we either want to limit to a resource type or we don't, but there's no concept of "resource type groups".I guess alternatively, a json:api client knows what all the resource types are via
/jsonapi, so could construct anINfilter with['node--article', 'node--page', ...]?Comment #20
gabesulliceExactly. I think an
INfilter is what one would do.Comment #21
gabesulliceThis patch does the smallest possible thing:
It does not address anything else, such as failing on
uid,title.valueanything else mentioned since #13.Let's move all that to a followup(s).
There are three patches:
uid.uuid === 400.uid.id === 400uid.id === 200as it should be.Comment #24
e0ipsoThis looks good! (except for red tests) It is not pretty that we have to do this, but there's no way around it.
Are we concerned about the use of
reset(…)? What if multiple resource types yield a different result? What if$resource_typesis[]?I get that a relationship is limited to an entity type (via entity reference), hence the UUID key will have a consistent property. However, maybe it's worth to add a check for future-proofing.
Comment #25
gabesulliceThis should fix the test errors.
@Wim Leers and I agreed that this patch should also solve the case of
body === 400,body.value === 200,title.value === 400andtitle === 200sincevalueis elided in the resource object because the field only has one property.We will not try to solve
entity:nodeandfield_image.meta.altin this issue, but we'll open a followup for it.Comment #26
wim leers+1!
Comment #28
gabesulliceThe 32 failures in the last patch can all be attributed to the fact that config entities are not fieldable, which caused the field resolution to escape early without mapping
id => uuid. This should fix it.Comment #29
gabesulliceThe attached patch should achieve the above requirement. I've tested this manually on a few examples, but I have not yet added test coverage.
Comment #31
wim leersThis fixes the failures in
FilterTest.Comment #32
wim leersStrengthen the test coverage: verify the executed SQL query is exactly the same as if we'd have constructed the EFQ without the properties being specified.
(This more strictly proves that results will not change even in more complex circumstances.)
Comment #33
wim leersAdd regression test and improve DX: rather than just saying
must be followed by a property name., we now actually list which are possible property names to filter by.(This is essentially bringing back some of the test coverage from before #31's fixes; to explicitly test that this is now no longer allowed.)
Comment #37
wim leersCS violations fixed.
Comment #39
wim leersThe regression test added in #33 is insufficient; that only covers the "non reference field" case. We need to test both the "reference field without (meta) properties" and the "reference field with (meta) properties" cases too.
This adds regression test coverage for the "reference field with (meta) properties" case.
Comment #40
wim leersThis lists "target_id" because that actually is the underlying property name. But … we really shouldn't expose this.
Fixed.
Now the DX for this edge case works as desired! 🎉
Comment #41
wim leersThis adds regression test coverage for the "reference field without (meta) properties" case.
Comment #45
wim leersNote that this is technically inaccurate for entity reference fields. Entity reference fields always have at least two properties:
target_idandentity. But only one of them is non-internal:target_id. So this means that technically, entity reference fields should also be treated as single-property fields.But then they ought to be special-cased, since a filter on an entity reference field still is required to specify the
idof the referenced entity.Fixed.
Comment #47
gabesulliceI think the more robust behavior here is to check for
DataReferenceDefinitionInterfacenot string matchtarget_idspecifically (even though all EntityReference fields usetarget_id).entityis a computed property. Can we do something with that rather than special case entity references?Comment #48
wim leersWhile starting to address the remaining failures (in
FieldResolverTest), I realized that there are two more regression tests that we want:Having excellent error responses is a huge help for the update path, and is crucial for DX in general. I don't want this to be an afterthought like #2971281: Test coverage: ambiguous filter expression paths. So let's just deal with it now.
Comment #49
wim leersThis makes those new tests pass.
Let me explain one especially tricky hunk in the interdiff:
Without this additional condition, we'd enter this branch and
static::getDataReferencePropertyName()would fail because it assumes that it's guaranteed that the current field is an entity reference field, which is not actually true.Comment #50
gabesulliceNit: Can this be
Filter by `promote`, not `promote.value` (the JSON:API module elides property names from single-property fields).?Comment #53
wim leersAnd this then finally makes
FieldResolverTestpass. It moves all the failing test cases to expected error cases. Only the test cases involvingtarget_idwere salvageable: if we'd change the now failing test cases to instead filter byid, they pass. All other failing tests are related to the new requirement to specify a concrete property.Comment #54
wim leers#47:
+1 — I intended to leave a self-review about exactly that, but you beat me to it :)
Maybe. But
entityis not only a computed property, it's also internal. Besides, that's the same problem as the one you pointed out above: it's still hardcoding a particular property name. I think your initial remark was spot-on; we should be reusing the same logic as\Drupal\jsonapi\Context\FieldResolver::getAllDataReferencePropertyNames()/\Drupal\jsonapi\Context\FieldResolver::getDataReferencePropertyName()is using.#50:
Absolutely!
In fact, I think there's another such string that can be improved:
In fact, I think this error message also could be improved. This filter path/expression is not really ambiguous … it's incomplete. So I think that's what it should say instead.
If anything, this is just another case of
Invalid nested filtering., not a case of ambiguity.I've wrapped up what I was doing, assigning back to @gabesullice.
Comment #56
gabesulliceFixes the CS violation.
Comment #58
gabesulliceThis addresses #50.
Comment #60
gabesulliceThis addresses the above.
Comment #61
gabesulliceAnd this fixes the error introduced in #58.
Comment #64
gabesulliceWhoops, looks like I missed some things in #61.
Comment #65
gabesulliceThis gets rid of the string match on
target_id.Comment #67
wim leersThese changes are not necessary.
I first though they were too. It's why I ended up writing code to assert the SQL queries are identical in #32.
Comment #68
gabesulliceI disagree ;) They're obviously invalid because text fields also have a
formatproperty. So, by our rules, there are >1 properties so the path requires a property specifier.Comment #70
wim leersHm … the failing test is very convincing :) I'm confused by my earlier conclusion then.Ohhhhh! My eyes were misleading me! I was referring to the hunk above it:
That's still unchanged. All good then! But that confuses me on a different level: I'd already made those changes a long time ago. Why is this showing up in an interdiff again? Did you just lose it along the way?
Comment #71
gabesullice@Wim Leers and I chatted about what was needed to take this patch to completion. We suspected that the
metabit we decided to postpone to a followup in #25 could actually be accomplished now.This does that.
Comment #72
gabesullice🙈 I applied your interdiffs 1 by 1 and completely missed #31!
Comment #73
gabesulliceComment #74
wim leers#71:
OMG! So few LoC! It passes!#71's interdiff is not actually part of the patch…#72: no worries 😄 I just thought I was going insane there 😵 😂
Comment #75
gabesulliceMega: 🤦♂️
That's what I git for getting fancy in #68 and then copy/pasting.
Comment #76
wim leersGREEN!
Just one nit:
This could use a comment for at least the outer if-statement.
Other than that, #71's interdiff is surprisingly simple, and clearly performs the job!
Comment #78
gabesulliceWOOOT!
🎉🎉🎉🎉🎉
On to RC2!
Comment #79
gabesulliceChange record published: https://www.drupal.org/node/3015183
Comment #80
jibranhmmm, this is a BC break.
It works.
It doesn't work.
imo, jsonapi can't assume
ida shorthand foruuid.Comment #81
wim leers“id” had been the UUID for years. Passing a serial ID instead indeed won’t work.
If you have a need for filtering by serial ID, there’s #3015759: `?filter[drupal_internal__id]=ID` does not work: drupal_internal__id should not be converted to uuid when filtering to make that work again.
This commit shipped with RC2. If my answer didn’t address your problem, please open a new issue. We cannot revert this anyway, since it alreadh shipped. Thanks!
Comment #82
jibranCreated #3017161: Filtering using serial ID doesn't work if the field name is 'id' to keep the discussion going.
Comment #83
wim leersThank you very much! 🙏