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