Closed (fixed)
Project:
JSON:API
Version:
8.x-2.x-dev
Component:
Code
Priority:
Major
Category:
Bug report
Assigned:
Unassigned
Reporter:
Created:
6 Jun 2018 at 01:19 UTC
Updated:
1 Aug 2018 at 17:54 UTC
Jump to comment: Most recent, Most recent file




Comments
Comment #2
gargsuchi commentedComment #3
wim leersThank you very much for this crystal-clear bug report! Expanded the issue summary with an example of
type.Initial question: did you select
8.x-2.x-devbecause that is the version you're using?Comment #4
wim leersI honestly have no idea how we're going to fix this without massive disruption. If we can find a way, some disruption is going to be unavoidable, so this is definitely 2.x material.
Comment #5
wim leersActually… there is a pretty simple answer to this. We should simply never normalize (expose)
identity key (which isnidforNode,uidforUser, butidforMenu), because JSON API strongly encourages the use of the spec-mandatedid, hence it's just useless noisebundleentity key (typeforNode,comment_typeforComment) — also useless noiseIn fact, it's this fact that it's useless noise that has made me wonder about this in the past. Omitting these would also make the JSON API responses easier to understand! In fact, this is probably one of the biggest WTFs for non-Drupalists that use a Drupal-powered JSON API implementation!
Comment #6
gabesulliceI think this is okay to leave as-is in 8.1.x. It's a spec violation, but I don't see how it could be a significant problem in practice.
+1
My only concern is that it we need to double-check that you do not need to know the Drupal ID to POST/PATCH an entity reference field on an individual route and that a UUID is sufficient (I'm 85% sure that this is case).
Comment #7
wim leersAgreed.
Comment #8
wim leersComment #9
wim leersThis makes
NodeTest::testGetIndividual()pass. @gargsuchi, would you like to do the same for our other test coverage? :)Comment #12
wim leersFixing the remaining test failures would be a great novice task!
Comment #13
gargsuchi commentedCurrently using 8.1.16.
Comment #14
sonnyktMy concern is that some entities require the id as references. For example, menu_link_content uses menu id for its menu_name attribute. Webform submission use webform id as its bundle.
Comment #15
wim leers@gabesullice in #6:
@sonnykt in #14:
Thanks to @sonnykt for digging in and explicitly confirming this is necessary.
MenuLinkContent'smenu_namefield indeed is required to useMenuids (not UUIDs).My initial reaction would be , but in the
MenuLinkContentexample that's not true, because itsmenu_namefield is defined like this:instead of as
Unless we fix all those field definitions, there's no hope of fixing this in JSON API. Because the metadata in Entity/Field API is incorrect/incomplete.
Comment #16
wim leersFurthermore: we should also stop omitting the
uuidfield inattributes! Because this is pure duplication. Perhaps that should be done in a separate issue though?Comment #17
gabesullices/stop/start/, right?
Comment #18
wim leersNo, stop.idcontains the UUID.attributes.uuidalso contains it. We should omit the latter.D'oh.
Stop exposing.
Start omitting.
So: yes! 😅😁
Comment #19
wim leersThis will definitely be perceived as an unnecessary BC break, so this will only land in 2.x.
Comment #20
wim leersRebased, and now also omitting the UUID.
Comment #21
wim leersUpdate expectation in
NodeTestfor #20.Comment #23
wim leersComment #25
wim leersUpdated test expectations.
Comment #27
wim leersWorking on fix…
Comment #28
wim leersComment #30
wim leersBesides the
MenuLinkContentedge case described in #15, I think this is ready.But … that entity type is already barely usable from any HTTP API: #2915792: MenuLinkContentAccessControlHandler does not allow "view" access without admin permission, making these entities inaccessible via REST, JSON API and GraphQL and entity reference fields. So I created #2984224: MenuLinkContent's "menu_name" base field is a string field rather than an entity reference field for this problem.
Comment #31
wim leersComment #32
wim leersAhhh … the classical "crosspost on d.o results in the node being unpublished" bug. Republished this issue node…
Comment #33
wim leersI just realized that so far, the patch only makes content entities comply. Time to do the same for config entities!
Comment #35
wim leersRepublishing for real.
Comment #37
wim leersComment #39
wim leers😅
Comment #40
wim leersThere are four config entity types that are a bit unfortunate: they have a
typefield. They are not omittable. The following config entity types have such a field:ActionFieldStorageConfigNodeTypeWorkflowThis is absolutely not fixable. It's also not reasonable to demand that those config entity types modify their data model for one particular HTTP API specification that they might be exposed via.
The only work-around I can think of, is to automatically alias them to
_type. This does that.Comment #43
wim leersCorrection on #40:
NodeTypelabels itsidentity key field astype. But we omit that field already anyway.Comment #44
wim leersFix 2 of the failures.
Comment #46
wim leersGreen 🤞
Comment #48
gabesulliceAdditionally, the [low line character] is allowed in member names, except as the first or last character
:\
Can we prefix them with
{$entity_type_id}_rather than just_or suffix them with_field?Comment #49
wim leersCR created: https://www.drupal.org/node/2984247.
Comment #50
wim leers#48 good call! Done.
Comment #52
wim leersOff-by-one error.
Comment #54
wim leersThe "comment" test is failing because the
{$entity_type_id}_prefix is the actual prefix used for a base field onComment. This is only causing problems because the current logic is blindly performing these transformations. And in fact,ResourceTypeis intended to be a pure value object, not something containing logic. So we can fix both at the same time.Will do this on Monday.
Comment #55
wim leersDoing #54 was surprisingly much work, but also makes for a surprisingly nice architectural clean-up, and makes things simpler for JSON API Extras! It makes
ResourceTypea much more valuable and capable value object, and helps a lot in making JSON API more debuggable and hence maintainable.It does mean there's less of a net negative diffstat though.
Comment #57
gabesulliceJust want to chime in and say that I'm +100 on this change. I really like the outcome :)
I've done a high-level review and have no concerns. But I'll do a very thorough review again when the patch is green.
Comment #58
wim leers🎉🎉🎉🎉🎉🎉🎉
Fixed CS violations and failing tests. In doing so, discovered that #2483407-33: Allow configuration schema fallback in ConfigEntityType::getPropertiesToExport() to work without an ID was the root cause, for which I had to add a work-around until that issue lands.
Comment #59
wim leersComment #60
gabesulliceI wonder if we should instead
assert(!in_array($internal_name, [$bundle_key, $uuid_key])before thecontinue. That ensures that the resource type is behaving correctly rather than overriding it.s/defualt/default/
Nice!
I either forgot or didn't realize that this was just to be removed altogether. I expected to see this test converted to expect
'node_type'. I think it's still useful to have a relationship to the bundle entity, that's how you can get bundle's label for example.I imagine you're shocked I took so long to say that, I'm sorry :\ Looking at the issue it should have been completely obvious to me. You even said:
I realize how quirky it is. Is there a way we could communicate that data in a better way? Maybe it's not even typical for a user to have access to view the bundle entity and wouldn't be able to access the bundle label in any event.
Comment #61
wim leerscontinueto the next field in the array if the field is the bundle or UUID entity key field. Because those fields are necessary inPATCHrequests. Hence the comment:I'd like that, but IDK how. Only thought for now: a
bundlelink. It'd be a Drupalism, but it's just a link. That's far less intrusive than the relationship we are currently exposing.Exactly.
Before bringing back relationships to bundle entities, I want consensus on how to do that.
I'm fine with just bringing it back like it was before (and automatically aliasing those fields to avoid violating the spec) and in that sense descoping "handle bundle entities in a more sensible way" from this issue.
Comment #63
gabesullice#61.1:
I think you were misreading my remark. I was asking "why should the $resource_type ever tell us that the
uuidorbundlefield is enabled? We should just ensure that the resource type disables it."BUT, I don't agree with myself today and after explaining that 😅
4:
I'm not comfortable with this big of a change without more feedback from @e0ipso and actual module users.
Comment #64
wim leersrelationshipsnormalization of JSON API, right?Rebased #61.
Comment #65
gabesullice1. Yes 😊
2. Yes, and we should probably file an issue for "[handling] bundle entities in a more sensible way".
Comment #66
wim leersDone!
Comment #67
wim leersCR updated: https://www.drupal.org/node/2984247.
Comment #69
wim leersConflicted with #2984607: Remove Drupal core <8.5 BC layer code and require Drupal >=8.5. Easy conflict resolution.
Solving those last two failures turned out to be pretty damn hard. 😱 Pretty much all of the existing test coverage needed to be updated to account for public vs internal field names. Which makes sense. And in turn means … that JSON API Extras' aliasing abilities are now pretty much guaranteed to not break anymore. (Since JSON API itself will already be doing aliasing of certain fields to comply with the spec, JSON API Extras can just piggyback on that.)
This took hours rather than the 15 minutes I was expecting 😐
Also: #2953346: Define related/relationship routes per field, not dynamically (with route parameters that need validating) landed this morning, and that resulted in more code needing updates…
Comment #71
e0ipsoSorry for coming late to this one.
I don't think this is advisable in a world with more services other than Drupal.
Entity ID:
Projects are sending data all around the interwebs, and (in some cases) they are using the entity ID for it. They could definitely use UUID, but this may require sensible work for existing denormalized data sources (Elastic Search, …). I don't think we should drop it and cause trouble when we can just leave it alone.
Bundle:
Resource type name and bundle are independent. For instance you can change it using JSON API Extras to potato and the bundle info is unnecessarily lost. This would be an unnecessary (and implementation-specific) coupling.
UUID:
I'm OK with this one.
Comment #72
wim leersThis should be green! 🤞
Comment #73
wim leers#71:
Why would this cause problems for Elastic Search?
Also: any existing site can continue to use JSON API 1.x for a long time to come — there's more test coverage proving things are working fine than for most contrib modules!
So that only leaves entity ID.
Comment #74
e0ipsoYes. And that was the right call, I'm still convinced of that. We have encouraged the trend to move towards UUID, and I'm proud of that. However that is not necessarily related to dropping any other fields.
it wouldn't, as I said They could definitely use UUID. However if a project has an index already running based off entity ID, we are making things hard for them.
I understand that we can drop the entity ID, I don't think we should just because we can. Also, locking people in older versions is probably not a viable solution.
Comment #75
justafishHaving the entity ID available is essential if you want to do anything in your API consuming application that links back to an admin path in Drupal e.g. contextual links that allow an editor to change content (this is typically done in clients by annotating sections with data-nid="5"). A very strong -1 from me for removing it.
Comment #76
wim leersI know you're both well-intentioned, but so am I. I intend to comply with the JSON API spec, because otherwise we break some JSON API clients. Not to mention that it's super confusing to anybody looking at Drupal's JSON API output: we have multiple identifiers! Worse, if we don't make this change now, and decide to do in a year or two (when JSON API is in core), then this will be impossible because it'd be a BC break. That's why I want to do it now, before 2.0 is tagged, because it gives us the chance to break BC once, for a future with fewer worries.
Once #2353611: Make it possible to link to an entity by UUID lands, this will actually work fine with UUIDs!
Comment #77
e0ipsoBut not the other examples that I provided. It feels that we keep telling the world that they are doing it wrong (the probably are!) and giving them a hard time so they are forced to be corrected in their behavior.
Well, I'm arguing we should not do it. Data is data. We should not be judgemental on what we allow to be exposed.
I think that's fine, as long as the
nidis only in the attributes then we're fine. There is no confusion in what the ID is. The spec makes that very clear.I'm confused about this, isn't the spec vocal only about the name of the property? I think that dropping the entity ID does not ensure that some other field is created that breaks the spec.
Comment #78
e0ipsoWe don't agree, and that makes better software. I still 💙 you, and I know you know that.
Comment #79
wim leersI haven't read #77 yet, my eyes were drawn to the blue heart emoji :D
I just want to confirm AS LOUD AS I CAN that HELL YES, disagreeing and then working towards consensus makes for better software! FLOSS FTW! 💙
Comment #80
gabesulliceI think @Wim Leers is right that fields like
nid,bundleandtypeare big DrupalWTFs.I can see how certain coupled solutions to a Drupal backend would want that data. And I recognize @e0ipso's point that we shouldn't "lock in" certain implementations to the 1.x (IOW, they should be a migration path, even if things need to be changed).
I think this all comes back to this: "how do we mask Drupalisms in a way that permits tight Drupal integrations?"
I suggested this on Slack to @Wim Leers and @e0ipso independently.
What if we make pseudo-field under
attributeslikeentity_keys?Then, the response would roughly look like:
This advertises that we don't encourage their use but still makes that data available. What do y'all think? @justafish, I didn't get pinged by you about this issue in Slack, so I haven't floated it by you yet :P
Comment #81
wim leersExcept there is: when to use which? What's the meaning of this extra ID?
You're right — but quite a few entity types did not alias their
identity key field (Nodealiased it tonid,Termtotid, etc, but not all entity types do this — basically only the "old school" ones).👏 Perfect description!
I like your proposal a lot. I'd personally even name it
drupal_internals, because that's clear even to a total outsider, whereasinternal_entity_keyssounds potentially important yet scary!Comment #82
gabesulliceI'm fine with that. I considered it too, but I felt like it polluted people's API responses a little. That's okay for now and I agree it's clearer to an outsider. It might even more effectively dissuade them from using it.
On the back of my mind is that a lot of our error messages and DX-related features loudly advertise "this API is built with Drupal". Which is okay... for now. As adoption grows, I think users will want a more "white-label" experience which let's them build an API that appears no different from one built from scratch specifically for their app/company.
Comment #83
e0ipsoI feel this solution is not solving the problem stated by the issue title.
I'd love to get a reaction to my previous comment:
If I add a field named "type" then the spec is still broken. If I add a relationship named "id" the spec is still broken. I don't see how doing anything to these fields fixes the issue.
I have other thoughts about the proposed solution, but I want to hold them until we've discussed this in depth.
Comment #84
gabesulliceThe spec reads: "[A] resource can not have an attribute and relationship with the same name, nor can it have an attribute or relationship named `type` or `id`"
By making those fields live in a new attribute not named
typeorid, I think it technically solves the problem.Yes, the spec only talks about the property name. The (most recent) proposed solution does not drop the entity ID.
I think the most interesting part of that remark is: "[It] does not ensure that some other field is created that breaks the spec."
Which is true... and maybe we should fix that, but that's technically not part of the issue scope as written. Do you think it should be?
Can you add fields named
typeorid? Aren't all configurable fields always prefixed byfield_?I can only think of base fields on a custom entity type not also part of an entity's keys which seems like a pretty small set. I'm not entirely sure what we could do about that or if we want to do anything about it. Other than that, there's jsonapi_extras, which I'd argue should be validating that any aliases are spec-compliant (if it's not doing so already).
Comment #85
e0ipsoBase fields should also be considered. Moreover, for configurable fields you can have a different field name prefix or none at all. It's a config setting on Field UI. You can also create configurable fields programmatically (not using field ui) and those don't get any prefix.
In my opinion making 2 fields not break the spec is not a satisfactory solution. A satisfactory solution is to make all fields not break the spec.
I think its not a pretty small set. Additionally you have to add any configurable field per my comment above.
This is a very good point. I don't think that's ensured. I created #2985375: Validate field aliases to ensure spec compliance for that.
I'm not saying that moving drupalisms to a well defined scope is an unworthy goal. We should definitely consider it in a separate issue. But it's a different problem from what's being reported.
Comment #86
gabesulliceTIL, I had no idea that that was configurable. That does indeed increase the set size.
It seems we need to expand the scope of this issue to handle all attribute and relationship field names.
Ideas?
Comment #87
e0ipsoBrainstorm:
1️⃣ What about checking if there is an
idortypefield and dropping it while normalizing? We could add partial success error that links to a d.o page explaining what happened and how to alias the field to some different name. The error object could even contain the key/value… or not.2️⃣ Maybe we can add a default alias for
typeandidto__typeand__idor similar. And then notify the dev that there is an alias applied using the same technique as above.Comment #88
wim leersBut those would already be aliased automatically by the current patch:
I verified this manually by changing the field prefix from
'field_'to'', then adding anidfield tonode--article. Even though it's stored asidin the database, JSON API exposes it asnode_id:(And of course this logic applies to both base fields and configurable fields. This manual test proves it works for configurable fields. The logic quoted above shows it doesn't care about what kind of field it is — base or configurable. And finally, #72's interdiff and the preceding ones already prove that it works for base fields.)
Agreed!
But … that's exactly what this patch does! Drupal already doesn't allow multiple fields to have the same name, for obvious reasons. That means that the only remaining problem is: fields names reserved by the JSON API spec. Those are
typeandid. And those are being automatically aliased (explained above in detail), which therefore makes all fields not break the spec!See above — this is already done!
See above — this is already done!
Comment #89
e0ipsoOMG. I'm so sorry about all the churn. I was thrown off by the (in my opinion out of scope) entity ID and bundle removal and an interrupted review. #84 and some other comments before seemed to verify that misunderstanding.
🙏🏽
That leaves the disagreement on the matter discussed in
internal_entity_keysand stuff. Also I think we should notify the dev of what we did with partial success instead of server logs.Do you think this is a fair summary of the things we don't have consensus yet?
Again, sorry for pestering.
Comment #90
wim leersNo worries :) I probably didn't communicate clearly enough! I probably should've posted a screenshot like the one in #88 earlier.
Let's see: let's first verify that we understand each other unambiguously!
@gabesullice proposed this in #80, I +1'd in #81 but suggested a different name, @gabesullice was open to that in #82, and you brought the conversation back to another discussion point in #83. So unless I'm overlooking it, I don't see a disagreeing opinion from you about this yet?
I don't know what this is referring to. No comments have mentioned "log" before. My best guess is that this is referring to
throw new \LogicException('The generated alias conflicts with an existing field. Please report this in the JSON API issue queue!');? Please confirm & clarify!Comment #91
e0ipsoWRT #81 and #82, I am OK with whatever solution, whatever name (I'm specially open to names containing poop emoji 💩).
My only requirement is that the properties work as expected for sparse fieldsets and (specially) filters. I think that shuffling properties around in the normalizers will have undesired effects on filterability because the json path to the property no longer matches the Entity Query path. But again, as long as we can filter and exclude properties I'm 💯 🆗.
Correct. Typically exceptions will be stored in the server logs, that's why I mentioned them. Sorry for the confusion.
Comment #92
wim leers😂
That exception will only occur if both of these are true for a
fooentity type + some bundle: A) there's anidortypefield, B) there also is afoo_idorfoo_typefield. Because then the automatic aliasing ofid/foowould attempt to alias it tofoo_id/foo_typerespectively, and that'd conflict with something pre-existing. I just tried to keep the logic super simple, to only address this if the need for this ever arises (I'm not sure that it will/does).Agreed!
Before actually implementing this, I'd like @gabesullice's +1 too.
Comment #93
e0ipsoOh. In that case as a developer I'd like to be notified of any unanticipated aliasing my responses suffer. I won't hold this as a blocker, maybe we want to have a follow up and work / discuss this separately?
Comment #94
wim leersThis affects you only if you use
idandtypeas field names. You'd think that if somebody develops an entity type specifically for use with JSON API, that they'd … just observe how it is exposed by JSON API while developing? So I don't think we need anything extra for that? Also, if they really want to violate the spec, then they can just override the alias back to the original using JSON API Extras.Comment #95
gabesulliceJust saying this so that it doesn't get stalled by accident:
I'm ambivalent WRT to the remaining points. In my opinion, this can be committed as is. I won't be marking this RTBC though, because @e0ipso should be the one to do it.
Comment #96
wim leersI took inspiration from + KISS by not doing
drupal_internals: { nid: 5, type: "article"}, which would require more complex data transformations.So this patch makes
nid: 5get aliased to💧nid: 5— which means that the entity ID (the last point of contention/consensus building) remains there, but is clearly marked as Drupal-y thanks to that drop emoji prefix.(That's also easy to grep for, so if others feel it should be more "professional", then that's easy to implement. I kinda like this though!)
Comment #97
wim leersThis is a leftover that should've been removed in #66.
Comment #99
wim leers4 fails caused by 3 mistakes — now fixed.
Comment #100
gabesulliceThanks @Wim Leers! Thanks for sticking with us while we took you on such a frustrating ride.
I feel like a wet blanket, but I really think this should be
drupal_internal__or similar. While the drop emoji is fun, it's a poor DX.Not everyone has a mac that makes it easy to type those characters out. When developing on Linux for example, I have to figure out what the emoji is called, google it, hope I find it, then copy/paste it or fight a battle figuring out how to get my keyboard to "type" the character.
Not to mention, it won't be at all clear to a non-Drupal developer that the drop is for Drupal.
Also, from the spec: U+0080 and above (non-ASCII Unicode characters; not recommended, not URL safe)
The emphasis is not mine! :P
Now get off of my lawn!
Comment #101
wim leers😀
🙈💩
Done. Took <1 minute.
Comment #102
gabesullice:) Thanks.
Still going to let @e0ipso have the final say on this one to RTBC or commit.
Comment #103
e0ipsoThe code looks good. I have 4 questions:
drupal_internal__but not for the others.Comment #104
wim leersComment #105
wim leersComment #106
wim leersComment #107
e0ipsoHmm. Did you check that the generated schema reflects the output? I'm pretty sure that if you move things in normalizers, then the schema fails. That's the main driver to move away from normalizers.
Comment #108
wim leersI'll check.
Comment #109
wim leersWow, apparently Schemata and OpenAPI simply have never supported JSON API Extras? That's very surprising to me — given Contenta shipping with both, I thought this was fixed a very long time ago… I updated #2882269, see #2882269-4: Support for JSON API's ResourceType::getPublicName() and ResourceType::isFieldEnabled().
I don't think we should block this issue on Schemata. Especially since for example #2967572: Small inaccuracies for JSON API schemas also just landed earlier today. We can iterate there while JSON API 2.x moves forward. I posted an initial patch at #2882269-6: Support for JSON API's ResourceType::getPublicName() and ResourceType::isFieldEnabled() — this makes everything in this patch work, in both Schemata, OpenAPI and OpenAPI ReDoc :)
Comment #111
gabesulliceThanks everyone!
Comment #112
wim leers🍻🍻🍻
Comment #113
wim leersPublished https://www.drupal.org/node/2984247.