Problem/Motivation
As an overall issue based on #2292707: GET on entity/taxonomy_vocabulary/{id} is not working it is weird we cannot POST/PATCH/DELETE for config entities.
This was a key reason for the JS Admin UI initiative being unable to proceed: the config it needed to modify was literally impossible to safely modify through APIs — the validation logic lived only in forms!
- Before #1928868: Typed config incorrectly implements Typed Data interfaces it was impossible for config entities to provide validation on the config level, so it was not possible to guarantee that the given input is valid.
- Before #3324150: Add validation constraints to config_entity.dependencies we had no test coverage to thoroughly test the validatability of config entities.
- Before #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support we had no mechanism to declare for a config schema type (and every config entity type corresponds 1:1 to a config schema type of
type: config_entity, for exampleNodeType→node.type.*) as "fully validatable".
Proposed resolution
- ✅
Allow config entities to opt into REST support by defining validation constraints for every node in the config schema tree for that config entity type (using the infrastructure that #1928868: Typed config incorrectly implements Typed Data interfaces and #1818574: Support config entities in typed data EntityAdapter added)Done in #3324150: Add validation constraints to config_entity.dependencies + #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support. - ✅
Leverage config validation to ensure the POSTed/PATCHed entities are validImplemented in this MR 🥳 - ✅
Implement constraints for theThis is obsolete thanks to #2869792: [meta] Add constraints to all config entity types already having marked multiple config entity types as fully validatable 👍config_testconfig entity type's entire config schema tree, and implement config validation, to prove that the approach works. - ✅
Open a Plan issue with dozens of child issues to add validation to all other config entity types: #2869792: [meta] Add constraints to all config entity types. - ✅
Expand the test coverage to test schema and constraint validationNOTE: Repeating all the validation test coverage from the
ConfigEntityValidationTestBasesubclasses is pointless. Still, a spot-check makes sense. For that reason,DateFormatTest::testPostValidationErrors()exists. Compare withDateFormatValidationTestand the JSON:APINodeTest::testPostNonExistingAuthor()to verify that this is indeed a consistent DX.NOTE: As soon as this lands, every additional config entity type that is marked fully validatable will have its JSON:API functional tests (guaranteed to exist thanks to
\Drupal\Tests\jsonapi\Kernel\TestCoverageTest) start failing (guaranteed thanks toResourceTestBase::setUp()skipping test methods for config entities if and only if the config entity type is NOT fully validatable). That means we will be able to guarantee that everything works consistently 👍 - ✅
→ follow-up: #3423459: [PP-4] JSON:API DELETE support for config entitiestestDeleteIndividual()akaDELETEsupport. Cleaning up deleted dependencies would open up a wormhole for now. - ✅
testRelated()+testRelationships(). → Follow-up for "relationships" and "related" created: #3423462: [PP-2] JSON:API relationship/related support for config entities - ✅
Add test coverage to verify that for fully validatable config entity types that 1) use plugins (such asEditor,FilterFormat,FieldStorageConfig, etc.), 2) those plugins have settings, 3) and creating/modifying such a config entity to use a plugin whose settings are NOT fully validatable, should result in either a 422 (like any other validation error), with a message such asThe value at property path foo.plugin_settings.baz cannot be validated. Please contact the developer of the "bar" foo plugin to make this validatable.
#3412361: Mark Editor config schema as fully validatable (Editor) landed, which made this possible.)
Remaining tasks
Needs further investigation: #2905594: Missing entity validation constraint: don't allow new entities when there is an existing one with the same ID by @dawehner in #181. This might be a bit of missing test coverage?- Added 429 response when a config entity already exist.
User interface changes
None.
API changes
- Addition:
\Drupal\Core\Entity\EntityConstraintViolationListnow accepts not justFieldableEntityInterfacebut alsoConfigEntityInterface - Addition:
\Drupal\Core\Config\TypedConfigManager::getOriginalMappingType()(although we could do without; it'd just result in duplication of logic) - Change:
ConfigEntityAdapter::validate()now returns an\Drupal\Core\Entity\EntityConstraintViolationListinstead of a\Symfony\Component\Validator\ConstraintViolationList, to allow config and content entities to be treated consistently. - Addition: (
@internal)\Drupal\jsonapi\Entity\EntityValidationTrait::validate()now also acceptsConfigEntityInterfaceand not justFieldableEntityInterface
⚠️ However, any contrib module that:
- provides a config entity type (common)
- subclasses
\Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase(uncommon) - has made its config entity type fully validatable (rare)
… will start seeing their tests failing due to their test not yet providing a valid POST or PATCH document for their config entity (these tests were skipped automatically).
Release notes snippet
JSON:API now supports POSTing and PATCHing config entities that are fully validatable. This enables a whole new world of decoupled Drupal applications.
| Comment | File | Size | Author |
|---|---|---|---|
| #284 | 2300677-nr-bot.txt | 117 bytes | needs-review-queue-bot |
| #283 | 2300677-jsonapi-config-entities-283-10.3.x.patch | 57.03 KB | wim leers |
Issue fork drupal-2300677
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #1
clemens.tolboomComment #2
berdirConfig entities have no validation and access API beside their UI's/Forms, so exposing over REST is tricky.
If we'd want that, it would be clearer if that would be a completely separate plugin and serialization, that would just directly use toArray().
Comment #3
clemens.tolboom@Berdir thanks for the quick response. But I don't understand. For me its 'just' an entity where rest manage permission. Why distinguish on Content versus Config? Patch seems valid to me :p
Patch skips field access for ConfigEntities checks but not entity view.
What do I miss?
Comment #5
tstoecklerYeah, I agree that access checking should be sufficient for config entities. We have been moving away from route-based access (i.e. using _permission directly) to entity-level access for config entities so that should work fine. And in terms of validation we can use the new
SchemaCheckTrait. It's not as powerful as content entity validation, but I think it too should be sufficient to validate that nothing completely bogus enters the system.Comment #6
berdirMaybe, but that still means that it would be a separate plugin for config entities and IMHO a feature request, not a bug. The only bug seems to be that you can configure rest.module to expose config entities right now an it's then choking on it...
Comment #7
clemens.tolboomI just checked service 7.x-3.x module which exposed 2 config `entities`
I myself ran into what menu item belong to ie 'main' which both are config entities :(
Use case: A headless Drupal with AngularJS menu controller.
From the list below I'm not sure we need all ConfigEntities available but at least menu, menu_link, tour are very useful.
@tstoeckler what code should be changed for CRUD through REST for ConfigEntities?
Comment #8
berdirWhat services supported and not is not really relevant, a feature in a contrib module doesn't mean that not having it in core is a regression :) Especially because 7.x did not differentiate between config and content entities, services.module did not integrate entities in a generic way and system is not an entity :)
If one config entity is supported then all are.
Comment #9
clemens.tolboomI was only trying to list and select 'valid' reasons to expose a ConfigEntity. In the end permissions guard what is exposed.
I hope for some pointers to fix this bug.
Comment #10
Grayside commentedConfiguration being what it is in Drupal, exposing it even behind permissions is a little like exposing custom module code if you've got the right permission. The delicate balance there seems pretty good for Contrib to explore, and maybe something gets into 8.1.
In practice, I expect it's not the full config entity you need, but some externally useful information (e.g., the name of a vocabulary), associated with the content it configures. Full CRUD support for Config would make this an API for building your Drupal site, and that seems like a glaring potential for massive security problems.
Comment #11
moshe weitzman commentedIn an effort to constrain scope. the REST team decided to focus on content entities and not config entities. I'd be happy if someone took up this important feature. Retitling and recategorizing accordingly.
Comment #12
clemens.tolboom@moshe weitzman it's good to focus on content.
I hope for some more pointers on how to solve this issue.
Comment #13
wim leersI'm no REST expert, but from what I can tell, the main problem is validation when modifying config entities.
What if core would only support reading config entities? That'd satisfy the majority and still defer the most complex parts to contrib.
Comment #14
clemens.tolboomWhen adding GET on ConfigEntityType we should block the other methods for sure otherwise we get core bug reports when people enable permissions on those methods.
Comment #15
clemens.tolboomXREF #1897612-16: Add YAML support to serialization module
Comment #16
clemens.tolboomTrying building a web app we are blocked for not GETting Vocabulary and Menu and even just the site name.
What options do we have to expose these resources and what more resources are valid candidates to expose for GET?
Comment #17
-enzo- commentedDuring the Spring of DrupalCon Amsterdam 2014 I did a debug of how get via Rest Config Entity
I did my test with Entity entity:entity_form_display and the URL I use test was http://example.com/entity/entity_form_display/node.article.default after pass the Basic Auth I got a Access Denied 403.
The access denied was trigger in Resource Drupal\Core\Entity\Entity\EntityFormDisplay. because the class Drupal\rest\Plugin\rest\resource\EntityResource tried to execute the method access and doesn't exist.
I will improve the function get of Class EntityResource
ASAP I got a patch I will upload.
Comment #18
clemens.tolboom@-enzo- this issue has a patch already. What is wrong with that patch?
Note there is #2339815: Limit EntityResource to content entities, rather than them causing fatal errors which is doing the opposite.
It would be great to have more use cases into the summary so please add yours.
Comment #19
-enzo- commentedUhhh my fault, I will review and provide a feedback if works for Entity Form Display
Comment #20
alansaviolobo commentedComment #21
clemens.tolboomComment #22
-enzo- commentedHi folks
I did a patch to support Entity Display entities via Rest, fixing the error trying to call access method with view parameters, also add current validation to confirm the user has access to get Entity Display definition.
To test this patch you can use the Chrome plugin Simple REST Client and the URI http://example.com/entity/entity_form_display/node.article.default remember enable the resource using the Rest UI module as you can see in the following image
As you can see in configuration you must to use Basic Auth in Rest Client and be sure the header Accept with application/json
As result you will get something similar to this output
Comment #23
dmouse@-Enzo- thanks, works for me! =)
Comment #24
clemens.tolboomPatch look great. Hope to review this week. This needs some tests.
Remove white line
prepend white line
Why add line breaks in the arguments list? Grepping on core source tree show lots of single line __construct.
Fix indentation of function body.
Use instanceOf instead
Comment #25
-enzo- commentedHI @clemens.tolboom I did a new patch following your recommendation, please check and let me know if works for you.
Comment #26
clemens.tolboomGeneral: this needs tests too to confirm the GET versus other ops work as expected.
This seems unrelated.
This seems unrelated.
Fix indentation.
Comment #27
-enzo- commentedHi @clemens.tolboom
I remove unrelated code and fix indentation
Comment #28
-enzo- commented@clemens.tolboom how I can do this " this needs tests too to confirm the GET versus other ops work as expected."?
Comment #30
MartijnBraam commentedI've tried the patch to get the contents of the main menu. (The path in REST UI is incorrect. The /entity part is missing)
The response is correct but useless:
The thing I needed was the items in the menu, not the metadata of the main menu.
Comment #31
-enzo- commentedHi @MartijnBraam
About the missing entity in end point, maybe you need to update the Rest UI module I did a patch to resolve that issue #2290605: REST URI paths changed to canonical paths.
About the entity menu did you request, after enable the Entity Menu Resorce as you can see in the following image.
When I tried to consume this end point using the URL http://example.com/entity/menu/main I got the following error
[Wed Oct 15 06:35:14 2014] [error] [client 127.0.0.1] PHP Fatal error: Call to a member function access() on a non-object in /Users/enzo/www/drupal8beta/core/modules/rest/src/Plugin/rest/resource/EntityResource.php on line 52I will debug and propose a new version of patch.
Comment #32
-enzo- commentedAttached you can find a new patch testes Menu, Nodes and Entity Display Form
Comment #33
clemens.tolboomComment #34
clemens.tolboomEhm ... I did not delete file from #32 ... how could I delete that file!?!?
Comment #35
clemens.tolboomI've tested using Rest UI to configure + permissions for anonymous with
both gives
Attached patch adds a test as an example. It needs some fixes.
Comment #37
clemens.tolboom@-enzo- I used your patch from #32
Below the changes I did. Interdiff was better :-/ I added a test which 'nicely' failed.
Added space after if
Don't use label.
Comment #38
-enzo- commentedHi @clemens.tolboom
Using you patch #35 I did a new patch, because the way you use to get the Id of plugin return empty because is not an object is an array, but if you test with admin user the validation never is executed.
About the test I can't test because I'm getting a awful error trying to list the test to execute.
ASAP I resolve this issue I will work in testing, but meanwhile do you have any idea what is wrong in test? Also the test must be cover in this issue or maybe we can create a new issue?
Comment #39
clemens.tolboom@-enzo- thanks for the feedback and sorry for the bad code :-/
Just remove devel module temporary.
I wrote #35 in order to show you were test could be written.
Test must be in this issue :-)
Comment #40
-enzo- commented@clemens.tolboom I follow your instructions and remove the devel issue, but now I got a new error if I try to run any Test you can see the error at #2341997: Uncaught exception - Circular reference detected for service "database".
Maybe you can consider to separate the Test in other issue and if work the patch apply to Drupal Core.
Any thoughts?
Comment #41
tim.plunkettComment #42
-enzo- commentedHey folks
I did a re-roll of path #38 to meet the latest changes in Drupal 8 but still works in Unit Test are required.
Comment #43
djbobbydrake commentedHi all. Tried the patch in 42 (support_configentity-2300677-42.patch), and still getting the metadata for menu:
Is this patch supposed to pull the actual menu items?
Comment #44
berdirNo, the patch is not. Menu links are something else entirely. The menu config entity *is* the menu metadata.
I still have the opinion that providing generic support for config entities is problematic and unneeded. In many cases, they will not give you the data you actually care about. See above.
The only real use case I see for the current content entity implementation is to synchronize data 1:1 between equal systems, e.g. a staging/production site. It is an internal representation of the raw data.
For config entities, we have the configuration sync/import API, and nothing we do could do here would come close to what that provides, with diffs, and sync flags and so on.
IMHO, if you want an API that you can use from an external source, e.g. an app, or a public API for your clients, especially if that involves config entities but likely even for content, then write it yourself, then you can define an API and structure that makes sense for your use case. Take the example in #43, where the expectation is to get all the menu links, which are actually plugins, with different sources. There is no way we can make a generic API that supports that, but it should be fairly easy to write a custom resource plugin that returns menu links in a simple structure.
Comment #45
-enzo- commented@Berdir I use this patch with a Headless Drupal Generator github.com/enzolutions/generator-marionette-drupal , in my case is good idea because in that way I can generate HTML5 forms based in Drupal 8 site current status.
I know you point an API is only for data, but with proper permissions you can expose this information to create great tools and services integrated with Drupal 8
Comment #46
webchickYeah, I really don't understand how you can write a custom front-end for a Drupal 8 site without supporting at least some config entities. The inability to get vocabulary names when displaying a node is a huge limitation.
Comment #47
clemens.tolboomToday I revisited our tiny effort to mimic garland using Angular https://github.com/build2be/drupal-8-rest-angularjs
It could not list the term names as we have a list of nodes on taxonomy/term/:tid which renders the term on HTML but there is no equivalent in REST context. Adding a view we can add a list of terms which partly solves term names.
But IMHO it should not be necessary to build that view on every site. It should be out of the box.
To really build the mentioned Garland App menu items are really needed. As menu items are not config entities (what are they?) I guess a contrib module should solve that :-/
Comment #48
andypostComment #49
-enzo- commented@clemens.tolboom Correct you can solve that with view because is content.
But in my case I want to create a REST Frontend #headless Drupal with Backbone with the capability to generate Forms for content types based in current content types definition in a Drupal 8 site, how you can solve that with out expose a config entity?
If you check the patch the rest resource require a specific permission to access config entities, so IMHO is not security issue. Also a Rest Resource has a security level with Authentication Provider selected when the Rest Resource is enabled.
If the argument to disable this option is force users to create content in Drupal instead of provide the option the user to decide how access his content even the admin content is the wrong angle.
Comment #50
-enzo- commentedHey folks
I did a re-roll of path #42 to meet the latest changes in Drupal 8 but still works in Unit Test are required.
Comment #51
clemens.tolboomIt seems like #2308745: Remove rest.settings.yml, use rest_resource config entities together with #2397271: REST configuration fails if it contains a plugin reference that does not exist is doing what this issue tries to attempt.Comment #52
clemens.tolboomI misread #2308745: Remove rest.settings.yml, use rest_resource config entities so remove my refs.
Comment #53
davidwbarratt commentedKicking of the testbot to see if we need a re-roll....
Comment #57
frobNot sure if this should still be 8.0 or 8.1 but, I will see if I can reroll this patch.
Comment #59
wim leersComment #60
dawehnerI worked a bit on it, here are some bits I ran into it on the meantime.
Comment #62
wim leersLooks great!
Shouldn't we perform config schema validation for config entities? See
\Drupal\Core\Config\Schema\SchemaCheckTrait— hattip @tstoeckler in #5.Comment #63
wim leersClosed #2650792: What permissions control REST's field_storage_config? as a duplicate, another person wondering why this doesn't work, and the lack of helpful errors also getting in the way.
I think it's safe to say this is at least major.
Comment #64
dawehnerMade some progress:
Comment #65
frobComment #67
dawehnerNote: This is blocked on #1928868: Typed config incorrectly implements Typed Data interfaces technically, doesn't mean though one cannot work here.
Comment #68
dawehnerLet's be more honest about it.
Comment #69
clemens.tolboomFixed links using _format now.
Comment #70
dawehnerLet's work first of the GET aspect of things, see #2724823: EntityResource: read-only (GET) support for configuration entities
Comment #71
frobwhat does pp-2 mean?
Comment #72
clemens.tolboom@frob postponed level 2 see #2335229: [meta] REST et al and #2721489: REST: top priorities for Drupal 8.2.x
Comment #73
wim leersComment #74
wim leers#2724823: EntityResource: read-only (GET) support for configuration entities landed. So this is now blocked on "just" #1928868: Typed config incorrectly implements Typed Data interfaces.
Comment #75
dawehner@Wim Leers
Do you mind updating your comment and replace
justwith"just"?Comment #76
wim leersDone :P
Comment #77
wim leersComment #78
wim leersThis is not blocked on reviews. If somebody wants to take it on despite the blocker, feel free to mark it as active. For now, marking as postponed.
Comment #79
dawehnerNote: #1928868: Typed config incorrectly implements Typed Data interfaces has a needs review patch now. Feedback there would be great.
Comment #80
dawehnerJust some intermedia reroll, nothing to see :)
Comment #83
dawehnerComment #84
dawehnerWe decided that #1928868: Typed config incorrectly implements Typed Data interfaces is the only blocker, yeah!
Comment #85
dawehnerI rerolled / started this patch based upon on #1928868: Typed config incorrectly implements Typed Data interfaces
Comment #86
dawehnerFurther progress:
All of them are now working with the
config_testentity type.There is still a hack inside
ConfigEntityBasefor example we have to remove of course.Setting this to needs review to get a testbot run.
Comment #89
wim leersThank you so much for getting this going again! EXCITING! :D Here's a review of the
*-review.patchpatch.Should this have a capital first letter? Interesting!
Clever :)
This is just wrapping all this code in an if-statement. Makes sense. (And is also happening in a few other patches.)
Yay, being able to delete this is great :)
This was the only call to that helper method, we can delete that helper method also now.
Same wrapping here.
You can make the first two fall-through cases.
Hm, this is a bit strange.
First: if there's only the
defaultcase, then we can just get rid of theswitchstatement altogether?Second: I'd have thought that in one case, the required permissions is
view config_testinstead?I can see that this only makes sense for content entities. But shouldn't you also get a validation error for config entities if you try to do this?
Nice!
Field access only exists for content entities (well, fieldable entities), so that makes sense.
Comment #90
dawehnerYeah I'm probably the person which with biggest confusion about the typed data validation system, but yeah this is the plugin id to be used here:
\Drupal\Core\Validation\Plugin\Validation\Constraint\LengthConstraintoh yeah I think you are right.
In general I think we should have a new config entity just to test REST validation for now ... given that other tests fail which are using this config entity in other places.
Do you think we should do that, or should we jump directly to fix the other tests as well?
Comment #91
wim leersI'm fine with either approach.
Comment #92
dawehnerWe need to fix it inline at some point anyway, given we also want tests for the test entity types. Here is some ongoing work.
This is kind of a lie :)
Comment #94
dawehner18 fails is still too much ...
Comment #96
dawehnerJust a reroll after the other issue landed, yeah!
Comment #97
jibranComment #98
wim leersTantalizingly close now!
Comment #100
dawehnerAdded a related issue which ideally we fix first: #2869809: Make it easy to get typed config representations of entities as we no longer would have to change a protected to a public method.
Comment #101
dawehnerIn general I'm not 100% convinced to get the patch in as it without having config schema, at least for the non test config entities. Of course this is up for discussion, but its something which should be take into account, if possible.
Comment #102
wim leersI'm assuming was intended to read .
I agree, I'm not comfortable with that either. That's too risky. In #98 I was just expressing my excitement of a green patch that is showing significant progress, and a good starting point for further work :)
Awesome job, Daniel!
Comment #103
dawehnerSo yeah I'm wondering whether we should opt in entity types over time we support.
Comment #104
berdirThat's exactly what I was thinking, some kind of annotation key that opts in to this. won't ensure contrib/custom properly validated third party settings, but it's the same with custom/contrib field types and fields for content entities.
Comment #105
wim leersSounds good :)
We can bring this back and make this work on a per-entity type basis.
Comment #106
dawehnerWhat about using an additional like:
_rest_resouce, so with an underscore to explain its kinda internal/will not exist forever?Comment #107
dawehnerSo something like this?
Comment #109
dawehnerBoolean logic is hard.
Comment #110
dawehnerComment #111
wim leers#106 suggests an underscore-prefixed annotation key. I'm fine with that.
But #107+#109 omit that leading underscore. Furthermore, I think this is confusing:
I think it'd be better to rename this to
__internal__config_entity_type_supports_validation: TRUE, and make that a default annotation key on entities, and make it default toFALSE.That's super explicit because super ugly, and then also sets it to the correct value for all config entity types by default :)
Comment #112
dawehnerWell I have problems with setting them ever to TRUE, which is why I removed the underscore. As of now no config entity out there provides config constrains, so I fear swapping the default would ever to possible in D8. Given that it is actually not internal during the D8 cycle. In D9 I would totally suggest to remove the capability.
Comment #113
wim leersFair. But then let's go with
Because this is not only about REST, there may be other functionality that can only work if a config entity supports validation.
Comment #114
wim leersThanks for your impressive work here @dawehner :)
We need this for the "129-character UUID should trigger a validation error" test, so this is definitely distinct from #2870878: Add config validation for UUIDs.
We're making this method public because of this.
We're making all these changes because to prove that this approach works, this issue is updating
config_testand its test coverage to prove thatPOSTing,PATCHing andDELETEing config entities is in fact possible.Nit: let's remove this comment.
All of this is duplicating
\Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait::validate(). Which is fine. But if we'd updateEntityResourceValidationTraitto have aprotected function processViolations($violations)method, then we wouldn't need to duplicate this brittle (yet existing code).This change is trivial: it's not changing the existing code; it's just wrapping it, in an
if instanceofcheck.This would become much clearer if we did what I suggested in #113.
Nit: missing docblock.
These can be merged.
I think we can remove this; we can just inspect the config entity type's annotation.
And we can then also update this message, to:
Same for PATCHing and DELETEing.
Is this change actually necessary? If it is, we should document this.
Hm… don't we want a validation error for config entities if you send multiple values for a single-value property?
(In other words: if you're sending a list or map for a boolean or string value?)
Comment #115
dawehner#114.2
Note: Once #2869809: Make it easy to get typed config representations of entities is in, we no longer need that.
#114.4
Totally
#114.5
Totally
#114.7
I initially thought this sounds a bit like a weird idea, but the more the think about it, this makes clear what the root cause of that is. One could have suggested something like: supports_update_rest, but you know, it does supports that. It just doesn't support the validation aspect of things.
#114.10
Done that
#114.12
#114.13
We should be totally able to to that. Let's give it a try. I would assume that this might trigger some alternative error message, as config validation/schema validation is triggered.
#115.15
You are absolutely right. Both can and should happen totally in parallel.
I know this will fail the test for now, but I have to stop working for a quick.
Comment #117
wim leersOh, nice! Updating issue title to reflect that this issue is blocked on that.
Great! :)
Interdiff review
Ohhhh, even better! This means all entity types have this in their annotation, and content entity types default to
TRUE, all others default toFALSE— this makes total sense!It's great to see things come together so nicely. Useful metadata across all entity types.
Better yet, this means that the
ContentModerationStatecontent entity can (and should!) setsupports_validation = FALSE, i.e. it should opt out from the default for content entity types. At least if #2779931: Add storage exception that enforces unique content_entity_type_id and content_entity_id on the content moderation state content entity, and add access control handler to forbid all access continues the direction it's gone in, which is: that entity type is considered internal, does not allow access for any operation, and does not support validation.This API addition in the form of a new annotation key therefore allows us to express things clearly, and not just for config entities.
I love it when things come together so nicely :)
Review
Same remark as #114.13, which covered
POSTing, this is testingPATCHing.Rerolled to fix my nits rather than mentioning them in this comment.
Comment #118
dawehnerNice!!G
Comment #120
dawehnerBoolean logic is hard ...
Comment #122
dawehnerSome progress here ...
Comment #124
dawehnerLet's fix the last failure ...
Comment #125
wim leersI think this is ready. This is now just hard-blocked on #2869809: Make it easy to get typed config representations of entities.
Great work :)
Nit: why this semicolon?
Comment #126
dawehnerYou mean colon? This was introduced to match the error message ...
Comment #127
wim leersEh yes, colon.
But this patch also introduces the error message; why even have the colon in the error message?
Comment #128
dawehnerI tried to have a message which is similar to:
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n"], static::$format), (string) $response->getBody());Comment #129
wim leersRelated: #2870878: Add config validation for UUIDs landed!
Comment #130
dawehnerI think we can unpostpone it now again, right?
Comment #133
dawehnerHere is a reroll for now, given we can now drop the custom validation for UUIDs.
Comment #135
wim leersWasn't this actually blocked on #2869809: Make it easy to get typed config representations of entities, not #2870878: Add config validation for UUIDs? Or I guess it was blocked on both? It seems that's the case. It's only blocked on #2869809: Make it easy to get typed config representations of entities for the
public function getSchemaWrapper()bit?Comment #136
dawehnerwell yeah. To be honest having uglyness for a short time is not necessarily wrong from a project managment point of view. I actually wanted it to be on needs review so the testbot picks it up again.
Comment #137
wim leers#2300677: JSON:API POST/PATCH support for fully validatable config entities is in, this is now unblocked! We just need a reroll now.
Comment #138
dawehnerHere is a quick reroll. I'm really not sure what we should do with the error messages ...
The message now is:
Unprocessable Entity: validation failed.\nuuid: This is not a valid UUID.\nwhich is drastically different to
Unprocessable Entity: validation failed.\nuuid.0.value: UUID: may not be longer than 128 characters.\nWhy is this the case? One reason is that we don't actually use the UUID constrain when we validate UUID field items, but rather all we check is the length, in the case of content entities.
Should the validator actually check also for the length and as such provide a more helpful message? I'm personally not sure whether the length message actually provides a better error message.
Comment #139
wim leersMe neither. I think the same UUID validation logic should be applied to content entities' UUIDs, to be honest. In fact, I'm shocked that that is apparently not already the case.
So how about we keep this, but open a follow-up issue to make content entities also use the UUID constraint validator, just like #2870878: Add config validation for UUIDs?
In that follow-up, this if/else could be simplified to a single assertion.
Comment #140
dawehner@berdir gave some historic background to the discussion.
It was argued that we control config entities 100% so we know exactly how it should look like. On the other hand content entities might come from other sources and as such their UUIDs look different. Given that we can guarantee less. I think for now we should be able to ignore this problem then.
Comment #141
wim leersWorks for me.
Comment #142
dawehner@Wim Leers
In other words we keep the behaviour as it is, right?
Comment #143
wim leersYep.
I think the only thing that remains for this to be RTBC'd is for an Entity API maintainer to review this (catch, Berdir, plach or tstoeckler), as well as a Configuration Entity API maintainer (Alex Pott or Tim Plunkett).
Comment #144
wim leersd.o--
Comment #145
wim leersUpdated IS.
Comment #146
wim leersFixing nits. Removing dead code. Removing solved
@todos.Comment #147
wim leersPer #117, make
ContentModerationStateopt out from validation, because it's considered internal per #2779931: Add storage exception that enforces unique content_entity_type_id and content_entity_id on the content moderation state content entity, and add access control handler to forbid all access.Comment #148
dawehnerThe changes from @Wim Leers in #146 and #147 seems fine for me
Comment #149
dawehnerSo who can be motivate to review the patch, maybe berdir?
Comment #150
wim leersThat would be wonderful.
Comment #151
berdirWe'll see about wonderful :)
The if makes sense of course because only fieldable entities have those methods.
But there is no alternative, so this means that you can patch all you want but $original_entity isn't actually changed for config entities?
This gets back to my rant about REST tests in the translation support issue that we're not actually testing any successfull cases right now other than that there is no error?
We even have a test for not changing something we shoudn't change, but we still have zero test coverage (as far as I see) to make sure that we actually *can* patch something? :)
I guess we also commented out enough coverage for config entity PATCH to also not test any validation failures? because at least that should have failed because the original entity is valid?
this whole hook is now pointless because that is now already the default implementation.
Comment #152
wim leersI promise you that
\Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPatch()is actually verifying you can patch something.By default, it reuses
getNormalizedPostEntity(). See\Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::getNormalizedPatchEntity(). Specific entity types can override it.In this case, it's changing the label from
LlamatoLlamam.So, can you be a bit more specific?
\Drupal\config_test\ConfigTestAccessControlHandler::checkAccess()simply returnsAccessResult::allowed(). This hook is changing that, so that a permission is needed for$op === 'view'. And the default is changed from::allowed()to::neutral().Comment #153
berdir1. It can execute a PATCH request without error. But unless I'm blind (which is possible), we have no assertion that the title change was actually *persisted*. The test is happy as long as there is a 200 response, which makes sense because the patch() method isn't really doing anything.
To rule out any blindness issues on my side, lets extend the test a bit to proof this :)
I think we should have a method to assert that each entity type PATCH actually correctly did what it had to do by reloading the entity and letting them assert the changes.
2. It used to do that. But this patch changes it and now both ConfigTestAccessControlHandler::checkAccess() and this hook now have a very similar implementation.
Again, to prove that, I just removed the hook now, and it's behaving as expected.
Comment #155
berdirso the access tests did fail, but if I see that correctly, they only failed due to the different error message?
Comment #156
wim leersThis is absolutely true. Let's fix that. Opened #2882717: EntityResourceTestBase: assert that PATCHed and POSTed entities contain the intended values. Let's postpone this on that.
Comment #157
wim leers#2882717: EntityResourceTestBase: assert that PATCHed and POSTed entities contain the intended values is blocked on review now. Once that's in, we can continue here.
Comment #158
dawehnerBack to normal mode
Comment #159
wim leersIndeed, yay! :)
And also: this now needs a reroll, because #153's interdiff can safely be reverted (since #2882717: EntityResourceTestBase: assert that PATCHed and POSTed entities contain the intended values expanded the generic test coverage, which means #153's added test coverage is now obsolete). And also #153's non-test change can be reverted, because it now should fail tests without that.
Comment #160
dawehnerSure
Comment #162
dawehnerHere is a fix :)
Comment #163
wim leersI think this looks great!
ConfigTestallows. The expected normalization for aGETrequest is:So I think we should have
ConfigTestResourceTestBaseimplement the optionalassertNormalizationEdgeCases(), where we could then assert that sending values for lots of these is not allowed. E.g.protected_property.For examples, see:
\Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait::assertNormalizationEdgeCases()\Drupal\Tests\datetime\Functional\EntityResource\EntityTest\EntityTestDatetimeTest::assertNormalizationEdgeCases()\Drupal\Tests\datetime\Functional\EntityResource\EntityTest\EntityTestDateonlyTest::assertNormalizationEdgeCases()Nit: missing "inheritdoc".
This is not consistent.
Perhaps an
@seefor each branch, pointing to the validation logic? Then it'll be clear to future readers of the patch why the validation errors are different.Nit: two spaces after "instanceof", should be one.
The indentation is no longer correct here.
I don't think we need this override in
ConfigTestResourceTestBasebecause we've fixed the generic one inEntityResourceTestBase?Keeping at NR because this patch would benefit from Entity API review too of course.
Comment #164
dawehner1. This makes sense. I turns out, even if its called protected, its just protected in some way. If you construct the config entity manually, which is what happens on denormalization, you can still change any value, as this test proves.
2-7. Addressed
Comment #166
damiankloip commentedLookin' good.
I read through the patch a couple of times and only have some minor things. It could well just me being silly.Maybe 'supports_validation' is the wrong terminology. That seems less descriptive and more about the implementation. What I mean is this could be a kind of misleading correlation between the naming and the functionality it unlocks when used with rest?
EDIT: Deleted the rest of the review. I mis-read that the default to TRUE was on content entities.
Comment #167
damiankloip commentedI think the current fails are because the
protected_propertyis not exported when toArray() is called (in ConfigEntityResource::validate) so it's not saved or anything either, but never validated against. Just ignored and discarded.Comment #168
berdirthat might explain the POST fail, didn't check that, but I'm still not seeing an implementation for config entities in patch()? Which means we're still not actually doing anything there and also means we still don't actually have validation for that part for config entities?
patch() receives two entities, one is the original, loaded entity and the other ($entity) has the submitted values. We need to copy those to $original_entity. Right now, that only happens inside the if ($entity instanceof FieldableEntityInterface) { and there no else.
Comment #169
damiankloip commentedYes, that seems like a very good point!
Comment #171
tedbowRerolling
Comment #172
dawehnerI can do let, let's see how this goes.
Comment #173
tedbowJust reroll #164
Comment #174
dawehnermeh
Comment #176
dawehnerHere is some test coverage + an actual implementation of patch for config entities.
Comment #177
tedbowI assume an entity could never be an instance of FieldableEntityInterface and ConfigEntityInterface, correct?
If so why not but this is an
elseifblock off the FieldableEntityInterface if statement.Otherwise it looks like all the logic FieldableEntityInterface where access is checked would be overwritten by the FieldableEntityInterface which doesn't check access. I know this is not the case but why not make it explicit?
Would we have to create a validator that extended
\Drupal\Core\TypedData\Validation\RecursiveValidatorto check for preexisting ids?Comment #178
dawehnerYeah this is not supported at the moment. I remember @tim.plunkett trying to figure that out, but gave up.
I like to use
elseifhere :)Nice catch! This sounds like a generic problem outside of this issue. Maybe we should open its own issue for that?
Comment #179
dawehnerOops, here are the actual patches ...
Comment #180
wim leers+1
Comment #181
dawehnerHere it is: #2905594: Missing entity validation constraint: don't allow new entities when there is an existing one with the same ID
Comment #182
dawehnerWe no longer need a followup, and I've just written a basic change record.
Comment #183
Anonymous (not verified) commented+Drupal 8.4.x, xxxx-xx-xx ...Comment #184
wim leers#176 was 40 KB, #179 is 107 KB, despite a <1 KB interdiff. Some other cruft ended up in there. Rerolling.
Comment #185
wim leersI worked on a final review round for this patch. I can't find a single thing to complain about! Exciting! 😀
So, on to the change record and issue then:
<del>.Comment #186
wim leersWOOOOT!
Major 👏 👏👏👏👏 to Daniel! 🎉
Comment #187
wim leersComment #188
dawehner@Wim Leers
OH wow I would have not expected to not have change records for adding constraints when we added the functionality earlier ...
Comment #189
wim leersHah, I was equally surprised :) But in the end, I think this makes sense — now we have one change record for this one unit of semantically related changes :)
Comment #190
amateescu commentedThis change is not a good one, IMHO. All entity types that implement
FieldableEntityInterfacehave to support validation because of the three methods from that interface:validate(),isValidationRequired()andsetValidationRequired().Also, even if
ContentModerationStateis an "internal" entity type which should not be interacted with directly, why wouldn't it support validation?Basically, I completely disagree with this proposed resolution point from the current issue summary:
Instead of this new
supports_validationflag on all entity types, I would suggest adding a newValidatableEntityInterface(orValidationEntityInterface) which would contain only those three methods mentioned above (validate(),isValidationRequired()andsetValidationRequired()) and makeconfig_testimplement it.Also, I was looking at
EntityResourceValidationTrait, which has a @todo pointing here that it could be changed to not be @internal anymore in this issue. That is a trait with a single method which this patch does not use at all, so we need to either remove that @todo or maybe remove the trait and move its code into\Drupal\rest\Plugin\rest\resource\EntityResource.Comment #191
wim leers#190: Thanks for reviewing!
Reading your comment, it seems most of your disagreement comes from opting out
ContentModerationStatefrom "supporting validation", right? I can understand that.On top of that, you think that only
@ConfigEntityTypeshould have thissupports_validationannotation key. And really, we shouldn't be adding any new annotation keys, but just use the existingvalidate()+isValidationRequired()+setValidationRequired()infrastructure. Which is currently tied to fieldable entities, but you're saying it could be extracted to a separate interface so that config entity types could opt in to it, but nothing would change for fieldable entity types.Did I understand that correctly?
(Intentionally not replying to the last paragraph of #190 right now, because it's a minor thing compared to everything else in #190.)
In any case, 🙏 thank you for your review! 👏
Comment #192
amateescu commented#191: Yes, you understood it correctly :)
The patch from #184 does not apply to HEAD anymore because of #2824851: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior, but here's an interdiff for it to show how I think this should look like.
If you agree with it, we should open a separate issue for the new interface because it's kind of important and it shouldn't be tucked away at the bottom of an issue that's already at 200 comments.
Comment #193
dawehnerSo does that mean you will not be able to opt out of validation once you have any kind of fields? Note: This approach would actually not work in this example, because
\Drupal\content_moderation\Entity\ContentModerationStateextendsContentEntityBase, which is fieldable :(Comment #194
amateescu commented@dawehner, see #190 for why we can't make any content entity type un-validatable. Since
FieldableEntityInterfacealready has those three methods related to validation, it would be an API break at this point.Comment #195
wim leers#192 looks awesome. +1 for a new issue to extract
ValidatableEntityInterface. Would you like me to create that issue, or would you rather do that yourself, @amateescu?If I then look at how it would affect the rest of the existing patch, then:
We could then also drop this.
We could move the bulk of that logic to a
ValidatableConfigEntityTrait(I don't think we'd want it inConfigEntityBase), which would implementvalidate()on behalf of the config entity using that trait, and it'd just call typed config validation.Comment #196
dawehnerI think moving that to
ConfigEntityBasewould be sort of a bad move potentially. On purpose config entities are not tied to the concept of typed data, which IMHO is actually a really good thing. Typed data is sort of something you can do, but normally you should not have to care about it.Comment #197
wim leersI didn't propose that, and @amateescu didn't either:
Do you also have concerns about that?
Comment #198
amateescu commentedHere's the issue for the new interface: #2907163: Split validation-related methods from FieldableEntityInterface into a separate ValidatableInterface
Comment #199
tedbowRerolled #184
Also a patch that adds suggest by @amateescu in #192 and interdiff between the 2.
I like this idea with the new trait.
Comment #200
tedbow2300677-199-ValidatableInterface.patch will fail.
Here is a try to get the tests to pass. Added ConfigValidatableTrait for config_test to use.
Comment #203
wim leersThis is technically a BC break. We should add it to the concrete implementation, not the interface.
Comment #204
dawehnerThis is just for tests. I hope we don't treat tests as part of our API.
Comment #205
wim leersTrue, but since this patch is meant to serve as an example, I think it makes sense to make the example unambiguous.
Comment #206
jofitzCoding standards corrections.
Comment #207
wim leersSince #199, this patch includes the patch for #2907163: Split validation-related methods from FieldableEntityInterface into a separate ValidatableInterface. Marking this postponed.
Comment #208
dawehnerWhat about this change? This does improve the readability a bit.
Comment #209
wim leersMakes it easier to understand 👍
Comment #210
xjmSo there are a number of concerns raised about this by subsystem maintainers in #2907163: Split validation-related methods from FieldableEntityInterface into a separate ValidatableInterface:
I recognize that the goal of this issue is to unblock REST functionality for config entities and it's not just a proposed architecture change in isolation. However, this could have pretty significant long-term consequences. I'd like to see this issue's summary updated with the counter-arguments to the proposed resolution here, and to get subsystem and framework manager input on this patch.
I'm also pretty sure there are issues out there about typed data validation vs. schema validation from before release, so we should track those down if possible.
Comment #211
dawehner@tedbow and myself also talked a bit how this issue relates to #2871591: Allow ComplexData in TypedData to specify computed properties that should be exposed in normalization and other contexts
That issue introduces the concept of exposable, so something which should be exposed to the outer world.
The problem we have in this issue is not the same to be honest, as we just want to hide the config entities when they don't yet implement validation properly. In an ideal future we should be able to assume that all config entities have the proper constrains, given that, no matter how we filter out the types now, it ideally won't have to stick around in 9.x.
Comment #212
tedbow@dawehner I thought from our discussions the problem was not just that we want to hide certain config entities that don't support validation is that some config entities should be internal i.e. not exposed via a REST API(or JSON API) regardless of whether they support validation or not, ContentModerationState being the first example of that in core.
Would it make sense to got back to approach in #184 and before except instead of
Don't add this but
protected $internal_only = FALSEThen ContentModerationState would set this to TRUE and REST(and JSON API/Graphql etc) would never expose this.
Comment #213
tedbowUPDATE: sorry this patch/approach is not really relevant to this issue. Feel free to skip, see #216
Here is re-roll of #184 but with using
internal_onlyinstead ofsupports_validation. So it only requires marking\Drupal\Core\Entity\EntityTypeasinternal_only = FALSEthen\Drupal\content_moderation\Entity\ContentModerationStateasinternal_only = TRUE.If we look at phpdoc for
ContentModerationStateMarking it internal for any REST API(or JSON API) purposes actually makes sense. If it should not be used programmatically directly but rather a field should be set on the entity then should be the case for REST also.
Currently I have just changed
\Drupal\rest\Plugin\rest\resource\EntityResource::availableMethodsto useinternal_onlybut this of course creates a Read-only REST resource. Should this and otherinternal_onlynot be part of the API at all? If so we would want to update\Drupal\rest\Plugin\Deriver\EntityDeriver::getDerivativeDefinitionsand not make derivatives at all forinteranal_onlyentities.If we want Drupal to be API First we have to have way to mark entities as not part of the API and not make the assumption that all entities are part of an external API.
Re @xjm's comment in #210
If we take this patches approach then we won't necessarily have "pretty significant long-term consequences". Validation for now won't be encapsulated at all in config entities and won't be tied to typed data but if #2907163: Split validation-related methods from FieldableEntityInterface into a separate ValidatableInterface seems to be the way to go later and we want to use the new
ValidatableInterfaceon config entities then we are free to do so later. We could then just get rid of\Drupal\rest\Plugin\rest\resource\ConfigEntityResource(mark as internal now) because it is only needed for the difference in validation between config and content entities.Right now I left
\Drupal\rest\Plugin\rest\resource\ConfigEntityResource::validateusing\Drupal::service('config.typed')but that could easily be changed.Comment #214
wim leers#212: I think we can leave
ContentModerationStateout of the discussion here. We can discuss it in a follow-up. We already havecontent_moderation_rest_resource_alter()to ensurerest.moduledoesn't expose it (nor the contrib https://www.drupal.org/project/restui module).The central question in this issue is: how can we let config entity types A) have support for validation, B) signal their support for validation on per-config entity type basis.
The 3 concerns explained in #210 show that the concern here is that we introduce i) coupling, ii) inconsistencies.
And I would like to see counterproposals from those who have counter arguments to the proposed resolution of the current patch.
Comment #216
tedbowre
Ok #212 & #213 probably misses the point of the issue. Sorry will update #213
Comment #217
tedbowSetting back to postponed as it was before #213
Comment #218
dawehnerI was wondering whether we can have a meeting to discuss this issue? It feels like we are kinda stuck right now.
Comment #219
amateescu commentedThat's a very nice idea! Good thing that most of us will be in Vienna next week :)
Comment #220
wim leersAgreed that a meeting would help. Both dawehner and amateescu were invited to the "API-First" Hard Problems meeting next week in Vienna, it probably makes sense to discuss this in that meeting.
Comment #221
wim leersIDK how that status change happened.
Comment #222
tedbowAdding related issue #1818574: Support config entities in typed data EntityAdapter
This would allow us to validate config entities in the same way as \Drupal\Core\Entity\ContentEntityBase::validate
Comment #223
dawehnerComment #224
Anonymous (not verified) commentedThe more additional issues, the better :)
Comment #225
dawehner1️⃣ It feels like it would be worth creating an issue simply to discuss, how we want to allow config entities to opt out of rest support, maybe for validation or some other reasons. Making a decision on that point we help moving this issue forward.
2️⃣ As part of the discussion in vienna we wanted to ensure that we have a decent test coverage. It feels like laying what we have already in the issue summary would be great to get an overview and help the committers.
3️⃣ Anything else?
Comment #226
amateescu commentedIf some kind of generic validation is added to all config entity types, I'm pretty sure that they can use the existing mechanism for opting out of being exposed by REST, which is
hook_rest_resource_alter.Comment #227
dawehnerWhen we don't have any kind of official API/flag other implementations like jsonapi, relaxed and graphql can't opt out either.
Comment #228
sam152 commentedWe have no way of telling if an entity type has correctly provided constraints to validate things which may otherwise have been validated in forms or methods on the entity class. For that reason I think this has to be opt in? From a DX perspective, I think a property on the annotation makes the most sense. Just a shame we'll have a duality between how config and content entities are supported.
I'm interested to know how access will work with REST too. For the
workflowentity the "update" operation doesn't grant you access to modify the entire tree of configuration. We have additional operations likedelete-state:foo. Some extra background in #2896726: Expand the entity access model for workflow states and transitions..I can't see many other implementations of this nature, but it does raise an interesting question: can we assume that authors of config entities intended the "update" access opperation to grant unfettered access to the entire config object. What if, I as an author only exposed a subset of the tree to the UI and it was my never my intention for the whole thing to be updatable by a user. Is opting into rest support off the table for me now?
Totally random example but in the views config object, it makes no sense for anyone to ever directly modify the
cache_metadatakey. Does "update" onviewgrant me this privilege. How do I support rest safely?Comment #229
dawehnerYou are rising indeed good questions. Maybe this actually means we need field level access or just a list of values you can actually manipulate ...
Comment #230
webchickThis is probably a terrible idea, but just spitballing. Do we need a new set of "opt-in" hooks? hook_rest_insert(), hook_rest_update(), etc? Might be a bit of code duplication, but that way we can surmise that the module author has considered some of these implications.
Comment #231
sam152 commentedPossibly some entity handler to smooth over any differences that can't be inferred generically?
Another question I had around this was in relation to plugins and schema provided by plugins. As a config entity with a plugin collection, how do I ensure all plugin implementations have also correctly provided constraints for their schema? Opting into rest support could possibly be seen as a BC break or expose an access bypass, because as an plugin implementor the only way config was updated was via the configuration forms I exposed and presumably validated.
I think one option could be, opt-in REST support for simple, fully validated configuration schema and for complex configuration entities there could be a framework around being able to provide custom REST resources which expose safer domain-specific endpoints. To continue the views example, safely providing support for an opperation like "adding/update a display" could live on an endpoint like
/entity/view/{view_id}/add-display/{handler}/{display_id}and the associated handler would only use methods onDisplayPluginInterface, which would presumably provide more safety than replacing a blob of configuration. In the previous example, thecache_metadatacan't be set with this interface and would be calculated as a result of this operation. It would be a far less glamorous option than managing to solve every entity type in one go, but the issues raised with the generic solutions so far seem significant.From a DX perspecitve, something like link templates and "form/route_provider" handers, but for rest?
Comment #232
dawehnerLet's stop merging everything into one issue: #2920756: Discuss how config entity types can opt out of REST support
Comment #234
dawehnerWhile #2920756: Discuss how config entity types can opt out of REST support is one thing, we still want to disable certain entity types as long they don't have validation support. Do we plan to make them internal till they have proper validation?
Comment #235
tedbowI don't think that will work. I think internal config entity types should not be exposed via an API at all. @see #2941622: Make REST's EntityResource deriver exclude internal entity types and mark ContentModerationState entity type as internal instead of the current hack
While as most config entity types will be exposed now for GET and we are looking for way to mark them validatable on Entity type basis so they can be exposed for POST and PATCH.
But then we also want to not add DELETE support because dependencies get very complicated on deletion.
So if we were add the 'supports_validation' now to the entity type annotation and later we wanted to DELETE support to some entity types via API what would we add for those entity types, 'supports_delete', 'supports_delete_validation'?
Maybe instead of tying the idea of exposing certain operations on entity types via the API to validation we explicitly add 'api_supported_operations' array to entity type annotation.
'api_supported_operations' then for content entity types this could default to all, and for config entities this could default to ['GET'].
Then core could choose for now to not expose operations all methods on config entity types and to update certain entity types to support more operations as they gain validations and it made sense to expose them.
The thing is even if we went the method of
ValidatableInterfacewould that always mean that we wanted to expose POST/PATCH on those entity types?ValidatableInterfacewould help for better code even if we had no API exposing modules. So we may want to add entity types that can be validated programmatically and still only want to expose GET for them.I think an example for would be a config entity type that is really a child of another type that only gets created programmatically when the parent entity gets created.
Comment #236
dawehnerOne of the problems of relying purely on a
ValidatableInterfaceI see is that config entities should always be validatable, at least conceptually as they have config schema.I really like this explicitness. Exposing this annotation on the entity level seems to make entities more API first.
Comment #237
wim leers#234:
I think not, because it's totally possible, reasonable and supportable to
GETconfig entities: that does not require validation.#235:
Correct. Except that some entity types are safe to delete. For example:
blockconfig entities. So we shouldn't just simply prevent deletion of all config entities, we should look at it on a case-by-case basis.This would be yet more metadata to be aware of and specify/set/potentially alter. I personally think that we can solve this by using the existing access control handler logic: for config entity types where dealing with dependencies upon deletion is complex, just return
AccessResult:::forbidden()for thedeleteoperation. Why can't that work? That'd then also affect Drupal/PHP code calling$config_entity->access('delete'), which is great: things would behave consistently. Because if it's unsafe for an HTTP API client, it's also unsafe for PHP code, right? Because both would be doing the exact same thing, have exactly the same consequences.I'm also fairly certain Entity API maintainers will not like to see HTTP API-only metadata on entity types. It was hard enough to build consensus for
::isInternal()/::setInternal()in #2871591: Allow ComplexData in TypedData to specify computed properties that should be exposed in normalization and other contexts and #2936714: Entity type definitions cannot be set as 'internal'. In those issues, it was a hard requirement that we not specifically tie this to HTTP APIs. This was a reasonable requirement put forth by Entity API maintainers. And I'd assume they'd apply the same here.The existing config entity form-based UIs massively rely on inheritance-based form code reuse:
many config entity types don't have their own access control handler, e.g.
Action, which then relies on\Drupal\Core\Entity\EntityAccessControlHandler::checkAccess()\Drupal\Core\Entity\EntityDeleteForm::buildForm()to automatically list affected dependencies and then ask for confirmation.$config_entity->access('delete')calls in core actually take dependencies into account …but then again they all seem to point to the canonical "delete" form which works as described above. But that doesn't mean nothing in contrib/custom code isn't doing
$config_entity->access('delete'): that's entirely valid, but currently a way to easily break a site.The main challenge appears to be surfacing the impact of a deletion: that logic currently lives in UI/form code, but in principle belongs in the access control/validation layers. Ideas:
reasonlist all config entities that should be deleted first — deleting them first would result in thedeleteoperation being allowedreasonlist all config entities that should be deleted first, and provide a value to be used in a subsequentDELETErequest header. The burden is then on the client to ask for confirmation from the user that those dependents are indeed safe to delete — and upon obtaining that confirmation, then repeat the request, with the first-response-provided value in some sort ofConfirmation-Code: <value>request header, which would then result in thedeleteoperation being allowed, and resulting in all those dependents also being deleted.In short, using the existing Entity Access API makes the entire Drupal ecosystem stronger. Because
$config_entity->access('delete')will actually be used, because the respective access control handlers would need to be updated to be accurate. I don't know yet how feasible that is, but in order to maximally use Drupal's Entity API, it seems like we are obligated to at least explore that?Comment #238
tedbow@Wim Leers yes I think we should explore handling this with the Entity API.
$config_entity->access('delete')then at what point would we decide to support delete on config objects in core REST? When all core config entity types are correctly handling this via access handlers? What about contrib and custom config entity types? It seems at some point we would just have to update\Drupal\rest\Plugin\rest\resource\EntityResource::availableMethods()to include DELETE for config entities. And there is no way to tell for config entity types if they have updated their access handlers to handle dependencies. So config entities would have to opt out.So when 8.0.0 shipped module developers could be sure their config entity types would not have DELETE exposed via REST and all of sudden they are exposed.
At least for PATCH and POST if we relied only exposing config entity types that supported the new
validatableInterfacethen any for any contrib and custom entity type they make a change, implement the interface, for their config entities to be exposed via REST. So in a sense it is opt in, which seems more BC safe.validatabelnterfacebut not to, support GET and not SUPPORT DELETE. because seems when DELETE gets added would be for all entity types.One problem this will not solve though is letting external
Comment #239
dawehnerWhen I talked with @alexpott about this particular topic he recommended to skip DELETE support for now. Deleting configuration is not just deleting a config entity, but it might trigger additional removals.
Comment #240
e0ipso#237:
I think the answer is that Drupal is not API-First nor it will be until (at least) Drupal 9. That means that some times you will want Drupal to be able to delete config entities of type A when the deletion is triggered via another deletion of type B (as #239 mentions). But at the same time you may not want to expose deletion capabilities of isolated entities of type B via the API. That's why I think that we need additional tools to access control handlers.
This has come up several times in the past as a pain point. While I understand the hesitation, this makes things harder in the API-First Initiative. It fragments approaches in the different contribs and forces maintaining the same feature in multiple different projects.
Yes this! Maybe I'd differ a bit about the approach of this particular issue, but I'm 100% behind this sentiment.
Comment #241
dawehner@amateescu, @berdir, @Wim Leers, @gabesullice, @alexpott, @bircher, @effulgentsia (maybe more I forgot ...) had a discussion about this at Drupalcon.
We ended up with the following decisions:
Comment #242
wim leersThanks for posting that, @dawehner! You beat me to it :P
Meeting doc: https://docs.google.com/document/d/1sCzGaxDVA6hKmPatSg-6uELXMu2pMJm5nBJJ...
And indeed, those were the decisions. After that meeting, I also found @tim.plunkett here at DrupalCon Nashville and walked him through it. He also agreed with this direction!
So with that, we have broad consensus. The full list of people who agree with this direction:
So … updated the issue! 🎉
Comment #243
effulgentsia commentedComment #244
wim leers#1818574: Support config entities in typed data EntityAdapter landed! 🎉🎉🎉🎉
The patch here can now be simplified, to use that infrastructure. Based on my notes (which are hopefully accurate), that means that we can address #190 without needing to do #2907163: Split validation-related methods from FieldableEntityInterface into a separate ValidatableInterface.
Any takers? :)
Comment #245
dawehnerHere is my try to reroll this patch.
Comment #258
wim leersHard-blocked on #2907163: Split validation-related methods from FieldableEntityInterface into a separate ValidatableInterface and soft-blocked on #2869792: [meta] Add constraints to all config entity types. At least one of the children of #2869792 needs to be finished before we can do this.
Comment #259
wim leersComment #260
wim leersGreat news: two of those children are done:
system.menu.*(Menuconfig entities) andshortcut.set.*(ShortcutSetconfig entities) are fully validatable!Even though we should land #2907163: Split validation-related methods from FieldableEntityInterface into a separate ValidatableInterface before this, that does not mean it should stop this issue from creating a PoC for how JSON:API would selectively expose config entities: only those config entities that are actually validatable would be allowed to be modified.
I'd love to sprint on this during DrupalCon Lille 🤓
Comment #261
dpiThis is older and has more issues.
Comment #262
wim leersThis likely would be impacted by/simplified by #3395099: [meta] Allow config types to opt in to config validation, use "FullyValidatable" constraint at root as signal to opt in to "keys/values are required by default".
Comment #263
wim leersSee #3395099-33: [meta] Allow config types to opt in to config validation, use "FullyValidatable" constraint at root as signal to opt in to "keys/values are required by default" … this is now unblocked!
Comment #266
wim leersYay!
testPostIndividual()ones for the 5 currently fully validatable config entity types (see #2869792: [meta] Add constraints to all config entity types): https://git.drupalcode.org/issue/drupal-2300677/-/pipelines/99655/test_r...POSTed:MenuTest. It's failing tests because it successfully created a menu config entity when it shouldn't have (it should've failed because a value was specified that does not exist on the fieldable entity). Next step: debugging this. But … we're getting somewhere 😄Comment #267
wim leersPOSTis working, with only 2@todos remaining and a13 files, +261, −42diffstat! 🚀Next up:
PATCHsupport. Then fix one of the@todos — the other one I just proposed a solution for in a comment, but requires an API addition inConfigManagerAFAICT. Marking in case somebody has an idea/insight for that one 🤞Comment #268
wim leers(Forgot to post this 2 days ago; this is probably helpful information for reviewers.)
That failing test in the second bullet of #266 hits an interesting edge case:
\Drupal\jsonapi\Normalizer\ConfigEntityDenormalizer::prepareInput()does not stripfield_rest_testEntityDenormalizerBase::denormalize(), becausedoes not transform anything
\Drupal\Core\Entity\EntityBase::__construct()that causes this to happen, because it does… which results in
Menu::$field_rest_testbeing set, but that's simply not validated!How does this compare to content entities? Well … turns out it's actually similar: it's
\Drupal\jsonapi\Normalizer\ContentEntityDenormalizer::prepareInput()that does the detection and doesSo we just need to match that. Did that in https://git.drupalcode.org/project/drupal/-/merge_requests/6704/diffs?co....
Comment #270
wim leersPOSTandPATCHtest coverage is passing.ConfigEntityValidationTestBasesubclasses is pointless. Still, a spot-check makes sense. For that reason,DateFormatTest::testPostValidationErrors()exists. Compare withDateFormatValidationTestand the JSON:APINodeTest::testPostNonExistingAuthor()to verify that this is indeed a consistent DX.\Drupal\Tests\jsonapi\Kernel\TestCoverageTest) start failing (guaranteed thanks toResourceTestBase::setUp()skipping test methods for config entities if and only if the config entity type is NOT fully validatable). That means we will be able to guarantee that everything works consistently 👍DELETEcreated: #3423459: [PP-4] JSON:API DELETE support for config entitiesNext: add test coverage to verify that for fully validatable config entity types that 1) use plugins (such as
Editor,FilterFormat,FieldStorageConfig, etc.), 2) those plugins have settings, 3) and creating/modifying such a config entity to use a plugin whose settings are NOT fully validatable, should result in either a 422 (like any other validation error), with a message such asThe value at property path foo.plugin_settings.baz cannot be validated. Please contact the developer of the "bar" foo plugin to make this validatable.This is blocked on either of the following child issues of #2869792: [meta] Add constraints to all config entity types landing:
FieldConfig,FieldStorageConfig,BaseFieldOverride)Editor)FilterFormatWorkflowThere are a few
@todos outstanding, but each of those are for clean-up or contain proposed solutions, so this is ready for review despite still being blocked — that blocker is only for making this have watertight security 👍Comment #271
wim leersDocument which API changes/additions were needed to make this a reality.
Comment #272
wim leersHid all patches. Last one was from >5 years ago. MRs were a distant dream back then 😄
(
document.querySelectorAll('#edit-field-issue-files-und-table input[type=checkbox]').forEach(el => el.checked = false);)Comment #273
wim leersRemoved 6 related issues:
Comment #274
wim leersFinally, #2907163 is a just nice-to-have rather than a hard blocker now. See #2907163-50: Split validation-related methods from FieldableEntityInterface into a separate ValidatableInterface. That was proposed before
ConfigEntityAdapterlanded a year after #198 (in #1818574: Support config entities in typed data EntityAdapter).Needs further investigation: #2905594: Missing entity validation constraint: don't allow new entities when there is an existing one with the same ID by @dawehner in #181. This might be a bit of missing test coverage. Updated IS.
🏁 Finished triaging all related issues. Issue is fully cleaned up just before it became a teenager 😅
This is ready for review now! 🚀 Start with the overview in #270.
Comment #275
wim leers#2002174: Allow vocabularies to be validated via the API, not just during form submissions landed. Merged
origin/11.x;VocabularyTestshould fail now 🤓Comment #276
wim leersFailed as expected!
#3412361: Mark Editor config schema as fully validatable also landed, so now I'm definitely unblocked on next steps here 👍
Comment #277
wim leersComment #278
wim leersNow a 422 response is generated when a subtree of the config is not actually fully validatable. That is one of the nastiest/trickiest aspects we need to handle here. It's proven to be working now, but the exact messaging is still TBD.
But … that means this is now definitely ready for review 🥳
Comment #279
andypostComment #280
bbralaInsanity. In a good way. ;)
I've gone through the code and did some comments. It was hard to poke real holes into this. There are a few areas we do need work, but those are mostly marked with the todo's.
Comment #281
wim leersAddressed one thing @bbrala spotted, answered some questions and … asked some questions that hopefully a config system maintainer will respond to 😄
Comment #282
bbralaGone through all responses and think a few more can be closed @Wim Leers :) Thanks for the awsers.
Note: Wim asking for feedback from someone with config experience (or maintainer there). Quoting for visibility:
https://git.drupalcode.org/project/drupal/-/merge_requests/6704#note_305097
Comment #283
wim leers#3440993: Improve JSON:API test failure messages to include errors when data is expected landed today and conflicted. Resolved.
I will do a live demo of this in my DrupalCon Portland talk next Monday. 🤓 So here's a backport to
10.3.x(against0da9964174175a013f8d517cdee1ce08e590f440).Comment #284
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #285
borisson_Since this was added to the IS, there was a complete rewrite of the patch, is it still valid? Do we have to postpone this on that?
Comment #286
borisson_I looked at the entire MR again, since it came up in one of Wim's XB weeks: https://wimleers.com/xb-week-24
I think the merge request looks great, and I don't have any big open questions, it needs a rebase and Wim needs to close some of the open questions (that's still only possible to do for the mr author).
We also need to find an answer to the question highlighted in #282.
Comment #287
bbralaI'll update this MR and the comments and such.
Comment #288
bbralaRebased, did need to change a few lines to adjust to return types and the changes to the base class (doTestPatchIndividual). Lets see what it tells us.
Comment #290
bbralaCrediting casey for helping with the config type definition puzzle.
Comment #291
bbralaI've updated the changerecord, went through credits. Also went through open credits and it seems all is addressed.
Now i need to investigate testing failures ;x
Comment #292
bbralaTest failure is unrealated. I feel this might actually be ready? o_O
Comment #293
bbralaLets recap current possible open issues.
EntityValidationTrait.php:111: @todo Add recursion … or add a helper to TypedConfigManager?ConfigEntityResourceTestBase: @todo regarding followup issues (delete/relations).ResourceTestBase:2086: @todo Config schema assumes at least high-level type compliance, which is violated here. Harden it to provide equivalently helpful error responses.Comment #294
bbralaHmm, also see some other thing that need work. Not sure the removal of the SKIP_TESTS is doing what it should.
I'll get back to this.
Comment #295
bbralaOk, tests are now properly failing on ConfigurationEntitites that are either missing a postDocument or those who are validatiable but have plugins that are not (see failing ActionTest).
Life does make sense again though, this issue can at least be worked on in this state ^^
Comment #296
bbrala#2905594: Missing entity validation constraint: don't allow new entities when there is an existing one with the same ID is no longer needed, added a check for existing config entities by id (and fallback to uuid) to make sure they don't exist.
Comment #297
xjmAmending attribution.