Skip to content

Commit

Permalink
feat: Validate fetched data
Browse files Browse the repository at this point in the history
Completes the experimental implementation of settings profiles.

Closes #106
  • Loading branch information
oliversalzburg committed Nov 24, 2023
1 parent 54c0d82 commit 0e53605
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 17 deletions.
1 change: 1 addition & 0 deletions packages/kitten-scientists/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"types": "./build/index.ts",
"dependencies": {
"@oliversalzburg/js-utils": "0.0.22",
"ajv": "8.12.0",
"date-fns": "2.30.0",
"semver": "7.5.4",
"tslib": "2.6.2"
Expand Down
17 changes: 17 additions & 0 deletions packages/kitten-scientists/source/UserScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,27 @@ export class UserScript {
this.engine.imessage("settings.imported");
}

/**
* Import settings from a URL.
* This is an experimental feature, and only allows using profiles from
* https://kitten-science.com/ at this time.
*
* @param url - The URL of the profile to load.
*/
async importSettingsFromUrl(url: string) {
const importState = new State(url);
const settings = await importState.resolve();
settings.report.aggregate(console);

const stateIsValid = await importState.validate();
if (!stateIsValid) {
cerror("Imported state is invalid and not imported.");
return;
}

const state = importState.merge();
this.setSettings(state);
this.engine.imessage("settings.imported");
}

/**
Expand Down
57 changes: 56 additions & 1 deletion packages/kitten-scientists/source/state/State.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TreeNode } from "@oliversalzburg/js-utils";
import { InvalidOperationError, TreeNode } from "@oliversalzburg/js-utils";
import AjvModule, { SchemaObject } from "ajv";
import { UserScript } from "../UserScript.js";
import { StateLoader } from "./StateLoader.js";
import { StateMerger } from "./StateMerger.js";
Expand All @@ -14,6 +15,12 @@ export class State extends TreeNode<State> {

constructor(originUrl: string, parent?: State) {
super(parent);
if (!originUrl.startsWith("https://kitten-science.com/")) {
throw new InvalidOperationError(
"While state import is experimental, you can only import from 'https://kitten-science.com/'!",
);
}

this.originUrl = originUrl;
this.loader = new StateLoader(this);
}
Expand All @@ -23,6 +30,54 @@ export class State extends TreeNode<State> {
return { loader: this.loader, report };
}

async validate() {
const schemaBaselineRequest = await fetch(
"https://schema.kitten-science.com/working-draft/baseline/engine-state.schema.json",
);
const schemaProfileRequest = await fetch(
"https://schema.kitten-science.com/working-draft/settings-profile.schema.json",
);
const schemaBaseline = (await schemaBaselineRequest.json()) as SchemaObject;
const schemaProfile = (await schemaProfileRequest.json()) as SchemaObject;

// FIXME: https://github.com/ajv-validator/ajv/issues/2047
const Ajv = AjvModule.default;
const ajv = new Ajv({ allErrors: true, verbose: true });
const validateBaseline = ajv.compile(schemaBaseline);
const validateProfile = ajv.compile(schemaProfile);

const validateLoader = (loader: StateLoader) => {
const data = loader.data;
const isValidBaseline = validateBaseline(data);
const isValidProfile = validateProfile(data);
if (isValidProfile && !isValidBaseline) {
console.log(`VALID Profile: ${loader.state.originUrl})`);
return true;
}
if (isValidProfile && isValidBaseline) {
console.log(`VALID Baseline: ${loader.state.originUrl})`);
return true;
}
console.log(`INVALID: ${loader.state.originUrl})`);
return false;
};

const validateNode = (node: State) => {
let childrenValid = true;
if (0 < node.children.size) {
for (const child of node.children) {
const childValid = validateNode(child);
if (!childValid) {
childrenValid = false;
}
}
}
return validateLoader(node.loader) && childrenValid;
};

return validateNode(this);
}

merge() {
return new StateMerger(this).merge(UserScript.unknownAsEngineStateOrThrow({ v: "2.0.0" }));
}
Expand Down
3 changes: 1 addition & 2 deletions packages/kitten-scientists/source/state/StateLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,10 @@ export class StateLoader extends TreeNode<StateLoader> {
return this.report;
}

// TODO: Validate again JSON schema.
this.#data = data;
const bases = await this.#resolveBases(this.#data, cache);
if (bases.length === 0) {
this.report.log("🍁 Profile is a leaf node and should be a baseline!");
this.report.log("🍁 Profile is a leaf node.");
} else {
this.report.log("🌳 Profile is a tree node.");
}
Expand Down
2 changes: 1 addition & 1 deletion packages/kitten-scientists/source/state/StateMerger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class StateMerger {

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

return stateSubject;
Expand Down
2 changes: 2 additions & 0 deletions schemas/.scripts/load-profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const state = new State(
const profile = await state.resolve();
profile.report.aggregate(console);

await state.validate();

const engineState = state.merge();
writeFileSync("load-profile.result.json", JSON.stringify(engineState, undefined, 2));
console.info("Result written to 'load-profile.result.json'.");
Expand Down
36 changes: 23 additions & 13 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ __metadata:
"@types/jquery": "npm:3.5.29"
"@types/semver": "npm:7.5.6"
"@types/web": "npm:0.0.120"
ajv: "npm:8.12.0"
date-fns: "npm:2.30.0"
semver: "npm:7.5.4"
tslib: "npm:2.6.2"
Expand Down Expand Up @@ -1295,7 +1296,16 @@ __metadata:
languageName: node
linkType: hard

"@types/node@npm:*, @types/node@npm:20.10.0":
"@types/node@npm:*":
version: 20.9.4
resolution: "@types/node@npm:20.9.4"
dependencies:
undici-types: "npm:~5.26.4"
checksum: c8b48ace4c7e17715fa901201c98275f8e5268cf5895a8d149777eb0ec6c3ef6c831ff3917e92da5453a5dbe13f230caa50b348a0601b0d50eb9e628010c0364
languageName: node
linkType: hard

"@types/node@npm:20.10.0":
version: 20.10.0
resolution: "@types/node@npm:20.10.0"
dependencies:
Expand Down Expand Up @@ -1543,27 +1553,27 @@ __metadata:
languageName: node
linkType: hard

"ajv@npm:^6.12.4":
version: 6.12.6
resolution: "ajv@npm:6.12.6"
"ajv@npm:8.12.0, ajv@npm:^8.11.0":
version: 8.12.0
resolution: "ajv@npm:8.12.0"
dependencies:
fast-deep-equal: "npm:^3.1.1"
fast-json-stable-stringify: "npm:^2.0.0"
json-schema-traverse: "npm:^0.4.1"
json-schema-traverse: "npm:^1.0.0"
require-from-string: "npm:^2.0.2"
uri-js: "npm:^4.2.2"
checksum: 41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71
checksum: ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e
languageName: node
linkType: hard

"ajv@npm:^8.11.0":
version: 8.12.0
resolution: "ajv@npm:8.12.0"
"ajv@npm:^6.12.4":
version: 6.12.6
resolution: "ajv@npm:6.12.6"
dependencies:
fast-deep-equal: "npm:^3.1.1"
json-schema-traverse: "npm:^1.0.0"
require-from-string: "npm:^2.0.2"
fast-json-stable-stringify: "npm:^2.0.0"
json-schema-traverse: "npm:^0.4.1"
uri-js: "npm:^4.2.2"
checksum: ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e
checksum: 41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71
languageName: node
linkType: hard

Expand Down

0 comments on commit 0e53605

Please sign in to comment.