UNPKG

@cowwoc/requirements

Version:

A fluent API for enforcing design contracts with automatic message generation.

146 lines (145 loc) 5.74 kB
import { ContextGenerator, AbstractValidator, assert, StringSection, ContextSection, requireThatStringIsNotEmpty, AssertionError, requireThatValueIsNotNull, assertThatValueIsNotNull } from "../../internal.mjs"; /** * Builds an error message. */ class MessageBuilder { static DIFF_LEGEND = `\ Legend ------ + : Add this character to the value - : Remove this character from the value [index] : Refers to the index of a collection element @line-number: Refers to the line number of a multiline string `; validator; message; failureContext = new Map(); /** * A string that describes the difference between the expected and actual values. */ diff = []; /** * @param validator - the validator * @param message - (optional) the error message (empty string when absent) * @throws AssertionError if: * <ul> * <li>any of the arguments are null</li> * <li>`message` is blank or does not end with a dot</li> * </ul> */ constructor(validator, message) { assertThatValueIsNotNull(validator, "validator"); if (!message.endsWith(".")) throw new AssertionError(`Message must end with a dot: ${message}`); this.validator = validator; this.message = message; } /** * Appends context to the error message. If the context previously contained a mapping for the name, the * old value is replaced. * * @param value - the value of the context * @param name - (optional) the name of the context (empty string if absent) * @returns this * @throws AssertionError if `name`: * <ul> * <li>is `undefined` or `null`</li> * <li>is empty</li> * <li>contains whitespace or a colon</li> * </ul> */ withContext(value, name) { requireThatValueIsNotNull(name, "name"); this.failureContext.set(name, value); return this; } /** * Adds a DIFF to the context that compares the value to an expected value * * @param actualName - the name of the value * @param actualValue - the object representation of the value * @param expectedName - the name of the expected value * @param expectedValue - the object representation of the expected value * @returns this */ addDiff(actualName, actualValue, expectedName, expectedValue) { const contextGenerator = new ContextGenerator(this.validator.getScope(), this.validator.configuration(), actualName, expectedName). actualValue(actualValue). expectedValue(expectedValue); this.diff.push(...contextGenerator.build()); return this; } /** * @returns the contextual information associated with a validation failure */ getValidatorContext() { const mergedContext = new Map(this.failureContext); for (const entry of this.validator.getContext()) mergedContext.set(entry[0], entry[1]); const stringMappers = this.validator.configuration().stringMappers(); const contextAsString = new Map(); for (const [key, value] of mergedContext.entries()) contextAsString.set(key, stringMappers.toString(value)); return new ContextSection(contextAsString); } /** * Quotes the name of a parameter, unless it references a method call. * * @param name - the name of a parameter * @returns the updated name */ static quoteName(name) { if (name.includes(".")) return name; return "\"" + name + "\""; } toString() { const context = []; this.addValidatorContextToContext(context); this.addDiffToContext(context); this.addErrorMessageToContext(context); return this.contextToString(context); } addDiffToContext(context) { if (this.diff.length === 0) return; if (context.length !== 0 || this.message.length !== 0) { // Add an extra newline in front of the diff context.push(new StringSection("")); } context.push(...this.diff); } addValidatorContextToContext(context) { const validatorContext = this.getValidatorContext(); if (validatorContext.value.size !== 0) context.push(validatorContext); } contextToString(context) { let maxKeyLength = 0; for (const section of context) maxKeyLength = Math.max(maxKeyLength, section.getMaxKeyLength()); let result = ""; const lines = []; for (const section of context) lines.push(...section.getLines(maxKeyLength)); result += lines.join("\n"); return result.toString(); } addErrorMessageToContext(context) { let updatedMessage; if (context.length === 0 && !this.message.includes("\n")) { requireThatStringIsNotEmpty(this.message, "message"); assert(this.message.endsWith("."), undefined, this.message); // Strip the period from the end of single-line messages, unless it contains a comma. if (this.message.includes(",")) updatedMessage = this.message; else updatedMessage = this.message.substring(0, this.message.length - 1); } else updatedMessage = this.message; context.unshift(new StringSection(updatedMessage)); } } export { MessageBuilder }; //# sourceMappingURL=MessageBuilder.mjs.map