Skip to content

Commit

Permalink
feat(ls): add linting rule for OAS server variable enum and default
Browse files Browse the repository at this point in the history
Refs #2787
Refs #2788
  • Loading branch information
frantuma committed Jul 31, 2023
1 parent cf1df55 commit f40f398
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/apidom-ls/src/config/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ enum ApilintCodes {
OPENAPI3_O_SERVER_VARIABLE_FIELD_ENUM_NON_EMPTY,
OPENAPI3_O_SERVER_VARIABLE_FIELD_DEFAULT_TYPE = 5080200,
OPENAPI3_O_SERVER_VARIABLE_FIELD_DEFAULT_REQUIRED,
OPENAPI3_O_SERVER_VARIABLE_FIELD_DEFAULT_IN_ENUM,
OPENAPI3_O_SERVER_VARIABLE_FIELD_DESCRIPTION_TYPE = 5080300,

OPENAPI3_0_PATHS = 5090000,
Expand Down Expand Up @@ -921,6 +922,7 @@ enum ApilintCodes {

OPENAPI3_1_SERVER_VARIABLE = 7060000,
OPENAPI3_1_SERVER_VARIABLE_FIELD_ENUM_TYPE = 7060100,
OPENAPI3_1_SERVER_VARIABLE_FIELD_DEFAULT_IN_ENUM,

OPENAPI3_1_PARAMETER = 7070000,
OPENAPI3_1_PARAMETER_FIELD_CONTENT_ALLOWED_FIELDS = 7070100,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes';
import { LinterMeta } from '../../../../apidom-language-types';

// eslint-disable-next-line @typescript-eslint/naming-convention
const defaultInEnum3_0Lint: LinterMeta = {
code: ApilintCodes.OPENAPI3_O_SERVER_VARIABLE_FIELD_DEFAULT_IN_ENUM,
source: 'apilint',
message: "'default' value should exist in the enum's values.",
severity: DiagnosticSeverity.Warning,
linterFunction: 'apilintIncludedInArray',
linterParams: ['parent.enum', false],
marker: 'value',
target: 'default',
data: {},
targetSpecs: [
{ namespace: 'openapi', version: '3.0.0' },
{ namespace: 'openapi', version: '3.0.1' },
{ namespace: 'openapi', version: '3.0.2' },
{ namespace: 'openapi', version: '3.0.3' },
],
};

export default defaultInEnum3_0Lint;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes';
import { LinterMeta } from '../../../../apidom-language-types';

// eslint-disable-next-line @typescript-eslint/naming-convention
const defaultInEnum3_1Lint: LinterMeta = {
code: ApilintCodes.OPENAPI3_1_SERVER_VARIABLE_FIELD_DEFAULT_IN_ENUM,
source: 'apilint',
message: "'default' value must exist in the enum's values.",
severity: DiagnosticSeverity.Error,
linterFunction: 'apilintIncludedInArray',
linterParams: ['parent.enum', false],
marker: 'value',
target: 'default',
data: {},
targetSpecs: [{ namespace: 'openapi', version: '3.1.0' }],
};

export default defaultInEnum3_1Lint;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const enumNonEmpty3_0Lint: LinterMeta = {
source: 'apilint',
message: "'enum' array should not be empty.",
severity: DiagnosticSeverity.Warning,
linterFunction: 'apilintArrayEmpty',
linterFunction: 'apilintArrayNotEmpty',
linterParams: [],
marker: 'key',
target: 'enum',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import enumType3_0Lint from './enum--type-3-0';
import enumNonEmpty3_0Lint from './enum--non-empty-3-0';
import enumType3_1Lint from './enum--type-3-1';
import defaultTypeLint from './default--type';
import defaultInEnum3_0Lint from './default--in-enum-3-0';
import defaultInEnum3_1Lint from './default--in-enum-3-1';
import defaultRequiredLint from './default--required';
import descriptionTypeLint from './description--type';

const lints = [
enumType3_0Lint,
enumNonEmpty3_0Lint,
enumType3_1Lint,
defaultInEnum3_0Lint,
defaultInEnum3_1Lint,
defaultTypeLint,
defaultRequiredLint,
descriptionTypeLint,
Expand Down
21 changes: 20 additions & 1 deletion packages/apidom-ls/src/services/validation/linter-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ export const standardLinterfunctions: FunctionItem[] = [
},
},
{
functionName: 'apilintArrayEmpty',
functionName: 'apilintArrayNotEmpty',
function: (element: Element): boolean => {
if (element) {
const elValue = element.toValue();
Expand Down Expand Up @@ -972,4 +972,23 @@ export const standardLinterfunctions: FunctionItem[] = [
return true;
},
},
{
functionName: 'apilintIncludedInArray',
function: (element: Element, path: string, arrayMustExist: boolean): boolean => {
if (element && (isString(element) || isNumber(element))) {
const api = root(element);

const targetEl = processPath(element, path, api);
if (!targetEl) {
return !arrayMustExist;
}
const isIncluded =
(targetEl as ArrayElement).findElements((e) => e.toValue() === element.toValue(), {
recursive: false,
}).length > 0;
return isIncluded;
}
return true;
},
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
openapi: 3.0.2
info:
title: test
version: 1.0.0
servers:
- url: https://{host}:{port}/{basePath}/{otherPath}
description: Live Bookstore dynamic servers
variables:
host:
default: test.book.com
enum:
- test.book.com
- staging.book.com
- sandbox.book.com
- production.book.com
port:
default: '443'
enum: []
basePath:
default: v2.0
enum:
- v1.0
- v1.1
- v1.2
- v1.3
otherPath:
default: foo
paths:
/a:
get:
operationId: aget
responses:
'200':
description: test
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
openapi: 3.1.0
info:
title: test
version: 1.0.0
servers:
- url: https://{host}:{port}/{basePath}/{otherPath}
description: Live Bookstore dynamic servers
variables:
host:
default: test.book.com
enum:
- test.book.com
- staging.book.com
- sandbox.book.com
- production.book.com
port:
default: '443'
enum: []
basePath:
default: v2.0
enum:
- v1.0
- v1.1
- v1.2
- v1.3
otherPath:
default: foo
paths:
/a:
get:
operationId: aget
104 changes: 104 additions & 0 deletions packages/apidom-ls/test/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3203,4 +3203,108 @@ describe('apidom-ls-validate', function () {

languageService.terminate();
});

it('oas / yaml - test server variable default and enum', async function () {
const validationContext: ValidationContext = {
comments: DiagnosticSeverity.Error,
maxNumberOfProblems: 100,
relatedInformation: false,
};

const spec = fs
.readFileSync(path.join(__dirname, 'fixtures', 'validation', 'oas', 'server-variables.yaml'))
.toString();
const doc: TextDocument = TextDocument.create(
'foo://bar/server-variables.yaml',
'yaml',
0,
spec,
);

const languageService: LanguageService = getLanguageService(contextNoSchema);

const result = await languageService.doValidation(doc, validationContext);
const expected: Diagnostic[] = [
{
range: { start: { line: 15, character: 6 }, end: { line: 15, character: 10 } },
message: "'enum' must be a non-empty array of strings",
severity: 1,
code: 7060100,
source: 'apilint',
data: {},
},
{
range: { start: { line: 16, character: 17 }, end: { line: 16, character: 22 } },
message: "'default' value must exist in the enum's values.",
severity: 1,
code: 7060101,
source: 'apilint',
data: {},
},
{
range: { start: { line: 19, character: 17 }, end: { line: 19, character: 21 } },
message: "'default' value must exist in the enum's values.",
severity: 1,
code: 7060101,
source: 'apilint',
data: {},
},
];
assert.deepEqual(result, expected as Diagnostic[]);

languageService.terminate();
});

it('oas / yaml - test server variable default and enum 3.0', async function () {
const validationContext: ValidationContext = {
comments: DiagnosticSeverity.Error,
maxNumberOfProblems: 100,
relatedInformation: false,
};

const spec = fs
.readFileSync(
path.join(__dirname, 'fixtures', 'validation', 'oas', 'server-variables-3-0.yaml'),
)
.toString();
const doc: TextDocument = TextDocument.create(
'foo://bar/server-variables.yaml',
'yaml',
0,
spec,
);

const languageService: LanguageService = getLanguageService(contextNoSchema);

const result = await languageService.doValidation(doc, validationContext);
const expected: Diagnostic[] = [
{
range: { start: { line: 15, character: 6 }, end: { line: 15, character: 10 } },
message: "'enum' array should not be empty.",
severity: 2,
code: 5080101,
source: 'apilint',
data: {},
},
{
range: { start: { line: 16, character: 17 }, end: { line: 16, character: 22 } },
message: "'default' value should exist in the enum's values.",
severity: 2,
code: 5080202,
source: 'apilint',
data: {},
},
{
range: { start: { line: 19, character: 17 }, end: { line: 19, character: 21 } },
message: "'default' value should exist in the enum's values.",
severity: 2,
code: 5080202,
source: 'apilint',
data: {},
},
];
assert.deepEqual(result, expected as Diagnostic[]);

languageService.terminate();
});
});

0 comments on commit f40f398

Please sign in to comment.