summaryrefslogtreecommitdiff
path: root/node_modules/@jet/environment/json/validation.js
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet/environment/json/validation.js
init commit
Diffstat (limited to 'node_modules/@jet/environment/json/validation.js')
-rw-r--r--node_modules/@jet/environment/json/validation.js250
1 files changed, 250 insertions, 0 deletions
diff --git a/node_modules/@jet/environment/json/validation.js b/node_modules/@jet/environment/json/validation.js
new file mode 100644
index 0000000..b338c4e
--- /dev/null
+++ b/node_modules/@jet/environment/json/validation.js
@@ -0,0 +1,250 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.unexpectedNull = exports.catchingContext = exports.context = exports.recordValidationIncidents = exports.endContext = exports.getContextNames = exports.beginContext = exports.messageForRecoveryAction = exports.isValidatable = exports.unexpectedType = exports.extendedTypeof = void 0;
+const optional_1 = require("../types/optional");
+/**
+ * Returns a string containing the type of a given value.
+ * This function augments the built in `typeof` operator
+ * to return sensible values for arrays and null values.
+ *
+ * @privateRemarks
+ * This function is exported for testing.
+ *
+ * @param value - The value to find the type of.
+ * @returns A string containing the type of `value`.
+ */
+function extendedTypeof(value) {
+ if (Array.isArray(value)) {
+ return "array";
+ }
+ else if (value === null) {
+ return "null";
+ }
+ else {
+ return typeof value;
+ }
+}
+exports.extendedTypeof = extendedTypeof;
+/**
+ * Reports a non-fatal validation failure, logging a message to the console.
+ * @param recovery - The recovery action taken when the bad type was found.
+ * @param expected - The expected type of the value.
+ * @param actual - The actual value.
+ * @param pathString - A string containing the path to the value on the object which failed type validation.
+ */
+function unexpectedType(recovery, expected, actual, pathString) {
+ const actualType = extendedTypeof(actual);
+ const prettyPath = (0, optional_1.isSome)(pathString) && pathString.length > 0 ? pathString : "<this>";
+ trackIncident({
+ type: "badType",
+ expected: expected,
+ // Our test assertions are matching the string interpolation of ${actual} value.
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ actual: `${actualType} (${actual})`,
+ objectPath: prettyPath,
+ contextNames: getContextNames(),
+ recoveryAction: recovery,
+ stack: new Error().stack,
+ });
+}
+exports.unexpectedType = unexpectedType;
+// endregion
+/**
+ * Determines if a given object conforms to the Validatable interface
+ * @param possibleValidatable - An object that might be considered validatable
+ *
+ * @returns `true` if it is an instance of Validatable, `false` if not
+ */
+function isValidatable(possibleValidatable) {
+ if ((0, optional_1.isNothing)(possibleValidatable)) {
+ return false;
+ }
+ // MAINTAINER'S NOTE: We must check for either the existence of a pre-existing incidents
+ // property *or* the ability to add one. Failure to do so will cause
+ // problems for clients that either a) use interfaces to define their
+ // view models; or b) return collections from their service routes.
+ return (Object.prototype.hasOwnProperty.call(possibleValidatable, "$incidents") ||
+ Object.isExtensible(possibleValidatable));
+}
+exports.isValidatable = isValidatable;
+/**
+ * Returns a developer-readable diagnostic message for a given recovery action.
+ * @param action - The recovery action to get the message for.
+ * @returns The message for `action`.
+ */
+function messageForRecoveryAction(action) {
+ switch (action) {
+ case "coercedValue":
+ return "Coerced format";
+ case "defaultValue":
+ return "Default value used";
+ case "ignoredValue":
+ return "Ignored value";
+ default:
+ return "Unknown";
+ }
+}
+exports.messageForRecoveryAction = messageForRecoveryAction;
+// region Contexts
+/**
+ * Shared validation context "stack".
+ *
+ * Because validation incidents propagate up the context stack,
+ * the representation used here is optimized for memory usage.
+ * A more literal representation of this would be a singly linked
+ * list describing a basic stack, but that will produce a large
+ * amount of unnecessary garbage and require copying `incidents`
+ * arrays backwards.
+ */
+const contextState = {
+ /// The names of each validation context on the stack.
+ nameStack: Array(),
+ /// All incidents reported so far. Cleared when the
+ /// context stack is emptied.
+ incidents: Array(),
+ // TODO: Removal of this is being tracked here:
+ // <rdar://problem/35015460> Intro Pricing: Un-suppress missing parent 'offers' error when server address missing key
+ /// The paths for incidents we wish to forgo tracking.
+ suppressedIncidentPaths: Array(),
+};
+/**
+ * Begin a new validation context with a given name,
+ * pushing it onto the validation context stack.
+ * @param name - The name for the validation context.
+ */
+function beginContext(name) {
+ contextState.nameStack.push(name);
+}
+exports.beginContext = beginContext;
+/**
+ * Traverses the validation context stack and collects all of the context names.
+ * @returns The names of all validation contexts on the stack, from oldest to newest.
+ */
+function getContextNames() {
+ if (contextState.nameStack.length === 0) {
+ return ["<empty stack>"];
+ }
+ return contextState.nameStack.slice(0);
+}
+exports.getContextNames = getContextNames;
+/**
+ * Ends the current validation context
+ */
+function endContext() {
+ if (contextState.nameStack.length === 0) {
+ console.warn("endContext() called without active validation context, ignoring");
+ }
+ contextState.nameStack.pop();
+}
+exports.endContext = endContext;
+/**
+ * Records validation incidents back into an object that implements Validatable.
+ *
+ * Note: This method has a side-effect that the incident queue and name stack are cleared
+ * to prepare for the next thread's invocation.
+ *
+ * @param possibleValidatable - An object that may conform to Validatable, onto which we
+ * want to stash our validation incidents
+ */
+function recordValidationIncidents(possibleValidatable) {
+ if (isValidatable(possibleValidatable)) {
+ possibleValidatable.$incidents = contextState.incidents;
+ }
+ contextState.incidents = [];
+ contextState.nameStack = [];
+ contextState.suppressedIncidentPaths = [];
+}
+exports.recordValidationIncidents = recordValidationIncidents;
+/**
+ * Create a transient validation context, and call a function that will return a value.
+ *
+ * Prefer this function over manually calling begin/endContext,
+ * it is exception safe.
+ *
+ * @param name - The name of the context
+ * @param producer - A function that produces a result
+ * @returns <Result> The resulting type
+ */
+function context(name, producer, suppressingPath) {
+ let suppressingName = null;
+ if ((0, optional_1.isSome)(suppressingPath) && suppressingPath.length > 0) {
+ suppressingName = name;
+ contextState.suppressedIncidentPaths.push(suppressingPath);
+ }
+ let result;
+ try {
+ beginContext(name);
+ result = producer();
+ }
+ catch (e) {
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ if (!e.hasThrown) {
+ unexpectedType("defaultValue", "no exception", e.message);
+ e.hasThrown = true;
+ }
+ throw e;
+ }
+ finally {
+ if (name === suppressingName) {
+ contextState.suppressedIncidentPaths.pop();
+ }
+ endContext();
+ }
+ return result;
+}
+exports.context = context;
+/**
+ * Create a transient validation context, that catches errors and returns null
+ *
+ * @param name - The name of the context
+ * @param producer - A function that produces a result
+ * @param caught - An optional handler to provide a value when an error is caught
+ * @returns <Result> The resulting type
+ */
+function catchingContext(name, producer, caught) {
+ let result = null;
+ try {
+ result = context(name, producer);
+ }
+ catch (e) {
+ result = null;
+ if ((0, optional_1.isSome)(caught)) {
+ result = caught(e);
+ }
+ }
+ return result;
+}
+exports.catchingContext = catchingContext;
+/**
+ * Track an incident within the current validation context.
+ * @param incident - An incident object describing the problem.
+ */
+function trackIncident(incident) {
+ if (contextState.suppressedIncidentPaths.includes(incident.objectPath)) {
+ return;
+ }
+ contextState.incidents.push(incident);
+}
+// endregion
+// region Nullability
+/**
+ * Reports a non-fatal error indicating a value was unexpectedly null.
+ * @param recovery - The recovery action taken when the null value was found.
+ * @param expected - The expected type of the value.
+ * @param pathString - A string containing the path to the value on the object which was null.
+ */
+function unexpectedNull(recovery, expected, pathString) {
+ const prettyPath = (0, optional_1.isSome)(pathString) && pathString.length > 0 ? pathString : "<this>";
+ trackIncident({
+ type: "nullValue",
+ expected: expected,
+ actual: "null",
+ objectPath: prettyPath,
+ contextNames: getContextNames(),
+ recoveryAction: recovery,
+ stack: new Error().stack,
+ });
+}
+exports.unexpectedNull = unexpectedNull;
+// endregion
+//# sourceMappingURL=validation.js.map \ No newline at end of file