Problem/Motivation
JSON produced by the HAL and JSON normalizers provides all data values as strings. This makes it difficult for strongly typed uses of the REST interface to interact with the JSON payload. Examples include: client libraries, auto-generated documentation, and schemas. By providing BooleanItems as booleans, IntegerItems as integers, and so on, the values output in JSON will have the correct type without client-side casting. This has the nice side effect of better lining up output with expectations of the Typed Data API.
Proposed resolution
Add additional field item normalizers casting values to explicitly match the type implied by the field item class.
Remaining tasks
None.
User interface changes
None.
API changes
None: this is an opt-in change. Existing sites continue to get the exact same responses, with booleans and integers as strings.
Only new sites will get the "correct" output by default.
See the change record: https://www.drupal.org/node/2837696.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #289 | interdiff.txt | 552 bytes | himanshu-dixit |
| #289 | 2751325-287.patch | 33.03 KB | himanshu-dixit |
| #284 | interdiff-2751325-279.txt | 2.24 KB | catch |
| #284 | 2751325-279.patch | 33.57 KB | catch |
| #282 | 2751325-281.patch | 34.79 KB | catch |
Comments
Comment #2
dawehnerHI. I'm wondering whether this is just an issue with hal_json or also one of json itself?
Comment #3
Grayside commentedYes it's an issue with both.
Comment #4
dawehnerGood to know!
I guess adapting
\Drupal\serialization\Normalizer\ComplexDataNormalizerand\Drupal\hal\Normalizer\FieldNormalizerusing
\Drupal\Core\TypedData\PrimitiveInterface::getCastedValueis probably the way to go.Yeah in general I'm wondering whether you would consider this as breaking change for other users of the api, aka. should this behaviour be opt in/opt out by default
Comment #5
Grayside commentedIf I were maintaining an API based on this I'd call it a major break. Not sure what the B/C constraints are for core's resource payloads.
Comment #6
dawehnerYeah not sure we have clearly thought about it, but yeah I would vote for an opt IN feature. It doesn't seem to be a break worth risking.
Comment #7
Grayside commentedComment #8
Grayside commentedHere's a first pass on the direction. Preliminary checks in my local environment show the correctly casted values. Needs Review to check for hidden problems.
Also, need to follow the path in core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php to see what can be done instead of dropping the new interface parameter.
This patch does not include tests, nor does it incorporate the opt-in functionality discussed above.
Comment #10
Grayside commentedFound another implementation of toArray() in need of the new $casted parameter. Dug in a bit on the tests. EntityAdapter::toArray() seems like it has to be the end of the line for the $casted behavior. Not checked but I would guess that the code flow ultimately traverses down into the type system where support for the new parameter has been implemented.
Comment #13
HalfChem commentedComment #14
HalfChem commentedComment #15
wim leersMoving to
serialization.modulebecause this affects bothjsonandhal_json, yet 100% of the changes are in the Serialization module. So it makes more sense as the component. It'll need to be RTBC'd by the maintainer of that component, damiankloip.I've also noticed this while working on #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method.
Comment #16
damiankloip commentedYes, this makes a lot of sense to me in general. I think we didn't do this a couple of years ago because it was tricky for reasons I forget now. But time has elapsed since then so I don't remember the details now :P
This is potentially a problem in the latest patch. TypedDataInterface does not contain the getCastedValue() method. That comes from the PrimitiveInterface. So we might need a new normalizer to handle this instead. Incidentally, as the IS states it might do but doesn't seem to be :) I think we might need a new normalizer for PrimitiveInterface that comes before TypedData and does what this current patch has done?
Comment #17
wim leersOh, darn! We're fortunate to have your eagle eyes!
That sounds entirely sensible!
Comment #18
dawehnerI could imagine we added it after serialization module went in.
Comment #19
Grayside commentedComment #20
damiankloip commentedYes, right on Daniel. I think that seems pretty likely.
Comment #21
wim leersRight :)
#2002102: Move TypedData primitive types to interfaces added
getCastedValue(), on June 30, 2016.The serialization functionality has been in Drupal core since 2012, and was moved into a module of its own in #1903784: Move serialization to own module, on February 9, 2013.
Comment #22
damiankloip commentedOK, here is a new approach (mentioned by me in #16). It does not rely on ComplexDataInterface anymore, as that doesn't contain the getCastedValue method anyway. The plus side is this also requires no other changes to ComplexDataInterface and anything implementing that. From my testing this works fine. Let's see what the testbot thinks. I didn't do an interdiff as ever line is different.
Comment #23
damiankloip commentedNeed to fix this docblock and property name.
The varous primitve types added to the test are kind of pointless at the moment too. I will try and change to use the actual classes for real.
Comment #24
damiankloip commentedUpdated tests to use real data objects.
Comment #27
dawehnerA couple of nitpicks. Overall it looks nice!
Note: We could use PrimitiveInterface::class here, see https://3v4l.org/JuLFp
Note: We could use some string keys here, like 'string', 'integer' and 'boolean'.
Comment #28
damiankloip commentedFix for the serialization test.
Comment #29
damiankloip commentedDo we care taht HHVM doesn't seem to like that approach? Some people at least attempt to use D8 with HHVM? Adding the keys to the data provider is certainly a good plan!
Comment #30
dawehnerThat seems to be a temporary bug for HHVM, because well, it works on either HHVM versions, see
Comment #32
damiankloip commentedwell, let's do it then.
Comment #34
Grayside commentedWell, thumbsup to this. Makes sense and it's more comfortable than tweaking things so far outside the normalizers.
I don't see that this trickles up to the HAL module though.
Comment #35
damiankloip commentedYes, this might not work with hal module as it is. I think the only place this is a problem is FieldItemNormalizer::normalize(), as it's doing
$values = $field_item->toArray();. Ideally this field item would be iterated instead and passed to the serializer?Comment #36
damiankloip commentedI.e. like this. I think this should work?
Comment #38
damiankloip commentedThose hal tests set up a mock serializer, which seems counter productive in a kernel test like that. It should use the actual service that has been set up correctly...
Comment #39
wim leersI agree this looks great!
This is essentially what is changing. It looks great!
Wrong class.
Nit: missing inheritdoc.
Wrong class.
I suspect that what is going on is the original entity having string values for a particular field, and the denormalized entity having int/bool/… values for the same field, and hence it detecting inequality and failing.
For this reason, I think we need to have HAL+JSON test coverage too.
Comment #41
damiankloip commentedWhat kind of hal test coverage do you mean? seems that the hal normalization tests are ok with this? or do you mean tweak the existing tests to type check too?
Comment #42
wim leersI mean: we want to have a test specifically asserting that HAL+JSON responses also have ints/bools.
Comment #43
damiankloip commentedok, yes. Agree. I think we do the same as we have done for the serialization module tests. Check the type too, not just assertEquals.
Comment #44
wim leers+1!
Comment #45
Grayside commentedComment #46
Grayside commentedThought I had more time. Well, here are some nit fixes.
Comment #47
Grayside commentedHere's a version without the test coverage that can apply to Drupal 8.2 installations.
Comment #48
dawehnerThere are some interesting test failures left:
Comment #50
dawehnerI'm looking into the failures for now
Comment #51
wim leersWoot!
Comment #52
dawehnerI hope that's it :)
Comment #53
wim leerss/on the lowest levels/in TypedData/
Why?
Comment #54
dawehnerMh, I don't understand it at the moment anymore, I mean its kind of the problems , mhhh, sorry I'm confused right now.
Comment #56
Grayside commentedApplied the #47 patch in an environment running Drupal 8.1.x. The JSON output from this site instance is providing a data source for a migration running in another Drupal 8.1.x environment. The following error was thrown by the migration:
Sharing this here to illustrate that this change might surface some of the compatibility issues discussed early in the issue, causing us to think of this as an opt-in change.
Since I need the patch for one serialization format, but not the one powering this migration, I'm going to custom override for the breaking format as a short-term solution.
Comment #57
Grayside commentedHere's a reroll of #47 with the NULLable magic from #52. Again, focused on 8.1.x/8.2.x.
Comment #58
wim leersSo this needs an opt-in BC flag, much like #2664780: Remove REST's resource- and verb-specific permissions for EntityResource, but provide BC and document why it's necessary for other resources did in
rest.settings. But we need it inserialization.settings, so it's actually more like #2768651: Let TimestampItem (de)normalize to/from RFC3339 timestamps, not UNIX timestamps, for better DX, which is currently in progress.Comment #59
damiankloip commentedHmm, the opt-in would have to be to decide whether the normalizer is added to the container I guess.
Comment #60
wim leers#59: This new normalizer is always active, as long as the
serializationmodule is installed. So any existing D8 site with theserializationmodule installed would need to keep the old behavior (but allow opting in to the new behavior), and any fresh installation of theserializationmodule would get the new behavior from the start.Comment #61
damiankloip commentedBut this would essentially render this normalizer useless, we would need to add this in a supportsNormalization implementation or something. I would rather see this just not added to the serializer in that case. Bringing config into normalizers seems questionable, which is kind of what I was trying to convey in #2768651: Let TimestampItem (de)normalize to/from RFC3339 timestamps, not UNIX timestamps, for better DX.
Comment #62
wim leersI don't understand what you mean. Can you explain why this normalizer would be useless? I think you also want to maintain BC for existing sites?
Comment #63
wim leersI just read #2768651-36: Let TimestampItem (de)normalize to/from RFC3339 timestamps, not UNIX timestamps, for better DX.3, where you wrote:
Ahhh! I get it now :) Yes, that makes a ton of sense. Keeping the normalizer simpler. Keeping the conditionality out of it. +1!
Comment #64
Grayside commentedAre we talking about creating a ServiceProvider class along the lines of https://www.drupal.org/node/2649646#comment-10751980, wherein we check for a bc config?
Comment #65
wim leers#64: yes :)
Comment #66
damiankloip commentedYes, pretty much. We already have a service provider, so it would probably just be adding this in another compiler pass.
Comment #67
damiankloip commentedMaybe my plan is flawed a little... using config in a compiler pass might not really work :)
Comment #68
wim leersWell,
\Drupal\language\LanguageServiceProviderdoes this too. It usesBootstrapConfigStorageFactory::get().Inspired by that, I did something similar for the CDN module: it only adds a particular middleware if the CDN module's config has a certain key set to a certain value. And I think that provides the clearest & closest example to what we need here:
I don't see why that cannot work for this use case as well.
Comment #69
Grayside commentedRecent testing of an API (and Schemata schema) using this patch led to #2832212: \Drupal\text\Plugin\Field\FieldType\TextItemBase normalization: 'format' property can be NULL but string expected from Typed Data. Not sure I believe this is a cause, but this seems like the most closely related change on the Drupal side.
Comment #70
damiankloip commentedAh @WimLeers, that's a good idea! Let's do that then!
Comment #71
damiankloip commentedHere's a reroll with UpdateTest changes missing. Not sure what happened to that test atm.
Comment #73
damiankloip commentedCan we guarantee that typed data is going to iterate over typed data instances? The docs are kind of unclear but I'm assuming so. However, why don't we move those checks to the actual normalizer that are meant to be getting the values instead? That should work I think? Let's see if we'er in the same position on failures.
Also, just added all the BC stuff too. Some inspiration and stuff yoinked from https://www.drupal.org/node/2768651 with a couple of methods split out in the compiler pass.
Comment #75
wim leersI don't know. I don't know Typed Data well enough to be able to answer that.
This one is for #2768651: Let TimestampItem (de)normalize to/from RFC3339 timestamps, not UNIX timestamps, for better DX, not this issue.
s/$rest_settings/$serialization_settings/
Nice, this sets up the infrastructure for more such cases in the future!
Comment #76
wim leersSo, regarding the test failures:
This means it was expecting that the first field for which "no access" error response is returned would be the
changedfield. But with this patch, that's now thecidfield.The reason for that failed assertion (and all others) is simple: some things that are strings in HEAD aren't strings anymore with this patch.
For this particular case, I added this debug output to
EntityResource::patch():which then outputs:
As you can see,
$original->get($field_name)->getValue()returns a string, but$entity->get($field_name)->getValue()returns an integer.The real question here is how it's possible that
getValue()can return a different value.Attached reroll fixes it at least for the failing
Nodetests.Comment #77
wim leersBTW, the test coverage in
EntityResourceTestBaseis not yet strictly validating types. It's only testing equality, which allows for typecasting. We may need to change that as part of this patch.i.e. these assertions:
Comment #79
dawehnerI think I can explain this behaviour. When you store data via PDO and retrieve it again, you get back strings. Welcome to the world of non strict languages.
Given that the
$original_entityis retrieved from the DB it gets the string values. #2310089: ATTR_EMULATE_PREPARES is not necessary for newer mysql versions anymore. explains you more about that.Here is a try to cast not only a single value but rather an entire field item list value, so we can compare it. At least the comment test passes with that.
Here is an implementation of that together with some documentation and improved test coverage.
Comment #80
damiankloip commentedI think I like this approach I think. It could lead to some additional field items getting saved if you had older style stringified data posted to an endpoint using this code but I think that overhead is relatively minor in this case.
It's not really the fault of this patch as it's already happening in some other places in rest module but E.g. patch/post methods are type hinting EntityInterface but really depend on FieldableEntityInterface.
Maybe 'NULL values', we don';t have NULL objects as such. We wish we did ;)
Maybe replace 'In a better world ...' with 'Ideally ...'.
Comment #81
damiankloip commentedNot sure this is needed now with this patch, or for parity, we should actually now replace all the values in these getExpectedNormalizedEntity methods with their casted values where appropriate?
Comment #82
wim leersThis.
Plus what I wrote in #77: we should now strictly compare the expected normalization (strict in terms of types, not in terms of field order).
Comment #83
dawehnerWell, we should check for fieldableentity interface, but our code should support config entities in the future without breaking signatures, if possible.
Let's see how many places we assume the wrong values now.
Comment #85
wim leersAFAIK
assertSame()also requires key order to be the same, which would explain the failed tests.Comment #86
damiankloip commentedYes assertSame() cares about the exact order too. We can try this, sorting the array first. See how we get on. Otherwise we will need a helper assertion method to iterate over the array recursively, compare each scalar value identically and make sure all keys exist.
Comment #87
damiankloip commentedComment #88
damiankloip commentedAs far as upgrade path tests to, I think we should just maybe test that when enabling the module you get the normalizer and updating you don't? How easily can we mimic an already installed serialization module? It might be better to just test this on a unit level for the compiler pass?
Comment #90
damiankloip commentedMissed one.
Comment #91
wim leersThis is looking awesome. Wanted to RTBC, but yes, upgrade path tests are still missing. (The BC layer and upgrade path itself is present, so removing those issue tags.)
In terms of upgrade path test coverage:
\Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBasealways tests the "current defaults" first, and THEN it tests the BC layer forbc_entity_resource_permissions:i.e. we first test current defaults, then we enable a BC flag, and observe the expected different behavior (i.e. 403 because you need to grant this extra permission, then a 200).
I think it'd be best if we do something similar in this case: observe that all integers and booleans are casted to strings. We can check that automatically, by iterating over everything that
getExpectedNormalizedEntity()returns and using PHP to cast all integers/booleans to strings, then assert that that is what the server is returning with again a strict equality assertion.Comment #92
wim leersDiscussed with damiankloip. I'll be taking on the writing of the test coverage. damiankloip can then review the test coverage. I'm more familiar with that test coverage, so he can work on something else in the mean time, so that should help speed things up :)
Comment #93
wim leersThis adds BC test coverage to
EntityResourceTestBase::testGet().Comment #94
wim leersOops. This should be removed.
I fixed this nit while touching the patch anyway.
Comment #95
damiankloip commentedThese changes to the tests look good to me. It's basically what I had locally before you took it over, minus the recursive function :) Just one question really:
Maybe we should consider only running this BC code for content entities? and just wrap this whole hunk in the interface check?
Sorry, forgot to remove that based on one of your previous comments!
Comment #97
wim leers#95.1: hm, that's an interesting idea. OTOH, the current test ensures that nothing changes for config entities, which I thought to be valuable. But you're right, they have a separate
ConfigEntityNormalizerso this code naturally doesn't run for them.I think I prefer what you propose. But I don't have a strong opinion either way. Whichever you prefer is fine by me!
Comment #98
wim leersHm… even though I only touched
EntityResourceTestBase(so no code touched, no other tests touched) in #93, #93 is failing inDrupal\Tests\serialization\Kernel\EntitySerializationTest. Does that mean this is a random fail? Retesting…Comment #100
damiankloip commentedHere is that change (to only run for fieldable entities). As well as the other test fail. I think..
Comment #102
damiankloip commentedComment #103
wim leersJust one nit:
Please ensure two blank lines in these two places, for consistency with the rest of the test file.
Comment #104
damiankloip commentedSpaces added! :)
Comment #105
wim leersCan you also make sure there's two newlines between these two?
Already RTBC'ing though.
Comment #106
damiankloip commentedHa, thanks. Here we are.
Comment #107
wim leersPerfect! :)
Comment #108
alexpottIt's weird you need to case here. I then I remember typed data is not primitively typed :( so
Comment #109
damiankloip commentedRight. It basically just returns the value as it's stored in the database... it should really return the correct type. I think there might be a reason that never happened. I remember berdir telling me something once. In other sad news, I don't think you can save the value of a Boolean field by setting an actual Boolean value :)
Comment #111
wim leersComment #112
damiankloip commentedRerolled
Comment #113
wim leersComment #114
alexpottSo we definitely need a CR and we need to think about any breaks that we happen here.
Comment #115
wim leersCR created: https://www.drupal.org/node/2837696.
Note that we explicitly don't break BC here. See the CR for details. This was not yet clear in the IS, so updated that too.
Comment #117
wim leersComment #119
wim leersComment #120
alexpottIf you do the config listener change below this should not be necessary.
8300
Let's just inline this. Because of this you need a config listener that forces a container rebuild if this setting changes. See \Drupal\language\EventSubscriber\ConfigSubscriber::onConfigSave - basically you need to ensure
\Drupal::service('kernel')->invalidateContainer();is called when this value changes.----------------------------------------------------------------------
FOUND 0 ERRORS AND 7 WARNINGS AFFECTING 7 LINES
----------------------------------------------------------------------
5 | WARNING | [x] Unused use statement
7 | WARNING | [x] Unused use statement
8 | WARNING | [x] Unused use statement
9 | WARNING | [x] Unused use statement
10 | WARNING | [x] Unused use statement
11 | WARNING | [x] Unused use statement
12 | WARNING | [x] Unused use statement
----------------------------------------------------------------------
PHPCBF CAN FIX THE 7 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
----------------------------------------------------------------------
FOUND 0 ERRORS AND 12 WARNINGS AFFECTING 12 LINES
----------------------------------------------------------------------
5 | WARNING | [x] Unused use statement
7 | WARNING | [x] Unused use statement
8 | WARNING | [x] Unused use statement
9 | WARNING | [x] Unused use statement
10 | WARNING | [x] Unused use statement
11 | WARNING | [x] Unused use statement
13 | WARNING | [x] Unused use statement
14 | WARNING | [x] Unused use statement
15 | WARNING | [x] Unused use statement
16 | WARNING | [x] Unused use statement
17 | WARNING | [x] Unused use statement
18 | WARNING | [x] Unused use statement
----------------------------------------------------------------------
PHPCBF CAN FIX THE 12 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
----------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
----------------------------------------------------------------------
5 | WARNING | [x] Unused use statement
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
Comment #121
damiankloip commentedThanks Alex, this is good feedback. I didn;t know the 8300 thing! Silly me. The subscriber has been added. I tested and seems to work as expected. Let's see if the tests like it.
Comment #122
wim leersNit:
===.No other remarks. Tentatively back to RTBC.
Comment #123
damiankloip commentedThanks Wim, I would quickly fix but I don't want to throw anything else at the test runners atm :)
Comment #124
wim leers#123: you could cancel the test in #121 and just post a reroll :)
Comment #125
damiankloip commentedTrue. That is effort, but why not!
Comment #126
damiankloip commentedComment #129
damiankloip commentedI think I know why this doesn't work in the tests. dfac() makes a call to DrupalKernel::updateModules() which (re) initializes the container. Just relying on the subscriber in tests doesn't work as the container is invalidated but not rebuild straight away.
Comment #130
wim leers#129: yep, and similar problem in #2815845: Importing (deploying) REST resource config entities should automatically do the necessary route rebuilding.
Comment #131
damiankloip commentedJust using rebuildContainer() doesn't work... Must be something else that is being relied on in dfac().
Comment #132
Grayside commented@#120 having a hard requirement for no typed primitives seems pretty exceptional, as systems generally need to deal with data and map it. If a system needs the simplicity of "all the things are strings", that should be supported as a feature itself rather than as an aspect of backwards compatibility.
It seems like if a given integration needs to support a format with this behavior, they can add or swap in normalizers that cast to string. Should core support that as a deliberate integration design outside of b/c?
Comment #133
damiankloip commentedIt's not about making that the default, it's about keeping responses the same for now. Since Drupal 8.0 all values have been strings. There could be lots of stuff using this, also relying on it being a string for whatever reason. The default behaviour with this patch will be to have typed primitive values, it is only the case where people are upgrading their sites that it will enable the BC flag and not add this new normalizer. People are then welcome to opt out of the BC handling as use the new defaults. Or you are mainly just saying that we should rename the flag we are using? and have this as a generic string casting rather than BC?
Comment #134
damiankloip commentedAdding back the dfac() call for now.
Comment #136
wim leers#134's interdiff pointing to #2815845 to fix it is inappropriate: the scope of that issue is to deal with route rebuilding, nothing else. I think the right solution is to manually reinitialize the container used in the test?
Comment #137
dawehnerYeah I think we better should use
$this->rebuildContainer();Also note, the other issue will not solve the problem here to be honest.
Comment #138
damiankloip commented#136 and #137, see my comment in #131. However, I think I was not calling the rebuildContainer method on the test class, but on the actual kernel directly.
Comment #139
damiankloip commentedOK, rebuildContainer() didn't work for me, but rebuildAll() did.
Comment #140
damiankloip commentedComment #142
wim leersI think this is ready again!
Comment #143
wim leersThis is blocking #2626924, see #2626924-127: Include processed text in normalizations: "text" field type's "processed" computed property should be non-internal and carry cacheability metadata.
Comment #144
wim leersI'd also argue this is a bug, not a feature.
Comment #146
wim leersComment #147
damiankloip commentedI realised that I didn't change the variable name in the update hook. This just fixes that (and actually just removes the variable -chaining all the method calls).
Comment #148
damiankloip commentedSorry, actual interdiff.
Comment #150
dawehnerThis is a random test failure ...
Comment #151
damiankloip commentedNeeds a reroll.
Comment #153
damiankloip commentedRebase FAIL.
Comment #154
damiankloip commentedBack to RTBC.
Comment #155
damiankloip commentedOK - me and Alex had a chat on IRC this morning. He is still has BC concerns about API responses changing with this patch. So we settled on always enabling this BC config setting, instead of enabling for modules being updated and disabling it when the module is enabled/installed.
This changes the default config value for 'bc_primitive_data_normalizer' to TRUE, so this matches the update handler.
Wim: This means we need to re-think how we handle string casted vs typed values in EntityResourceTestBase. I have handled this by adding a new getExpectedNormalizedEntityWithCastedStringValues() method, this just wraps the getExpectedNormalizedEntity() value with the string casting. It means we need to check whether the entity is fieldable before doing so. On the plus side, it looks like there are not many occurrences of this to change. If you hate it and think there is a better solution please go for it! :) The alternative seems to be to go back to having all string values in getExpectedNormalizedEntity(), which would mean we'd need to iterate over all the fields for the entity, and cast the values to the appropriate type that way.
Comment #156
wim leersHUH!??!?? So we'll still continue to do the stupid/crazy thing by default? How does that make any sense? :( Then we might as well not do it and say
The whole point is that new sites get sane behavior by default, and existing sites continue to receive the old behavior. Is that not exactly what "backwards compatibility" means? I don't understand what could possibly be "BC concerns about API responses changing".
Of course API responses must change, if the responses' contents don't make change. If we can't change anything about the responses, then we also cannot ever make the REST module's responses remotely sane. Because if this can't change, then none of these can't change either:
Comment #157
tedbowI looked back over the discussion of @damiankloip and @alexpott on IRC.
My understanding of @alexpott's concern is this
If we could make the above work it would have the advantage of being able to write clients for Drupal's REST API and not have to worry when a site was installed as far as basic things about the response such as data types. For clients that write a service that deals with multiple Drupal 8 sites this would be a major advantage.
For any individual site that was installed past 8.2.x this in many cases though would be a major disadvantage. Drupal 8's REST module had many problems at 8.0.0 and we are working to improve it, this being just 1 example. These issues have a lot of input and the changes mostly seemed to agreed upon to be just better for clients. If all new sites could not get these changes by default(or very easily) Drupal's REST would still be perceived as broken.
So how to solve this problem and let clients be written that work across multiple drupal sites and also let new sites easily get all the improvements we are making?
Here is my suggestion:
One other addition would be to able to set a site wide setting REST Drupal drupal-minor-version. It would be checked by RESTSettingService::checkSetting() if the header was not present.
So why "bc_primitive_data_normalizer should be set to false for new sites"?
I think new sites are going to expect saner defaults. People new to Drupal don't want to have to check through all the bc_X's and make updates and are probably going to start writing their clients before they figure out they exist.
Developers like "Company X" are probably more familiar with Drupal or at least writing their client is more a major part of their practice then someone installing Drupal once and making a couple REST requests.
An important distinction is that this is not API version and maybe x-drupal-minor-version is bad name to avoid confusion.
Comment #158
wim leersWe had a call/discussion with @damiankloip, @tedbow, @drpal, @prestonso and myself about #155, i.e. about Alex Pott's concerns. For completeness, I've also attached the chat log of damiankloip + Alex Pott for posterity.
Interesting points that came up that hadn't come up fore yet:
better_restcontrib module that would be born out of frustration if issues like this don't land: Alex' sample scenario would still be broken!Comment #159
Grayside commentedThis seems like the point where software versioning and API versioning come into conflict. We are really talking about how to support "version 1" of Drupal REST while moving forward to "version 2" of those resources. This also brushes against #2813311: API versioning: decide & document a backwards-compatibility policy for REST resources.
Comment #160
skyredwangI have done quite a lot Android development. Android uses Java, which is a more type strict language. Having to deal with bug makes the client side less efficient.
I do have a question: PHP BC Math extension outputs numbers as String. I am wondering if this patch would automatically converts BCMath outputs to the correct type, or it becomes the module's responsibility to convert BCMath's result when receiving response via REST?
Comment #161
hampercm commentedTo me, it seems very important to make improvements to REST like this one available out-of-the box. Otherwise, less experienced developers will see core REST's default functionality and think that is all they can expect from it. I get @alexpott's concerns, but it seems unrealistic to expect disparate Drupal systems to all be compatible, unless you put some effort into making them so.
Comment #162
wim leersReuploading the RTBC patch of #153 to be able to mark this RTBC, to be able to get feedback from a core committer to #156 + #157 + #158 + #161. This issue is super important for REST/serialization, and blocks tons of other progress.
Comment #164
tedbowRandom DateRange test fail. Retested and set to Needs Review
Comment #166
tedbowA different random fail? FilterFormatAccessTest
Comment #167
skyredwangtrying again.
Comment #168
wim leersComment #170
alexpottNeeds a reroll for 8.4.x
Comment #171
alexpottComment #172
catchFor me I would make the bc layer opt-in rather than opt-out.
This is a bug fix that may or may-not break an existing client. If it doesn't break a client, then the site gets updated and will run happily ever after.
If it does get broken, there's a quick fix to enable the bc layer if you don't want to fix/don't have control over the client.
If we do it the current way, then assuming we leave the bc layer in until 9.x and remove it then, it just increases the chances (by the number of clients implemented between now and then that rely on string output that otherwise wouldn't have been) that people run into this problem when they upgrade to 9.x
Comment #173
jofitzRe-roll (started before comment #172 so does not take that into account).
Comment #175
wim leersOkay so @catch agrees with what we've been proposing… but @alexpott does not. Either catch and Alex Pott need to discuss this among themselves (they're both core committers), or we all need to get on a call together. What do you think?
Comment #176
alexpottYes it does look as though @catch and are coming at this from opposite view points. As this basically a release management question of how we break I’m happy to defer my opinion. I would have expected the release manager to be more conservative than the framework manager - but as catch is both I guess you never can guess :)
Comment #177
alexpottThe most recent approach #162/#173 is opt in BC so we're inline with @catch's wishes.
Comment #178
effulgentsia commented#173 is missing the
PrimitiveDataNormalizer.phpfile that was in #162.I discussed this with @catch and @alexpott, and we agreed on removing this update function from this patch. And then creating a followup issue in which to discuss whether to add it or not. That way, if this patch lands into 8.3 (which is a big if, since that's only about 12 hours until the beta commit freeze), we have the rest of the beta/rc cycle to decide on what we want the default to be for existing sites in production.
Comment #179
tedbow@Jo Fitzgerald thanks for re-rolling. I think all the errors were around \Drupal\serialization\EventSubscriber\BcConfigSubscriber not existing
Re-rolled #162 again. So from reading #162 to onwards I think should be good for RTBC if the tests pass
Comment #180
effulgentsia commentedI diffed the patch in #179 with the one in #162 to make sure all it contains is a reroll plus the removal of
serialization_update_8300(). Since that's the extent of the change, back to RTBC.Comment #181
effulgentsia commentedPer #178, we should remove this. Except I don't know if we should remove the entire function, or just make it set the value to FALSE instead. What have we done elsewhere? Do we always need an update function when introducing a new config key, even if we want to set it to the default?
Since in this issue, the update function, if it exists, would only set the new config value to its default, we shouldn't reference it from these places. I think it's fine to just remove these lines, and if we need more clarifying comments, we could punt that to a followup.
With #1, this would no longer be true. Both new sites and existing sites would get the same default. So this comment needs an update. Again, a simple removal of incorrect information would be fine, and non-trivial comment additions could be punted to a followup.
This is a confusing name. Because if false, we run the normalizer. And if true, we don't. If we want
falseto be the value for getting the new behavior andtrueto be the value for getting the bc behavior, then perhaps rename tobc_primitives_as_stringsor similar?That label is not a grammatically correct phrase. Maybe something like
'Whether to retain pre Drupal 8.3 behavior of serializing all primitive items as strings.'?Comment #182
tedbow@effulgentsia good points
1. I am removing this. I don't see why we need an update function because when reading the config we would get the default anyways.
2. removed
3 removed. Left the @see to this issue
4. used "bc_primitives_as_strings" that seems good.
5. Replaced with suggestion.
Comment #183
xjmSo I asked the other committers earlier but guess it did not get posted back here yet; shouldn't there be an upgrade path regardless of whether it's setting a TRUE or a FALSE? It's still an addition to the schema and so leads to diffs if it's saved explicitly (even as FALSE) any time after a site upgrades to whatever release contains it. If we provide an upgrade path, config changes once as of the release; if we don't, it can change randomly and add stuff confusingly in the future.
We discussed possibly announcing this on g.d.o/core when we do make the change, and adding it to the release notes of whatever release contains it. Given that the bugfix could break client apps that don't even know or care their data is coming from a D8 site, I'd like us to be pretty confident in our approach to handling the BC and upgrade path. For me trying to sort it in order to backport with less than 24h to the commit freeze for the beta makes it difficult to be confident in the solution. So I would prefer to do this for 8.4.x only. It's a DX frustration, but it's also the current behavior, and it can be worked around.
Comment #184
xjmAlso in general I think it is a bad idea to defer upgrade paths (or even decisions about them) to followups except in emergency; it has always bit us in the past when we do and it makes the release less shippable which is a no-no for the Drupal 8 release schedule.
Comment #185
xjmIn general, we do not add @see to the issue that introduced a change, only to followup issues not yet done if there is need for an @todo. If people want to see which issue introduced something for background, they can use git blame. But for someone just reading the schema for the documentation, this issue is totally overwhelming and will not give them the information they need. They will open the page because the docs tell them to, and then be at a loss as to what they're supposed to do, more confused than before.
If the existing paragraph is insufficient to explain the setting, then we should add more documentation to the comment to explain anything else they need to know.
Comment #186
xjm"Casted" is not a contemporary English word, at least not American English. The past participle is "cast".
...It looks like this misspelling exists in our typed data API already. So I guess that is out of scope.
However, maybe we can at least make the docs a bit better. A "cast[ed] value" is ambiguous, it could mean cast to string, or cast to the logical type, or anything. The docs for PrimitiveInterface::getCastedValue() say "Gets the primitive data value casted to the correct PHP type."
So I think the words "to the correct type" explain the purpose a lot better.
This is really confusing to read. Its value is its value, and casting can change its value. What is actually happening is that the item is being cast to the canonical data type for the data.
Comment #187
catchThere should be an upgrade path to set the config value explicitly, to avoid diffs with config import/export just due to updating core. It's a minor reason for an upgrade path, but still a good one.
@xjm the follow-up isn't about deferring the upgrade path, it's about potentially revisiting the decision on the default for the bc layer if the current approach here turns out to break a lot of real clients. There are arguments for and against each direction.
We have no idea how many clients are written to work with multiple different Drupal sites vs. specifically for one site and the extent to which the person running the client has control over the site in the latter case. The impact of either decision could be different for each of those cases.
One issue with leaving this until 8.4.x is that's an extra six months for people to write clients based on the current behaviour that could break when this is committed. i.e. the earlier this is fixed, the less it will break. It does give a longer window to find out how much it breaks before it gets into a patch release though.
Yet another option is commit it early, but revert it from 8.3.x during beta if we run into unexpected (or bigger than expected) issues.
Comment #188
alexpottDiscussed this a bit more with @catch this morning. One of the things that makes this issue extremely tricky is that the longer we leave it but want to change it the worse the BC issues will get.
It feels like there are two options with the least worst outcomes:
EITHER: get this in and default to typed values. And suffer the BC consequences - and at least there is a workaround for any site affected.
OR: postpone this issue on REST API versioning - need to search for an existing issue. REST API versioning is likely to be extremely tricky to implement.
It's up to the D8 release managers - @catch, @cilefen and @xjm to make the call here - I don't envy the choice.
Patch attached puts the update function back but changes the default to not use the BC layer and returns a message so users are at least informed. It's important that the change record contains instructions on how to enable the BC mode since you can't do that via the UI and fixes @xjm's comments #186 and #185
Comment #189
wim leersYes.
Nobody in the world has solved API versioning! Postponing this on that being solved is pretty much equivalent with postponing forever.
I very much like the approach in #188.
Comment #190
catchI'd lean towards getting this in now. If there turns out to be fallout we have a chance to revert it (or reverse the bc layer decision) before 8.3.0 still.
I don't really like waiting until 8.4.x or starting with the bc layer turned off, since that's likely going to end up with more fallout at a later date.
Comment #191
wim leers#190++. We can still revert before 8.3.0.
I'm being bold: RTBC'ing again.
Comment #192
xjmHow about a hook_requirements() warning based on the value of the BC flag?
Comment #193
xjmAt first I saw red in tests and saw red because I couldn't find the replacement, but this is deleting test setup code, presumably because we don't need it anymore with this fix. So that's cool.
Comment #194
xjmAlso maybe the flag being TRUE should always go on the status report regardless, to warn people that their API is different from the current expectation.
Comment #195
xjmNit: pre-Drupal 8.3.
Does this have its own tests, and should we have a followup to deprecate it for 9.x at some point?
Comment #196
xjmNot true anymore.
Comment #197
xjmAlso the CR is still lying and saying it is BC by default; if we are going to commit this we must correct that.
NW for that and then NR for:
hook_requirements()for runtime that if the state flag is set you see the same message as from thehook_update_N()on your status report.We agreed it should not be a warning but I think at least something in
hook_requirements()is the best way to inform people.Also, if we don't already, a followup to discuss the REST API versioning question and a followup to explore whether to let clients request which version of data they prefer.
Comment #198
xjmComment #199
alexpottI expected to find a big issue discussing REST API versioning - only found #2813311: API versioning: decide & document a backwards-compatibility policy for REST resources so far. I related that one to this.
Comment #200
xjmUltranit: Double blank lines.
Comment #201
xjmSo @Wim Leers says the double blank lines are intentional. I disagree but since it's not a tested standard yet it can be overlooked. #197 is what needs to be addressed here.
Comment #202
alexpottI've added the hook_requirements to the Rest module since that is how the serialization change is exposed. I couldn't come up with test that made sense if just the serialization module was installed.
Attaching screenshots as test evidence.
Comment #203
damiankloip commentedThis looks good, I was just about to upload a patch with some similar changes too - one question being "Why doesn't this move to REST module", as well as, 'Why shouldn't we just check the config value directly rather than storing an additional value in state'.
The only remark I have is that we are still only assuming that serialization is only used for REST module. I guess in core it is, so this is not a big deal.
Comment #204
xjmThanks! Two thoughts here:
Comment #205
xjmOh, also."BC layer" is kind of an insider label. Should it be called "backwards-compatibility mode"?
And I think we can remove the word "types" from the label. Fewer words that way so it is easier to understand.
I'll try to think of text improvements that might make the text shorter and clearer for site developers.
Comment #206
xjmAlso the CR is still not updated, so the link in the hook doesn't actually tell them the right thing or what exactly to do.
Comment #207
wim leersE.g. the JSON API contrib module is also affected by this. So I agree that we should move the
hook_requirements()implementation out of therestmodule, into theserializationmodule. However, that makes it a lot more abstract. Plus, the JSON API module has no stable releases yet.So I think from a usability POV, it's best to keep the warning about the REST module. So, +1, and disagreeing with myself one sentence prior :P
#204
Comment #208
xjm@damiankloip, are there any other use cases for the serialization module that we should take into consideration for this issue and the communication about it?
@Wim Leers:
I feel like you are answering a question I didn't ask... there are improvements possible to the text, both ones I suggested and others.
Comment #209
xjmThe CR is still not accurate. It says:
That's not true, in a dangerous way. This patch does not enable the BC layer by default. People need to turn it on themselves if they want it, which also means we need to tell them how, e.g. with a short list that has an example drush command, a link to the configuration API, and a link to settings override docs. They also need to understand the consequences of making that choice, consequences about maintainability that were bad enough that we are not providing the BC layer by default.
Comment #210
xjmIf it's going to be removed for Drupal 9, we should have a followup issue to mark the BC layer deprecated in 8.4.x or whatever. Currently, it is not deprecated in the patch.
Comment #211
damiankloip commentedYes, I would say we would certainly want to remove the BC layer in D9 and default to the proper data types that this achieves.
@xjm: Sure, a module that springs to mind that's a good example is default content (currently has indirect dependency through hal).
Comment #212
xjmAh that's a great example. Will default content need any changes or upgrade path for data that is now properly typed?
Comment #213
dawehnerI doubt any JS/PHP client would have to change. Noone is really strict about types in that world. I think its more about JAVA and maybe stuff like elm, poor amitaibu ;) which could potential requite to change.
For me its kinda easy: Don't touch existing sites as much as possible, ship with the better behaviour on new sites. For me breaking site is just unnecessary. If someone cares about it, they are able to change the setting and be done with it.
Comment #214
xjmHm that's not what the current patch does though. At @catch's request the BC layer is off by default, even for updated sites. But hopefully most apps will not break.
Thanks @dawehner.
Comment #215
wim leersIndeed, @catch proposed we don't do enable the BC layer at all, and the patch was modified to do exactly that, but the CR was not updated.EDIT: rewritten for clarity:
CR now updated: https://www.drupal.org/node/2837696/revisions/view/10343125/10343658.
Comment #216
cilefen commentedI agree with this.
Comment #217
damiankloip commentedThat's what our patch was originally doing, up to #153.
Comment #218
wim leersSo:
hook_requirements(), to make it as easy/obvious as possible for existing sites to opt in to this if they're affected -> all sites get the new behavior by defaultFinally, it must be said that while this is technically a BC break, for many clients this is not going to make any difference.
@alexpott deferred to @catch. But now it seems still not everybody is on board with @catch's proposal
How do we proceed from here?
Comment #219
andrewbelcher commentedMy two cents as someone making use of REST in D8, I find #218.1 quite unnerving. I have one D8 site consuming another and have had issues with data types, so simultaneously want this fix, but know I will need to update the client site when it changes.
Not sure if this creates more hassle than its worth, but what about something slightly in the middle. We add to the BC layer the ability for clients to request the old format with a GET parameter or similar. 8.3 ships with #218.1, but hook_requirement() and hook_update_N() messages to say that in 8.4 it will serve the new format by default.
Clients can still request the old format, but have 6 months notice of it. Server admins can opt out of the change by setting a variable prior to updating to 8.4?
Even if the client requesting different formats is too much, ensuring there is a variable well documented in the CR/release notes that can prevent #218.1 prior to update may mitigate some danger?
Comment #220
wim leers@andrewbelcher Thanks for chiming in — it's super super valuable to get feedback from people actually affected by this change!
First: I'm glad to hear you're confirming you want this fix, because you've had issues because of it.
Second: with all 3 approaches there is a clear variable you can modify to get the behavior you want! All three ship with the same BC layer! They all just have different values set:
bc_primitives_as_stringsconfiguration key on:truefalsefalsefalsetruetrueIn all of those cases, you're still free to modify the value of
bc_primitives_as_strings, because it's just configuration. We're just discussing what those default values should be!Which one would you prefer, and why?
Comment #221
andrewbelcher commentedIf I were developing something new, I would definitely want the improved behaviour. With my existing system, I would want to manage that transition myself.
Upgrading major version you would expect to have to deal with those kind of changes, I don't know if you would going up a minor version. Also, the window for going up a major version (if I understand correctly) would be longer as security fixes would still be available for the previous major (LTS), whereas that's not the case for minor releases.
So I guess that would probably put me on #218.1. I suppose my suggestion previously was to do somewhere between #218.1 and #218.2 by making the behaviour of existing sites change in the next minor release, which gives 6 months of visibility for systems to be able to mange it themselves. If I hadn't come across this issue/seen a CR, that would mean I'm not caught out unless I also don't look at my site when I update it.
I think the danger with #218.3 is that we end up with a lot of systems unable to make the move to 9, whereas if we change the behavior now, it will only be systems built in the last year or so.
Comment #222
wim leersInteresting, so if I update the table to show that option, it looks like this:
bc_primitives_as_stringsconfiguration key on:truefalsetruefalsefalsefalsefalsetruetrueThat could also work. Indeed a middle ground between #218.1 and #218.2.
Comment #223
tedbowI would be in favor of #221 if we couldn't get #218.1. But it seems like it would be important that in 8.4 there should warning on the status report page that says:
Also in the update to 8.5 there should be message for sites that are having this change implemented for them to let them know.
I think even friendlier to developers would be if there could be some way after 8.5(and beyond?) to keep a message about the change on the status report page.
I am thinking of the situation where a developer does nothing in the upgrade to 8.4. Everything works fine in their REST integrations. Then they update to 8.5 they see the message in the upgrade but don't fully realize how it might affect their REST integrations. Then a week a so later they start to get weird behaviour in 1 of their clients(maybe not all because they might handle casting better). Are they going to make the connection to the message they saw during the update but is now gone?
I think an important question is are we deciding on a general policy for this kind of change to REST responses/serialization? It would beneficial for developers to know that this is how these types of changes will happen in the future. It also would allow these types of issues to be resolved faster in the future.
For instance probably if we had already had an issue like this and had a policy to follow then this change would have made it into 8.3.
Comment #224
skyredwang8.3.x hasn't shipped yet, and we have to wait until 8.4 to fix this bug? No production in the world is running on 8.3.x yet, and what about 8.2.x? I thought the idea of having a BC layer is to let someone (who needs to fix this bug) to fix this bug in the production today.
Comment #225
wim leers#223:
I'd like that. But it's likely that there will be examples of encoding and normalization changes that are less disruptive, e.g. pure additions to GET responses, which are not required to be sent on POST/PATCH requests. But I think we'll extract a basic policy from this issue, for cases like this one.
Indeed!
Indeed…
#224: It might still ship in 8.3.x. It will depend on when we reach consensus ;) Also, even the act of adding a BC layer can be disruptive. We want to ensure that people can brainlessly update to patch versions. Only minor versions can be slightly disruptive. Patch releases must be zero disruptive.
Also, the point of a beta is that we now only add bugfixes to 8.3.0-beta.
Comment #226
eelkeblokDoesn't this mean the point from #219, to allow clients to somehow control this (GET-parameter?), needs to be entertained more? The concern is, after all, that some clients might run into trouble because of this change, so it seems not particular logical (if maybe easier to implement) to make this a central configuration on the server side.
Comment #227
hampercm commentedTo me, the best option seems to be #221, with a message on the status report page indicating that the BC support is temporary. Existing sites won't simply break upon update to the next minor version, and will have a hopefully clear notice regarding the changes that will need to be made by 6 months time. BC is important, but holding BC behavior over until 9.0.0 seems like it could lead to problems later. Plenty of methods and classes have been deprecated and will be removed before 9.0.0; I think this type of BC holdover should be treated similarly, encouraging developers to move forward.
New sites should use the new behavior, so developers creating a site don't mistakenly build something on top of the old behavior, and need to fix it later. Hopefully developers would see the status page notice even if #218.3 was chosen, but I can certainly imagine some people not realizing the ramifications of that message, and finding themselves in a very discouraging bind when they eventually do.
It seems vital to get this into 8.3.0, as leaving it another 6 months to be fixed will mean many more applications will be built using the old behavior, and need to be changed. Unfortunately no one option can possibly make everyone happy--each of us is in a different situation and has our own priorities and preferences--so let's pick a less disruptive option that still makes timely progress.
Comment #228
xjmThe deadline for disruptive changes in 8.3.x has already passed. The release and framework managers have agreed to consider a policy exception for this issue in light of the BC and maintainability concerns, but it is discretionary and will be a release management decision. The goal should still to be to come up with the best solution to the BC problem.
The problem with the proposal in #221 (as represented in #222 at least) is the classic Configuration update problem: We have no way of knowing whether the site owner got the value from the default at the time of their 8.4.x update, or whether they set it on purpose because they want their client apps to love strings.
So for me, the question is still about whether the update hook sets the BC layer to on or off. @catch strongly prefers it be off. I think @alexpott and I are both ambivalent; there are strong cases for and against each. Many others have raised that it is actually a disruptive break for the BC layer to be off by default, e.g. @dawehner, @cilefen, @andrewbelcher, @Grayside.
I also agree with #226. I keep thinking that the application should treat this the way we would treat a required BC break for a PHP API: provide a fallback to the old behavior, and let the caller choose if it needs to. If this were a method that needed a signature change, we would either add a new optional parameter, or add a new method and deprecate the old one as a wrapper. Can we provide similar sensible DX and BC for a third-party app somehow for this change?
Also, let's go forward with the
hook_requirements()messaging regardless; the final version may need to be updated based on our final decision but it does help site owners' awareness at least.Comment #229
andrewbelcher commentedFor the config issues, perhaps a 3 way value would work (on, off or default/temporary)? Or two config keys?
For clients requesting the BC behaviour, I'd thought either a parameter (eg
_legacy=1) or possibly encapsulating it in the format (eg_format=json_string)?One other thought that may be worth considering - what about requiring a value to be explicitly set before the update? Could throw an update exception if it's not set. Would need a really clear helpful message and an obvious way to set it through the UI, but that would avoid unexpected changes whilst maintaining high visibility that the the change is deemed important?
Comment #230
xjmI've had a big long think about this issue and the recent feedback. How about we flip this all around?
Comment #231
andrewbelcher commented#230.2: What is the advantage of new installs using the old behaviour only to be changed in 6 months time? That feels like more of a maintenance nightmare - the BC layer is still going to exist, so any maintenance/support issues are still going to need clarity of whether the BC layer is on/off, regardless of the default.
#230.4: I'm not sure future proofing is really the way an app developer will think. I would expect most app developers would use the default unless there is a problem. App developers may not even know that it's a Drupal 8 API...
Comment #232
wim leersThis is the huge problem in this proposal.
That requires us to solve REST API versioning, which nobody in the world has ever solved.
Plus, it requires some extra flag to be sent by the client: a request header or a URL query argument. Which is making it Drupal-specific. Which is bad: we don't want to add Drupalisms. As @andrewbelcher said in #231:
Comment #233
catchRight at the moment I think we should commit this without the bc layer because we have zero reports of this breaking any clients. I'm hoping the people who posted the patch to prevent workarounds in their REST clients tested it with them.
If we get such a report, we still have some time to commit the bc layer (which is already written) as a follow-up. The REST API is not PHP code and we don't have the same bc promise - we'd also potentially fix a int-as-string return from a PHP method without a bc layer if it was seen as a clear bug. The most likely fallout from this seems to me to be that either clients do unnecessary casting, or they continue to work.
Comment #234
wim leersI like this.
This times a thousand!
YES!
In this particular case, disruption really is very low, so I find myself agreeing with #233, to my own surprise.
Comment #235
dawehner@alexpott, @damiankloip and myself discussed that. We kinda believe that we actually fix way more problems (problems which are maybe not known yet by people) by pushing this change out, than what we prevent by having a BC layer in the first place.
Comment #236
andrewbelcher commentedThe nervousness for me is really around security releases. Say a significant security bug is found a week or two after 8.3 is released, it gets fixed in 8.3.1. I have an 8.2 site and I now have to decide whether to ignore the security release or potentially break things for clients of my service.
In my case, I'm also the client of my service, so it essentially means I have to rush through testing and fixing any issues.
The mitigation is that I think most of my issues with types was related to tests, not live functionality, which I wouldn't be surprised if that is the norm.
Comment #237
xjm@catch:
Well, @andrewbelcher said in #219 that they would have to make changes in response to it.
@Wim Leers:
A complete solution would involve API versioning, but sometimes we need one-off workaround rather than complete solutions. A one-off solution would be a single one-off metadata flag, something like #229.
@dawehner:
So just to confirm, you have changed your recommendation from #213?
Comment #238
xjmOh and re: #233 / #234:
That really seems like a stretch to me. I would not ever imagine that Drupal's public REST API was not covered by Drupal's public API BC policy.
I'm not necessarily against backporting this (edit: actually I would also really prefer it). I just am concerned that the one contributor who actually has apps has said it is a disruption, but the discussion keeps downplaying that and prioritizing maintenance burden over managing such disruptions to users. See #236.
Comment #239
skyredwangFrom app developer perspective, I have to do one of two things below in the world without API versioning:
1. Push out a client side fix to support both Integer and String today/now even when the server end is not updated yet. This fix prepares the app in the transition period. Then, in the future, remove the String handling.
2. Let the app automatically detect the server API changes, if there is any, force self to update to a new app version before opens up.
The work won't change, if the bug fix releases in 8.3.x or 8.4.x. Given that this bug and discussion has been around for a while, I am already prepared to deal with it, and hope to fix it sooner than later.
On the other hand, from Drupal 8 web services SDK/library type of project perspective, fixing this primitive problem now is always better than 6 months later.
Comment #240
dawehnerYes, actually I think I totally flipped my opinion.
More as a general statement: If we want to be API first, our public REST API has to treated conceptually similar than our PHP code.
Comment #241
Grayside commented#56 points to a problem introduced by this patch when using one Drupal 8 site as a migration source for another. My solution was to make sure this change did not apply to the format I needed as the migration source. I suppose I could have modified the migration to cast all values to strings.
I see REST API versioning in Drupal as de facto hypermedia-style approach via custom serialization formats, so it makes sense to me that the useful level for a B/C concern is on the format level as just another choice. Lacking that, I'd prefer break B/C but have it available opt-in.
Comment #242
damiankloip commentedYes, fair point. I think at this point (and after discussing with Daniel and Alex yesterday) my preference would be to ship this with the BC code but default this to being DISABLED. So we have the 'BC break' as default, but people can easily opt-in to the string behaviour. That would also appease @andrewbelcher and his comment in #236 too I think.
Otherwise, can we just get on with this? The comments are getting way out of hand and it is not being picked apart and theorised far too much.
Comment #243
xjmSorry, no. Post-8.0.0, and especially in a beta phase, we can't just stop discussing and commit a BC break because we are tired of discussing it.
@skyredwang:
That's helpful perspective. So I think we should announce this upcoming break now, regardless of what's committed to which core branch or with what sort of BC implementation. The question is where to communicate it that app devs will see. g.d.o/core, for a start. Any other ideas?
@Grayside:
Thanks. For this, do you mean an opt-in at the site level (as in the current patch) or an opt-in for the client (something like #229, that @Wim Leers and others have concerns about adding)?
Comment #244
andrewbelcher commentedJust to confirm - I am OK with a BC break, as long as enabling BC is trivial. As a more general statement, I think it should be as obviously stated as possible (such as the suggestion of messages from the update/on the status page). A trivial, well documented fix to a visible problem I think is a good balance of the conflicting aims.
I think server side BC layer is essential, client side (as an addition) would make things easier for app developers to switch over if they already have a client base. I would suggest do a server side fix now and follow up with a client side option to avoid getting stuck. It may even be fine to only exist in contrib world - that avoids having to support it indefinitely.
Comment #245
xjm@andrewbelcher Have you seen issues specifically with this patch applied to the server site? Or just workarounds needed for the existing bug in HEAD with 8.2.x?
Comment #246
wim leersRegarding API versioning, there is so very much to read. I read all of the links below. And they all contradict each other in many ways. They each have different recommendations. Some of the recommendations won't work because they assume
Acceptheader-based negotiation, which Drupal 8 chose not to support for good reasons: #2481453: Implement query parameter based content negotiation as alternative to extensions.Acceptrequest header, which e.g. Wikipedia does: https://www.mediawiki.org/wiki/API_versioningVersionrequest header, irrelevant to us#237 (@xjm):
#229 mentioned two options for this:
?_legacy=1or custom formats/format flavors, such as?_format=json_string. Neither are acceptable.?_legacy=1is only acceptable if it will be the only such query string ever. And even then it really hurts the perception of Drupal's REST API. The second is problematic, because A) it'll open the door to dozens of variations, B) what if you want both the changes made by this issue, and those of another? How are you going to express that?What I could live with, is something like
?version=8.2and?version=8.3. If you build your site against Drupal 8.2, this would ensure it continues to work forever (or perhaps for 2 releases, at which point we'd deprecate it — we can decide that later, on a case-per-case basis).That still would not allow you to enable/disable changes one-by-one, but it'd allow you to lock in to a specific Drupal 8 release. That's also in line with some of the reading I did above.
However… that still won't help with changes in normalizers in contrib modules. You'd want to take care of those too.
And even worse, it doesn't reflect changes in the structure (fields added/changed) of an entity type! So it's pretty misleading.
Which is why I didn't want to open this can of worms in this issue.
#238 (@xjm):
But we also don't offer BC for the responses that we send, nor for the HTML they contain. As long as things remain "reasonably the same", it's fine. We don't completely change the markup: we keep the overall structure, the
idandclassattributes, but the specifics can change. How is this not comparable to that?#213 + #235 + #240 (@dawehner): I don't know how to interpret what you're saying anymore. In #213, you say you doubt any client will have to change. In #235, I think you say you want to ship the bugfix without the BC layer. And in #240, I think you're saying that the same BC concerns must be applied to the REST API as we apply to our PHP code.
#242 (@damiankloip): so you're voting for option #218.2 as visualized in the table in #222, correct?
#244 (@andrewbelcher):
+1
I think this is kind of what @damiankloip was getting at in his last sentence in #242. We can keep thinking of more and more ways to deal with this. We can even postpone this on some advanced solution that none of us have thought of yet. But is it necessary? Isn't a simple on/off switch in config sufficient? At least for now?
This is frankly one of the main reasons why the REST module (and Serialization and HAL modules) should have shipped as experimental. This is one of the key things to solve: API versioning. But because it shipped in the state that it did, we've now already spent over a year of bugfixing, meticulously taking into account the consequences of each and every change. If we could just have fixed bugs in the mean time, we would have had the mental and time capacity to work on API versioning.
Comment #247
Grayside commentedI opened #2813311: API versioning: decide & document a backwards-compatibility policy for REST resources as a prelude to an API versioning discussion. While API versioning is a difficult and "unsolved" beast, that's because there are multiple best practices and you need to pick one. As far as Accept header concerns, Drupal made the decision to use the _format querystring parameter in lieu of the Accept header, owning that to run with Accept-header based versioning brokered by querystring is the logical next step short of typical "resource (URL)"-driven versioning. Agreed, out of scope :)
I do not think making this a client-requestable option is a good idea for lack of a coherent plan on versioning. Amongst other things, this becomes no longer a B/C item, but a feature of the Drupal REST system. "Use strings instead of specific types by default?" would become an option I'd expect to find in the REST UI module. By making it a server-side decision, it gives the Drupal site owner the responsibility of choosing and supporting it. By making it request-selectable the site owner is responsible for B/C to any API clients they may be supporting.
Without a sound API versioning strategy, we should minimize the complexity of API surface we're pushing downstream.
Comment #248
damiankloip commented@WimLeers, yes, I am voting for shipping with the BC layer in place, but always defaulting the value to FALSE (if the module maintainer opinion actually means anything). Your interpretation about my previous comment is also correct, we already know that there are (potentially) a minority of cases that could break with this change, but likely if they are relying on this behaviour we will be doing them a favour anyway, as it will fix their client code - relying on strings probably breeds way more bugs in the long run.
@xjm, it's not that I'm tired of this discussion (although, yes, it is getting pretty repetitive), it is that we are just going over the same points again and again. We are not going to solve API versioning here, so let's not keep discussing it. Catch was for making this change now, and we had general consensus. If there are concerns about needing easy BC opt-in, then we just ship with the BC layer that we already have in this patch (as has been the case for a long time now). I'm confused about any more benefits we get from discussing these points further here. We either do this, or we don't. If we don't then it sets a precedent for any other issue that tries to improve serialization, hal, or REST module. Which basically means we can't do anything else for the rest of the 8.x release cycle? Again, REST is not the only thing affected by this change. Lots of other usage can benefit from this improvement.
I am tempted to just fork the core serialization module to contrib and make all the changes there (of which there are numerous). Other modules can then depend on that if they wish. Core could then just have the 'safe' changes.
Comment #249
wim leersExactly. Hence my arguing against "one-off" in #246. If we really want this to use API versioning, we should block this on an API versioning strategy.
This. This times a thousand.
This is why I don't think this issue should be blocked on API versioning. This is a pure common sense change. Breakage will be minimal. Fixing any broken client would be trivial. And if you don't want to fix a broken client, there's still the simple switch on the server side.
So, I'm also +1 for this approach. It's minimally disruptive and easily recoverable.
I've clarified the title of #2813311: API versioning: decide & document a backwards-compatibility policy for REST resources and bumped it to major. This is the issue that @xjm requested in #197 as a follow-up.
So, the approach of shipping with BC but having it disabled by default (i.e. option #218.2 in the table in #222)… is exactly what the latest patch, in #201 is doing. And in fact, that's what @catch and @alexpott already agreed to do back in #188!
I RTBC'd #188 in #191. #188 was marked NW in #197 because A) CR needed to be updated, B) message in
hook_requirements(). B was done in #201. A was done in #215.We seem to have arrived back at the same conclusion. Back to RTBC.
Comment #250
xjmI would still like to see an answer to #245. If the answer is "Not with this patch, just in general" then for me that sounds like all the decoupled app developers on the issue (@andrewbelcher, @Grayside, @skyredwang) do prefer the hard BC break but with server-side BC layer allowed. @Grayside's feedback about the client-side one-off is also helpful. With @dawehner also changing his position there, this sounds like reluctant consensus at least. ;)
I also think the info hook needs some work and we need to do a g.d.o/core post before commit. I don't have the energy to play status wars anymore though, so assigning to myself.
Comment #251
wim leersIf anybody disagrees with option #218.2 in the table in #222, please mark this NR again.
I marked it RTBC because AFAICT everybody is fine with this approach.
Comment #252
xjmActually, we do offer really strict BC for markup in D8. Classy and Stable. That's OT, but also an example of why BC matters. Opening #2813311: API versioning: decide & document a backwards-compatibility policy for REST resources is a good step although doesn't help us as much right now. I do think the clean BC break is probably best for maintainability (as mentioned) and that issue will help us with future similar problems.
Comment #253
wim leersYes, for the templates. Which define the page structure.
But the contents of the templates are subject to change. Render arrays are specifically not frozen. They are what define what goes in the slots in the templates.
This issue is similar to that: the structure remains the same, what goes in the slots changes slightly.
Comment #254
damiankloip commentedAlso, we can benefit a lot from these changes in the relaxed and replication modules.
What are we waiting on now?
Just this? Does info hook == help hook?
Comment #255
xjmAgain, I assigned to myself to take care of it. Thanks!
Comment #256
Grayside commentedHere is the code I used to override this patch for my use of the JSON serializer format while allowing the other formats to behave as expected. Just in case someone comes along with the same difficulty on an all-or-nothing bc option.
In example.services.yml
example/src/Normalizer/UncastedPrimitiveDataNormalize.php
Comment #257
xjm@Grayside, that is really helpful, thanks! I wonder if we could generalize that and put it as an example workaround in the change record? People will be more likely to find it that way than in this issue thread. Adding an example workaround was actually one of the reasons I assigned the issue to myself but you have a much better handle on it than I do.
Comment #259
mpdonadioThink #2808335: Changed time not updated when only non-translatable fields are changed on a translated entity caused this to need a re-roll. Didn't look at how complex it would be, but a simple rebase has merge conflicts.
Comment #260
xjmOkay, I updated the change record as I was requesting in #197 and #209 since this was not done and I was failing to communicate what I thought was needed. The changes I've made there need review from someone well-versed in this. Please review:
https://www.drupal.org/node/2837696/revisions/view/10343658/10358830
It includes @Grayside's example as I requested above, also.
Next I'm going to work on the in-code documentation and a g.d.o/core announcement.
Unassigning from myself to make it clear I'm not providing the needed reroll though. Please don't commit this until I've had a chance to sign off though as some of my feedback (admittedly nonspecific and lost in a lot of text) is still not addressed.
Comment #261
xjmI'm sorry, but I really disagree that this is remotely similar. A serialized response is meant to be manipulated by a third-party application that may be in an entirely different language. If that third-party application tries to access integers or boolean values as strings, it can spew notices or break outright. It's not the same as a render array being altered by a Drupal module and it is a BC break. It's a good BC break, but let's please not downplay the fact that it is.
Comment #262
tedbowRe-rolled
Comment #263
xjmNR for the CR changes in #260. Thanks!
Comment #264
tedbow@xjm I update the change record description before @Grayside's code example. It wasn't clear that what the code was doing was using strings for just 1 format. I changed the focus from different behavior per application because that made it seems like even if 2 applications were requesting the same format one could have strings and the other cast values. That is not what the code did.
Here are my changes: https://www.drupal.org/node/2837696/revisions/view/10358830/10359036
Here are both @xjm and my changes together: https://www.drupal.org/node/2837696/revisions/view/10343658/10359036
Comment #265
xjmThanks @tedbow. I had intended the first paragraph to be about a different scenario -- what app could developers could do on their own. Maybe they ran together too much.
What I want to document in the change record still is: If I have an app that consumes serialized output from Drupal, but do not have control over the server site, what do I have to do in response to this change? Since we've sort of agreed on not providing a way for the client to request one format or the other itself.
String-casting the input was a simple workaround that was suggested until the app is updated as necessary. I mentioned it only at a conceptual level since we have no idea what language an app might be in or how it approaches typing.
Any suggestions on how we can add that to the list of possible workarounds? That's the one piece of information I really think we need to communicate. Once that's in place in the CR and accurate, I'll post on g.d.o/core referencing it.
Thanks!
Comment #266
xjmThe committers have all agreed to backport this to 8.3.x before RC once the needed docs stuff is taken care of. So, explicitly moving back to 8.3.x and tagging for visibility.
Comment #267
xjmPer @Wim Leers, @damiankloip is working on a small improvement/reroll, so assigning to him. Thanks!
Comment #268
damiankloip commentedAh, yes. So I just had a thought, that #2768651: Let TimestampItem (de)normalize to/from RFC3339 timestamps, not UNIX timestamps, for better DX made some changes to this BC stuff that makes it a bit more flexible, and allows other modules to use the functionality, rather than re-implement their own. This issue mentioned is a good example, with something like hal utilising this functionality. It replaces the hard coding of the BC settings in the compiler pass and relies on the services being tagged instead.
This patch extracts those changes out, with a couple of small tweaks.
Comment #269
wim leers#268 merely refactored the code that determines whether new normalizers are enabled or not for BC reasons. Rather than it being code, it's now declarative: it's declared in the service's attributes.
The CR says this:
That's now super super clear when you look at
serialization.services.yml:I also reviewed the CR again. No remarks, other than some formatting nitpicks which I fixed.
Comment #270
xjmThanks @Wim Leers and @damiankloip. I'll give this a final look today.
Comment #271
xjmPosted #2856414: "Casted" is not a (contemporary, contextually relevant) word for my nit about using a misspelled non-word in the API because it bugged me again when I read the patch again but is so not worth anything here. :)
Comment #272
xjmIn the course of trying to improve the info hook I remembered all this:
Did we ever come to a resolution on this? I don't see one. I'm fine with a followup to expose it with Serialization instead of (only) REST if the strings are the same, but generally we aren't supposed to make string changes during RC. (Adding the same string in different places would be a fine RC change by me but the same text wouldn't necessarily make sense in both places.) OTOH it might be worth an exception to add a string for that.
Comment #273
xjmAlright, here's my proposed improved text:
Header
Text: Default behavior (BC layer disabled)
Text: BC layer enabled
I think this is better because it informs the user of which choice is the recommended default, to reduce the future maintenance burden of sites choosing string output for no reason.
Edit: Realized the last text was long and I can make it more succinct. Edit 2: And the first one as well.
Comment #274
xjmPatch needs a reroll atm; couple merge conflicts from recent Serialization commits. I'm comfortable with the patch, CR, etc. with something like the improvements I propose in #272.
Thanks everyone!
Comment #275
dawehnerHere is a reroll.
Comment #276
himanshu-dixit commentedComment #277
xjmThanks @dawehner.
So then I think we just need to address #273 and maybe #272 (though I won't block it on the latter) and this is ready! Today is the last day to backport this for the RC.
Comment #278
catchRe-rolled with xjm's suggested changes from #272 which look sensible to me.
Comment #279
xjmLooks good to me.
This I think we can change also per #273.
Comment #280
alexpottI think we should move this to the serialization module. We might decide to implement more warnings if REST is enabled. But moving it to serialization covers the bases better. As pointed out by others serialization is used by modules other than REST. I put it in REST originally because the message I had in mind made more sense there. The new message makes more sense in serialization.
Plus I don't think we need to capitalise
BooleanComment #281
xjmWe do actually; it's someone's name. This is documented for our text standards somewhere *handwave* and we've had core issues to correct it before.
Comment #282
catchComment #283
alexpott@xjm pointed out that I'm wrong on the capitalisation of boolean. (til) It should be Boolean
Comment #284
catchMoved too much of rest_requirements() and didn't include the header change.
Comment #285
catchHere's the header change, managed to skip it in the interdiff.
Comment #286
alexpottOut-of-scope change. Can be removed on commit.
The new serialization requirements look great.
Comment #287
xjm@alexpott I don't understand what the out-of-scope change is? We have to declare the function.
Comment #288
xjmAh this is what @alexpott meant; the extra newline is out of scope.
Comment #289
himanshu-dixit commentedHere is the new patch fixing what @alexpott mentioned.
Comment #292
xjmAlright, committed and pushed to 8.4.x and backported to 8.3.x as we agreed, and published the change record. Thanks so much everyone for going the extra mile on this to make sure we handled this in the most effective way possible.
Comment #293
dawehnerCongrats to everyone!
Comment #294
wim leersYAY!
This unblocked:
:)
Comment #295
wim leersFYI: I see that @catch created #2856594: Discuss whether to deprecate serialization string casting bc layer before 9.x. I think all of your thoughts are most welcome there.
Comment #297
himanshu-dixit commentedAttributing the contribution to GSoC.