Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4c4e92e

Browse files
committedJul 1, 2021
Switch oas-validator to @stoplightio/spectra
Most tests are passing but some are still failing.
1 parent 3e2eb50 commit 4c4e92e

13 files changed

+133
-93
lines changed
 

‎packages/openapi-to-graphql/lib/index.js

+3-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/openapi-to-graphql/lib/index.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/openapi-to-graphql/lib/oas_3_tools.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export declare function methodToHttpMethod(method: string): HTTP_METHODS;
5252
* Resolves on a validated OAS 3 for the given spec (OAS 2 or OAS 3), or rejects
5353
* if errors occur.
5454
*/
55-
export declare function getValidOAS3(spec: Oas2 | Oas3, oasValidatorOptions: object, swagger2OpenAPIOptions: object): Promise<Oas3>;
55+
export declare function getValidOAS3(spec: Oas2 | Oas3, swagger2OpenAPIOptions: object): Promise<Oas3>;
5656
/**
5757
* Counts the number of operations in an OAS.
5858
*/

‎packages/openapi-to-graphql/lib/oas_3_tools.js

+15-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/openapi-to-graphql/lib/oas_3_tools.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/openapi-to-graphql/lib/types/options.d.ts

-8
Original file line numberDiff line numberDiff line change
@@ -240,14 +240,6 @@ export declare type InternalOptions<TSource, TContext, TArgs> = {
240240
* header.
241241
*/
242242
sendOAuthTokenInQuery: boolean;
243-
/**
244-
* We use the oas-validator library to validate Swaggers/OASs.
245-
*
246-
* We expose the options so that users can have more control over validation.
247-
*
248-
* Based on: https://github.com/Mermade/oas-kit/blob/master/docs/options.md
249-
*/
250-
oasValidatorOptions: object;
251243
/**
252244
* We use the swagger2graphql library to translate Swaggers to OASs.
253245
*

‎packages/openapi-to-graphql/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@
6666
"testRegex": "/test/.*\\.test\\.(ts|tsx|js)$"
6767
},
6868
"dependencies": {
69+
"@stoplight/spectral": "^5.9.1",
6970
"debug": "^4.2.0",
7071
"deep-equal": "^2.0.1",
7172
"form-urlencoded": "^4.2.1",
7273
"graphql-subscriptions": "^1.1.0",
7374
"graphql-type-json": "^0.3.2",
7475
"json-ptr": "^1.3.1",
7576
"jsonpath-plus": "^4.0.0",
76-
"oas-validator": "^5.0.2",
7777
"pluralize": "^8.0.0",
7878
"request": "^2.88.0",
7979
"swagger2openapi": "^7.0.2"

‎packages/openapi-to-graphql/src/index.ts

+1-9
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ const DEFAULT_OPTIONS: InternalOptions<any, any, any> = {
116116
sendOAuthTokenInQuery: false,
117117

118118
// Validation options
119-
oasValidatorOptions: {},
120119
swagger2OpenAPIOptions: {},
121120

122121
// Logging options
@@ -144,7 +143,6 @@ export function createGraphQLSchema<TSource, TContext, TArgs>(
144143
spec.map((ele) => {
145144
return Oas3Tools.getValidOAS3(
146145
ele,
147-
internalOptions.oasValidatorOptions,
148146
internalOptions.swagger2OpenAPIOptions
149147
)
150148
})
@@ -161,11 +159,7 @@ export function createGraphQLSchema<TSource, TContext, TArgs>(
161159
* If the spec is OAS 2.0, attempt to translate it into 3, then try to
162160
* translate the spec into a GraphQL schema
163161
*/
164-
Oas3Tools.getValidOAS3(
165-
spec,
166-
internalOptions.oasValidatorOptions,
167-
internalOptions.swagger2OpenAPIOptions
168-
)
162+
Oas3Tools.getValidOAS3(spec, internalOptions.swagger2OpenAPIOptions)
169163
.then((oas) => {
170164
resolve(translateOpenAPIToGraphQL([oas], internalOptions))
171165
})
@@ -212,7 +206,6 @@ function translateOpenAPIToGraphQL<TSource, TContext, TArgs>(
212206
sendOAuthTokenInQuery,
213207

214208
// Validation options
215-
oasValidatorOptions,
216209
swagger2OpenAPIOptions,
217210

218211
// Logging options
@@ -251,7 +244,6 @@ function translateOpenAPIToGraphQL<TSource, TContext, TArgs>(
251244
sendOAuthTokenInQuery,
252245

253246
// Validation options
254-
oasValidatorOptions,
255247
swagger2OpenAPIOptions,
256248

257249
// Logging options

‎packages/openapi-to-graphql/src/oas_3_tools.ts

+70-49
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ import { InternalOptions } from './types/options'
3535

3636
// Imports:
3737
import * as Swagger2OpenAPI from 'swagger2openapi'
38-
import * as OASValidator from 'oas-validator'
38+
import {
39+
Spectral as OASValidator,
40+
isOpenApiv2,
41+
isOpenApiv3
42+
} from '@stoplight/spectral'
3943
import debug from 'debug'
4044
import { handleWarning, MitigationTypes } from './utils'
4145
import * as jsonptr from 'json-ptr'
@@ -131,7 +135,6 @@ export function methodToHttpMethod(method: string): HTTP_METHODS {
131135
*/
132136
export function getValidOAS3(
133137
spec: Oas2 | Oas3,
134-
oasValidatorOptions: object,
135138
swagger2OpenAPIOptions: object
136139
): Promise<Oas3> {
137140
return new Promise((resolve, reject) => {
@@ -161,8 +164,25 @@ export function getValidOAS3(
161164
) {
162165
preprocessingLog(`Received OpenAPI Specification - going to validate...`)
163166

164-
OASValidator.validate(spec, oasValidatorOptions)
165-
.then(() => resolve(spec as Oas3))
167+
const validator = new OASValidator()
168+
validator.registerFormat('oas3', isOpenApiv3)
169+
170+
validator
171+
.loadRuleset('spectral:oas')
172+
.then(() => validator.run(spec))
173+
.then((results) => {
174+
for (const result of results) {
175+
if (result.severity < 1) {
176+
return reject(
177+
`Invalid OpenAPI Specification '${
178+
(spec as Oas3).info.title
179+
}'. [${result.path.join('.')}] ${result.message}`
180+
)
181+
}
182+
}
183+
184+
resolve(spec as Oas3)
185+
})
166186
.catch((error) =>
167187
reject(
168188
`Could not validate OpenAPI Specification '${
@@ -419,7 +439,7 @@ export function getSchemaTargetGraphQLType<TSource, TContext, TArgs>(
419439
oas: Oas3
420440
): TargetGraphQLType | null {
421441
let schema: SchemaObject
422-
if ("$ref" in schemaOrRef && typeof schemaOrRef.$ref === 'string') {
442+
if ('$ref' in schemaOrRef && typeof schemaOrRef.$ref === 'string') {
423443
schema = resolveRef(schemaOrRef.$ref, oas)
424444
} else {
425445
schema = schemaOrRef as SchemaObject
@@ -529,7 +549,10 @@ function hasNestedOneOfUsage(schema: SchemaObject, oas: Oas3): boolean {
529549
Array.isArray(schema.oneOf) &&
530550
schema.oneOf.some((memberSchemaOrRef) => {
531551
let memberSchema: SchemaObject
532-
if ("$ref" in memberSchemaOrRef && typeof memberSchemaOrRef.$ref === 'string') {
552+
if (
553+
'$ref' in memberSchemaOrRef &&
554+
typeof memberSchemaOrRef.$ref === 'string'
555+
) {
533556
memberSchema = resolveRef(memberSchemaOrRef.$ref, oas)
534557
} else {
535558
memberSchema = memberSchemaOrRef as SchemaObject
@@ -560,7 +583,10 @@ function hasNestedAnyOfUsage(schema: SchemaObject, oas: Oas3): boolean {
560583
schema.anyOf.some((memberSchemaOrRef) => {
561584
let memberSchema: SchemaObject
562585

563-
if ("$ref" in memberSchemaOrRef && typeof memberSchemaOrRef.$ref === 'string') {
586+
if (
587+
'$ref' in memberSchemaOrRef &&
588+
typeof memberSchemaOrRef.$ref === 'string'
589+
) {
564590
memberSchema = resolveRef(memberSchemaOrRef.$ref, oas)
565591
} else {
566592
memberSchema = memberSchemaOrRef as SchemaObject
@@ -809,11 +835,11 @@ export function getRequestSchemaAndNames(
809835
requestBodyObjectOrRef !== null
810836
) {
811837
// Resolve reference if applicable. Make sure we have a RequestBodyObject:
812-
if ("$ref" in requestBodyObjectOrRef && typeof requestBodyObjectOrRef.$ref === 'string') {
813-
requestBodyObject = resolveRef(
814-
requestBodyObjectOrRef.$ref,
815-
oas
816-
)
838+
if (
839+
'$ref' in requestBodyObjectOrRef &&
840+
typeof requestBodyObjectOrRef.$ref === 'string'
841+
) {
842+
requestBodyObject = resolveRef(requestBodyObjectOrRef.$ref, oas)
817843
} else {
818844
requestBodyObject = requestBodyObjectOrRef as RequestBodyObject
819845
}
@@ -859,14 +885,11 @@ export function getRequestSchemaAndNames(
859885
) {
860886
// Resolve payload schema reference if applicable
861887
if (
862-
"$ref" in payloadSchemaOrRef &&
888+
'$ref' in payloadSchemaOrRef &&
863889
typeof payloadSchemaOrRef.$ref === 'string'
864890
) {
865891
fromRef = payloadSchemaOrRef.$ref.split('/').pop()
866-
payloadSchema = resolveRef(
867-
payloadSchemaOrRef.$ref,
868-
oas
869-
)
892+
payloadSchema = resolveRef(payloadSchemaOrRef.$ref, oas)
870893
} else {
871894
payloadSchema = payloadSchemaOrRef as SchemaObject
872895
}
@@ -946,11 +969,11 @@ export function getResponseSchemaAndNames<TSource, TContext, TArgs>(
946969
// Get response object
947970
const responseObjectOrRef = operation?.responses?.[statusCode]
948971
if (typeof responseObjectOrRef === 'object' && responseObjectOrRef !== null) {
949-
if ("$ref" in responseObjectOrRef && typeof responseObjectOrRef.$ref === 'string') {
950-
responseObject = resolveRef(
951-
responseObjectOrRef.$ref,
952-
oas
953-
)
972+
if (
973+
'$ref' in responseObjectOrRef &&
974+
typeof responseObjectOrRef.$ref === 'string'
975+
) {
976+
responseObject = resolveRef(responseObjectOrRef.$ref, oas)
954977
} else {
955978
responseObject = responseObjectOrRef as ResponseObject
956979
}
@@ -984,14 +1007,11 @@ export function getResponseSchemaAndNames<TSource, TContext, TArgs>(
9841007
responseObject?.content?.[responseContentType]?.schema
9851008
// Resolve response schema reference if applicable
9861009
if (
987-
"$ref" in responseSchemaOrRef &&
1010+
'$ref' in responseSchemaOrRef &&
9881011
typeof responseSchemaOrRef.$ref === 'string'
9891012
) {
9901013
fromRef = responseSchemaOrRef.$ref.split('/').pop()
991-
responseSchema = resolveRef(
992-
responseSchemaOrRef.$ref,
993-
oas
994-
)
1014+
responseSchema = resolveRef(responseSchemaOrRef.$ref, oas)
9951015
} else {
9961016
responseSchema = responseSchemaOrRef as SchemaObject
9971017
}
@@ -1121,11 +1141,11 @@ export function getLinks<TSource, TContext, TArgs>(
11211141
const responseObjectOrRef = responses[statusCode]
11221142

11231143
let response: ResponseObject
1124-
if ("$ref" in responseObjectOrRef && typeof responseObjectOrRef.$ref === 'string') {
1125-
response = resolveRef(
1126-
responseObjectOrRef.$ref,
1127-
oas
1128-
)
1144+
if (
1145+
'$ref' in responseObjectOrRef &&
1146+
typeof responseObjectOrRef.$ref === 'string'
1147+
) {
1148+
response = resolveRef(responseObjectOrRef.$ref, oas)
11291149
} else {
11301150
response = responseObjectOrRef as ResponseObject
11311151
}
@@ -1136,7 +1156,10 @@ export function getLinks<TSource, TContext, TArgs>(
11361156
const linkObjectOrRef = epLinks[linkKey]
11371157

11381158
let link: LinkObject
1139-
if ("$ref" in linkObjectOrRef && typeof linkObjectOrRef.$ref === 'string') {
1159+
if (
1160+
'$ref' in linkObjectOrRef &&
1161+
typeof linkObjectOrRef.$ref === 'string'
1162+
) {
11401163
link = resolveRef(linkObjectOrRef.$ref, oas)
11411164
} else {
11421165
link = linkObjectOrRef as LinkObject
@@ -1173,17 +1196,15 @@ export function getParameters(
11731196
// First, consider parameters in Path Item Object:
11741197
const pathParams = pathItem.parameters
11751198
if (Array.isArray(pathParams)) {
1176-
const pathItemParameters: ParameterObject[] = pathParams.map(
1177-
(p) => {
1178-
if ("$ref" in p && typeof p.$ref === 'string') {
1179-
// Here we know we have a parameter object:
1180-
return resolveRef(p.$ref, oas) as ParameterObject
1181-
} else {
1182-
// Here we know we have a parameter object:
1183-
return p as ParameterObject
1184-
}
1199+
const pathItemParameters: ParameterObject[] = pathParams.map((p) => {
1200+
if ('$ref' in p && typeof p.$ref === 'string') {
1201+
// Here we know we have a parameter object:
1202+
return resolveRef(p.$ref, oas) as ParameterObject
1203+
} else {
1204+
// Here we know we have a parameter object:
1205+
return p as ParameterObject
11851206
}
1186-
)
1207+
})
11871208
parameters = parameters.concat(pathItemParameters)
11881209
}
11891210

@@ -1192,7 +1213,7 @@ export function getParameters(
11921213
if (Array.isArray(opObjectParameters)) {
11931214
const operationParameters: ParameterObject[] = opObjectParameters.map(
11941215
(p) => {
1195-
if ("$ref" in p && typeof p.$ref === 'string') {
1216+
if ('$ref' in p && typeof p.$ref === 'string') {
11961217
// Here we know we have a parameter object:
11971218
return resolveRef(p.$ref, oas)
11981219
} else {
@@ -1262,12 +1283,12 @@ export function getSecuritySchemes(
12621283
const securitySchemeOrRef = oas.components.securitySchemes[schemeKey]
12631284

12641285
// Ensure we have actual SecuritySchemeObject:
1265-
if ("$ref" in securitySchemeOrRef && typeof securitySchemeOrRef.$ref === 'string') {
1286+
if (
1287+
'$ref' in securitySchemeOrRef &&
1288+
typeof securitySchemeOrRef.$ref === 'string'
1289+
) {
12661290
// Result of resolution will be SecuritySchemeObject:
1267-
securitySchemes[schemeKey] = resolveRef(
1268-
securitySchemeOrRef.$ref,
1269-
oas
1270-
)
1291+
securitySchemes[schemeKey] = resolveRef(securitySchemeOrRef.$ref, oas)
12711292
} else {
12721293
// We already have a SecuritySchemeObject:
12731294
securitySchemes[schemeKey] = securitySchemeOrRef as SecuritySchemeObject

‎packages/openapi-to-graphql/src/types/options.ts

-9
Original file line numberDiff line numberDiff line change
@@ -298,15 +298,6 @@ export type InternalOptions<TSource, TContext, TArgs> = {
298298

299299
// Validation options
300300

301-
/**
302-
* We use the oas-validator library to validate Swaggers/OASs.
303-
*
304-
* We expose the options so that users can have more control over validation.
305-
*
306-
* Based on: https://github.com/Mermade/oas-kit/blob/master/docs/options.md
307-
*/
308-
oasValidatorOptions: object
309-
310301
/**
311302
* We use the swagger2graphql library to translate Swaggers to OASs.
312303
*

‎packages/openapi-to-graphql/test/fixtures/example_oas.json

+1-3
Original file line numberDiff line numberDiff line change
@@ -1481,8 +1481,7 @@
14811481
},
14821482
"links": {
14831483
"Author": {
1484-
"$ref": "ThisIsHereAndFormattedThisWayBecauseOfAnIssueWithADependency",
1485-
"operationRef": "Example API 3#/paths/~1authors~1{authorId}/get",
1484+
"operationRef": "./example_oas3.json#/paths/~1authors~1{authorId}/get",
14861485
"parameters": {
14871486
"authorId": "$request.path.username"
14881487
},
@@ -1575,7 +1574,6 @@
15751574
}
15761575
},
15771576
"ReviewsWithOperationRef": {
1578-
"$ref": "ThisIsHereAndFormattedThisWayBecauseOfAnIssueWithADependency",
15791577
"operationRef": "#/paths/~1products~1{id}~1reviews/get",
15801578
"parameters": {
15811579
"id": "$request.path.product-id",

‎packages/openapi-to-graphql/test/fixtures/example_oas3.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,7 @@
216216
},
217217
"links": {
218218
"Employee": {
219-
"$ref": "ThisIsHereAndFormattedThisWayBecauseOfAnIssueWithADependency",
220-
"operationRef": "Example API#/paths/~1users~1{username}/get",
219+
"operationRef": "./example_oas.json#/paths/~1users~1{username}/get",
221220
"parameters": {
222221
"username": "$request.path.authorId"
223222
},

‎packages/openapi-to-graphql/test/oas_3_tools.test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,41 @@ test('Handle encoded JSON pointer references', () => {
216216
}
217217
}
218218
})
219+
220+
test('Validate 3.1.0 openapi', async () => {
221+
const oas = {
222+
openapi: '3.1.0',
223+
info: {
224+
title: 'test',
225+
version: '0.0.1',
226+
license: {
227+
identifier: 'UNLICENSED', // This is 3.1.0+ only
228+
name: 'test'
229+
}
230+
},
231+
paths: {}
232+
}
233+
234+
const result = await Oas3Tools.getValidOAS3(oas, {})
235+
236+
expect(result.openapi).toBe('3.1.0')
237+
})
238+
239+
test('Invalid 3.0.0 openapi', async () => {
240+
const oas = {
241+
openapi: '3.0.0',
242+
info: {
243+
title: 'test',
244+
version: '0.0.1',
245+
license: {
246+
identifier: 'UNLICENSED', // This is invalid
247+
name: 'test'
248+
}
249+
},
250+
paths: {}
251+
}
252+
253+
await expect(Oas3Tools.getValidOAS3(oas, {})).rejects.toMatch(
254+
'info.license.identifier'
255+
)
256+
})

0 commit comments

Comments
 (0)
Please sign in to comment.