@zowe/imperative
Version:
framework for building configurable CLIs
878 lines • 48.5 kB
JavaScript
"use strict";
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SyntaxValidator = void 0;
const fs = require("fs");
const util_1 = require("util");
const messages_1 = require("../../../messages");
const CliUtils_1 = require("../../../utilities/src/CliUtils");
const constants_1 = require("../../../constants");
const CommandUtils_1 = require("../utils/CommandUtils");
const logger_1 = require("../../../logger");
const utilities_1 = require("../../../utilities");
/**
* The Imperative default syntax validator. Accepts the input arguments, command
* definitions, and a response object. Validates the syntax and issues the
* appropriate error messages if necessary.
*
* TODO - Consider limiting to a single purpose of producing error documents
* TODO - that will be outputted by the command processor in a "more structured"
* TODO - fasion.
*
* @export
* @class SyntaxValidator
*/
class SyntaxValidator {
/**
* Create the object - maintains the definition used to validate against the input arguments.
* @param {ICommandDefinition} commandDefinition: The command definition document
* @param {ICommandDefinition} fullCommandDefinition: The full command definition
*/
constructor(commandDefinition, fullCommandDefinition) {
/**
* Get an instance of the logger.
* @private
* @type {Logger}
* @memberof SyntaxValidator
*/
this.mLogger = logger_1.Logger.getImperativeLogger();
/**
* The error list that is appended to the error response object.
* @private
* @type {ICommandValidatorError}
* @memberof SyntaxValidator
*/
this.mErrorList = [];
this.mCommandDefinition = commandDefinition;
this.mOptionDefinitionsMap = [];
if (this.mCommandDefinition.options) {
for (const option of this.mCommandDefinition.options) {
this.mOptionDefinitionsMap[option.name] = option;
}
}
this.mDefinitionTree = fullCommandDefinition;
}
/**
* Validate the command syntax.
* @param {CommandResponse} responseObject: The response object to output the messages.
* @param {ICommandArguments} commandArguments
* @return {Promise<ICommandResponse>}
*/
validate(responseObject, commandArguments) {
return new Promise((validationComplete) => {
const syntaxValid = this.validateSyntax(commandArguments, responseObject);
validationComplete({ valid: syntaxValid });
});
}
/**
* Validate the options. Includes automatic validation based on option
* and command definition as well as
* custom validation provided by the user
* @return {boolean}: True if the options are valid
*/
validateSyntax(commandArguments, responseObject) {
var _a;
const optionDefs = this.mOptionDefinitionsMap;
let valid = true;
const util = CommandUtils_1.CommandUtils;
const fullCommandName = CommandUtils_1.CommandUtils.getFullCommandName(this.mCommandDefinition, this.mDefinitionTree);
/**
* Prevent empty string options, regardless of if they are
* required or not e.g. --zosmf-profile (without a value)
*/
if (!(this.mCommandDefinition.options == null)) {
for (const option of this.mCommandDefinition.options) {
if (!(commandArguments[option.name] == null) &&
(option.type !== "stringOrEmpty" && commandArguments[option.name] === "") ||
option.type !== "boolean" && commandArguments[option.name] === true) {
valid = false;
this.emptyValueError(responseObject, option.name);
}
}
}
const expectedUnderscoreLength = fullCommandName.split(" ").length;
/**
* Reject unknown positional arguments
* TODO Investigate removing this because currently it is not being used. After updating
* yargs to version >14, it handles unknown positionals automatically in strict mode, before
* Imperative gets a chance to detect them.
*/
if (this.mCommandDefinition.type === "command" &&
!(this.mCommandDefinition.name == null) &&
commandArguments._.length > expectedUnderscoreLength) {
valid = false;
this.unknownPositionalError(responseObject, commandArguments, expectedUnderscoreLength);
}
else {
this.mLogger.trace("no unknown positionals. Length of positional arguments was: %s. Contents of _ were %s, Expected " +
"\"_\" to have length of %s", commandArguments._.length, commandArguments._, expectedUnderscoreLength);
}
/**
* If there is a set of options of which at least one must be specified,
* make sure at least one of them was specified by the user.
*/
if (this.mCommandDefinition.mustSpecifyOne &&
this.mCommandDefinition.mustSpecifyOne.length > 0) {
let atLeastOneRequiredOptionFound = false;
for (const option of Object.keys(commandArguments)) {
if (util.optionWasSpecified(option, this.mCommandDefinition, commandArguments) &&
this.mCommandDefinition.mustSpecifyOne.indexOf(option) >= 0) {
atLeastOneRequiredOptionFound = true;
this.mLogger.debug(".mustSpecifyOneOf() satisfied by %s", option);
break;
}
}
if (!atLeastOneRequiredOptionFound) {
this.mustSpecifyOneError(responseObject);
valid = false;
}
}
/**
* If there is a set of options of which at least one must be specified,
* make sure at least one of them was specified by the user.
*/
if (this.mCommandDefinition.onlyOneOf &&
this.mCommandDefinition.onlyOneOf.length > 0) {
const specified = [];
for (const option of this.mCommandDefinition.onlyOneOf) {
if (util.optionWasSpecified(option, this.mCommandDefinition, commandArguments)) {
specified.push(option);
this.mLogger.debug(".onlyOneOf option present: %s", option);
}
else {
this.mLogger.debug(".onlyOneof option not specified: %s", option);
}
}
if (specified.length > 1) {
this.onlyOneOfError(responseObject, specified);
valid = false;
}
else {
this.mLogger.debug(".onlyOneOf validation passed. %d of the following options were specified: " +
"[%s]", specified.length, this.mCommandDefinition.onlyOneOf.join(", "));
}
}
/**
* Check for missing positional arguments. We can enforce this at the "yargs" definition level, however
* we would like to have more syntax validation and control over the error message. Therefore, the mark them
* as "optional" to yargs and enforce the required here.
*/
if (!(this.mCommandDefinition.positionals == null) && this.mCommandDefinition.positionals.length > 0) {
const missingPositionals = [];
for (const positional of this.mCommandDefinition.positionals) {
if (positional.required) {
// Use replace to trim possible ... which is used for arrays
const positionalName = positional.name.replace("...", "");
if (commandArguments[positionalName] == null ||
positional.type !== "stringOrEmpty" && commandArguments[positionalName] === "") {
missingPositionals.push(positional);
}
}
}
if (missingPositionals.length > 0) {
this.missingPositionalParameter(missingPositionals, responseObject);
valid = false;
}
/**
* Validate that the positional parameter matches the supplied regex.
*/
for (const positional of this.mCommandDefinition.positionals) {
if (!(commandArguments[positional.name] == null)) {
if (positional.regex) {
if (commandArguments[positional.name]
.toString().match(new RegExp(positional.regex)) == null) {
valid = false;
this.positionalParameterInvalid(positional, commandArguments[positional.name], responseObject);
}
}
if (positional.type === "number") {
valid = this.validateNumeric(commandArguments[positional.name], positional, responseObject, true) && valid;
// Convert to number for backwards compatability
if (valid) {
const changedOptions = CliUtils_1.CliUtils.setOptionValue(positional.name, [], parseFloat(commandArguments[positional.name]));
for (const [k, v] of Object.entries(changedOptions)) {
commandArguments[k] = v;
}
}
}
if (!(positional.stringLengthRange == null) &&
!(positional.stringLengthRange[0] == null) &&
!(positional.stringLengthRange[1] == null)) {
valid = this.validateOptionValueLength(positional, commandArguments[positional.name], responseObject, true) && valid;
}
}
}
}
for (const optionName of Object.keys(optionDefs)) {
const optionDef = optionDefs[optionName];
/**
* Are any required options omitted?
*/
if (optionDef.required && !util.optionWasSpecified(optionName, this.mCommandDefinition, commandArguments)) {
this.missingOptionError(optionDef, responseObject);
valid = false;
}
/**
* If omitting an option implies that some other option must be specified,
* validate that with any missing options
*/
if (!(optionDef.absenceImplications == null) && optionDef.absenceImplications.length > 0) {
for (const implication of optionDef.absenceImplications) {
if (!util.optionWasSpecified(optionName, this.mCommandDefinition, commandArguments)
&& !util.optionWasSpecified(implication, this.mCommandDefinition, commandArguments)) {
this.absenceImplicationError(optionDef, responseObject);
valid = false;
break;
}
}
}
/**
* validations that only apply if the option has been specified
* We consider setting a flag to false to be "not specifying it"
*/
if (util.optionWasSpecified(optionName, this.mCommandDefinition, commandArguments)) {
// yargs puts options specified multiple times into an array even if they are string
// type. We want to prevent this.
if (optionDef.type !== "array" && Array.isArray(commandArguments[optionName])) {
valid = false;
this.specifiedMultipleTimesError(optionDef, responseObject);
}
// if the option type IS array but the value provided is not an array,
// that's an error
if (optionDef.type === "array" && !Array.isArray(commandArguments[optionName])) {
valid = false;
this.notAnArrayError(optionDef, responseObject, commandArguments[optionName]);
}
// check if the value of the option conforms to the allowableValues (if any)
if (!(optionDef.allowableValues == null)) {
// Make a copy of optionDef, so that modifications below are only used in this place
const optionDefCopy = JSON.parse(JSON.stringify(optionDef));
// Use modified regular expressions for allowable values to check and to generate error
optionDefCopy.allowableValues.values = optionDef.allowableValues.values.map((regex) => {
// Prepend "^" if not existing
if (!regex.startsWith("^")) {
regex = "^" + regex;
}
// Append "$" if the last char is an escaped "$" or chars other than "$"
if (!regex.endsWith("$") || regex.endsWith("\\$")) {
regex = regex + "$";
}
return regex;
});
const optionValue = commandArguments[optionName];
const optionValueArray = Array.isArray(optionValue) ? optionValue : [optionValue];
optionValueArray.filter(value => !this.checkIfAllowable(optionDefCopy.allowableValues, value))
.forEach(value => {
this.invalidOptionError(optionDef, responseObject, value);
valid = false;
});
}
if (!(optionDef.conflictsWith == null) && optionDef.conflictsWith.length > 0) {
for (const conflict of optionDef.conflictsWith) {
if (util.optionWasSpecified(conflict, this.mCommandDefinition, commandArguments)) {
this.optionCombinationInvalidError(optionDef, this.getOptionDefinitionFromName(conflict), responseObject);
valid = false;
}
}
}
/**
* Check validity of implications
*/
if (!(optionDef.implies == null) && optionDef.implies.length > 0) {
for (const implication of optionDef.implies) {
if (!util.optionWasSpecified(implication, this.mCommandDefinition, commandArguments)) {
this.optionDependencyError(optionDef, this.mOptionDefinitionsMap[implication], responseObject);
valid = false;
}
}
}
/**
* Check validity of 'implication alternatives' (.impliesOneOf())
*/
if (!(optionDef.impliesOneOf == null) && optionDef.impliesOneOf.length > 0) {
let implicationSatisfied = false;
for (const implication of optionDef.impliesOneOf) {
if (util.optionWasSpecified(implication, this.mCommandDefinition, commandArguments)) {
this.mLogger.debug(".impliesOneOf() was satisfied by %s", implication);
implicationSatisfied = true;
break;
}
}
if (!implicationSatisfied) {
this.implicationAlternativeError(optionDef, responseObject);
valid = false;
}
}
/**
* Check whether local files exist if they are supposed to
*/
if (optionDef.type === "existingLocalFile") {
if (!fs.existsSync(commandArguments[optionDef.name])) {
this.fileOptionError(optionDef, commandArguments, responseObject);
valid = false;
}
else {
this.mLogger.debug("the local file %s existed as required", commandArguments[optionDef.name]);
}
}
else if (optionDef.type === "boolean") {
valid = this.validateBoolean(commandArguments[optionDef.name], optionDef, responseObject) && valid;
}
else if (optionDef.type === "number") {
valid = this.validateNumeric(commandArguments[optionDef.name], optionDef, responseObject) && valid;
// Convert to numbers for backwards compatibility - sets all possible values
if (valid) {
const changedOptions = CliUtils_1.CliUtils.setOptionValue(optionDef.name, (_a = optionDef.aliases) !== null && _a !== void 0 ? _a : [], parseFloat(commandArguments[optionDef.name]));
for (const [k, v] of Object.entries(changedOptions)) {
commandArguments[k] = v;
}
}
}
/**
* Validate that the option's value is valid json.
*/
else if (optionDef.type === "json") {
try {
JSON.parse(commandArguments[optionDef.name]);
}
catch (e) {
valid = false;
this.invalidJsonString(e, this.mOptionDefinitionsMap[optionDef.name], responseObject, commandArguments[optionDef.name]);
}
}
/**
* Validate option values
*/
if (!(optionDef.numericValueRange == null) &&
!(optionDef.numericValueRange[0] == null) &&
!(optionDef.numericValueRange[1] == null)) {
valid = this.validateOptionValueRange(optionDef, commandArguments[optionDef.name], responseObject) && valid;
}
/**
* Validate option lengths
*/
if (!(optionDef.stringLengthRange == null) &&
!(optionDef.stringLengthRange[0] == null) &&
!(optionDef.stringLengthRange[1] == null)) {
valid = this.validateOptionValueLength(optionDef, commandArguments[optionDef.name], responseObject) && valid;
}
/**
* Validate array duplication
*/
if (optionDef.type === "array" && optionDef.arrayAllowDuplicate === false) {
const value = commandArguments[optionDef.name];
if (Array.isArray(value)) {
valid = this.validateArrayDuplicate(optionDef, value, responseObject) && valid;
}
}
if (!(optionDef.valueImplications == null) && Object.keys(optionDef.valueImplications).length > 0) {
for (const value of Object.keys(optionDef.valueImplications)) {
const implicationObject = optionDef.valueImplications[value];
if (implicationObject.isCaseSensitive &&
commandArguments[optionName] === value ||
!implicationObject.isCaseSensitive &&
commandArguments[optionName].toUpperCase() === value.toUpperCase()) {
for (const impliedOption of implicationObject.impliedOptionNames) {
if (!util.optionWasSpecified(impliedOption, this.mCommandDefinition, commandArguments)) {
this.valueRequiresAdditionalOption(optionDef, value, this.getOptionDefinitionFromName(impliedOption), responseObject);
valid = false;
}
}
}
}
}
}
}
// TODO - call custom validator?
return valid;
}
/**
* Issue an error message indicating that the JSON string provided is not valid.
* @param {Error} error - the JSON parse try/catch error.
* @param {ICommandOptionDefinition} optionDefinition - The Option definition
* Validate the options. Includes automatic validation based on option
* and command definition as well as custom validation provided by the user
* @param {CommandResponse} responseObject: The response object for producing messages.
* @return {boolean}: True if the options are valid, false if there is a syntax error
*/
invalidJsonString(error, optionDefinition, responseObject, valueSpecified) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
// TODO - check if JSON can be positional
const msg = responseObject.console.error(`Invalid JSON string supplied for the following option:\n${this.getDashFormOfOption(optionDefinition.name)}\n` +
`\nYou specified:\n${valueSpecified}\n` +
`\nJSON parsing failed with the following error:\n${error.message}`);
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDefinition.name, definition: optionDefinition });
}
/**
* Issue the 'file must exist' error
* @param {ICommandOptionDefinition} optionDefinition: the option definition for which the user specified a non-existent file
* @param {CommandResponse} responseObject: The response object for producing messages.
* @param {ICommandArguments} commandArguments: The arguments specified by the user.
* @param isPositional - is the option a positional option? defaults to false
*/
fileOptionError(optionDefinition, commandArguments, responseObject, isPositional = false) {
const mustacheSummary = this.getMustacheSummaryForOption(optionDefinition, isPositional);
mustacheSummary.value = commandArguments[optionDefinition.name];
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject
.console.error("Invalid file path specified for option:\n{{long}} {{aliases}}\n" +
"\nYou specified:\n\"{{&value}}\"\n\nThe file does not exist", mustacheSummary);
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDefinition.name, definition: optionDefinition });
}
/**
* Build up an object used for filling in syntax error messages with fields from an
* option definition
* @param optionDefinition - the option definition to use
* @param isPositional - is the option a positional option? defaults to false
* @returns {{long: string, aliases: string, description: string}} -
* an object used to replace {{variables}} with mustache
*/
getMustacheSummaryForOption(optionDefinition, isPositional = false) {
let aliasString;
if (!isPositional) {
const def = optionDefinition;
aliasString = def.aliases != null && def.aliases.length > 0 ?
"(" + def.aliases.map((alias) => {
return this.getDashFormOfOption(alias);
}).join(",") + ")" : "";
}
else {
aliasString = "";
}
let longName;
if (isPositional) {
longName = CliUtils_1.CliUtils
.getPositionalSyntaxString(optionDefinition.required, optionDefinition.name);
}
else {
longName = CliUtils_1.CliUtils.getDashFormOfOption(optionDefinition.name);
}
return {
long: longName,
aliases: aliasString,
description: utilities_1.TextUtils.wordWrap(optionDefinition.description)
};
}
/**
* Get the 'dash form' of an option as it would appear in a user's command,
* appending the proper number of dashes depending on the length of the option name
* @param {string} optionName - e.g. my-option
* @returns {string} - e.g. --my-option
*/
getDashFormOfOption(optionName) {
const dashes = optionName.length > 1 ? constants_1.Constants.OPT_LONG_DASH :
constants_1.Constants.OPT_SHORT_DASH;
return dashes + optionName;
}
getOptionDefinitionFromName(name) {
const defs = this.mOptionDefinitionsMap;
for (const defName of Object.keys(defs)) {
if (defName === name) {
return defs[defName];
}
}
throw new Error("No such option was defined: " + name);
}
/**
* Accept the input and check for a match.
* @param {string} input: the input value to check.
* @param {ICommandOptionAllowableValues} allowable: The set of allowable values.
* @returns {boolean}: true if the value is allowable.
*/
checkIfAllowable(allowable, input) {
let matchFound = false;
for (const value of allowable.values) {
const flags = allowable.caseSensitive ? "g" : "ig";
if (new RegExp(value, flags).test(input)) {
matchFound = true;
break;
}
}
return matchFound;
}
/**
* Issue the 'option is required' error.
* @param {ICommandOptionDefinition} optionDefinition: the definition for this option
* @param {CommandResponse} responseObject: The response object for producing messages.
*/
missingOptionError(optionDefinition, responseObject) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("Missing Required Option:\n{{long}} {{aliases}}\n\n" +
"Option Description:\n{{description}}", this.getMustacheSummaryForOption(optionDefinition));
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDefinition.name, definition: optionDefinition });
}
/**
* Issue error message indicating that the positional parameter specified does not match the regex.
* @param {ICommandPositionalDefinition} positionalDefinition: The positional argument definition.
* @param {string} specified: The argument that was specified.
* @param {CommandResponse} responseObject: The response object for producing messages.
*/
positionalParameterInvalid(positionalDefinition, specified, responseObject) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("Invalid format specified for positional option:\n{{parameter}}\n\n" +
"You specified:\n{{spec}}\n\nOption must match the following regular expression:\n{{format}}", {
parameter: positionalDefinition.name, format: positionalDefinition.regex,
spec: specified, desc: utilities_1.TextUtils.wordWrap(positionalDefinition.description)
});
this.appendValidatorError(responseObject, { message: msg, optionInError: positionalDefinition.name, definition: positionalDefinition });
}
/**
* Validate the that option's value is within the range specified. If not, error messages will be issued.
*
* @param {ICommandOptionDefinition} optionDefinition: The option definition
* @param {string} optionValue: The option value
* @param {CommandResponse} responseObject: The response object for producing messages.
* @return {boolean}: false if the option's value is not valid.
*/
validateOptionValueRange(optionDefinition, optionValue, responseObject) {
let valid = true;
const min = optionDefinition.numericValueRange[0];
const max = optionDefinition.numericValueRange[1];
if (optionValue < min ||
optionValue > max) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("Invalid numeric value specified for option:\n{{option}}\n\n" +
"You specified:\n{{value}}\n\n" +
"Value must be between {{min}} and {{max}} (inclusive)", {
option: this.getDashFormOfOption(optionDefinition.name),
length: optionValue,
value: optionValue,
min,
max,
});
valid = false;
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDefinition.name, definition: optionDefinition });
}
return valid;
}
/**
* Validate the that option's value is within the range specified. If not, error messages will be issued.
*
* @param {ICommandOptionDefinition|ICommandPositionalDefinition} optionDefinition: The option definition
* @param {string} optionValue: The option value
* @param {CommandResponse} responseObject: The response object for producing messages.
* @param isPositional - is this a positional option? this method works with regular options and positionals
* @return {boolean}: false if the value is not valid.
*/
validateOptionValueLength(optionDefinition, optionValue, responseObject, isPositional = false) {
let valid = true;
const min = optionDefinition.stringLengthRange[0];
const max = optionDefinition.stringLengthRange[1];
if (optionValue.length < min || optionValue.length > max) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("Invalid value length for option:\n{{option}}\n\n" +
"You specified a string of length {{length}}:\n{{optionValue}}\n\n" +
"The length must be between {{min}} and {{max}} (inclusive)", {
option: isPositional ? optionDefinition.name : this.getDashFormOfOption(optionDefinition.name),
length: optionValue.length,
optionValue,
min,
max
});
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDefinition.name, definition: optionDefinition });
valid = false;
}
return valid;
}
/**
* Validate if the option's value as array contains duplicates. If yes, error messages will be issued.
*
* @param {ICommandOptionDefinition} optionDefinition: The option definition
* @param {string} optionValue: The option value
* @param {CommandResponse} responseObject: The response object for producing messages.
* @return {boolean}: false if the value is not valid.
*/
validateArrayDuplicate(optionDefinition, optionValue, responseObject) {
const existingValuesSet = new Set();
const duplicateValuesSet = new Set();
// Determine duplicate values
for (const value of optionValue) {
if (existingValuesSet.has(value)) {
duplicateValuesSet.add(value);
}
else {
existingValuesSet.add(value);
}
}
// Print error for each duplicate value
for (const duplicateValue of duplicateValuesSet) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("Duplicate value specified for option:\n{{option}}\n\n" +
"You specified:\n{{value}}\n\n" +
"Duplicate values are not allowed", {
option: this.getDashFormOfOption(optionDefinition.name),
value: duplicateValue
});
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDefinition.name, definition: optionDefinition });
}
const valid = duplicateValuesSet.size === 0;
return valid;
}
/**
* Issue the options require one another (dependency) error.
*
* @param {ICommandOptionDefinition} optionDef1: the first option that requires the second (or visa-versa)
* @param {ICommandOptionDefinition} optionDef2: the first option that requires the second (or visa-versa)
* @param {CommandResponse} responseObject: The response object for producing messages.
*/
optionDependencyError(optionDef1, optionDef2, responseObject) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("If you specify the following option:\n{{option}}\n\nYou must also specify:\n{{dependsOn}}", {
option: this.getDashFormOfOption(optionDef1.name),
dependsOn: this.getDashFormOfOption(optionDef2.name),
});
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDef1.name, definition: optionDef2 });
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDef2.name, definition: optionDef2 });
}
/**
* "impliesOneOf" (implicationAlternatives) condition was not satisfied
* @param {ICommandOptionDefinition} optionDef: the option that requires at least one of a group of options
* @param {CommandResponse} responseObject: The response object for producing messages.
*/
implicationAlternativeError(optionDef, responseObject) {
const implications = "[" + optionDef.impliesOneOf.map((implication) => {
return this.getDashFormOfOption(this.getOptionDefinitionFromName(implication).name);
}).join(",") + "]";
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("If you specify the following option:\n{{option}}\n\n" +
"You must also specify at least one of the following:\n{{dependsOn}}", {
option: this.getDashFormOfOption(optionDef.name),
dependsOn: implications
});
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDef.name, definition: optionDef });
}
/**
* If an option was specified, some other option should have been specified
* @param {ICommandOptionDefinition} optionDef: the option whose absence implies the presence of other options.
* @param {CommandResponse} responseObject: The response object for producing messages.
*/
absenceImplicationError(optionDef, responseObject) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("If you do not specify the following option:\n{{option}}\n" +
"\nYou must specify one of these options:\n[{{dependsOn}}]", {
option: this.getDashFormOfOption(optionDef.name),
dependsOn: optionDef.absenceImplications.map((option) => {
return this.getDashFormOfOption(this.getOptionDefinitionFromName(option).name);
}).join(", ")
});
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDef.name, definition: optionDef });
}
/**
* Issue the options are mutually exclusive error.
* @param {ICommandOptionDefinition} optionDef1: the first of the conflicting options.
* @param {ICommandOptionDefinition} optionDef2: the second of hte conflicting options.
* @param {CommandResponse} responseObject: The response object for producing messages.
*/
optionCombinationInvalidError(optionDef1, optionDef2, responseObject) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("The following options conflict (mutually exclusive):\n{{a}}\n{{b}}", {
a: this.getDashFormOfOption(optionDef1.name),
b: this.getDashFormOfOption(optionDef2.name)
});
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDef1.name, definition: optionDef1 });
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDef2.name, definition: optionDef2 });
}
/**
* If the option was specified multiple times despite not being an array type option, that's a syntax error
* @param {CommandResponse} responseObject: The response object for producing messages.
* @param {ICommandOptionDefinition} failingOption: The option with the non-allowable value
*/
specifiedMultipleTimesError(failingOption, responseObject) {
const mustacheSummary = this.getMustacheSummaryForOption(failingOption);
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("You cannot specify the following option multiple times:\n{{long}} {{aliases}}", mustacheSummary);
this.appendValidatorError(responseObject, { message: msg, optionInError: failingOption.name, definition: failingOption });
}
/**
* If the option requires one of a set of values and the value provided doesn't match
* @param {CommandResponse} responseObject: The response object for producing messages.
* @param {ICommandOptionDefinition} failingOption: The option with the non-allowable value
* @param {any} value - the value specified by the user which was not an array
*/
notAnArrayError(failingOption, responseObject, value) {
const mustacheSummary = this.getMustacheSummaryForOption(failingOption);
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("The following option is of type 'array', but an array was not specified:\n{{long}} {{aliases}}" +
"\n\nYou specified: " + value
+ "\n\nIf you are attempting to specify an array from an environmental variable, specify the value " +
"delimited by spaces. If one of the values contains a space, you may surround it with single quotes.\nExample:" +
"MY_VAR=\"value1 value2 'value 3 with space'", mustacheSummary);
this.appendValidatorError(responseObject, { message: msg, optionInError: failingOption.name, definition: failingOption });
}
/**
* If the option requires one of a set of values and the value provided doesn't match
* @param {CommandResponse} responseObject: The response object for producing messages.
* @param {ICommandOptionDefinition} failingOption: The option with the non-allowable value
* @param value - the value that was specified by the user
*/
invalidOptionError(failingOption, responseObject, value) {
const mustacheSummary = this.getMustacheSummaryForOption(failingOption);
mustacheSummary.allowed = (0, util_1.inspect)(failingOption.allowableValues.values);
mustacheSummary.optionVal = value;
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("Invalid value specified for option:\n{{long}} {{aliases}}\n\n" +
"You specified:\n{{optionVal}}\n\n" +
"The value must match one of the following options:\n{{allowed}}.", mustacheSummary);
this.appendValidatorError(responseObject, { message: msg, optionInError: failingOption.name, definition: failingOption });
}
/**
* If this option's specification requires another option to be present. e.g. '--type TXT' requires that
* '--maxlinelength' be specified. That condition was not satisfied, so issue an error message
*
* @param {ICommandOptionDefinition} optionDef: The option definition whose value requires
* more options which were not specified
* (e.g. '--type TXT' the specification of TXT requires that the user specify '--maxlinelength')
* @param {string} value: The value that requries additional options (e.g. TXT in '--type TXT'
* @param {ICommandOptionDefinition} requires: The parameter that it requires.
* @param {CommandResponse} responseObject: The response object for producing messages.
*/
valueRequiresAdditionalOption(optionDef, value, requires, responseObject) {
const aMustache = this.getMustacheSummaryForOption(optionDef);
const bMustache = this.getMustacheSummaryForOption(requires);
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("If you specify the value {{value}}" +
" for option {{a_long}} {{a_short}}, " +
"you must also specify a value for the option {{b_long}} {{b_short}}\nDescription:\n{{b_description}}", {
value,
a_long: aMustache.long,
a_short: aMustache.aliases,
b_long: bMustache.long,
b_short: bMustache.aliases,
b_description: utilities_1.TextUtils.wordWrap(bMustache.description)
});
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDef.name, definition: optionDef });
}
/**
* Validate that the option's value is a boolean type
* @param {any} value: The value passed to validate.
* @param {ICommandOptionDefinition} optionDefinition: The definition for this option.
* @param {CommandResponse} responseObject: The response object for producing messages.
* @param isPositional - is the option a positional option? defaults to false
*/
validateBoolean(value, optionDefinition, responseObject, isPositional = false) {
const mustacheSummary = this.getMustacheSummaryForOption(optionDefinition, isPositional);
mustacheSummary.value = value;
if (value !== undefined && value !== true && value !== false) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject
.console.error("Invalid value specified for option:\n{{long}} {{aliases}}\n\n" +
"You specified:\n{{value}}\n\n" +
"The value must be a boolean (true or false).", mustacheSummary);
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDefinition.name, definition: optionDefinition });
return false;
}
return true;
}
/**
* Validate that the option's value is numeric.
* @param {any} value: The value passed to validate.
* @param {ICommandOptionDefinition| ICommandPositionalDefinition} optionDefinition: The definition for this option.
* @param {CommandResponse} responseObject: The response object for producing messages.
* @param isPositional - is the option a positional option? defaults to false
*/
validateNumeric(value, optionDefinition, responseObject, isPositional = false) {
const mustacheSummary = this.getMustacheSummaryForOption(optionDefinition, isPositional);
mustacheSummary.value = value;
if (isNaN(value)) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject
.console.error("Invalid value specified for option:\n{{long}} {{aliases}}\n\n" +
"You specified:\n{{value}}\n\n" +
"The value must be a number", mustacheSummary);
this.appendValidatorError(responseObject, { message: msg, optionInError: optionDefinition.name, definition: optionDefinition });
return false;
}
return true;
}
/**
* If one of a set of options are required, issue an error message with the list of required options.
*/
mustSpecifyOneError(responseObject) {
const missingOptionNames = this.mCommandDefinition.mustSpecifyOne.map((option) => {
return this.getDashFormOfOption(this.getOptionDefinitionFromName(option).name);
});
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("You must specify one of the following options for this command:\n[%s]", missingOptionNames.join(", "));
this.appendValidatorError(responseObject, { message: msg, optionInError: missingOptionNames.toString() });
}
/**
* If more than one of a set of options are specified, issue an error message with the list of offending options
*/
onlyOneOfError(responseObject, specified) {
const onlyOneOf = this.mCommandDefinition.onlyOneOf.map((option) => {
return this.getDashFormOfOption(this.getOptionDefinitionFromName(option).name);
});
const specifiedDashForm = specified.map((option) => {
return this.getDashFormOfOption(option);
});
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("You may specify only one of the following options for this command:\n[%s]\n\n" +
"You specified the following:\n[%s]", onlyOneOf.join(", "), specifiedDashForm.join(", "));
this.appendValidatorError(responseObject, { message: msg, optionInError: onlyOneOf.toString() });
}
/**
* If the user specifies no value for an option that requires a string value,
* that's an error
*/
emptyValueError(responseObject, optionName) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const msg = responseObject.console.error("No value specified for option:\n%s\n\n" +
"This option requires a value of type:\n%s\n\n" +
"Option Description:\n%s", CliUtils_1.CliUtils.getDashFormOfOption(optionName), this.getOptionDefinitionFromName(optionName).type, utilities_1.TextUtils.wordWrap(this.getOptionDefinitionFromName(optionName).description));
this.appendValidatorError(responseObject, { message: msg, optionInError: optionName });
}
/**
* If the user specifies an extra positional option
*/
unknownPositionalError(responseObject, commandArguments, expectedUnderscoreLength) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const badOptionsSummary = commandArguments._.slice(expectedUnderscoreLength)
.map((argument) => {
return "\"" + argument + "\"";
}).join(", ");
const msg = responseObject.console.error("You specified the following unknown values: %s.\n\n Could not " +
"interpret them as a group, command name, or positional option.", badOptionsSummary);
this.appendValidatorError(responseObject, { message: msg, optionInError: "unknown" });
}
/**
* Issue an error message indicating the missing positional parameters
* @param {string[]} missingPositionals - The list of missing positional parameters for the command.
* @param {CommandResponse} responseObject: The response object for producing messages.
*/
missingPositionalParameter(missingPositionals, responseObject) {
for (const missing of missingPositionals) {
responseObject.console.errorHeader(messages_1.syntaxErrorHeader.message);
const message = responseObject.console.error("Missing Positional Argument: {{missing}}\n" +
"Argument Description: {{optDesc}}", { missing: missing.name, optDesc: utilities_1.TextUtils.wordWrap(missing.description) });
this.appendValidatorError(responseObject, {
message,
optionInError: missing.name, definition: missing
});
}
}
/**
* Append the validator error to the response object.
* @param {CommandResponse} responseObject: The Zowe response object
* @param {ICommandValidatorError} error: The error to append.
*/
appendValidatorError(responseObject, error) {
this.mErrorList.push(error);
responseObject.data.setObj(this.mErrorList);
}
}
exports.SyntaxValidator = SyntaxValidator;
/**
* Command option short/long
*/
SyntaxValidator.SHORT = "-";
SyntaxValidator.LONG = "--";
//# sourceMappingURL=SyntaxValidator.js.map