feat: new assert notPattern (#942)

This commit is contained in:
Roman Hotsiy
2022-11-18 12:40:31 -06:00
committed by GitHub
parent 7aed12badc
commit 1d89a8de70
6 changed files with 73 additions and 7 deletions

View File

@@ -4,9 +4,9 @@ apis:
rules: rules:
assert/summary-pattern: assert/summary-pattern:
subject: subject:
type: Operation type: Operation
property: summary property: summary
message: Operation summary should have 'test' word assertions:
assertions:
pattern: '/test/' pattern: '/test/'
notPattern: /summary/

View File

@@ -5,7 +5,21 @@ exports[`E2E lint assertions-pattern-error 1`] = `
validating /openapi.yaml... validating /openapi.yaml...
[1] openapi.yaml:20:16 at #/paths/~1pet~1findByStatus/get/summary [1] openapi.yaml:20:16 at #/paths/~1pet~1findByStatus/get/summary
Operation summary should have 'test' word summary-pattern assertion failed because the Operation summary didn't meet the assertions: "summary example" should match a regex /test/
18 | get:
19 | operationId: example
20 | summary: summary example
| ^^^^^^^^^^^^^^^
21 | tags:
22 | - foo
Error was generated by the summary-pattern assertion rule.
[2] openapi.yaml:20:16 at #/paths/~1pet~1findByStatus/get/summary
summary-pattern assertion failed because the Operation summary didn't meet the assertions: "summary example" should not match a regex /summary/
18 | get: 18 | get:
19 | operationId: example 19 | operationId: example
@@ -19,7 +33,7 @@ Error was generated by the summary-pattern assertion rule.
/openapi.yaml: validated in <test>ms /openapi.yaml: validated in <test>ms
❌ Validation failed with 1 error. ❌ Validation failed with 2 errors.
run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file. run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.

View File

@@ -65,6 +65,7 @@ maxLength | integer | Asserts a maximum length (exclusive) of a string or list (
minLength | integer | Asserts a minimum length (inclusive) of a string or list (array). See [minLength example](#minlength-example). minLength | integer | Asserts a minimum length (inclusive) of a string or list (array). See [minLength example](#minlength-example).
nonEmpty | boolean | Asserts a property is not empty. See [nonEmpty example](#nonempty-example). nonEmpty | boolean | Asserts a property is not empty. See [nonEmpty example](#nonempty-example).
pattern | string | Asserts a value matches a regex pattern. See [regex pattern example](#pattern-example). pattern | string | Asserts a value matches a regex pattern. See [regex pattern example](#pattern-example).
notPattern | string | Asserts a value doesn't match a regex pattern. See [regex notPattern example](#notpattern-example).
mutuallyExclusive | [string] | Asserts that listed properties (key names only) are mutually exclusive. See [mutuallyExclusive example](#mutuallyexclusive-example). mutuallyExclusive | [string] | Asserts that listed properties (key names only) are mutually exclusive. See [mutuallyExclusive example](#mutuallyexclusive-example).
mutuallyRequired | [string] | Asserts that listed properties (key names only) are mutually required. See [mutuallyRequired example](#mutuallyrequired-example). mutuallyRequired | [string] | Asserts that listed properties (key names only) are mutually required. See [mutuallyRequired example](#mutuallyrequired-example).
ref | boolean \| string | Asserts a reference object presence in object's property. A boolean value of `true` means the property has a `$ref` defined. A boolean value of `false` means the property has not defined a `$ref` (it has an in-place value). A string value means that the `$ref` is defined and the unresolved value must match the pattern (for example, `'/paths\/. *\.yaml$/'`). See [ref example](#ref-example).| ref | boolean \| string | Asserts a reference object presence in object's property. A boolean value of `true` means the property has a `$ref` defined. A boolean value of `false` means the property has not defined a `$ref` (it has an in-place value). A string value means that the `$ref` is defined and the unresolved value must match the pattern (for example, `'/paths\/. *\.yaml$/'`). See [ref example](#ref-example).|
@@ -381,6 +382,20 @@ rules:
pattern: /test/ pattern: /test/
``` ```
### `notPattern` example
The following example asserts that the operation summary doesn't start with "The".
```yaml
rules:
assert/operation-summary-contains-test:
subject:
type: Operation
property: The summary
assertions:
notPattern: /^The/
```
### `casing` example ### `casing` example
The following example asserts the casing style is `PascalCase` for `NamedExamples` map keys. The following example asserts the casing style is `PascalCase` for `NamedExamples` map keys.

View File

@@ -43,6 +43,24 @@ describe('oas3 assertions', () => {
}); });
}); });
describe('notPattern', () => {
it('value should not match regex pattern', () => {
expect(asserts.notPattern('test string', '/test me/', baseLocation)).toEqual([]);
expect(asserts.notPattern('test string', '/test/', baseLocation)).toEqual([
{ location: baseLocation, message: '"test string" should not match a regex /test/' },
]);
expect(
asserts.notPattern(['test string', 'test me'], '/test other/', baseLocation)
).toEqual([]);
expect(asserts.notPattern(['test string', 'test me'], '/test me/', baseLocation)).toEqual([
{
message: '"test me" should not match a regex /test me/',
location: baseLocation.key(),
},
]);
});
});
describe('ref', () => { describe('ref', () => {
it('value should have ref', () => { it('value should have ref', () => {
expect(asserts.ref({ $ref: 'text' }, true, baseLocation, { $ref: 'text' })).toEqual([]); expect(asserts.ref({ $ref: 'text' }, true, baseLocation, { $ref: 'text' })).toEqual([]);

View File

@@ -18,6 +18,7 @@ export type AssertionFn = (
export type Asserts = { export type Asserts = {
pattern: AssertionFn; pattern: AssertionFn;
notPattern: AssertionFn;
enum: AssertionFn; enum: AssertionFn;
defined: AssertionFn; defined: AssertionFn;
required: AssertionFn; required: AssertionFn;
@@ -40,6 +41,7 @@ export const runOnKeysSet = new Set<keyof Asserts>([
'mutuallyRequired', 'mutuallyRequired',
'enum', 'enum',
'pattern', 'pattern',
'notPattern',
'minLength', 'minLength',
'maxLength', 'maxLength',
'casing', 'casing',
@@ -53,6 +55,7 @@ export const runOnKeysSet = new Set<keyof Asserts>([
]); ]);
export const runOnValuesSet = new Set<keyof Asserts>([ export const runOnValuesSet = new Set<keyof Asserts>([
'pattern', 'pattern',
'notPattern',
'enum', 'enum',
'defined', 'defined',
'undefined', 'undefined',
@@ -69,18 +72,33 @@ export const asserts: Asserts = {
pattern: (value: string | string[], condition: string, baseLocation: Location) => { pattern: (value: string | string[], condition: string, baseLocation: Location) => {
if (typeof value === 'undefined') return []; // property doesn't exist, no need to lint it with this assert if (typeof value === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
const values = runOnValue(value) ? [value] : value; const values = runOnValue(value) ? [value] : value;
const regx = regexFromString(condition); const regex = regexFromString(condition);
return values return values
.map( .map(
(_val) => (_val) =>
!regx?.test(_val) && { !regex?.test(_val) && {
message: `"${_val}" should match a regex ${condition}`, message: `"${_val}" should match a regex ${condition}`,
location: runOnValue(value) ? baseLocation : baseLocation.key(), location: runOnValue(value) ? baseLocation : baseLocation.key(),
} }
) )
.filter(isTruthy); .filter(isTruthy);
}, },
notPattern: (value: string | string[], condition: string, baseLocation: Location) => {
if (typeof value === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
const values = runOnValue(value) ? [value] : value;
const regex = regexFromString(condition);
return values
.map(
(_val) =>
regex?.test(_val) && {
message: `"${_val}" should not match a regex ${condition}`,
location: runOnValue(value) ? baseLocation : baseLocation.key(),
}
)
.filter(isTruthy);
},
enum: (value: string | string[], condition: string[], baseLocation: Location) => { enum: (value: string | string[], condition: string[], baseLocation: Location) => {
if (typeof value === 'undefined') return []; // property doesn't exist, no need to lint it with this assert if (typeof value === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
const values = runOnValue(value) ? [value] : value; const values = runOnValue(value) ? [value] : value;

View File

@@ -258,6 +258,7 @@ const AssertionDefinitionAssertions: NodeType = {
properties: { properties: {
enum: { type: 'array', items: { type: 'string' } }, enum: { type: 'array', items: { type: 'string' } },
pattern: { type: 'string' }, pattern: { type: 'string' },
notPattern: { type: 'string' },
casing: { casing: {
enum: [ enum: [
'camelCase', 'camelCase',