Skip to content

Commit

Permalink
feat: validate x-sdk-operations extension
Browse files Browse the repository at this point in the history
Changes:
- add custom Spectral rule to validate x-sdk-operations
- add custom Spectral function to invoke ajv JSON Schema validator on x-sdk-operations JSON Schema

Tests:
- ensure warnings issued for invalid x-sdk-operations schema

Docs:
- document `ibm-sdk-operations` rule
  • Loading branch information
Barrett Schonefeld committed Mar 31, 2021
1 parent fd34717 commit 6a49b38
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 2 deletions.
6 changes: 6 additions & 0 deletions docs/spectral-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ responses:

**Default Severity**: warn

## ibm-sdk-operations

Validates the structure of the `x-sdk-operations` object.

**Default Severity**: warn

## response-error-response-schema

`4xx` and `5xx` error responses should provide good information to help the user resolve the error. The error response validations are based on the design principles outlined in the [errors section of the IBM API Handbook](https://cloud.ibm.com/docs/api-handbook?topic=api-handbook-errors). The `response-error-response-schema` rule is more lenient than what is outlined in the handbook. Specifically, the `response-error-response-schema` rule does not require an Error Container Model and allows for a single Error Model to be provided at the top level of the error response schema or in an `error` field.
Expand Down
14 changes: 12 additions & 2 deletions src/spectral/rulesets/.defaultsForSpectral.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ extends: spectral:oas
functionsDir: './ibm-oas'
functions:
- error-response-schema
- ibm-sdk-operations
- response-example-provided
rules:

Expand Down Expand Up @@ -101,8 +102,8 @@ rules:
content-entry-provided:
description: Request bodies and non-204 responses should define a content object
given:
- $.paths[*][*].responses[?(@property != '204')]
- $.paths[*][*].requestBody
- $.paths[*][*].responses[?(@property != '204')]
- $.paths[*][*].requestBody
severity: warn
formats: ["oas3"]
resolved: true
Expand All @@ -120,6 +121,15 @@ rules:
then:
field: schema
function: truthy
# custom Spectral rule to ensure valid x-sdk-operations schema
ibm-sdk-operations:
message: "{{error}}"
given: $.paths[*][*][x-sdk-operations]
severity: warn
formats: ["oas3"]
resolved: true
then:
function: ibm-sdk-operations
# custom Spectral rule to ensure response example provided
response-example-provided:
message: "{{error}}"
Expand Down
118 changes: 118 additions & 0 deletions src/spectral/rulesets/ibm-oas/ibm-sdk-operations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const AJV = require('ajv');
const ajv = new AJV({ allErrors: true, jsonPointers: true });
const jsonSchema = {
$schema: 'http://json-schema.org/draft-07/schema#',
title: 'IBM SDK Operations Extension',
description: 'sdk operations extension schema',
properties: {
'x-sdk-operations': {
properties: {
'request-examples': {
additionalProperties: {
$ref: '#/definitions/x-sdk-request-examples-array'
}
}
},
additionalProperties: false
}
},
definitions: {
'x-sdk-request-examples-array': {
type: 'array',
items: {
$ref: '#/definitions/x-sdk-request-example'
}
},
'x-sdk-request-example': {
properties: {
name: {
type: 'string',
description:
'The name or title of the example. In documentation it should appear as a header above the example.'
},
contentType: {
type: 'array',
items: {
type: 'string',
description: 'The media type of the response in this example.'
}
},
example: {
type: 'array',
description:
'An array of code or text elements that make up the example',
items: {
$ref: '#/definitions/x-sdk-request-example-element'
}
},
response: {},
description: {
type: 'string'
}
},
required: ['name', 'example'],
additionalProperties: false
},
'x-sdk-request-example-element': {
description: 'An element of the request example.',
properties: {
name: {
type: 'string',
description:
'The name or title of the example element. In documentation it should appear as a header above the example element.'
},
type: {
type: 'string',
description:
'The element type indicates the type of content in the element. `text` elements contain a textual description or explanation, possibly using markdown for rich text elements. `code` elements contain code appropriate for the language of the request example. `code` elements will be presented in a `<pre><code>` block in documentation and should contain no markup or escapes other than escapes for quote and backslash (required for JSON).',
enum: ['text', 'code']
},
source: {
type: 'array',
items: {
type: 'string'
},
description:
'Content of the example element as an array of strings. The content is formed by simple concatenation of the array elements.'
}
},
required: ['type', 'source']
}
}
};

module.exports = function(sdkOperations, _opts, paths) {
const errors = [];
const sdkSchema = {
'x-sdk-operations': sdkOperations
};
const rootPath = paths.target !== void 0 ? paths.target : paths.given;
const validate = ajv.compile(jsonSchema);
if (!ajv.validate(jsonSchema, sdkSchema)) {
return formatAJVErrors(validate.errors, rootPath);
}
return errors;
};

function formatAJVErrors(errors, rootPath) {
const errorList = [];
errors.forEach(function(err) {
errorList.push({
message: err.message,
path: getErrPath(err, rootPath)
});
});
return errorList;
}

function getErrPath(err, rootPath) {
const relativePath = err.dataPath.split('/');
if (relativePath.length > 1) {
const strippedPath = relativePath.splice(
relativePath.indexOf('x-sdk-operations') + 1,
relativePath.length
);
return [...rootPath, ...strippedPath];
}
return rootPath;
}
53 changes: 53 additions & 0 deletions test/spectral/tests/custom-rules/ibm-sdk-operations.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const inCodeValidator = require('../../../../src/lib');

describe('spectral - test that x-sdk-operations schema violations cause errors', function() {
let res;

beforeAll(async () => {
const spec = {
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'ErrorAPI'
},
servers: [{ url: 'http://api.errorapi.com/v1' }],
paths: {
path1: {
post: {
'x-sdk-operations': {
'request-examples': {
type: 13,
notafield: 'asdf'
}
}
}
}
}
};

res = await inCodeValidator(spec, true);
});

it('should warn for invalid x-sdk-operations schema', function() {
const expectedWarnings = res.warnings.filter(
warn => warn.rule === 'ibm-sdk-operations'
);
expect(expectedWarnings.length).toBe(2);
expect(expectedWarnings[0].path).toEqual([
'paths',
'path1',
'post',
'x-sdk-operations',
'request-examples',
'type'
]);
expect(expectedWarnings[1].path).toEqual([
'paths',
'path1',
'post',
'x-sdk-operations',
'request-examples',
'notafield'
]);
});
});

0 comments on commit 6a49b38

Please sign in to comment.