Skip to content

Commit 55059f9

Browse files
authored
improve(validation): array schema items (#1765)
* add failing tests * implement cross-version array schema validation
1 parent d211d5e commit 55059f9

File tree

5 files changed

+202
-19
lines changed

5 files changed

+202
-19
lines changed

src/plugins/validate-semantic/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import * as refsOAS3ValidateActions from "./validators/oas3/refs"
1616
import * as refs2and3ValidateActions from "./validators/2and3/refs"
1717
import * as parameters2and3ValidateActions from "./validators/2and3/parameters"
1818
import * as paths2and3ValidateActions from "./validators/2and3/paths"
19+
import * as schemas2and3ValidateActions from "./validators/2and3/schemas"
1920

2021
export default function SemanticValidatorsPlugin({getSystem}) {
2122

@@ -57,7 +58,8 @@ export default function SemanticValidatorsPlugin({getSystem}) {
5758
...operationsOAS3ValidateActions,
5859
...refsOAS3ValidateActions,
5960
...parameters2and3ValidateActions,
60-
...paths2and3ValidateActions
61+
...paths2and3ValidateActions,
62+
...schemas2and3ValidateActions
6163
}
6264
},
6365
}

src/plugins/validate-semantic/selectors.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export const isOAS3RequestBody = (state, node) => (sys) => {
6060
}
6161

6262
export const isParameterSchema = (state, node) => (sys) => {
63+
if(sys.specSelectors.isOAS3 && sys.specSelectors.isOAS3()) {
64+
// OAS3
65+
return node.key === "schema" && sys.validateSelectors.isParameter(node.parent)
66+
}
6367
// parameter.x.in != body
6468
if(sys.validateSelectors.isParameter(node) && node.node.in !== "body") {
6569
return true
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export const validate2And3TypeArrayRequiresItems = () => (system) => {
2+
return system.validateSelectors
3+
.allSchemas()
4+
.then(nodes => {
5+
return nodes.reduce((acc, node) => {
6+
const schemaObj = node.node
7+
const { type, items } = schemaObj || {}
8+
if(type === "array" && typeof items === "undefined") {
9+
acc.push({
10+
message: "Schemas with 'type: array', require a sibling 'items: ' field",
11+
path: node.path,
12+
level: "error",
13+
})
14+
} else if(type === "array" && (typeof items !== "object" || Array.isArray(items))) {
15+
acc.push({
16+
message: "`items` must be an object",
17+
path: [...node.path, "items"],
18+
level: "error",
19+
})
20+
}
21+
return acc
22+
}, [])
23+
})
24+
}

src/plugins/validate-semantic/validators/schema.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,6 @@ export const validateMinAndMax = () => (system) => {
3333
})
3434
}
3535

