-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: allow spectral rules to be defined in the validaterc #202
Conversation
fb0b6d6
to
52e53ff
Compare
@@ -1,6 +1,6 @@ | |||
extends: [[spectral:oas, off]] | |||
formats: [oas2, oas3] | |||
functionsDir": ../functions | |||
functionsDir: ../functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surprised spectral didn't complain about that
// Combine user ruleset with the default ruleset | ||
// The default ruleset will take precendence in any rules it defines (including extended rules) | ||
const defaultRuleset = await readRuleset(defaultSpectralRulesetURI); | ||
mergeRules(spectralRuleset.rules, defaultRuleset.rules); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth trying to see if you can achieve the same merging by simply calling await spectral.loadRuleset([spectralRuleset, defaultSpectralRulesetURI]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup this does achieve the same, I wasn't confident it did at first but it seems to be working as expect! I updated the PR with this
52e53ff
to
fea6b48
Compare
fea6b48
to
47f5156
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's looking better, just a couple of more things that need to be changed. Also, if possible, some unit tests to test the validaterc changes would be helpful.
@@ -106,6 +106,30 @@ const defaults = { | |||
'schemas': { | |||
'json_or_param_binary_string': 'warning' | |||
} | |||
}, | |||
'spectral': { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: what'll happen if this new section is missing from the validaterc? Just want to ensure that not having this new section will not break current users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also need to update the init
command to generate the new section we're adding here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since i'm calling getConfigObject
if there is no spectral section in the validaterc, it will take the rules from the spectral rules from .defaultsForValidator.js
and these changes also allow the init
command to generate the new spectral section
); | ||
|
||
// Combine user ruleset with the default ruleset | ||
// The default ruleset will take precendence in any rules it defines (including extended rules) | ||
await spectral.loadRuleset([spectralRulesetURI, defaultSpectralRulesetURI]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we're using loadRuleset
we should update this to load a custom user config AND our default ruleset iff a user provided a custom ruleset file. The way you have it now, spectralRulesetURI
would just return the default ruleset if a user didn't specify a custom config so you would load the same ruleset twice.
Also, I think we should load our ruleset file first, that way we don't unintentionally override any openapi rules they might pull in their ruleset file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont think there is a problem with loading a ruleset twice since it won't create any duplicates. Currently it will load the user config and the .defaultsForSpectral.yaml
config or the .defaultsForSpectral.yaml
config twice, how should this be changed? I can change it to load the .defaultsForSpectral.yaml
first, should I make any other changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. Yes, I think we should load the .defaultsForSpectral.yaml
first
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok i updated it to load the defaults first
47f5156
to
0f8b165
Compare
@rmkeezer I'm not sure if my suggestion to use |
ae2d029
to
08722d2
Compare
@jorge-ibm Ah, apparently |
08722d2
to
5e72858
Compare
Looks like everything is working as expected now, though I still think some tests would be helpful. The only thing I think should be fixed before merging is this duplicate message displaying whenever the validaterc file isn't present:
I'm assuming we want to add documentation on how to enable/edit our spectral rules from the validaterc file. @dpopp07 what are your thoughts? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for requesting another set of changes, but I think this PR is really close.
5e72858
to
f9f1354
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good! I left a few minor comments but the primary reason I am "requesting changes" is because we need to have some tests for this behavior to ensure it does what we expect it to and to prevent regressions as we keep working on the Spectral stuff
@@ -46,7 +48,7 @@ const parseResults = function(results, debug) { | |||
}; | |||
|
|||
// setup: registers the oas2/oas3 formats, and attempts to load the ruleset file | |||
const setup = async function(spectral) { | |||
const setup = async function(spectral, defaultMode = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anywhere setup
would be called without specifying this value? It looks like the one place we call it, we provide the value. So this default seems redundant. If it can be called in other places, that's a different story!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I found it 🙂 This happens on this line but we have the default value there as a variable - so I think it would be better to pass it in that let it be potentially redefined. This should be addressed before approval
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making that change - will you remove the default value here now?
const setup = async function(spectral, defaultMode = false) { | |
const setup = async function(spectral, defaultMode) { |
); | ||
|
||
// Combine user ruleset with the default ruleset | ||
// The default ruleset will take precendence in any rules it defines (including extended rules) | ||
await spectral.loadRuleset([defaultSpectralRulesetURI, spectralRulesetURI]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this handles the case in which users define their own configuration with the Spectral format? But we don't let them override our default Spectral rules with that format?
I think this is what we want but as someone not very familiar with Spectral, I find it a little confusing. I appreciate the comments here, they are helpful, but will you add a couple more to provide context for why this is there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Matthew correct me if I'm wrong but with this PR this is what the user can do now:
- They can override the severity level and disable/enable our spectral rules from the validaterc file.
- They can add any new custom rules to the validator using a custom rule set. Even if they don't include our spectral rules in their ruleset file, our spectral rules will still be loaded.
- If a user wants to override our spectral rules using their ruleset file, they would have to:
- remove that spectral rule from the validaterc file
- add a rule with the same name to their ruleset file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Correct
- Correct
- Correct, but I realized that the omitting the spectral rule from the validaterc file did not remove it and still overrode the severity the user would specify.
This was due to the config.get() function getting the defaultObject when there was no validaterc found, this default object specified the same rules as the default validaterc and would take priority. So I added a new flag to prevent this defaultObject from overriding it so it acts as you should expect it to like 3.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds good although I'm slightly confused because if there is no validaterc file, the default object would be used, and the default object doesn't include the spectral rules. So it should be the same no spectral rules being defined, right?
4f22704
to
0b126a7
Compare
package.json
Outdated
@@ -8,7 +8,8 @@ | |||
"scripts": { | |||
"link": "npm install -g", | |||
"unlink": "npm uninstall -g", | |||
"test": "jest test/", | |||
"test": "jest test/ && jest test/spectral/tests/spectral-validator.test.js --runInBand", | |||
"test:seq": "jest test/spectral/tests/spectral-validator.test.js --runInBand", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added tests to check reading the default config .validaterc, and the user config .spectral.yaml, but to test them I had the test create a temporary config file.
However, this interferes with other tests when run in parallel, so I have this test file running sequentially after the regular tests.
Is there a better solution for tests like these or is it fine to run this file sequentially?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe try to spy on the fs package? https://stackoverflow.com/questions/50066138/mock-fs-function-with-jest . Not sure, @dpopp07 might have more experience doing something like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to write tests that don't require this step but I don't fully understand the issue yet so I'll need to look at it a little more
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting better but I think there are some simplifications to be made that I did not notice before, specifically around reading the config object. Let me know what you think
@@ -46,7 +48,7 @@ const parseResults = function(results, debug) { | |||
}; | |||
|
|||
// setup: registers the oas2/oas3 formats, and attempts to load the ruleset file | |||
const setup = async function(spectral) { | |||
const setup = async function(spectral, defaultMode = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making that change - will you remove the default value here now?
const setup = async function(spectral, defaultMode = false) { | |
const setup = async function(spectral, defaultMode) { |
package.json
Outdated
@@ -8,7 +8,8 @@ | |||
"scripts": { | |||
"link": "npm install -g", | |||
"unlink": "npm uninstall -g", | |||
"test": "jest test/", | |||
"test": "jest test/ && jest test/spectral/tests/spectral-validator.test.js --runInBand", | |||
"test:seq": "jest test/spectral/tests/spectral-validator.test.js --runInBand", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to write tests that don't require this step but I don't fully understand the issue yet so I'll need to look at it a little more
|
||
// Combine default/user ruleset with the validaterc spectral rules | ||
// The validaterc rules will take precendence in the case of duplicate rules | ||
let configObject = await config.get(defaultMode, chalk, null, true, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I think I have a way around these new arguments. The config.get
function was never meant to be called more than once, which is why we’re seeing these oddities. We already have the processed configObject, which should include the spectral rules, before this setup
method is ever called. We should just pass it in here and use that.
We can even pass in just the spectral rules and default to an empty object. If no spectral rules are passed in, we assume the defaults but let the user override
src/cli-validator/runValidator.js
Outdated
@@ -166,7 +166,7 @@ const processInput = async function(program) { | |||
// or the default ruleset | |||
const spectral = new Spectral(); | |||
try { | |||
await spectralValidator.setup(spectral); | |||
await spectralValidator.setup(spectral, defaultMode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should pass the config object directly to the spectral module so that it doesn't need to be read again. We might even be able to pass in something like configObject.spectral
to simplify the logic in the module (I'll let you sort that out).
await spectralValidator.setup(spectral, defaultMode); | |
await spectralValidator.setup(spectral, configObject); |
chalk, | ||
configFileOverride, | ||
silentMode, | ||
doNotUseDefault |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we pass the config object to the spectral module in runValidator
, we won't need to call this method again and these two new arguments are no longer needed
); | ||
|
||
// Combine user ruleset with the default ruleset | ||
// The default ruleset will take precendence in any rules it defines (including extended rules) | ||
await spectral.loadRuleset([defaultSpectralRulesetURI, spectralRulesetURI]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds good although I'm slightly confused because if there is no validaterc file, the default object would be used, and the default object doesn't include the spectral rules. So it should be the same no spectral rules being defined, right?
@@ -108,3 +112,155 @@ describe('spectral - test spectral-validator.js', function() { | |||
); | |||
}); | |||
}); | |||
|
|||
const sequential = process.argv.includes('--runInBand'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only question on the tests is - is there a way to just look at what rules spectral is ready to use after it's defined? Then we could just call the setup function and make sure it does what we want it to. I think that would be much simpler.
That said, I do like the idea of having an end-to-end test to verify the behavior we're looking for in a real life scenario. We can discuss later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was able to spyOn the config files so the end-to-end test isn't as troublesome anymore. For non-end-to-end tests, it's a bit difficult with spectral since creating a ruleset needs a file first and add/modifying rules is a bit messy using spectral's functions.
0b126a7
to
3183944
Compare
3183944
to
d722281
Compare
I updated the PR with @dpopp07 's suggestions for using the defaultObject instead of calling config.get() inside the spectral run. This worked well! I also updated the unit test to using jest.spyOn to replace the file URL and the returned config file with their respective spectral changes. So the test does not need to run sequentially anymore! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good! My last recommendation would be to add at least 1 test case that uses the commandLineValidator
vs the inCodeValidator
to invoke the validator.
const defaultMode = false; | ||
validationResults = await inCodeValidator(oas3InMemory, defaultMode); | ||
|
||
// Ensure mockConig was called and revert it to its original state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Ensure mockConig was called and revert it to its original state | |
// Ensure mockConfig was called and revert it to its original state |
// There should be no errors and 2 warnings for a rule not off | ||
it('test no errors and no warnings', function() { | ||
expect(validationResults.errors.length).toBe(0); | ||
expect(validationResults.warnings.length).toBe(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the two warnings from a non-spectral rule?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, there is a warning for Definition was declared but never used in document
, i'm clarifying the comment for this
await spectral.loadRuleset([defaultSpectralRulesetURI, spectralRulesetURI]); | ||
|
||
// Combine default/user ruleset with the validaterc spectral rules | ||
// The validaterc rules will take precendence in the case of duplicate ruless |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// The validaterc rules will take precendence in the case of duplicate ruless | |
// The validaterc rules will take precendence in the case of duplicate rules |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I left some extremely minor comments to address before merging but I think it's good otherwise
await spectral.loadRuleset([defaultSpectralRulesetURI, spectralRulesetURI]); | ||
|
||
// Combine default/user ruleset with the validaterc spectral rules | ||
// The validaterc rules will take precendence in the case of duplicate ruless |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// The validaterc rules will take precendence in the case of duplicate ruless | |
// The validaterc rules will take precendence in the case of duplicate rules |
@@ -10,6 +10,8 @@ const defaultConfig = require('../../.defaultsForValidator'); | |||
// global objects | |||
const readFile = util.promisify(fs.readFile); | |||
const defaultObject = defaultConfig.defaults; | |||
// Clear spectral rules from defaultObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes sense but I was confused at first - maybe just add a bit more info in the comment like
// Clear spectral rules from defaultObject | |
// Clear spectral rules from defaultObject, only use the explicit values in the validaterc file | |
// the default values for spectral come from another file |
a3cc44d
to
5d99f80
Compare
5d99f80
to
6b2eb7b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! 👍
# [0.31.0](v0.30.1...v0.31.0) (2020-10-15) ### Features * allow spectral rule severity to be modified in the validaterc ([#202](#202)) ([dc81079](dc81079))
🎉 This PR is included in version 0.31.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
ref: https://github.ibm.com/arf/planning-sdk-squad/issues/2170
followup of: #195
These changes allow spectral rules to have their severity level changed in the
.validatrc
and allow custom rules defined in a.spectral.yaml
file to not be overridden by the.defaultsForSpectral.yaml
. Although the.defaultsForSpectral.yaml
will still override the.spectral.yaml
severity level of any existing rule.defaultsForSpectral.yaml
defines a severity for, including the extended rules.