Problem/Motivation
Quoting @bircher, maintainer of https://www.drupal.org/project/config_split, https://www.drupal.org/project/config_filter, etc., after I asked him What concrete things have gone wrong due to bugs in configuration, especially if they were caused by overriding/splitting/… — any of the advanced Configuration Management things?
what can go wrong, well sorting mostly... oh and to know if a key is required or not. So sometimes when a value is null or the array empty we can omit the key and sometimes not
👉 This issue aims to address the "sometimes […] we can omit the key and sometimes not" part. See #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support for the other part.
When talking about "keys" here, we're referring to the keys in a type: mapping. The root of every configuration object is a mapping — so the top-level keys you see in any MODULENAME.settings.yml file for example is a key.
For configuration management purposes it's critical to know which keys are required and which ones can be omitted. This can be entirely arbitrary: it depends on the PHP code that interacts with the stored configuration whether the key is required or optional.
In other words: today requiredness is implicit: it depends on the module's code. It's impossible to know based on the config schema. Only with trial & error can you figure this out … and even then, a single new code path could possibly cause a key that seemed optional to suddenly be required.
In general, most keys are implicitly required, with the exception of third_party_settings and subkeys of dependencies.
This leads to configuration management modules needing to add hardcoded hacks such as
// @todo find a better way to know which elements are required.
if ($type->getDataDefinition()->getDataType() === 'config_dependencies') {
// Except for sub keys of dependencies.
unset($diff[$key]);
}
Steps to reproduce
Use modules like https://www.drupal.org/project/config_split and https://www.drupal.org/project/config_filter.
Proposed resolution
#3324150: Add validation constraints to config_entity.dependencies introduced the ValidKeys constraint. It can be configured in one of two ways:
ValidKeys: ['foo', 'bar']→ allows these two keysValidKeys: '<infer>'→ automatically infers the allowed keys based on the keys defined for this mapping in the config schema — this removes the need to sprinkle the entire config schema with statements like the above
But that only determines which keys are allowed, not which ones are required.
- So let's update the existing
ValidKeysconstraint to not just allowing keys, but requiring keys. - ⚠️ DO NOT CHANGE ANY BEHAVIOR FOR EXISTING CODE/CONFIG SCHEMAS! ⚠️ That is the mistake this issue was making until #77. All the points below apply ONLY to config schema type definitions that have
constraints: FullyValidatable: ~.
In this issue,
only a single typefour types get that (the one below,system.menu.*andshortcut.set.*, because these already have validation constraints for everything, pluseditor.editor.*to demonstrate how much more helpful schema can be if we use it intentionally):config_test.types.fully_validatable: type: config_test.types constraints: FullyValidatable: ~ - The infrastructure to power
ValidKeysdetecting missing required keys was added in #3401883: Introduce Mapping::getValidKeys(), ::getRequiredKeys() etc.. This issue is only modifying the validation constraint. - Assuming the config object/config entity type's config schema type has opted in (IOW: is marked as fully validatable), then the
ValidKeysConstraintValidatorwill respect the required & optional keys for everytype: mappings it contains. - Add test coverage to prove that simple config is not affected until they opt in: see
SchemaCheckTraitTest. - Add test coverage to prove that config entity types are not affected until they opt in: see
\Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase::testRequiredPropertyKeysMissing()
Consequences
- This has zero effect on contributed modules' config, because A) config schema type definitions must opt in, B) config validation does not yet run for them (not in tests, not in production, since #3361423: [meta] Make config schema checking something that can be ignored when testing contrib modules.
- This has zero effect on core modules' config, because A) config schema type definitions must opt in.
- The only way this can affect Drupal core/contrib in production: since #3364506: Add optional validation constraint support to ConfigFormBase, simple configuration forms that were updated to not use form-based validation, but config schema-based validation, will be affected, if the simple config they're editing (e.g.
update.settings,system.site…) have theFullyValidatableconstraint. This issue does not add it to any config schema types in Drupal core. Simple config forms already using do run validation constraints, but sincesetRequired()will never be called, theNotNullconstraint validator will never run, and hence this does not cause any behavioral changes.
Remaining tasks
Review.
User interface changes
None.
API changes
None.
Data model changes
None.
Release notes snippet
N/A
| Comment | File | Size | Author |
|---|---|---|---|
| #75 | 3364108-nr-bot.txt | 90 bytes | needs-review-queue-bot |
| #59 | 3364108-nr-bot.txt | 90 bytes | needs-review-queue-bot |
Issue fork drupal-3364108
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 #2
wim leersComment #6
wim leersThis should land after #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support, because that requires fewer config schema infrastructure additions and changes.
Comment #7
wim leers#3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support is almost ready, expect progress here next week!
Comment #8
wim leersThe blocker was just RTBC'd at #3364109-19: Configuration schema & required values: add test coverage for `nullable: true` validation support, time to get this started!
Comment #9
wim leersCommits explained:
👆This causes tests to fail 👍 Subsequent commits aim to get this to green again. Current failure:
RequiredKeysConstraint(Validator), modeled afterValidKeysConstraint(Validator)which was added in #3324150: Add validation constraints to config_entity.dependencies.RequiredKeys: <infer>by default for alltype: mapping, just like we did forValidKeysin #3376794: `type: mapping` should use `ValidKeys: <infer>` constraint by default.Expected test output at this point:
👆 No more hard crash, but an actual config validation error 👍
dependencies, but we shouldn't require every config entity to list very possible dependency type. So let's mark those keys optional.Expected test output at this point:
👆 Previous validation errors gone, new validation error instead 👍
_coreis necessary for Drupal core only, it's an internal concern. Config is not REQUIRED to contain it for it to be valid. So let's mark this key optional.Expected test output at this point:
👆 Previous validation error gone, new validation error instead. Now we've left the realm of "generic simple config & generic config entity validation config schema errors", and have entered the realm of the config schema specific to the config at hand:
config_test.system👍system.test:bazas optional. So did that, and documented why.Expected test output at this point:
👆 Previous validation error gone, new validation error instead. We're running into a long-standing bug with PECL YAML, for which #3205480: Drop PECL YAML library support in favor of only Symfony YAML has been open for a few years.
core/modules/config/tests/config_test/config/install/config_test.types.ymlhas the octal key-value pair commented out, and hence we have no choice but to mark this key as optional.Expected test output at this point:
✅ Tests are green again!
This concludes the basic infrastructure required for this issue.
Next steps:
For now, I'd like to get a review of this part already 🤓 Just a review of the general pattern and approach. Doing all the remaining work will have to wait for #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support, because doing all the next steps now will likely cause painful conflicts.
Comment #10
bircherI like the approach.
I didn't know that
$definition->toArray();and then reading the keys is the way to do it. But since this is core this is allowed.To me it makes sense. And I am curious to see how many things fail when this is turned on for everything.
Comment #11
borisson_The documentation in #9 is great, this is super clear. Just like bircher, I'm also very curious to see what breaks when this is turned on for everything.
Comment #12
wim leersNow that I've got A) confirmation for the approach, B) 2 curious people besides me … pushed commit that runs all Unit & Kernel tests! 👍🤓🤓
Comment #13
wim leersOkay, I'm intrigued, that's not as bad as I thought it would be! 21.5K passes, 1.6K fails 🤩
Let's transplant the
::$ignoredPropertyPathsinfrastructure from #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support and see how many things we'd need to ignore or fix! 🤓Comment #14
wim leersInfra lifted out of #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support. Should have the exact same test results.
Comment #15
wim leersAdded things to ignore to
::$ignoredPropertyPathsto makeCommentFieldAccessTestand\Drupal\Tests\system\Kernel\Migrate\d7\MigrateActionsTestpass tests.To be continued. But it's looking promising! 😊
Comment #16
wim leersThe number of potential config schema property in Views is mind-boggling! 😳
I've already accumulated some 460 lines of ignored property paths!
On the bright side, I think there's a lot of of these ignored property paths for
views.view.*that would be conditionally required: AFAICT many of these would be inherited from thedefaultdisplay to all other displays. Dealing with that will require issues of its own — we always knew sanitizing Views' configuration would be a big undertaking.Then again: it's by far the most complex, most pluggable, most configurable thing we have in Drupal core, so it only makes sense! 😊
Comment #17
wim leersVery big push today. The end is near! 🕵️♀️
Comment #18
wim leersDown to 18 failures!
And 💯% of those 18 failures look like this:
Analysis
editor.editor.*:image_uploadhas 5 keys:statusschemedirectorymax_sizemax_dimensionsHowever, when
status === FALSE, the other 4 keys clearly do not make sense to exist!Conclusion: this indicates the
RequiredKeysvalidation constraint needs to support conditionally required keys.I'm sure there's many more examples of this all across Drupal core. But because:
Editor(Text Editor) config entity, which is already the most validatable config entity type ever since #3231364: Add CKEditor 5 module to Drupal coreI'd like to use this one as the single concrete example of how to adopt conditionally required keys. A single concrete example + abstract test coverage is enough to prove it works reliably; we don't want to burden this issue with adopting it all over the place. (
SchemaCheckTrait::$ignoredPropertyPathsis effectively already our todo-list for things to re-assess, where in some places conditionally required keys will make sense. But each of those need sign-off from the relevant subsystem maintainer(s).)🚀 Just pushed 2 commits that introduce the necessary infrastructure + test coverage.
Comment #19
wim leersIssue summary updated for #18.
Comment #20
wim leers🥳
Now we only need to add
in a bunch of places! 👍
Just pushed that :)
Comment #21
wim leersNow most of the failures are due to
… turns out this is because of bugs in the config schema: some use
type: filterinstead oftype: mapping— which actually results in a recursive schema definition! 😳 Config schema does not care nor help detect this 🫠 That's a fight for another day. For now, let's fix this.Comment #22
wim leersAnd with one final commit, this should now be green!
I know that as a next step, we need to run all tests. But, before doing that, I'd like to get a review from @bircher especially, but ideally more people, such as @borisson_ and others 😇
Comment #23
wim leersAha!
Looks like I need to tweak things a little bit further, because CKEditor 5's existing validation constraints already are generating a better (more specific, more context-aware, more humane) error message.
Will do that. But I think the two remaining CKEditor 5-only test failures prove that this overall system works! 😊
Comment #24
wim leersCKEditor5EnabledConfigurablePluginswas causing that.Ready for round two! 😄
Comment #25
bircherThe conversation from slack to put it more in context:
I meant you can have optional types this way:
Of course type Null is not a schema type, but we could make one, ie a value that is always null, ignoring everything else.
also of course the optional_type definition doesn't go in that file.. but this is how we make dynamic schemas and I think then the logic of whether a key is required or not can already be described this way. Similarly to how we also don't have more of a conditional validation on whether the value is required or follow some rules based on other values outside of dedicated validation constraints.
So we could say requiredKey: false and it will mostly work since it will also not have null values when the status is true.
In the end what I would be mostly interested in when analysing the schema is to know if the resulting array needs to have the key or not..
Comment #26
wim leersOne piece of important context is missing — @bircher's original concern as stated in Slack:
AFAICT this means that he's not a fan of
requiredKeyIf, which accepts nottrueorfalse, but{path: 'some_key', value: 'some value'}. That'd be a big difference compared to all other config schema properties, which all are either a string or a boolean (with the exception ofmappingandsequence).@bircher, can you confirm that's indeed your main concern? 🙏
Concerns about @bircher's proposal for conditionally required keys in #25
I had written in Slack:
In #25, @bircher shows what he meant with a concrete example. 👏 Thank you! 😊 That helps a lot!
So turns out that the concrete config wouldn't have to change (good), but the config schema would have to (bad, but less bad). It does make the schema much more verbose though 😳
I also worry that the already very abstract config schema will become even more abstract/complex to create. There currently is no way to verify it's a valid/sensible config schema, which is why for example a circular schema does not trigger any complaints and has hence been broken in core for years 🙈
There is one important bug in the concrete example in #25:
It would not be valid to define the
mappingproperty inmax_dimensionsin the case ofoptional_type.mapping.0. It would actually need to be:… which implies this would lead to a significant increase in the number of config schema types that
TypedConfigManagerwould need to know about — from 1 to 5 in this single example:editor.editor.*editor.editor.*,optional_type.string.0,optional_type.string.1,optional_type.mapping.editor_image_upload_max_dimensions.0,optional_type.mapping.editor_image_upload_max_dimensions.1Now, some of those would be reusable (
optional_type.string.*), but many wouldn't (none of the conditional mappings ones).IMHO this is infeasibly complex. Ironically, it'd be infeasibly complex in an effort to simplify the cumulative config schema infrastructure — but as I hope the above shows, it'd actually make things far more complex for the actual developer 😞 (Hence why I use the word "infeasibly".)
🙈 The current MR fails to catch the inverse: conditionally required keys that should NOT be present
Your proposal made me realize I had forgotten about the inverse side of required keys: when
image_upload.statusisfalse, we should indeed not requireimage_upload.scheme(the MR is already doing that part 👍) … but we should actually forbidimage_upload.scheme(the MR is not yet doing that part 🐛)!👍 Added test coverage for that. It failed tests 👍
Fresh look
type: some_type||some_subtypenotation — perhaps using that could make sense.👎 No, that's only designed for reading the resulting type as computed by
TypedConfigManager::getDefinition()handling type inheritance. Introducing this would make the config schema infrastructure even more complex.nullableandtranslatablethat are supported for every type, and then there are some type-specific properties listed. Fortype: mapping, onlymappingis currently a type-specific property. The current MR is essentially makingrequiredKeyandrequiredKeyIfadditional type-specific properties.That naming is perhaps confusingly similar to "required values" (which is what #3364108: Configuration schema & required keys is handling), which is solely about
nullable: truebeing set, and hence works for all types.But this issue is about "required keys", which applies only to
type: mapping. So perhapsomittableandomittableIfwould be better names?You even mentioned that in Slack:
👎 That still wouldn't address your concern that we'd be adding a new config schema property with a complex structure: it's neither a boolean nor a string.
requiredKey: false, remove support for conditionally required keys, and instead make conditionally required keys a responsibility of validation constraints?CKEditor 5 already has something like this:
\Drupal\ckeditor5\Plugin\Validation\Constraint\EnabledConfigurablePluginsConstraint, which verifies that a plugin that is enabled and is configurable in fact has CKE5 plugin configuration present in theConfig⇒ configuration guaranteed to be present when needed\Drupal\ckeditor5\Plugin\Validation\Constraint\ToolbarItemDependencyConstraint, which verifies that if configuration is present, that the corresponding toolbar item is also present ⇒ configuration guaranteed to be absent when not needed👍 This seems the only viaable path forward: a new
ConditionallyRequiredKeysvalidation constraint, which removes the need to add new properties (see https://www.drupal.org/node/1905070#properties) to config schema.Implemented fresh look point 3 in
5a2ad02d1368de65f8d9bd1e010f640da4db8f9b. I hope that addresses your concerns, @bircher 🤞Comment #28
wim leersIS updated. It now specifically references @bircher's #25 and @phenaproxima's MR comment with similar concerns to explain the current architecture. 👍
Comment #29
wim leers212 test failures in the full test run. Many are in update path tests, as well as in CKEditor 5/Text Editor tests (which is to be expected given I used
editor.editor.*:image_uploadas the sole concrete example forConditionallyRequiredKeys).This seems very doable! 👍😊
Comment #30
bircherOk so my idea from 25 didn't work straight away because it would require a new type that wants its value to be empty/null.
But we can still do this conditional magic without anything new with the following example:
The reason this works is because the mapping information is inherited from the definition it is used on.
So we can add a new validation constraint.. but we don't have to necessarily as far as I can tell.
Comment #31
bircherOh just a comment on my code comment...
it only works for the happy path :(
if the data is not there it won't parse the type.
Comment #32
wim leers💡
After back-and-forth, I'm now starting to see the light in @bircher's proposal 🤓😄
Also, there's a pre-existing example of this in core, which I noticed while running the tests after I got it working:
👍
RE: blocker for #30
#31: Worked around that in
\Drupal\Core\Validation\Plugin\Validation\Constraint\RequiredKeysConstraint::resolveMapping()👍 😄 We can choose to move that into a newMapping::getSchemaElements()or something like that later if we like. Detailed comments in place.🚀
I worked the entire day to figure out how to rely solely on schema to figure out which keys are required vs optional vs extraneous, so that the test coverage in
\Drupal\Tests\editor\Kernel\EditorValidationTest::testImageUploadSettingsAreConditionallyRequired()would not need to change at all! What a journey that was! 🤯But it WORKS!
🤓
It's rather complicated … but that's only because config schema is complicated, and because the whole point of #30 is to rely solely on the config schema's existing support for dynamic types to determine when which type is actually used: for example
type: core_date_format_pattern.[%parent.locked]in HEAD resolves to eithercore_date_format_pattern.0orcore_date_format_pattern.1already too — this uses that exact same mechanism.To ensure the correctness of it all, and to simplify future maintainers' lives, I added a lot of assertions to keep things straight. (It's almost a hard requirement for anything config schema-related IMO 😅 — at least I cannot keep track of things otherwise 🙈)
I'll once again need to update the issue summary for this approach.
I HOPE that this is the one that gets @bircher's firm approval 🙏😇
Comment #33
wim leersOh, wow! Some of the test failures are actually proving that this is totally worth it, for example:
👆 The logic I added figured out that
settingsis not just required forcontent.links, it's conditionally required. That's much more helpful of an error message.Just pushed fe3aa1871378de6972d978f1d6b7a1657f78db60, which will make some additional tests pass.
The fact that this just made it work automatically for many existing types is just definitive proof that the approach proposed by @bircher is the right one.
Turns out that
editor.editor.*:image_uploadwas just a bad example, because it doesn't follow Config Schema best practices. #30 makes it use best practices … and then it Just Works 👍😊Comment #34
wim leersTweaked the ignored property paths to expect the
conditionally required keymessage when appropriate (seegit diff cd045a0e6c08a41c42e7d6ad5d0b959d051c6c87 0c745c33ecac926a4bba4f035f7935efd7b771c5to the full set of changes).Now hitting a new core bug, which #3361034: Make 'sequence' Typed Data instances return type of 'sequence' instead of 'list' recently almost fixed, but apparently not quite:
\Drupal\Core\Config\Schema\SequenceDataDefinition::getDataType()was wrong before (it hardcodedreturn 'list';) but is still wrong now (return 'sequence';), it must instead return the current sequence type or subtype:return $definition['type'];.( is already present 👍).
Just pushed that, which should make this green again… 🤓
Comment #35
wim leersThat was green, yay!
Time to break it again, but for good reasons 🤓
Over the weekend, I was thinking about this. I was not quite satisfied with the
'SOMETHING' is a conditionally required key.message: it lacks precision. WHY is it conditionally required? What does that even mean?!? 🤔 🙈🤷♂️Thanks to the massive rewrite that I did based on @bircher's proposal (see #32 and related commits), I actually should have everything I need to generate a very precise message. That should allow us to make a big leap forward in having Drupal generate messages that actually help make config schema less mysterious. 🥳👍
So, here's the logic, plus a first round of updated tests that should make the before vs after crystal clear:
and
Much better, right?! 😃
Comment #36
wim leersIssue summary updated for #32.
Comment #37
bircherI have a few high level remarks. I thought I had posted a review to this issue already, but I guess it got corss posted with #33 and I didn't notice.
First and foremost I don't like that the boolean
requiredKeyon the schema does now more than indicating whether a key is required or not. I am not against being able to express this information, after all this is what my "dummy null type" suggestion from #25 was supposed to do.I also don't like that you can only set it to false even though true is redundant.
As it is now actually there are three states:
You have it spelled out in the code already.
Secondly and related to the first is that this information is not accessible from a resolved schema but only if you analyse what other schema types a particular type could have resolved to. This is highly unpractical for modules that try to use the schema to do something to arbitrary config. It would be much better if the logic to decide was either more explicit in the schema or in class other than the validation constraint so that the validation constraint can just check if the condition is met and fail the validation with a pretty message.
For example we could add a method to
\Drupal\Core\Config\Schema\Mappingto return which keys are in this resolved instance to be used and which to be omitted or set to empty.Comment #38
wim leersThanks for the review, @bircher! 🙏😊 Let's get this DONE! 😄
Can you elaborate why you write this now "does more"? Because in my reading, that still is the only thing it does. 🤔
Can you elaborate why you do not like this? I'm open to allowing
requiredKey: true. But it's confusing and pointlessly verbose to allow this because the very purpose of this issue is to make all keys defined for a mapping in its schema required. If it's the default, then there's no reason to state that explicitly in the schema. That's why I specifically added explicit validation for this (config schema is sorely lacking validation: the learning curve of writing config schema is steep, the DX is essentially "use a debugger"): to not make config schema even harder to write! 😅This is inaccurate. A key being optional or extraneous is unrelated to the value for that key.
Accurate imprecise version:
requiredKey: falseIS NOT specified for itrequiredKey: falseIS specified for itrequiredKey: falseIS NOT specified for that key in its eventual (resolved) typerequiredKey: falseIS specified for that key in its eventual (resolved) type(Dynamic type: most commonly:
[%parent.type])Accurate precise version (yay truth tables):
requiredKey: falsedefined in that type?requiredKey?ckeditor5.elementckeditor5.elementckeditor5.elementckeditor5.elementcore_date_format_pattern.[%parent.locked]core_date_format_pattern.00and1— see #32)core_date_format_pattern.[%parent.locked]core_date_format_pattern.00and1— hypothetical)optional_type.string.image_upload_status_[%parent.status]optional_type.string.image_upload_status_10, not1)optional_type.string.image_upload_status_[%parent.status]optional_type.string.image_upload_status_00, not1)👆 The axes are:
requiredKey: false: yes 🆚 noAs you can see, despite 6 possible combinations, there are only 4 possible outcomes.
Comment #39
wim leersWill make this happen! 👍💪
(This is why we have the issue tag 👍😅)
Comment #40
bircherI think I don't understand what you mean by "optional".
For all practical purposes a mapping key is either there or not, there are only two states. Now if the key is required then the mapping needs to have it even with an empty value. so far so good.
When the key, however, is not required then it is therefore optional. Now the question is if you should set the key with an empty value or not set it at all. Being optional, I would understand both to be valid.
An example here is a role which depends on a module but then a permission is removed and the role no longer depends on the module. should it still have an empty module key under the dependencies? are both options valid? will they result in a unnecessary diff if the code that made this transition is not the same for the active config and the sync config?
For me this means optional, or not required. And by looking at how dependencies or third party settings work, I would say not required means unset it if empty.
Now the extraneous property effectively means that no value is allowed. So this is definitely a quality that I would not associate with being optional.
But more importantly in the current approach the extraneousnes depends on other branches of types. And one can very well make one of the branches be dynamic again and maybe circle to a previous one, or have a type more depending on other modules that may or may not be installed.
My argument is that because this is not immediately visible from the schema it is hard to grasp. Both from config schema consumer perspective and a module author that struggles to make a schema that validates their config.
In other words if you need a table to document how it works it is not simple enough.
Comment #41
wim leersUh oh! 🫣
Reading #40, it's clear that the one thing you're worried about is the "extraneous" validation error message.
I explained the reason for this in #18 (and the issue summary also specifically links to that comment):
If those 4 keys besides
statusdo not make sense to exist, then keeping them around would be:That's why I worked hard to add support for that: so that somebody auditing a site's config or looking at the diffs for a site's config can make sense of it.
👆 @bircher Do you agree with that rationale? If not: why not?
Comment #42
bircherYes I understand that these keys should not be there and are therefore extraneous.
But they are not optional! they are forbidden! they are required to not exist! That is a different thing than being optional.
And I think it would be much more expressive for the schema to say that explicitly.
For example like in my suggestion from #25 with a new type which can not contain any value. But other options are also possible, for example adding yet another flag
requiredEmpty: trueThe flaw with putting too much information into the boolean flag is that it limits what the conditional config can express. Bear in mind that the example here uses a conditional schema of [%parent.boolean_state] there are usually more than two options often a string. the way the current MR implements it you can not make the key optional in one branch,
Comment #43
borisson_Do you have an example of this kind of optional states?
Comment #44
bircherRE #43: Just search
[%in all files ending in.ymland you will see lots of config schema.Just a few examples in the general area of editors and filters ie the what you would be familiar with:
filter_settings.[%parent.id]where the id is a string.ckeditor5.plugin.[%key]editor.settings.[%parent.editor]where editor is a plugin name. (plugin ids is a very commonway to have a dynamic schema)block.settings.[id](so the type is inferred from the value itself. (there are a handful of examples like that)[%parent.%parent.%type].third_party.[%key]Comment #45
wim leersFinished #39 — API additions:
Mapping::getResolvedDataDefinitions(): Resolves (dynamic) subtypes of `type: mapping`, to determine the exact data definitions for each key in the mapping, as prescribed by config schema.Mapping::getValidKeys()Mapping::getRequiredKeys()Mapping::getOptionalKeys()Mapping::getConditionallyOptionalKeys()The first one is for fairly advanced use cases, the last 4 are what you were really hoping for I think, @bircher? 😊 2–4 return the concrete values for the given
Mappinginstance. The last one computes a result that will be the same for every instance of this particular mapping (i.e. at this relative property path — so for example it'll compute the same for theimage_uploadproperty path on everyEditorconfig entity. It uses::getResolvedDataDefinitions()to achieve this.P.S.:
4 files changed, 333 insertions(+), 409 deletions(-)👍😄Comment #46
wim leers#42:
Hm … 🤔🤔🤔
I see your point! 👍
So actually the problem here is that I am conflating "required/optional" (
RequiredKeysconstraint, being added here) 🆚 "allowed/forbidden" (ValidKeysconstraint). This is how it should really work:👇
ValidKeysconstraint (a key-value pair in a mapping either has either a valid key or an invalid key: it's valid if it's specified for this mapping type)RequiredKeysconstraint… which means that I'm incorrectly putting this logic for "extraneous keys" in the
RequiredKeysconstraint validator instead of in theValidKeysvalidator, where it belongs: a key is extraneous if some types for a given dynamic mapping type allow that key, but not the current type!Now that I've implemented all that, look at we get magnificently beautiful and helpful errors like this:
or
🤩
So now it finally is fully using config schema as intended 😊🚀 That's what I was hoping for all along: to make virtually no changes to the existing config schema (nor its infrastructure), and just surface actually helpful messages! 👍
Stay tuned while I get this to green … hopefully the last iteration! 🤓 The revised API additions:
Mapping::getValidKeys()Mapping::getRequiredKeys()Mapping::getOptionalKeys()Mapping::getConditionallyValidKeys()Comment #47
bircherYes! I think we are converging to a great solution.
I didn't check the latest code, but I hope it doesn't use the
requiredKeyfor deciding that in the other branch the key is not optional and therefore in this branch it is forbidden.But I had an idea last night that would solve your problem of making keys forbidden without anything new. ie this schema works even in Drupal 9 and the
ValidKeysconstraint would take care of it. Since nothing here is optional.Comment #48
bircherhahaha lol..
I was curious and took a peek at the MR anyway and looking at the code now I see that you had the same idea!! excellent!
I will review it more in detail later when I have some time for it.
Comment #49
wim leersThe mysterious failures I couldn't reproduce: due to #2492171: Provide options to sanitize filenames (transliterate, lowercase, replace whitespace, etc) having landed in the mean time 🙈 Extracted the migration test improvement to #3197324-58: Exception trace cannot be serialized because of closure.
Green again!
I need to shift attention to other things and I'll be on vacation starting next Wed until the Fri the week after it. I'm hoping that while I'm out:
Mapping🤓🧐(and maybe — a man can dream — I'll come back to this issue being unblocked because #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support landed? 😇)
Comment #50
borisson_I reviewed the pull request again, this is looking super solid and is well documented. I think I understand most of what is going on, and based on the conversations here, and in slack, with @bircher it looks like it catches all the special cases we currently have. I am curious to see what happens if we let this loose on contrib, but since the should not be doing any schema checking yet, there are no issues with that either.
I think the state this has ended up in is a lot more solid than what we started with, kudos to @Wim Leers to keep up with all the comments on this issue.
Comment #51
smustgrave commented@borisson_ should this be RTBC?
Comment #52
bircherThe MR needs to be "re-rolled". There are two merge conflicts.
The second one I think is pretty trivial to solve, the first one I am less sure.
I tried to use this new API in config split on the line mentioned in the issue summary here. And I haven't done manual checks but the relevant test passes when core is on this MR branch. See #3390285: Use required keys of mappings instead of special casing
I did have to change some things to make the config validation happy and I haven't finished that yet because I am using config from a core test module and it makes my test fail.. so that is on me and I need to fix that. But I just wanted to note that this is much more strict now. And is probably more disruptive than we would like. but there is another issue to address that if my memory is right.
But over all I am happy with where this is going! the API addition is useful and solves the problem it is set out to solve.
Comment #53
wim leers#3382453: \Drupal\Tests\ckeditor5\Kernel\ValidatorsTest::test() obscures 2nd, 3rd etc violation message for the same property path was extracted from this issue and landed. That explains 1 of the 2 conflicts. The other conflict is #3382580: Add new `ImmutableProperties` constraint, which tightened the existing test infra. MR updated! 👍
Thank you! (Linking the issue.)
Nothing enforces config to be valid in Drupal core today.
Modules can choose when to opt in. Site owners can optionally choose to validate all their config, by using https://www.drupal.org/project/config_inspector's Drush command.
Nobody is being forced into disruption. Everybody is given the choice to not change anything, or to reduce the pain of configuration-triggered bugs (module maintainers) or managing configuration (site maintainers).
Module maintainers know what the expected structure is. So module maintainers can provide automatic update paths, just like core has already done in a whole range of issues — most recently: #3380480: Views should require a label, rather than falling back to an unhelpful ID. Even if only core writes upgrade paths for all of the invalid config, then this is still worth doing. But hopefully, eventually, by Drupal 11 or maybe only by Drupal 12, we'd be able to do this for all config.
🥳🥳🥳🥳 So … what's next? 🤓😊 Besides cleaning up this MR, what is blocking an RTBC from you?
Comment #54
smustgrave commentedHave not reviewed but see it has some test failures.
Also do CR updates and the follow up still need to happen?
Comment #55
wim leersIndeed, did not yet find the time to solve those. Will get that done in the next 2 days.
Comment #56
wim leersAll done here now!
Test coverage has been finished, the obsolete
ConditionallyRequiredKeysstuff is gone.@smustgrave I think this should update the change record at https://www.drupal.org/node/3362879, rather than a separate change record, because now the validation is actually behaving as people expected (similar to what we did at #3376794: `type: mapping` should use `ValidKeys: <infer>` constraint by default, which also did not get its own change record).
However, a separate change record just for marking a key as optional would probably make sense?
Comment #57
wim leersWalked @alexpott through this yesterday, together with @borisson_ and @bircher.
Conclusions:
editor.editor.*:image_uploadto provide at least one concrete example of how config schemas can be refined to more accurately capture the intent of what configurations make sense (in this case: when disabling image uploads it makes no sense to have configuration stored for the directory where to upload it into!) — he preferred doing it here rather than in a follow-up issue, to provide a concrete exampleComment #58
wim leers@alexpott Wouldn't it be easier to understand the concrete example in this issue (image uploads) if it were done in a separate merge request, so that the consequences of those schema changes can be seen independent of the infrastructure we're adding here?
(It'd mean extra work for me, but I think it might be better for Drupal users looking for an example.)
Comment #59
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 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 #60
borisson_Merged 11.x back into this branch, also hopefully the MR should be green now.
Comment #61
borisson_I'm not sure if I can rtbc this issue now that I did some work on it, but to me it looks like it is done.
I'll start creating followups when this lands, so leaving that tag in place.
Comment #62
borisson_Created (and fixed) an example followup already: #3398330: Remove system.mail from SchemaCheckTrait ignored property paths.
Comment #63
wim leersI’m working on the missing test coverage.
See https://git.drupalcode.org/issue/drupal-3364108/-/pipelines/43969/test_r....
It is impossible to test the new
RequiredKeysconstraint in isolation. (Due to hardcoded assumptions inDataDefinitionthat are definitely out of scope to fix.)Combine that with the observation that it doesn’t really make sense to separate it from the
ValidKeysconstraint (they are complementary in what they do, butRequiredKeysmust currently be made aware of what options are passed toValidKeysto ensure all edge cases are handled correctly), and the logical conclusion is: we should have ONLYValidKeysand just make it more capable.It is already impossible anyway for any other type to reuse this validator, so it simplifies everything: fewer edge cases to test, simpler logic, one less TODO, simpler test coverage.
Will do that and update the issue summary. 👍
Comment #64
wim leersDone. ✅
Next up: explicit test coverage for the new methods on
\Drupal\Core\Config\Schema\Mapping.Comment #65
wim leersBug deep in core, discovered here, but with a work-around in place: #3400181: Follow-up for #2663410: calling TypedConfigManager::getDefinition() causes cache pollution, work-around: https://git.drupalcode.org/project/drupal/-/merge_requests/4094/diffs?co....
Comment #66
wim leersWhew! 🤓
DONE 🥳
Removing , because at this point it does not matter anymore which lands first: this one or #3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support. In fact, this one is much higher impact and should probably land first at this point!
IMHO the best way to observe this in action is through
\Drupal\Tests\editor\Kernel\EditorValidationTest::testImageUploadSettingsAreConditionallyRequired(), because it most clearly demonstrates how this issue/MR will both improve the UX (precise error messages) and the DX (no more "garbage" or "noise" in config).Recommended review plan
EditorValidationTest::testImageUploadSettingsAreConditionallyRequired()testeditor.schema.ymlchangesimage_uploadandsetImageUploadSettingsin the diff): many tests were required to be updated, because test fixtures must now be strictly valid (in core — contrib tests are unaffected thanks to #3379899: Follow-up for #3361534: config validation errors in contrib modules should cause deprecation notices, not test failures and #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"\Drupal\KernelTests\Config\Schema\MappingTesttest, and make sure to understand all the@see-referenced things\Drupal\Core\Config\Schema\Mapping← this makes up the bulk of the complexity. But it's not new complexity: it's "just" automatically making sense of all of the existing config schemas!ValidKeysConstraintValidatorand observe how it uses these methods.config_splitand other modules can use — see #3390285: Use required keys of mappings instead of special casingComment #67
phenaproximaAs you can tell, I reviewed this.
A lot of it was relatively straightforward and makes sense. The stuff that really ties my brain into knots is the stuff that was added to
Mapping. That's damn complicated. The inline comments are very helpful, but IMHO, they would be improved by having a lot of examples to illustrate what is being transformed into what (if that makes sense), at every stage of the logic.I just found it quite difficult to visualize the stages of resolving these dynamic types, and what their possible keys are, from comments alone.
Having said all that...this is extraordinarily complex stuff and I doubt a more heroic effort could have been made here. My fear is that this will be too intimidating for anyone else to maintain. The priority is getting this functionality into core, though; refactoring, if there's any useful refactoring to be done, can come later.
I did not get around to reviewing the changes in the constraint validators;
Mappingwas very daunting and I needed a break after reading it.Comment #68
wim leers+1, will do! I personally really like having those kinds of comments too 😊
Comment #69
wim leersTurns out that because this was rebased on top of the recently landed #3382510: Introduce a new #config_target Form API property to make it super simple to use validation constraints on simple config forms, and adopt it in several core config forms, which made
FileSystemFormuse validation constraints, that we run intopathbeing a required key. But that was deprecated into #3039026: Deprecate file_directory_temp() and move to FileSystem service, so it makes sense that that key is not set.So why is it still in the config schema? Because it was necessary for tests. Why was it not deprecated? Because #2997100: Introduce a way to deprecate config schemas landed a year later.
Conclusion:
system.fileComment #70
wim leersDown to only 4 failures, Continuing to get this back to green and in a more reviewable state.
This MR is likely too impractical to land in one piece (even if #3400368 were to land today), so I will extract issues from this one, once all feedback has been addressed and the tests are green.
Comment #71
wim leersNew issues:
filter_settings.filter_htmlhere)@tododiscovered here: #3401876: Fix bugs in update path surfaced by config validationconfig_splitin contrib)Comment #72
wim leersPP-2 for #3400368: Deprecate path.temporary in system.file configuration schema + #3401883: Introduce Mapping::getValidKeys(), ::getRequiredKeys() etc..
This can still be reviewed to see how this builds on top of #3401883 though!
Comment #73
wim leersWell well, https://git.drupalcode.org/project/drupal/-/merge_requests/4094/diffs?co... in response to @phenaproxima's review uncovered a bug:
%key-based dynamism did not yet generate appropriate validation error messages!This also means the test coverage in
\Drupal\KernelTests\Config\Schema\MappingTestand\Drupal\KernelTests\Core\TypedData\ValidKeysConstraintValidatorTestis insufficient.Comment #74
wim leers#3400368: Deprecate path.temporary in system.file configuration schema landed 🚢
Comment #75
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 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 #76
wim leersComment #77
wim leersMaking this opt-in using the mechanism proposed at
Already proven to be successful over at #3364109-49: Configuration schema & required values: add test coverage for `nullable: true` validation support 🕺
Comment #78
wim leersTurns out the hardening I just did here turned up over at #3401883: Introduce Mapping::getValidKeys(), ::getRequiredKeys() etc. too after I addressed @alexpott's feedback, because thanks to that feedback the validation of the schema now happens earlier 👍
Comment #79
wim leers#3401883: Introduce Mapping::getValidKeys(), ::getRequiredKeys() etc. landed! I didn't get to finish #77 yet, but that having landed will definitely make this MR infinitely easier to review & read 😄
Comment #80
wim leersFound a bug in
TypedConfigManager: #3403782: Calling TypedConfigManager::create() does not use the definitions of TypedConfigManager (config schema), but of TypedDataManager (field types).Comment #81
wim leersSimilar to #3364108-49: Configuration schema & required keys, I reverted all changes, got tests to pass on HEAD, and then introduced the opt-in infrastructure proposed at #3395099-19: [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".
Similar to #3364108-51: Configuration schema & required keys, the MR was nice and simple until a702b532 (no more >1000 lines being added to
$ignoredPropertyPaths!).Then I opted in the
Editorconfig entity type by adding theFullyValidatableconstraint. Which then required schema changes and test adjustments. I'm fine with omitting all of that from this MR, but @alexpott indicated at DrupalCon Lille he'd prefer to demonstrate how this can be used. I think that could happen in a separate issue too, but I'm not opposed to adding it here.Finally, I added test coverage similar to #3364108-51, to prove that config entity types are not affected by this change until they opt in: setting
type: mappingproperties on config entities that have not opted in now are proven to not trigger any validation errors, which also proves the absence of disruption.Change record created: https://www.drupal.org/node/3404425, one change record updated by creating a draft revision and updated the issue summary.
Comment #82
phenaproximaReviewed this and found some relatively minor stuff, but in general it seems pretty good.
It's really complicated, but I also understand that other MRs are merged into this one right now and will be stripped out when they land in other issues.
Comment #83
wim leers@claudiu.cristea independently found the same "invalid filter settings schema" problem and worked on a tightly scoped fix in #3404431: Filter settings schema types are incorrect. That issue is now RTBC, and much preferable over the far bigger undertaking to prevent this bug pattern from ever getting reintroduced, for which we still have #3401837: Add basic validation to config schema definitions.
Comment #84
wim leersAll feedback from @phenaproxima has been addressed. 12 open threads on the MR remain, most of which hopefully will be confirmed to be resolved by @phenaproxima.
Those remaining threads should not stop a subsystem maintainer review, because the review is in nitpicking territory already. (It's been ready for 13 days now, since #78.)
Comment #85
wim leersAll feedback from @phenaproxima once again addressed.
Extracted #3406478: Refine ValidKeysConstraintValidator's messages: use #3401883's new Mapping infrastructure from this issue, which does NOT do the more complex/harder to understand "required keys" change, but only the refinement of the existing message of the
ValidKeysvalidation constraint.Once that lands, this MR becomes 30% smaller.
Comment #86
wim leers#3364109: Configuration schema & required values: add test coverage for `nullable: true` validation support landed!
That means the changes to be merged. It was the most complex merge conflict of the year for sure 😳 I think I got it! 🤞
51 files, +1129, -15145 files, +1049, -148This is still soft-postponed on #3406478: Refine ValidKeysConstraintValidator's messages: use #3401883's new Mapping infrastructure, that would remove
4 files, +361, -60from this MR.Comment #87
wim leersMerged in upstream. This caused a failure in
\Drupal\Tests\block\Kernel\Migrate\d6\MigrateBlockTestdue to #3371219: Remove forum from block migration tests (landed 2 days ago), whose expected messages become more precise thanks to this issue. 👍 Trivial fix!Comment #88
wim leersMerged in upstream because #3379091: Make NodeType config entities fully validatable landed! 🚀
\Drupal\Tests\node\Kernel\NodeTypeValidationTestpasses. Let's find out if everything else does too. 👀Comment #89
borisson_Now that #3406478: Refine ValidKeysConstraintValidator's messages: use #3401883's new Mapping infrastructure landed, I'm removing the postponed status from this issue.
Comment #90
wim leersMerged in upstream; resolved conflicts with #3406478: Refine ValidKeysConstraintValidator's messages: use #3401883's new Mapping infrastructure. Before:
45 files, +1053, -151. After:44 files, +761, -91. 🥳Due to #3379091: Make NodeType config entities fully validatable having landed, we have three test failures (also already occurring at #88).
3 test failures:
\Drupal\Tests\forum\Kernel\Migrate\d7\MigrateTaxonomyTermTest\Drupal\Tests\forum\Kernel\Migrate\d7\MigrateTaxonomyTermTranslationTest\Drupal\Tests\menu_ui\Functional\MenuUiNodeTest::testMainMenuIsPrioritized()The first 2 are occurring because they contain a
NodeTypeconfig entity that specifiesthird_party_settings.menu_ui.parent, butnode.type.*.third_party.menu_uiinmenu_ui.schema.ymldictatesthird_party_settings.menu_ui.allowed_menusalso is required.For the third it's the other way around:
allowed_menusis defined, butparentis not.We can make tests pass by doing
… but it would be better to fix #3408165: Fully validatable config schema types: support dynamic types instead.
On top of that: this issue is modifying
editor.editor.*:image_uploadto demonstrate how to evolve config schema to take advantage of dynamically required keys, which means there's now two reasons to fix #3408165: Fully validatable config schema types: support dynamic types.I think it probably would be easier to fix #3408165: Fully validatable config schema types: support dynamic types here.
Comment #91
wim leersExpanded test coverage. It failed 👍
Then pushed the solution to handle the dynamic type boundaries as suggested by @effulgentsia at #3364109-60: Configuration schema & required values: add test coverage for `nullable: true` validation support.
Comment #92
wim leersThere were failures in
EditorValidationTestand\Drupal\Tests\ckeditor5\Kernel\ValidatorsTest.Marking all of core's text editor plugin configuration as well as the CKEditor 5 plugin configuration as fully validatable made all tests green again — because the test coverage specific to Text Editor + CKEditor 5 was already expecting validation errors that this issue was introducing.
(Complying with @effulgentsia's observation at #3364109-60: Configuration schema & required values: add test coverage for `nullable: true` validation support that this should be opt-in to prevent disruption was proven in practice by that. 😄)
Comment #93
borisson_We can probably extract more from this into smaller issues, but I think it is now in a very reviewable state, the Change Request is still up to date with the changes in the MR.
Comment #94
effulgentsia commentedThis MR looks really great. My only hesitation with committing it is the various changes in here related to enforcing the lack of any other keys when an editor's
image_upload.statusis FALSE. For example, the need to remove those other keys from Umami'seditor.editor.basic_html.yml. I think that should all be removed from this MR and moved to a follow-up that gets its own commit and its own change record.I don't think separating that out needs to affect this MR too much though. For example, could we accomplish that by adding a
editor.image_upload_settings.0config schema object that doesn't add
FullyValidatable? We can still leave FullyValidatable in place oneditor.image_upload_settings.1.The reason I want to punt on enforcing FullyValidatable on
editor.image_upload_settings.0is that I think it could be useful in some situations to have default config that adds the upload configuration even with a status=false, so that someone could change the status to true and have the other stuff already properly configured. Maybe I'm wrong and that's not useful, but in either case, I'd like us to discuss that in its own issue without holding up this one.Other than that, I think this is good to go.
Comment #95
wim leers🥳
I've suggested that before, so I'm totally fine with that 😊 It's @alexpott who requested during DrupalCon Lille for this to stay part of this MR to showcase it here. But, a lot has changed then, so I doubt he'd be opposed.
No, that is not enough. Observe it by applying this:
and then running
DemoUmamiProfileTest::testConfig().You'll get:
… because those in the case of
editor.image_upload_settings.0, none of those keys are defined.🤔 Oh, I thought it was the mere additional complexity in this MR, I didn't realize it'd be this.
This I disagree with. 😅
I disagree because: where do we draw the line? Are we going to allow arbitrary "default optional values" everywhere in config where something is conditionally configured? For example, would we provide default configuration for some filter plugins in the text format just in case they get enabled at some point?
Note that the values in Demo Umami already have no meaning at all, they are just the materialized-to-config-YAML values that are actually defined in the logic at
editor_image_upload_settings_form()!And that is actually supporting the point I've been making (see #66 and earlier): this is usually just meaningless noise! Not only was this config not used, it's just values that happened to be the default values at the time the config was created 🙈
I'm fine with splitting off all the
Editor-related changes to a separate issue. But then I'd prefer to extract the entirety of those changes, not just that one subset, because as I've shown above: it's neither trivially possible nor desirable. If the desirability in this particular case needs further discussion, just say so, and I'd be happy to split it off into a new issue 😊Comment #96
phenaproxima@effulgentsia confirmed in Acquia Slack that splitting off the Editor-related changes from this MR is a reasonable compromise. Self-assigning to take care of that.
Comment #97
phenaproximaOpened #3412361: Mark Editor config schema as fully validatable to land the Editor and CKEditor 5 changes separately.
Comment #98
phenaproximaOK, I reverted basically all changes in the CKEditor5 and Editor modules. I think tests oughta pass.
Restoring RTBC here because this was basically all reverts; I did not make any substantive changes.
Comment #100
effulgentsia commentedPushed to 11.x, and published the CR. The CR doesn't currently mention that requiredKey can be set to false, so that might be a good thing to add to it.
Also tagging this (not just this issue, but all the stricter validation features in aggregate) for 10.3.0 release highlights.
Comment #101
phenaproximaThanks, @effulgentsia! Good catch on the missing info from the CR; I've added that.
Comment #102
wim leersWonderful! 🥳
This allowed me to close the meta/plan issue at #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", because only one issue remains in that plan: #3408158-6: Run config validation for config schema types opted in to be fully validatable, which is now unblocked 👍
This also means that we can truly start working on #2300677: JSON:API POST/PATCH support for fully validatable config entities, after 9.5 years of being blocked 🚀 🤯
Comment #104
berdirThis causes a PHP warning when a sequence schema uses the old, deprecated but still supported format: #3437109: Undefined array key "type" in TypedConfigManager::getStaticTypeRoot()