Skip to content

Commit 39b0d22

Browse files
committed
refactor validation (json-schema mostly)
Change json-schema validation to be able to push in different schemas Tweak semantic validation to allow overriding the validators that are run
1 parent 99fa166 commit 39b0d22

File tree

19 files changed

+631
-251
lines changed

19 files changed

+631
-251
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// JSON-Schema ( draf04 ) validator
2+
import JsonSchemaWebWorker from "./validator.worker.js"
3+
import PromiseWorker from "promise-worker"
4+
import debounce from "lodash/debounce"
5+
import swagger2Schema from "./swagger2-schema.js"
6+
import oas3Schema from "./oas3-schema"
7+
8+
// Lazily created promise worker
9+
let _promiseWorker
10+
const promiseWorker = () => {
11+
if(!_promiseWorker)
12+
_promiseWorker = new PromiseWorker(new JsonSchemaWebWorker())
13+
return _promiseWorker
14+
}
15+
16+
export const addSchema = (schema, schemaPath=[]) => () => {
17+
promiseWorker().postMessage({
18+
type: "add-schema",
19+
payload: {
20+
schemaPath,
21+
schema
22+
}
23+
})
24+
}
25+
26+
// Figure out what schema we need to use ( we're making provision to be able to do sub-schema validation later on)
27+
// ...for now we just pick which base schema to use (eg: openapi-2-0, openapi-3.0, domain-2-0, etc)
28+
export const getSchemaBasePath = () => ({ specSelectors }) => {
29+
// Eg: openapiDomain-3.0
30+
// openapi-3.0
31+
// later on... ['openapi-2.0', 'paths', 'get']
32+
const isOAS3 = specSelectors.isOAS3 ? specSelectors.isOAS3() : false
33+
const isSwagger2 = specSelectors.isSwagger2 ? specSelectors.isSwagger2() : false
34+
const isAmbiguousVersion = isOAS3 && isSwagger2
35+
36+
// Refuse to handle ambiguity
37+
if(isAmbiguousVersion)
38+
return []
39+
40+
if(isSwagger2)
41+
return ["openapi-2.0"]
42+
43+
if(isOAS3)
44+
return ["openapi-3.0"]
45+
46+
}
47+
48+
49+
export const setup = () => ({jsonSchemaValidatorActions}) => {
50+
// Add schemas , once off
51+
jsonSchemaValidatorActions.addSchema(swagger2Schema, ["openapi-2.0"])
52+
jsonSchemaValidatorActions.addSchema(oas3Schema, ["openapi-3.0"])
53+
}
54+
55+
56+
export const validate = ({spec, path=[], ...rest}) => system => {
57+
// stagger clearing errors, in case there is another debounced validation
58+
// run happening, which can occur when the user's typing cadence matches
59+
// the latency of validation
60+
// TODO: instead of using a timeout, be aware of any pending validation
61+
// promises, and use them to schedule error clearing.
62+
setTimeout(() => {
63+
system.errActions.clear({
64+
source: system.jsonSchemaValidatorSelectors.errSource()
65+
})
66+
}, 50)
67+
system.jsonSchemaValidatorActions.validateDebounced({spec, path, ...rest})
68+
}
69+
70+
// Create a debounced validate, that is lazy
71+
let _debValidate
72+
export const validateDebounced = (...args) => system => {
73+
// Lazily create one...
74+
if(!_debValidate) {
75+
_debValidate = debounce((...args) => {
76+
system.jsonSchemaValidatorActions.validateImmediate(...args)
77+
}, 200)
78+
}
79+
return _debValidate(...args)
80+
}
81+
82+
export const validateImmediate = ({spec, path=[]}) => system => {
83+
// schemaPath refers to type of schema, and later might refer to sub-schema
84+
const baseSchemaPath = system.jsonSchemaValidatorSelectors.getSchemaBasePath()
85+
86+
// No base path? Then we're unable to do anything...
87+
if(!baseSchemaPath.length)
88+
throw new Error("Ambiguous schema path, unable to run validation")
89+
90+
return system.jsonSchemaValidatorActions.validateWithBaseSchema({spec, path: [...baseSchemaPath, ...path]})
91+
}
92+
93+
export const validateWithBaseSchema = ({spec, path=[]}) => system => {
94+
const errSource = system.jsonSchemaValidatorSelectors.errSource()
95+
96+
return promiseWorker().postMessage({
97+
type: "validate",
98+
payload: {
99+
jsSpec: spec,
100+
specStr: system.specSelectors.specStr(),
101+
schemaPath: path,
102+
source: errSource,
103+
}
104+
}).then(({results, path}) => {
105+
system.jsonSchemaValidatorActions.handleResults(null, {results, path})
106+
}, err => {
107+
system.jsonSchemaValidatorActions.handleResults(err, {})
108+
})
109+
}
110+
111+
export const handleResults = (err, {results}) => system => {
112+
if(err) {
113+
// Something bad happened with validation.
114+
throw err
115+
}
116+
117+
system.errActions.clear({
118+
source: system.jsonSchemaValidatorSelectors.errSource()
119+
})
120+
121+
if(!Array.isArray(results)) {
122+
results = [results]
123+
}
124+
125+
// Filter out anything funky
126+
results = results.filter(val => typeof val === "object" && val !== null)
127+
128+
if(results.length) {
129+
system.errActions.newSpecErrBatch(results)
130+
}
131+
}
132+
133+
export default function() {
134+
return {
135+
statePlugins: {
136+
jsonSchemaValidator: {
137+
actions: {
138+
addSchema,
139+
validate,
140+
handleResults,
141+
validateDebounced,
142+
validateImmediate,
143+
validateWithBaseSchema,
144+
setup,
145+
},
146+
selectors: {
147+
getSchemaBasePath,
148+
errSource() {
149+
// Used to identify the errors generated by this plugin
150+
return "json-schema"
151+
}
152+
}
153+
},
154+
spec: {
155+
wrapActions: {
156+
validateSpec: (ori,system) => (...args) => {
157+
ori(...args)
158+
const [ spec, path ] = args
159+
system.jsonSchemaValidatorActions.validate({spec,path})
160+
}
161+
},
162+
}
163+
}
164+
}
165+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import "src/polyfills"
2+
import registerPromiseWorker from "promise-worker/register"
3+
import Validator from "./validator"
4+
5+
const validator = new Validator()
6+
7+
registerPromiseWorker(({ type, payload }) => {
8+
9+
if(type == "add-schema") {
10+
const { schema, schemaPath } = payload
11+
validator.addSchema(schema, schemaPath)
12+
return
13+
}
14+
15+
if(type == "validate") {
16+
const { jsSpec, specStr, schemaPath, source } = payload
17+
let validationResults = validator.validate({
18+
jsSpec, specStr, schemaPath, source
19+
})
20+
21+
return { results: validationResults }
22+
}
23+
24+
})

0 commit comments

Comments
 (0)