Skip to content
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

Merged
merged 1 commit into from
Oct 15, 2020

Conversation

rmkeezer
Copy link
Contributor

@rmkeezer rmkeezer commented Oct 2, 2020

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.

@rmkeezer rmkeezer force-pushed the spectral_config_refactor branch 2 times, most recently from fb0b6d6 to 52e53ff Compare October 2, 2020 16:53
@@ -1,6 +1,6 @@
extends: [[spectral:oas, off]]
formats: [oas2, oas3]
functionsDir": ../functions
functionsDir: ../functions
Copy link
Contributor

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);
Copy link
Contributor

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]);

Copy link
Contributor Author

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

Copy link
Contributor

@jorge-ibm jorge-ibm left a 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': {
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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]);
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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

Copy link
Contributor Author

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

@jorge-ibm
Copy link
Contributor

@rmkeezer I'm not sure if my suggestion to use loadRuleset vs mergeRules broke something, but I pulled your fork locally and wasn't able to overwrite the severity level of our rules using the validaterc file.

@rmkeezer rmkeezer force-pushed the spectral_config_refactor branch 2 times, most recently from ae2d029 to 08722d2 Compare October 6, 2020 18:37
@rmkeezer
Copy link
Contributor Author

rmkeezer commented Oct 6, 2020

@jorge-ibm Ah, apparently spectral.mergeRules does not seem to work at all, but using the mergeRules function from the spectral ruleset does. I've updated the PR with that change, it should now correctly override the severity again.

@jorge-ibm
Copy link
Contributor

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:

JorgeWoComputer:openapi-validator-rkeezer jrangel$ lint-openapi -p test/spectral/mockFiles/oas3/enabled-rules.yml 
[Warning] No .validaterc file found. The validator will run in default mode.
To configure the validator, create a .validaterc file.
[Warning] No .validaterc file found. The validator will run in default mode.
To configure the validator, create a .validaterc file.

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?

Copy link
Contributor

@jorge-ibm jorge-ibm left a 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.

Copy link
Contributor

@jorge-ibm jorge-ibm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I'll approve but we should hold off on merging until @dpopp07 or @mkistler review it as well

Copy link
Member

@dpopp07 dpopp07 left a 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

src/cli-validator/utils/processConfiguration.js Outdated Show resolved Hide resolved
@@ -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) {
Copy link
Member

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!

Copy link
Member

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

Copy link
Member

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?

Suggested change
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]);
Copy link
Member

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?

Copy link
Contributor

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:

  1. They can override the severity level and disable/enable our spectral rules from the validaterc file.
  2. 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.
  3. 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Correct
  2. Correct
  3. 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.

Copy link
Member

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?

@rmkeezer rmkeezer force-pushed the spectral_config_refactor branch 2 times, most recently from 4f22704 to 0b126a7 Compare October 12, 2020 15:46
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",
Copy link
Contributor Author

@rmkeezer rmkeezer Oct 12, 2020

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?

Copy link
Contributor

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.

Copy link
Member

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

@rmkeezer rmkeezer requested a review from dpopp07 October 12, 2020 17:25
Copy link
Member

@dpopp07 dpopp07 left a 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) {
Copy link
Member

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?

Suggested change
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",
Copy link
Member

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);
Copy link
Member

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

@@ -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);
Copy link
Member

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).

Suggested change
await spectralValidator.setup(spectral, defaultMode);
await spectralValidator.setup(spectral, configObject);

chalk,
configFileOverride,
silentMode,
doNotUseDefault
Copy link
Member

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]);
Copy link
Member

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');
Copy link
Member

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

Copy link
Contributor Author

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.

@rmkeezer
Copy link
Contributor Author

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!

Copy link
Contributor

@jorge-ibm jorge-ibm left a 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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);
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// The validaterc rules will take precendence in the case of duplicate ruless
// The validaterc rules will take precendence in the case of duplicate rules

Copy link
Member

@dpopp07 dpopp07 left a 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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
Copy link
Member

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

Suggested change
// 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

@rmkeezer rmkeezer force-pushed the spectral_config_refactor branch 2 times, most recently from a3cc44d to 5d99f80 Compare October 15, 2020 15:07
Copy link
Contributor

@mkistler mkistler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! 👍

@rmkeezer rmkeezer merged commit dc81079 into IBM:master Oct 15, 2020
dpopp07 pushed a commit that referenced this pull request Oct 15, 2020
# [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))
@dpopp07
Copy link
Member

dpopp07 commented Oct 15, 2020

🎉 This PR is included in version 0.31.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants