fix: setValidatorCompiler with addSchema#5188
Conversation
|
for #5178 |
|
Nice! Good, now all the magic happens around here: fastify/lib/schema-controller.js Lines 107 to 113 in 84ad944 Here is where we set the Validator Compiler, which is the function you are testing in your test case. |
|
@metcoder95 correct. I was thinking it should just check my analysis: The test cases unfortunately don't actually use any schema extensions and just succeed because the default validator works totally fine as long as you don't extend JSON schema itself. So that's why it never got detected that it does nothing in these cases. |
|
@Eomm yes it would make the problem go away, but I believe it also disables the functionality of recreating the validatorCompiler if schemas are added. Note the second part of the
[edit: what I said was wrong. the boolean |
|
Ok I will take a stab at developing a minimal fix, since now I am invested. Easier to discuss when we are looking at code... |
checking that boolean seems more correct 👍🏼
I would keep it minimal.
Correct, but when the user set a custom validator using |
|
I can remove the very verbose comment from the code after we discuss :) |
|
Basically the ugly part is that one mechanism (schema controller) does have separate references to the factory (builder) and the compiler, and the other mechanism (setSchemaValidator) doesn't. So we can't actually recreate the compiler in that case, which is reflected in the now quite ugly conditional. But it is the truth... |
For your discussion: as a new user of the framework, I would have much preferred if |
lib/schema-controller.js
Outdated
| // WARNING: isCustomValidatorCompiler will be true if there is a custom builder OR if there is a custom compiler | ||
| // installed directly via setValidatorCompiler, leading to this messy condition. Compilers installed via | ||
| // setValidatorCompiler will not be invalidated and recreated if schemas are added. | ||
| const isReady = this.validatorCompiler !== undefined && (this.isCustomValidatorCompiler || !this.addedSchemas) |
There was a problem hiding this comment.
For your discussion: as a new user of the framework, I would have much preferred if
setSchemaValidatordidn't exist or was marked deprecated and bad, because it is unsafe and trying to use it wasted a bunch of time. If I had found the other mechanism was there (schema controller opts) then I would have just used that. Unfortunately sample code and google hits made me find the old ("easy" but also dangerous) mechanism and I didn't know it.
@Eomm is there any plan to drop this API in v5?
So far I believe it should be ok to do this; we just need to verify the risk is documented somewhere.
There was a problem hiding this comment.
@Eomm this is resolved from my point of view since the "legacy" API no longer breaks after this fix, so I don't care if you deprecate it or not :) but it seems more like something for you to track.
i.e. I think this is for you to resolve.
|
Found a mistake in the change I made. It would also no longer recreate compilers if you use a custom builder. I will try again. |
|
Here's a test from
[edit: never mind, figured it out; I did not understand register creates a new instance for the plugin, which has its own child schema controller] |
|
ok new version is up, it is all green. I did not squash and rebase since there were discussions on the previous commits. Would you prefer I squash and rebase to have a single commit for discussion? |
Sure, plain merge or rebase works |
|
unrelated: are the unnamed (arrow) functions I introduced to make the compiler function into a builder causing the coverage check failures? do I need to tag them somehow as "these don't count?" [update: yes that was the problem. I can resolve it by making them into something more disgusting like a bound named function, if you're ok with that. (see 7aa414a)] |
| this.compilersFactory = Object.assign( | ||
| {}, | ||
| this.compilersFactory, |
There was a problem hiding this comment.
| this.compilersFactory = Object.assign( | |
| {}, | |
| this.compilersFactory, | |
| Object.assign( | |
| this.compilersFactory, |
There was a problem hiding this comment.
@Eomm In this context, this.compilersFactory is an alias to this.parent.compilersFactory. So using it as the first argument of Object.assign() will clobber the parent's compilersFactory. I only figured that out after trying the same thing and having the excellent test cases fail :)
There was a problem hiding this comment.
It is also written 😂
Anyway: could you add a test named "the setValidatorCompiler overwrites the compilersFactory validator"?
I think we don't have this use case.
Then LGTM 👍🏼
There was a problem hiding this comment.
@Eomm I wrote some tests. Can you please take a look? Do we agree the last one should succeed? (because it fails currently)
In the "backwards" case where the child instance has a custom builder, it does not work. It crashes out throwing that the extended schema is invalid, meaning
fastify/test/schema-validation.test.js
Line 1167 in f2b165c
There was a problem hiding this comment.
PS: I changed the style of the tests a bit to actually USE custom schema instead of just testing that the compilers get called, because I felt at least some of the tests should make sure the resulting compiled schema validators are used at the right times.
There was a problem hiding this comment.
update: test case being discussed here is temporarily commented out
There was a problem hiding this comment.
Can you please take a look? Do we agree the last one should succeed? (because it fails currently)
Yes and no 😄
The issue is that:
setValidatorCompileris sync in the sense that it setsthis.validatorCompilerwhen it runs- meanwhile
setSchemaControlleris totally async and related to the applicationtion loadings, so I think they will fight for sure
To fix the test, I moved the setValidatorCompiler after the plugin registration, this ensures all the events are processed in order and the test passes.
I don't think we have a golden bullet here to make it work in a different way, because we can't execute buildValidator logic at that time, because the user may run an addSchema later!
It is a chicken and egg issue this one.
There was a problem hiding this comment.
To fix the test, I moved the
setValidatorCompilerafter the plugin registration, this ensures all the events are processed in order and the test passes.I don't think we have a golden bullet here to make it work in a different way, because we can't execute
buildValidatorlogic at that time, because the user may run anaddSchemalater! It is a chicken and egg issue this one.
Yeah I also suspected it would be questionable what "correct" behavior would be. One thing I was considering is that the user's code order should decide. In other words, when you call setSchemaController, it should synchronously delete the installed custom compilers, so that they will be asynchronously recreated on the next setupValidator and setupSerializer. In other words, they would behave as if they synchronously compete, so the behavior matches the user's code order?
I just wasn't sure if that would work, so I figured we should discuss.
custom validator compilers installed directly via setValidatorCompiler were removed on server start if any schema was previously added via addSchema new approach is to register any compiler from setValidatorCompiler or setSerializerCompiler as a custom builder, so everything is handled consistently implementation note: schema controllers by default alias their parent's compilers factory collection so you have to clone it to make local changes tests for 5178 tests for custom validator replacement test for setSerializerCompiler with addSchema
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
test that validation compiler is called if schemas registered, tests for issue 5178
expected to fail with current code:
npx tap test/schema-validation.test.js -g 'External AJV instance'