diff --git a/src/__tests__/__fixtures__/document-with-external-refs.json b/src/__tests__/__fixtures__/document-with-external-refs.json new file mode 100644 index 000000000..c91588f1e --- /dev/null +++ b/src/__tests__/__fixtures__/document-with-external-refs.json @@ -0,0 +1,8 @@ +{ + "todo": { + "$ref": "./models/todo-full.v1.json" + }, + "empty": { + "$ref": "" + } +} diff --git a/src/__tests__/__fixtures__/document-with-external-refs.oas2.json b/src/__tests__/__fixtures__/document-with-external-refs.oas2.json deleted file mode 100644 index a7ccd7298..000000000 --- a/src/__tests__/__fixtures__/document-with-external-refs.oas2.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "title": "To-dos", - "version": "1.0", - "description": "This OpenAPI v2 (Swagger) file represents a real API that lives at http://todos.stoplight.io.\n\nIt exposes functionality to manage to-do lists." - }, - "host": "todos.stoplight.io", - "schemes": ["http"], - "paths": { - "/todos/{todoId}": { - "get": { - "operationId": "GET_todo", - "summary": "Get Todo", - "tags": ["Todos"], - "responses": { - "200": { - "description": "", - "schema": { - "$ref": "./models/todo-full.v1.json" - }, - "examples": { - "application/json": { - "id": 1, - "name": "get food", - "completed": false, - "completed_at": "1955-04-23T13:22:52.685Z", - "created_at": "1994-11-05T03:26:51.471Z", - "updated_at": "1989-07-29T11:30:06.701Z" - } - } - } - } - } - } - } -} diff --git a/src/__tests__/spectral.jest.test.ts b/src/__tests__/spectral.jest.test.ts index fa816aa32..1a39ed297 100644 --- a/src/__tests__/spectral.jest.test.ts +++ b/src/__tests__/spectral.jest.test.ts @@ -5,7 +5,6 @@ import * as nock from 'nock'; import * as path from 'path'; import { Document } from '../document'; -import { isOpenApiv2 } from '../formats'; import { pattern } from '../functions/pattern'; import * as Parsers from '../parsers'; import { httpAndFileResolver } from '../resolvers/http-and-file'; @@ -97,93 +96,57 @@ describe('Spectral', () => { }); test('should report issues for correct files with correct ranges and paths', async () => { - const documentUri = normalize(path.join(__dirname, './__fixtures__/document-with-external-refs.oas2.json')); + const documentUri = normalize(path.join(__dirname, './__fixtures__/document-with-external-refs.json')); const spectral = new Spectral({ resolver: httpAndFileResolver }); - await spectral.loadRuleset('spectral:oas'); - spectral.registerFormat('oas2', isOpenApiv2); + spectral.setRules({ + 'requires-type': { + given: ['$..allOf', '$.empty'], + then: { + field: 'type', + function: 'truthy', + }, + }, + }); const document = new Document(fs.readFileSync(documentUri, 'utf8'), Parsers.Json, documentUri); const results = await spectral.run(document); - expect(results).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - code: 'oas2-schema', - path: ['paths', '/todos/{todoId}', 'get', 'responses', '200', 'description'], - range: { - end: { - character: 29, - line: 17, - }, - start: { - character: 27, - line: 17, - }, - }, - source: documentUri, - }), - expect.objectContaining({ - code: 'oas2-schema', - path: [], - range: { - end: { - character: 1, - line: 37, - }, - start: { - character: 0, - line: 0, - }, + expect(results).toEqual([ + { + code: 'requires-type', + message: '`empty.type` property is not truthy', + path: ['empty'], + range: { + end: { + character: 3, + line: 6, }, - source: expect.stringContaining('__fixtures__/models/todo-full.v1.json'), - }), - expect.objectContaining({ - code: 'path-params', - path: ['paths', '/todos/{todoId}', 'get'], - range: { - end: { - character: 7, - line: 33, - }, - start: { - character: 13, - line: 11, - }, + start: { + character: 11, + line: 4, }, - source: documentUri, - }), - expect.objectContaining({ - code: 'info-contact', - path: ['info'], - range: { - end: { - character: 3, - line: 6, - }, - start: { - character: 10, - line: 2, - }, + }, + severity: DiagnosticSeverity.Warning, + source: documentUri, + }, + { + code: 'requires-type', + message: '`allOf.type` property is not truthy', + path: ['allOf'], + range: { + end: { + character: 3, + line: 33, }, - source: documentUri, - }), - expect.objectContaining({ - code: 'operation-description', - path: ['paths', '/todos/{todoId}', 'get'], - range: { - end: { - character: 7, - line: 33, - }, - start: { - character: 13, - line: 11, - }, + start: { + character: 11, + line: 3, }, - source: documentUri, - }), - ]), - ); + }, + severity: DiagnosticSeverity.Warning, + source: expect.stringContaining('__fixtures__/models/todo-full.v1.json'), + }, + ]); }); test('properly decorates results with metadata pertaining to the document being linted', async () => { diff --git a/src/documentInventory.ts b/src/documentInventory.ts index 31eae63a3..c9c35b7a6 100644 --- a/src/documentInventory.ts +++ b/src/documentInventory.ts @@ -109,12 +109,21 @@ export class DocumentInventory { if ($ref === null) return null; const scopedPath = [...safePointerToPath($ref), ...newPath]; - let resolvedDoc; + let resolvedDoc = this.document; if (isLocalRef($ref)) { resolvedDoc = source === this.document.source ? this.document : this.referencedDocuments[source]; } else { - const extractedSource = extractSourceFromRef($ref)!; + const extractedSource = extractSourceFromRef($ref); + + if (extractedSource === null) { + return { + document: resolvedDoc, + path: getClosestJsonPath(resolvedDoc.data, path), + missingPropertyPath: path, + }; + } + source = isAbsoluteRef(extractedSource) ? extractedSource : resolve(source, '..', extractedSource); resolvedDoc = source === this.document.source ? this.document : this.referencedDocuments[source];