36-
export const validateTypeArrayRequiresItems = () => (system) => {
37-
return system.validateSelectors
38-
.allSchemas()
39-
.then(nodes => {
40-
return nodes.reduce((acc, node) => {
41-
const schemaObj = node.node
42-
if(schemaObj.type === "array" && typeof schemaObj.items === "undefined") {
43-
acc.push({
44-
message: "Schemas with 'type: array', require a sibling 'items: ' field",
45-
path: node.path,
46-
level: "error",
47-
})
48-
}
49-
return acc
50-
}, [])
51-
})
52-
}
53-
5436
export const validateTypeKeyShouldBeString = () => (system) => {
5537
return system.validateSelectors
5638
.allSchemas()
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import expect from "expect"
2+
import validateHelper from "../validate-helper.js"
3+
4+
describe(`validation plugin - semantic - 2and3 schemas`, () => {
5+
describe(`array schemas must have an Object value in "items"`, () => {
6+
it("should return an error for an array items value in Swagger 2", () => {
7+
const spec = {
8+
swagger: "2.0",
9+
"paths": {
10+
"/pets": {
11+
"get": {
12+
"parameters": [
13+
{
14+
name: "myParam",
15+
in: "query",
16+
type: "array",
17+
items: [{ type: "object" }]
18+
}
19+
]
20+
}
21+
}
22+
}
23+
}
24+
25+
return validateHelper(spec)
26+
.then(system => {
27+
const allErrors = system.errSelectors.allErrors().toJS()
28+
const firstError = allErrors[0]
29+
expect(allErrors.length).toEqual(1)
30+
expect(firstError.path).toEqual(["paths", "/pets", "get", "parameters", "0", "items"])
31+
expect(firstError.message).toEqual("`items` must be an object")
32+
})
33+
})
34+
it("should return an error for an array items value in OpenAPI 3", () => {
35+
const spec = {
36+
openapi: "3.0.0",
37+
"paths": {
38+
"/pets": {
39+
"get": {
40+
"parameters": [
41+
{
42+
name: "myParam",
43+
in: "query",
44+
schema: {
45+
type: "array",
46+
items: [{ type: "object" }]
47+
}
48+
},
49+
]
50+
}
51+
}
52+
}
53+
}
54+
55+
return validateHelper(spec)
56+
.then(system => {
57+
const allErrors = system.errSelectors.allErrors().toJS()
58+
const firstError = allErrors[0]
59+
expect(allErrors.length).toEqual(1)
60+
expect(firstError.path).toEqual(["paths", "/pets", "get", "parameters", "0", "schema", "items"])
61+
expect(firstError.message).toEqual("`items` must be an object")
62+
})
63+
})
64+
it("should return an error for a missing items value for an array schema in Swagger 2", () => {
65+
const spec = {
66+
swagger: "2.0",
67+
"paths": {
68+
"/pets": {
69+
"get": {
70+
"parameters": [
71+
{
72+
name: "myParam",
73+
in: "query",
74+
type: "array"
75+
}
76+
]
77+
}
78+
}
79+
}
80+
}
81+
82+
return validateHelper(spec)
83+
.then(system => {
84+
const allErrors = system.errSelectors.allErrors().toJS()
85+
const firstError = allErrors[0]
86+
expect(allErrors.length).toEqual(1)
87+
expect(firstError.message).toEqual("Schemas with 'type: array', require a sibling 'items: ' field")
88+
expect(firstError.path).toEqual(["paths", "/pets", "get", "parameters", "0"])
89+
})
90+
})
91+
it("should return an error for a missing items value for an array schema in OpenAPI 3", () => {
92+
const spec = {
93+
openapi: "3.0.0",
94+
"paths": {
95+
"/pets": {
96+
"get": {
97+
"parameters": [
98+
{
99+
name: "myParam",
100+
in: "query",
101+
schema: {
102+
type: "array"
103+
}
104+
},
105+
]
106+
}
107+
}
108+
}
109+
}
110+
111+
return validateHelper(spec)
112+
.then(system => {
113+
const allErrors = system.errSelectors.allErrors().toJS()
114+
const firstError = allErrors[0]
115+
expect(allErrors.length).toEqual(1)
116+
expect(firstError.path).toEqual(["paths", "/pets", "get", "parameters", "0", "schema"])
117+
expect(firstError.message).toEqual("Schemas with 'type: array', require a sibling 'items: ' field")
118+
})
119+
})
120+
it("should not return an error for a missing items value for a non-array schema in Swagger 2", () => {
121+
const spec = {
122+
swagger: "2.0",
123+
"paths": {
124+
"/pets": {
125+
"get": {
126+
"parameters": [
127+
{
128+
name: "myParam",
129+
in: "query",
130+
type: "object"
131+
}
132+
]
133+
}
134+
}
135+
}
136+
}
137+
138+
return validateHelper(spec)
139+
.then(system => {
140+
const allErrors = system.errSelectors.allErrors().toJS()
141+
expect(allErrors.length).toEqual(0)
142+
})
143+
})
144+
it("should not return an error for a missing items value for a non-array schema in OpenAPI 3", () => {
145+
const spec = {
146+
openapi: "3.0.0",
147+
"paths": {
148+
"/pets": {
149+
"get": {
150+
"parameters": [
151+
{
152+
name: "myParam",
153+
in: "query",
154+
schema: {
155+
type: "object"
156+
}
157+
},
158+
]
159+
}
160+
}
161+
}
162+
}
163+
164+
return validateHelper(spec)
165+
.then(system => {
166+
const allErrors = system.errSelectors.allErrors().toJS()
167+
expect(allErrors.length).toEqual(0)
168+
})
169+
})
170+
})
171+
})

0 commit comments

Comments
 (0)