summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/JsonSchema.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/modules/JsonSchema.sys.mjs176
1 files changed, 176 insertions, 0 deletions
diff --git a/toolkit/modules/JsonSchema.sys.mjs b/toolkit/modules/JsonSchema.sys.mjs
new file mode 100644
index 0000000000..62b063ec8a
--- /dev/null
+++ b/toolkit/modules/JsonSchema.sys.mjs
@@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A facade around @cfworker/json-schema that provides additional formats and
+ * convenience methods whil executing inside a sandbox.
+ */
+
+const sandbox = new Cu.Sandbox(null, {
+ wantComponents: false,
+ wantGlobalProperties: ["URL"],
+});
+
+Services.scriptloader.loadSubScript(
+ "chrome://global/content/third_party/cfworker/json-schema.js",
+ sandbox
+);
+
+/**
+ * A JSON Schema string format for URLs intended to go through Services.urlFormatter.
+ */
+Cu.exportFunction(
+ function validateMozUrlFormat(input) {
+ try {
+ const formatted = Services.urlFormatter.formatURL(input);
+ return Cu.waiveXrays(sandbox.fastFormat).uri(formatted);
+ } catch {
+ return false;
+ }
+ },
+ sandbox.fastFormat,
+ { defineAs: "moz-url-format" }
+);
+
+// initialBaseURI defaults to github.com/cfworker, which will be confusing.
+Cu.evalInSandbox(
+ `this.initialBaseURI = initialBaseURI = new URL("http://mozilla.org");`,
+ sandbox
+);
+
+/**
+ * A JSONSchema validator that performs validation inside a sandbox.
+ */
+class Validator {
+ #inner;
+ #draft;
+
+ /**
+ * Create a new validator.
+ *
+ * @param {object} schema The schema to validate with.
+ * @param {object} options Options for the validator.
+ * @param {string} options.draft The draft to validate against. Should be one
+ * of "4", "6", "7", "2019-09", or "2020-12".
+ *
+ * If the |$schema| key is present in the
+ * |schema|, it will be used to auto-detect the
+ * correct version. Otherwise, 2019-09 will be
+ * used.
+ * @param {boolean} options.shortCircuit Whether or not the validator should
+ * return after a single error occurs.
+ */
+ constructor(
+ schema,
+ { draft = detectSchemaDraft(schema), shortCircuit = true } = {}
+ ) {
+ this.#draft = draft;
+ this.#inner = Cu.waiveXrays(
+ new sandbox.Validator(Cu.cloneInto(schema, sandbox), draft, shortCircuit)
+ );
+ }
+
+ /**
+ * Validate the instance against the known schemas.
+ *
+ * @param {object} instance The instance to validate.
+ *
+ * @return {object} An object with |valid| and |errors| keys that indicates
+ * the success of validation.
+ */
+ validate(instance) {
+ return this.#inner.validate(Cu.cloneInto(instance, sandbox));
+ }
+
+ /**
+ * Add a schema to the validator.
+ *
+ * @param {object} schema A JSON schema object.
+ * @param {string} id An optional ID to identify the schema if it does not
+ * provide an |$id| field.
+ */
+ addSchema(schema, id) {
+ const draft = detectSchemaDraft(schema, undefined);
+ if (draft && this.#draft != draft) {
+ console.error(
+ `Adding a draft "${draft}" schema to a draft "${
+ this.#draft
+ }" validator.`
+ );
+ }
+ this.#inner.addSchema(Cu.cloneInto(schema, sandbox), id);
+ }
+}
+
+/**
+ * A wrapper around validate that provides some options as an object
+ * instead of positional arguments.
+ *
+ * @param {object} instance The instance to validate.
+ * @param {object} schema The JSON schema to validate against.
+ * @param {object} options Options for the validator.
+ * @param {string} options.draft The draft to validate against. Should
+ * be one of "4", "6", "7", "2019-09", or "2020-12".
+ *
+ * If the |$schema| key is present in the |schema|, it
+ * will be used to auto-detect the correct version.
+ * Otherwise, 2019-09 will be used.
+ * @param {boolean} options.shortCircuit Whether or not the validator should
+ * return after a single error occurs.
+ *
+ * @returns {object} An object with |valid| and |errors| keys that indicates the
+ * success of validation.
+ */
+function validate(
+ instance,
+ schema,
+ { draft = detectSchemaDraft(schema), shortCircuit = true } = {}
+) {
+ const clonedSchema = Cu.cloneInto(schema, sandbox);
+
+ return sandbox.validate(
+ Cu.cloneInto(instance, sandbox),
+ clonedSchema,
+ draft,
+ sandbox.dereference(clonedSchema),
+ shortCircuit
+ );
+}
+
+function detectSchemaDraft(schema, defaultDraft = "2019-09") {
+ const { $schema } = schema;
+
+ if (typeof $schema === "undefined") {
+ return defaultDraft;
+ }
+
+ switch ($schema) {
+ case "http://json-schema.org/draft-04/schema#":
+ return "4";
+
+ case "http://json-schema.org/draft-06/schema#":
+ return "6";
+
+ case "http://json-schema.org/draft-07/schema#":
+ return "7";
+
+ case "https://json-schema.org/draft/2019-09/schema":
+ return "2019-09";
+
+ case "https://json-schema.org/draft/2020-12/schema":
+ return "2020-12";
+
+ default:
+ console.error(
+ `Unexpected $schema "${$schema}", defaulting to ${defaultDraft}.`
+ );
+ return defaultDraft;
+ }
+}
+
+export const JsonSchema = {
+ Validator,
+ validate,
+ detectSchemaDraft,
+};