@teamkeel/wasm 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/keel.wasm +0 -0
- package/dist/wasm.js +21 -0
- package/encodeWasm.js +30 -0
- package/index.d.ts +70 -0
- package/index.js +43 -0
- package/index.test.ts +219 -0
- package/lib/main.go +285 -0
- package/lib/wasm_exec.js +661 -0
- package/lib/wasm_exec_node.js +23 -0
- package/package.json +20 -0
package/encodeWasm.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
|
|
3
|
+
const wasmCode = fs.readFileSync(__dirname + "/dist/keel.wasm");
|
|
4
|
+
const encoded = Buffer.from(wasmCode, "binary").toString("base64");
|
|
5
|
+
|
|
6
|
+
fs.writeFileSync(
|
|
7
|
+
__dirname + "/dist/wasm.js",
|
|
8
|
+
`
|
|
9
|
+
function asciiToBinary(str) {
|
|
10
|
+
if (typeof atob === "function") {
|
|
11
|
+
// this works in the browser
|
|
12
|
+
return atob(str);
|
|
13
|
+
} else {
|
|
14
|
+
// this works in node
|
|
15
|
+
return new Buffer(str, "base64").toString("binary");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function decode(encoded) {
|
|
20
|
+
var binaryString = asciiToBinary(encoded);
|
|
21
|
+
var bytes = new Uint8Array(binaryString.length);
|
|
22
|
+
for (var i = 0; i < binaryString.length; i++) {
|
|
23
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
24
|
+
}
|
|
25
|
+
return bytes.buffer;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports.wasm = decode("${encoded}");
|
|
29
|
+
`
|
|
30
|
+
);
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function format(schema: string): Promise<string>;
|
|
2
|
+
|
|
3
|
+
export function validate(req: ValidateRequest): Promise<ValidationResult>;
|
|
4
|
+
|
|
5
|
+
export function completions(
|
|
6
|
+
req: GetCompletionsRequest
|
|
7
|
+
): Promise<CompletionResult>;
|
|
8
|
+
|
|
9
|
+
export function getDefinition(
|
|
10
|
+
req: GetDefinitionRequest
|
|
11
|
+
): Promise<DefinitionResult>;
|
|
12
|
+
|
|
13
|
+
export interface DefinitionResult {
|
|
14
|
+
schema?: Position;
|
|
15
|
+
function?: { name: string };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SchemaDefinition {
|
|
19
|
+
schema: SchemaDefinition;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GetCompletionsRequest {
|
|
23
|
+
position: Position;
|
|
24
|
+
schemaFiles: SchemaFile[];
|
|
25
|
+
config?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface GetDefinitionRequest {
|
|
29
|
+
position: Position;
|
|
30
|
+
schemaFiles: SchemaFile[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ValidateRequest {
|
|
34
|
+
schemaFiles: SchemaFile[];
|
|
35
|
+
config?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SchemaFile {
|
|
39
|
+
filename: string;
|
|
40
|
+
contents: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface Position {
|
|
44
|
+
filename: string;
|
|
45
|
+
line: number;
|
|
46
|
+
column: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CompletionItem {
|
|
50
|
+
description: string;
|
|
51
|
+
label: string;
|
|
52
|
+
insertText: string;
|
|
53
|
+
kind: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface CompletionResult {
|
|
57
|
+
completions: CompletionItem[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ValidationError {
|
|
61
|
+
code: string;
|
|
62
|
+
pos: Position;
|
|
63
|
+
endPos: Position;
|
|
64
|
+
hint: string;
|
|
65
|
+
message: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ValidationResult {
|
|
69
|
+
errors: ValidationError[];
|
|
70
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require("./lib/wasm_exec_node");
|
|
2
|
+
|
|
3
|
+
const { wasm } = require("./dist/wasm.js");
|
|
4
|
+
|
|
5
|
+
async function keel() {
|
|
6
|
+
if (globalThis.keel) {
|
|
7
|
+
return globalThis.keel;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const go = new globalThis.Go();
|
|
11
|
+
|
|
12
|
+
const wasmModule = await WebAssembly.instantiate(wasm, go.importObject);
|
|
13
|
+
go.run(wasmModule.instance);
|
|
14
|
+
|
|
15
|
+
return globalThis.keel;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function format() {
|
|
19
|
+
const api = await keel();
|
|
20
|
+
return api.format(...arguments);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function validate() {
|
|
24
|
+
const api = await keel();
|
|
25
|
+
return api.validate(...arguments);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function completions() {
|
|
29
|
+
const api = await keel();
|
|
30
|
+
return api.completions(...arguments);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function getDefinition() {
|
|
34
|
+
const api = await keel();
|
|
35
|
+
return api.getDefinition(...arguments);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
format,
|
|
40
|
+
validate,
|
|
41
|
+
completions,
|
|
42
|
+
getDefinition,
|
|
43
|
+
};
|
package/index.test.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { format, validate, completions, getDefinition } from "./index";
|
|
2
|
+
import { test, expect } from "vitest";
|
|
3
|
+
|
|
4
|
+
const configFile = `
|
|
5
|
+
environment:
|
|
6
|
+
default:
|
|
7
|
+
- name: "TEST"
|
|
8
|
+
value: "test"
|
|
9
|
+
|
|
10
|
+
staging:
|
|
11
|
+
- name: "TEST_2"
|
|
12
|
+
value: "test2"
|
|
13
|
+
|
|
14
|
+
secrets:
|
|
15
|
+
- name: API_KEY
|
|
16
|
+
required:
|
|
17
|
+
- "production"
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
test("format", async () => {
|
|
21
|
+
const schema = `model Person { fields { name Text } }`;
|
|
22
|
+
const formatted = await format(schema);
|
|
23
|
+
expect(formatted).toEqual(`model Person {
|
|
24
|
+
fields {
|
|
25
|
+
name Text
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("format - invalid schema", async () => {
|
|
32
|
+
const schema = `model Person {`;
|
|
33
|
+
const formatted = await format(schema);
|
|
34
|
+
expect(formatted).toEqual(schema);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("completions", async () => {
|
|
38
|
+
const schema = `model Person {
|
|
39
|
+
fields {
|
|
40
|
+
name Te
|
|
41
|
+
}
|
|
42
|
+
}`;
|
|
43
|
+
const result = await completions({
|
|
44
|
+
schemaFiles: [
|
|
45
|
+
{
|
|
46
|
+
filename: "schema.keel",
|
|
47
|
+
contents: schema,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
position: {
|
|
51
|
+
filename: "schema.keel",
|
|
52
|
+
line: 3,
|
|
53
|
+
column: 16,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(result.completions.map((x) => x.label)).toContain("Text");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("completions - multi file", async () => {
|
|
61
|
+
const result = await completions({
|
|
62
|
+
schemaFiles: [
|
|
63
|
+
{
|
|
64
|
+
filename: "schema.keel",
|
|
65
|
+
contents: `
|
|
66
|
+
model Person {
|
|
67
|
+
fields {
|
|
68
|
+
name
|
|
69
|
+
}
|
|
70
|
+
}`,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
filename: "other.keel",
|
|
74
|
+
contents: `
|
|
75
|
+
enum Category {
|
|
76
|
+
Sport
|
|
77
|
+
Finance
|
|
78
|
+
}
|
|
79
|
+
`,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
position: {
|
|
83
|
+
filename: "schema.keel",
|
|
84
|
+
line: 4,
|
|
85
|
+
column: 10,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(result.completions.map((x) => x.label)).toContain("Category");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("completions - with config", async () => {
|
|
93
|
+
const result = await completions({
|
|
94
|
+
schemaFiles: [
|
|
95
|
+
{
|
|
96
|
+
filename: "schema.keel",
|
|
97
|
+
contents: `
|
|
98
|
+
model Person {
|
|
99
|
+
@permission(
|
|
100
|
+
expression: ctx.secrets.
|
|
101
|
+
)
|
|
102
|
+
}`,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
position: {
|
|
106
|
+
filename: "schema.keel",
|
|
107
|
+
line: 4,
|
|
108
|
+
column: 29,
|
|
109
|
+
},
|
|
110
|
+
config: configFile,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(result.completions.map((x) => x.label)).toContain("API_KEY");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("validate", async () => {
|
|
117
|
+
const schema = `model Person {
|
|
118
|
+
fields {
|
|
119
|
+
name Foo
|
|
120
|
+
}
|
|
121
|
+
}`;
|
|
122
|
+
const { errors } = await validate({
|
|
123
|
+
schemaFiles: [{ filename: "schema.keel", contents: schema }],
|
|
124
|
+
config: configFile,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(errors[0].message).toEqual("field name has an unsupported type Foo");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("validate - multi file", async () => {
|
|
131
|
+
const schemaA = `model Customer {
|
|
132
|
+
fields {
|
|
133
|
+
orders Order[]
|
|
134
|
+
}
|
|
135
|
+
}`;
|
|
136
|
+
const schemaB = `model Order {
|
|
137
|
+
fields {
|
|
138
|
+
customer Customer
|
|
139
|
+
}
|
|
140
|
+
}`;
|
|
141
|
+
const { errors } = await validate({
|
|
142
|
+
schemaFiles: [
|
|
143
|
+
{ filename: "customer.keel", contents: schemaA },
|
|
144
|
+
{ filename: "hobby.keel", contents: schemaB },
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(errors.length).toEqual(0);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("validate - invalid schema", async () => {
|
|
152
|
+
const schema = `model Person {
|
|
153
|
+
fields {`;
|
|
154
|
+
const { errors } = await validate({
|
|
155
|
+
schemaFiles: [{ filename: "schema.keel", contents: schema }],
|
|
156
|
+
config: configFile,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(errors[0].code).toEqual("E025");
|
|
160
|
+
expect(errors[0].message).toEqual(` unexpected token "<EOF>" (expected "}")`);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("getDefinition", async () => {
|
|
164
|
+
const result = await getDefinition({
|
|
165
|
+
position: {
|
|
166
|
+
line: 7,
|
|
167
|
+
column: 21,
|
|
168
|
+
filename: "myschema.keel",
|
|
169
|
+
},
|
|
170
|
+
schemaFiles: [
|
|
171
|
+
{
|
|
172
|
+
filename: "myschema.keel",
|
|
173
|
+
contents: `
|
|
174
|
+
model Person {
|
|
175
|
+
fields {
|
|
176
|
+
name Text
|
|
177
|
+
}
|
|
178
|
+
operations {
|
|
179
|
+
list getPeople(name)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
`,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(result).toEqual({
|
|
188
|
+
function: null,
|
|
189
|
+
schema: {
|
|
190
|
+
filename: "myschema.keel",
|
|
191
|
+
line: 4,
|
|
192
|
+
column: 5,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("getDefinition - no result", async () => {
|
|
198
|
+
const result = await getDefinition({
|
|
199
|
+
position: {
|
|
200
|
+
line: 1,
|
|
201
|
+
column: 1,
|
|
202
|
+
filename: "myschema.keel",
|
|
203
|
+
},
|
|
204
|
+
schemaFiles: [
|
|
205
|
+
{
|
|
206
|
+
filename: "myschema.keel",
|
|
207
|
+
contents: `
|
|
208
|
+
model Person {
|
|
209
|
+
fields {
|
|
210
|
+
name Text
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
`,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(result).toBeNull();
|
|
219
|
+
});
|
package/lib/main.go
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
//go:build wasm
|
|
2
|
+
|
|
3
|
+
package main
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"syscall/js"
|
|
8
|
+
|
|
9
|
+
"github.com/teamkeel/keel/config"
|
|
10
|
+
"github.com/teamkeel/keel/schema"
|
|
11
|
+
"github.com/teamkeel/keel/schema/completions"
|
|
12
|
+
"github.com/teamkeel/keel/schema/definitions"
|
|
13
|
+
"github.com/teamkeel/keel/schema/format"
|
|
14
|
+
"github.com/teamkeel/keel/schema/node"
|
|
15
|
+
"github.com/teamkeel/keel/schema/parser"
|
|
16
|
+
"github.com/teamkeel/keel/schema/reader"
|
|
17
|
+
"github.com/teamkeel/keel/schema/validation/errorhandling"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
func init() {
|
|
21
|
+
// we have to declare our functions in an init func otherwise they aren't
|
|
22
|
+
// available in JS land at the call time.
|
|
23
|
+
js.Global().Set("keel", js.ValueOf(map[string]any{
|
|
24
|
+
"validate": js.FuncOf(validate),
|
|
25
|
+
"format": js.FuncOf(formatSchema),
|
|
26
|
+
"completions": js.FuncOf(provideCompletions),
|
|
27
|
+
"getDefinition": js.FuncOf(getDefinition),
|
|
28
|
+
}))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func main() {
|
|
32
|
+
done := make(chan bool)
|
|
33
|
+
<-done
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// newPromise wraps the provided function in a Javascript Promise
|
|
37
|
+
// and returns that promise. It then either resolves or rejects
|
|
38
|
+
// the promise based on whether fn returns an error or not
|
|
39
|
+
func newPromise(fn func() (any, error)) any {
|
|
40
|
+
handler := js.FuncOf(func(this js.Value, args []js.Value) any {
|
|
41
|
+
resolve := args[0]
|
|
42
|
+
reject := args[1]
|
|
43
|
+
|
|
44
|
+
go func() {
|
|
45
|
+
// handle panics
|
|
46
|
+
defer func() {
|
|
47
|
+
if r := recover(); r != nil {
|
|
48
|
+
msg := "panic"
|
|
49
|
+
switch r.(type) {
|
|
50
|
+
case string:
|
|
51
|
+
msg = r.(string)
|
|
52
|
+
case error:
|
|
53
|
+
e := r.(error)
|
|
54
|
+
msg = e.Error()
|
|
55
|
+
}
|
|
56
|
+
// err should be an instance of `error`, eg `errors.New("some error")`
|
|
57
|
+
errorConstructor := js.Global().Get("Error")
|
|
58
|
+
errorObject := errorConstructor.New(msg)
|
|
59
|
+
reject.Invoke(errorObject)
|
|
60
|
+
}
|
|
61
|
+
}()
|
|
62
|
+
|
|
63
|
+
data, err := fn()
|
|
64
|
+
if err != nil {
|
|
65
|
+
// err should be an instance of `error`, eg `errors.New("some error")`
|
|
66
|
+
errorConstructor := js.Global().Get("Error")
|
|
67
|
+
errorObject := errorConstructor.New(err.Error())
|
|
68
|
+
reject.Invoke(errorObject)
|
|
69
|
+
} else {
|
|
70
|
+
resolve.Invoke(js.ValueOf(data))
|
|
71
|
+
}
|
|
72
|
+
}()
|
|
73
|
+
|
|
74
|
+
return nil
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
promiseConstructor := js.Global().Get("Promise")
|
|
78
|
+
return promiseConstructor.New(handler)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Expected argument to definitions API:
|
|
82
|
+
//
|
|
83
|
+
// {
|
|
84
|
+
// position: {
|
|
85
|
+
// filename: "",
|
|
86
|
+
// line: 1,
|
|
87
|
+
// column: 1,
|
|
88
|
+
// },
|
|
89
|
+
// schemaFiles: [
|
|
90
|
+
// {
|
|
91
|
+
// filename: "",
|
|
92
|
+
// contents: "",
|
|
93
|
+
// },
|
|
94
|
+
// ],
|
|
95
|
+
// config: "",
|
|
96
|
+
// }
|
|
97
|
+
func provideCompletions(this js.Value, args []js.Value) any {
|
|
98
|
+
return newPromise(func() (any, error) {
|
|
99
|
+
positionArg := args[0].Get("position")
|
|
100
|
+
pos := &node.Position{
|
|
101
|
+
Filename: positionArg.Get("filename").String(),
|
|
102
|
+
Line: positionArg.Get("line").Int(),
|
|
103
|
+
Column: positionArg.Get("column").Int(),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
schemaFilesArg := args[0].Get("schemaFiles")
|
|
107
|
+
schemaFiles := []*reader.SchemaFile{}
|
|
108
|
+
for i := 0; i < schemaFilesArg.Length(); i++ {
|
|
109
|
+
f := schemaFilesArg.Index(i)
|
|
110
|
+
schemaFiles = append(schemaFiles, &reader.SchemaFile{
|
|
111
|
+
FileName: f.Get("filename").String(),
|
|
112
|
+
Contents: f.Get("contents").String(),
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
configSrc := args[0].Get("config")
|
|
117
|
+
var cfg *config.ProjectConfig
|
|
118
|
+
if configSrc.Truthy() {
|
|
119
|
+
// We don't care about errors here, if we can get a config object
|
|
120
|
+
// back we'll use it, if not then we'll run validation without it
|
|
121
|
+
cfg, _ = config.LoadFromBytes([]byte(configSrc.String()))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
completions := completions.Completions(schemaFiles, pos, cfg)
|
|
125
|
+
|
|
126
|
+
untypedCompletions := toUntypedArray(completions)
|
|
127
|
+
|
|
128
|
+
return map[string]any{
|
|
129
|
+
"completions": js.ValueOf(untypedCompletions),
|
|
130
|
+
}, nil
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Expected argument to definitions API:
|
|
135
|
+
//
|
|
136
|
+
// {
|
|
137
|
+
// position: {
|
|
138
|
+
// filename: "",
|
|
139
|
+
// line: 1,
|
|
140
|
+
// column: 1,
|
|
141
|
+
// },
|
|
142
|
+
// schemaFiles: [
|
|
143
|
+
// {
|
|
144
|
+
// filename: "",
|
|
145
|
+
// contents: "",
|
|
146
|
+
// },
|
|
147
|
+
// ],
|
|
148
|
+
// }
|
|
149
|
+
func getDefinition(this js.Value, args []js.Value) any {
|
|
150
|
+
return newPromise(func() (any, error) {
|
|
151
|
+
positionArg := args[0].Get("position")
|
|
152
|
+
pos := definitions.Position{
|
|
153
|
+
Filename: positionArg.Get("filename").String(),
|
|
154
|
+
Line: positionArg.Get("line").Int(),
|
|
155
|
+
Column: positionArg.Get("column").Int(),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
schemaFilesArg := args[0].Get("schemaFiles")
|
|
159
|
+
schemaFiles := []*reader.SchemaFile{}
|
|
160
|
+
for i := 0; i < schemaFilesArg.Length(); i++ {
|
|
161
|
+
f := schemaFilesArg.Index(i)
|
|
162
|
+
schemaFiles = append(schemaFiles, &reader.SchemaFile{
|
|
163
|
+
FileName: f.Get("filename").String(),
|
|
164
|
+
Contents: f.Get("contents").String(),
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
def := definitions.GetDefinition(schemaFiles, pos)
|
|
169
|
+
if def == nil {
|
|
170
|
+
return nil, nil
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return toMap(def)
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func formatSchema(this js.Value, args []js.Value) any {
|
|
178
|
+
return newPromise(func() (any, error) {
|
|
179
|
+
src := args[0].String()
|
|
180
|
+
ast, err := parser.Parse(&reader.SchemaFile{
|
|
181
|
+
FileName: "schema.keel",
|
|
182
|
+
Contents: src,
|
|
183
|
+
})
|
|
184
|
+
if err != nil {
|
|
185
|
+
// if the schema can't be parsed then just return it as-is
|
|
186
|
+
return src, nil
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return format.Format(ast), nil
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Expected argument to validate API:
|
|
194
|
+
//
|
|
195
|
+
// {
|
|
196
|
+
// schemaFiles: [
|
|
197
|
+
// {
|
|
198
|
+
// filename: "",
|
|
199
|
+
// contents: "",
|
|
200
|
+
// },
|
|
201
|
+
// ],
|
|
202
|
+
// config: "<YAML config file>"
|
|
203
|
+
// }
|
|
204
|
+
//
|
|
205
|
+
// The config file source is optional.
|
|
206
|
+
func validate(this js.Value, args []js.Value) any {
|
|
207
|
+
return newPromise(func() (any, error) {
|
|
208
|
+
|
|
209
|
+
schemaFilesArg := args[0].Get("schemaFiles")
|
|
210
|
+
schemaFiles := []reader.SchemaFile{}
|
|
211
|
+
for i := 0; i < schemaFilesArg.Length(); i++ {
|
|
212
|
+
f := schemaFilesArg.Index(i)
|
|
213
|
+
schemaFiles = append(schemaFiles, reader.SchemaFile{
|
|
214
|
+
FileName: f.Get("filename").String(),
|
|
215
|
+
Contents: f.Get("contents").String(),
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
builder := schema.Builder{}
|
|
220
|
+
|
|
221
|
+
configSrc := args[0].Get("config")
|
|
222
|
+
if configSrc.Truthy() {
|
|
223
|
+
// We don't care about errors here, if we can get a config object
|
|
224
|
+
// back we'll use it, if not then we'll run validation without it
|
|
225
|
+
config, _ := config.LoadFromBytes([]byte(configSrc.String()))
|
|
226
|
+
if config != nil {
|
|
227
|
+
builder.Config = config
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
_, err := builder.MakeFromInputs(&reader.Inputs{
|
|
232
|
+
SchemaFiles: schemaFiles,
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
if err != nil {
|
|
236
|
+
errs, ok := err.(*errorhandling.ValidationErrors)
|
|
237
|
+
if !ok {
|
|
238
|
+
return nil, err
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
validationErrors, err := toMap(errs)
|
|
242
|
+
if err != nil {
|
|
243
|
+
return nil, err
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return validationErrors, nil
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return map[string]any{
|
|
250
|
+
"errors": []any{},
|
|
251
|
+
}, nil
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// js.ValueOf can only marshall map[string]any to a JS object
|
|
256
|
+
// so for structs we need to do the struct->json->map[string]any dance
|
|
257
|
+
func toMap(v any) (map[string]any, error) {
|
|
258
|
+
b, err := json.Marshal(v)
|
|
259
|
+
if err != nil {
|
|
260
|
+
return nil, err
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
var res map[string]any
|
|
264
|
+
err = json.Unmarshal(b, &res)
|
|
265
|
+
return res, err
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
func toUntypedArray(items []*completions.CompletionItem) (i []any) {
|
|
269
|
+
for _, item := range items {
|
|
270
|
+
b, err := json.Marshal(item)
|
|
271
|
+
|
|
272
|
+
if err != nil {
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
var res any
|
|
276
|
+
err = json.Unmarshal(b, &res)
|
|
277
|
+
|
|
278
|
+
if err != nil {
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
i = append(i, res)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return i
|
|
285
|
+
}
|