Skip to content

Commit 0e53605

Browse files
feat: Validate fetched data
Completes the experimental implementation of settings profiles. Closes #106
1 parent 54c0d82 commit 0e53605

File tree

7 files changed

+101
-17
lines changed

7 files changed

+101
-17
lines changed

packages/kitten-scientists/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"types": "./build/index.ts",
2727
"dependencies": {
2828
"@oliversalzburg/js-utils": "0.0.22",
29+
"ajv": "8.12.0",
2930
"date-fns": "2.30.0",
3031
"semver": "7.5.4",
3132
"tslib": "2.6.2"

packages/kitten-scientists/source/UserScript.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,27 @@ export class UserScript {
238238
this.engine.imessage("settings.imported");
239239
}
240240

241+
/**
242+
* Import settings from a URL.
243+
* This is an experimental feature, and only allows using profiles from
244+
* https://kitten-science.com/ at this time.
245+
*
246+
* @param url - The URL of the profile to load.
247+
*/
241248
async importSettingsFromUrl(url: string) {
242249
const importState = new State(url);
243250
const settings = await importState.resolve();
244251
settings.report.aggregate(console);
252+
253+
const stateIsValid = await importState.validate();
254+
if (!stateIsValid) {
255+
cerror("Imported state is invalid and not imported.");
256+
return;
257+
}
258+
259+
const state = importState.merge();
260+
this.setSettings(state);
261+
this.engine.imessage("settings.imported");
245262
}
246263

247264
/**

packages/kitten-scientists/source/state/State.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { TreeNode } from "@oliversalzburg/js-utils";
1+
import { InvalidOperationError, TreeNode } from "@oliversalzburg/js-utils";
2+
import AjvModule, { SchemaObject } from "ajv";
23
import { UserScript } from "../UserScript.js";
34
import { StateLoader } from "./StateLoader.js";
45
import { StateMerger } from "./StateMerger.js";
@@ -14,6 +15,12 @@ export class State extends TreeNode<State> {
1415

1516
constructor(originUrl: string, parent?: State) {
1617
super(parent);
18+
if (!originUrl.startsWith("https://kitten-science.com/")) {
19+
throw new InvalidOperationError(
20+
"While state import is experimental, you can only import from 'https://kitten-science.com/'!",
21+
);
22+
}
23+
1724
this.originUrl = originUrl;
1825
this.loader = new StateLoader(this);
1926
}
@@ -23,6 +30,54 @@ export class State extends TreeNode<State> {
2330
return { loader: this.loader, report };
2431
}
2532

33+
async validate() {
34+
const schemaBaselineRequest = await fetch(
35+
"https://schema.kitten-science.com/working-draft/baseline/engine-state.schema.json",
36+
);
37+
const schemaProfileRequest = await fetch(
38+
"https://schema.kitten-science.com/working-draft/settings-profile.schema.json",
39+
);
40+
const schemaBaseline = (await schemaBaselineRequest.json()) as SchemaObject;
41+
const schemaProfile = (await schemaProfileRequest.json()) as SchemaObject;
42+
43+
// FIXME: https://github.com/ajv-validator/ajv/issues/2047
44+
const Ajv = AjvModule.default;
45+
const ajv = new Ajv({ allErrors: true, verbose: true });
46+
const validateBaseline = ajv.compile(schemaBaseline);
47+
const validateProfile = ajv.compile(schemaProfile);
48+
49+
const validateLoader = (loader: StateLoader) => {
50+
const data = loader.data;
51+
const isValidBaseline = validateBaseline(data);
52+
const isValidProfile = validateProfile(data);
53+
if (isValidProfile && !isValidBaseline) {
54+
console.log(`VALID Profile: ${loader.state.originUrl})`);
55+
return true;
56+
}
57+
if (isValidProfile && isValidBaseline) {
58+
console.log(`VALID Baseline: ${loader.state.originUrl})`);
59+
return true;
60+
}
61+
console.log(`INVALID: ${loader.state.originUrl})`);
62+
return false;
63+
};
64+
65+
const validateNode = (node: State) => {
66+
let childrenValid = true;
67+
if (0 < node.children.size) {
68+
for (const child of node.children) {
69+
const childValid = validateNode(child);
70+
if (!childValid) {
71+
childrenValid = false;
72+
}
73+
}
74+
}
75+
return validateLoader(node.loader) && childrenValid;
76+
};
77+
78+
return validateNode(this);
79+
}
80+
2681
merge() {
2782
return new StateMerger(this).merge(UserScript.unknownAsEngineStateOrThrow({ v: "2.0.0" }));
2883
}

packages/kitten-scientists/source/state/StateLoader.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,10 @@ export class StateLoader extends TreeNode<StateLoader> {
7777
return this.report;
7878
}
7979

80-
// TODO: Validate again JSON schema.
8180
this.#data = data;
8281
const bases = await this.#resolveBases(this.#data, cache);
8382
if (bases.length === 0) {
84-
this.report.log("🍁 Profile is a leaf node and should be a baseline!");
83+
this.report.log("🍁 Profile is a leaf node.");
8584
} else {
8685
this.report.log("🌳 Profile is a tree node.");
8786
}

packages/kitten-scientists/source/state/StateMerger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class StateMerger {
2323

2424
// Clean up merged state.
2525
(stateSubject as Record<string, string>).$schema =
26-
"https://github.com/kitten-science/kitten-scientists/raw/main/schemas/draft-01/settings-profile.schema.json";
26+
"https://schema.kitten-science.com/working-draft/settings-profile.schema.json";
2727
delete stateSubject.extends;
2828

2929
return stateSubject;

schemas/.scripts/load-profile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const state = new State(
1010
const profile = await state.resolve();
1111
profile.report.aggregate(console);
1212

13+
await state.validate();
14+
1315
const engineState = state.merge();
1416
writeFileSync("load-profile.result.json", JSON.stringify(engineState, undefined, 2));
1517
console.info("Result written to 'load-profile.result.json'.");

yarn.lock

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,7 @@ __metadata:
746746
"@types/jquery": "npm:3.5.29"
747747
"@types/semver": "npm:7.5.6"
748748
"@types/web": "npm:0.0.120"
749+
ajv: "npm:8.12.0"
749750
date-fns: "npm:2.30.0"
750751
semver: "npm:7.5.4"
751752
tslib: "npm:2.6.2"
@@ -1295,7 +1296,16 @@ __metadata:
12951296
languageName: node
12961297
linkType: hard
12971298

1298-
"@types/node@npm:*, @types/node@npm:20.10.0":
1299+
"@types/node@npm:*":
1300+
version: 20.9.4
1301+
resolution: "@types/node@npm:20.9.4"
1302+
dependencies:
1303+
undici-types: "npm:~5.26.4"
1304+
checksum: c8b48ace4c7e17715fa901201c98275f8e5268cf5895a8d149777eb0ec6c3ef6c831ff3917e92da5453a5dbe13f230caa50b348a0601b0d50eb9e628010c0364
1305+
languageName: node
1306+
linkType: hard
1307+
1308+
"@types/node@npm:20.10.0":
12991309
version: 20.10.0
13001310
resolution: "@types/node@npm:20.10.0"
13011311
dependencies:
@@ -1543,27 +1553,27 @@ __metadata:
15431553
languageName: node
15441554
linkType: hard
15451555

1546-
"ajv@npm:^6.12.4":
1547-
version: 6.12.6
1548-
resolution: "ajv@npm:6.12.6"
1556+
"ajv@npm:8.12.0, ajv@npm:^8.11.0":
1557+
version: 8.12.0
1558+
resolution: "ajv@npm:8.12.0"
15491559
dependencies:
15501560
fast-deep-equal: "npm:^3.1.1"
1551-
fast-json-stable-stringify: "npm:^2.0.0"
1552-
json-schema-traverse: "npm:^0.4.1"
1561+
json-schema-traverse: "npm:^1.0.0"
1562+
require-from-string: "npm:^2.0.2"
15531563
uri-js: "npm:^4.2.2"
1554-
checksum: 41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71
1564+
checksum: ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e
15551565
languageName: node
15561566
linkType: hard
15571567

1558-
"ajv@npm:^8.11.0":
1559-
version: 8.12.0
1560-
resolution: "ajv@npm:8.12.0"
1568+
"ajv@npm:^6.12.4":
1569+
version: 6.12.6
1570+
resolution: "ajv@npm:6.12.6"
15611571
dependencies:
15621572
fast-deep-equal: "npm:^3.1.1"
1563-
json-schema-traverse: "npm:^1.0.0"
1564-
require-from-string: "npm:^2.0.2"
1573+
fast-json-stable-stringify: "npm:^2.0.0"
1574+
json-schema-traverse: "npm:^0.4.1"
15651575
uri-js: "npm:^4.2.2"
1566-
checksum: ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e
1576+
checksum: 41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71
15671577
languageName: node
15681578
linkType: hard
15691579

0 commit comments

Comments
 (0)