Skip to content

Commit

Permalink
Merge pull request #479 from NomicFoundation/centralized-errors
Browse files Browse the repository at this point in the history
Centralize error descriptions
  • Loading branch information
zoeyTM authored Sep 28, 2023
2 parents 0b8d11a + 076dc9b commit ec86c8f
Show file tree
Hide file tree
Showing 43 changed files with 827 additions and 310 deletions.
11 changes: 6 additions & 5 deletions packages/core/src/build-module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IgnitionError } from "./errors";
import { ERRORS } from "./errors-list";
import { ModuleConstructor } from "./internal/module-builder";
import { isValidIgnitionIdentifier } from "./internal/utils/identifier-validators";
import { IgnitionModule, IgnitionModuleResult } from "./types/module";
Expand All @@ -23,17 +24,17 @@ export function buildModule<
moduleDefintionFunction: (m: IgnitionModuleBuilder) => IgnitionModuleResultsT
): IgnitionModule<ModuleIdT, ContractNameT, IgnitionModuleResultsT> {
if (typeof moduleId !== "string") {
throw new IgnitionError(`\`moduleId\` must be a string`);
throw new IgnitionError(ERRORS.MODULE.INVALID_MODULE_ID);
}

if (!isValidIgnitionIdentifier(moduleId)) {
throw new IgnitionError(
`The moduleId "${moduleId}" contains banned characters, ids can only contain alphanumerics or underscores`
);
throw new IgnitionError(ERRORS.MODULE.INVALID_MODULE_ID_CHARACTERS, {
moduleId,
});
}

if (typeof moduleDefintionFunction !== "function") {
throw new IgnitionError(`\`moduleDefintionFunction\` must be a function`);
throw new IgnitionError(ERRORS.MODULE.INVALID_MODULE_DEFINITION_FUNCTION);
}

const constructor = new ModuleConstructor();
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IgnitionValidationError } from "./errors";
import { IgnitionError } from "./errors";
import { ERRORS } from "./errors-list";
import {
DEFAULT_AUTOMINE_REQUIRED_CONFIRMATIONS,
defaultConfig,
Expand Down Expand Up @@ -83,9 +84,9 @@ export async function deploy<

if (defaultSender !== undefined) {
if (!accounts.includes(defaultSender)) {
throw new IgnitionValidationError(
`Default sender ${defaultSender} is not part of the provided accounts`
);
throw new IgnitionError(ERRORS.VALIDATION.INVALID_DEFAULT_SENDER, {
defaultSender,
});
}
} else {
defaultSender = getDefaultSender(accounts);
Expand Down
322 changes: 322 additions & 0 deletions packages/core/src/errors-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
export const ERROR_PREFIX = "IGN";

/**
* ErrorDescriptor is a type that describes an error.
* It's used to generate error codes and messages.
*
* @beta
*/
export interface ErrorDescriptor {
number: number;
// Message can use templates. See applyErrorMessageTemplate
message: string;
}

export function getErrorCode(error: ErrorDescriptor): string {
return `${ERROR_PREFIX}${error.number}`;
}

export const ERROR_RANGES: {
[category in keyof typeof ERRORS]: {
min: number;
max: number;
title: string;
};
} = {
GENERAL: {
min: 1,
max: 99,
title: "General errors",
},
INTERNAL: {
min: 100,
max: 199,
title: "Internal Ignition errors",
},
MODULE: {
min: 200,
max: 299,
title: "Module related errors",
},
SERIALIZATION: {
min: 300,
max: 399,
title: "Serialization errors",
},
EXECUTION: {
min: 400,
max: 499,
title: "Execution errors",
},
RECONCILIATION: {
min: 500,
max: 599,
title: "Reconciliation errors",
},
WIPE: {
min: 600,
max: 699,
title: "Wipe errors",
},
VALIDATION: {
min: 700,
max: 799,
title: "Validation errors",
},
};

export const ERRORS = {
GENERAL: {
ASSERTION_ERROR: {
number: 1,
message: "Internal Ignition invariant was violated: %description%",
},
UNSUPPORTED_DECODE: {
number: 2,
message: "Ignition can't decode ethers.js value of type %type%: %value%",
},
},
INTERNAL: {
TEMPLATE_INVALID_VARIABLE_NAME: {
number: 100,
message:
"Variable names can only include ascii letters and numbers, and start with a letter, but got %variable%",
},
TEMPLATE_VARIABLE_NOT_FOUND: {
number: 101,
message: "Variable %variable%'s tag not present in the template",
},
TEMPLATE_VALUE_CONTAINS_VARIABLE_TAG: {
number: 102,
message:
"Template values can't include variable tags, but %variable%'s value includes one",
},
},
MODULE: {
INVALID_MODULE_ID: {
number: 200,
message: "Module id must be a string",
},
INVALID_MODULE_ID_CHARACTERS: {
number: 201,
message:
'The moduleId "%moduleId%" contains banned characters, ids can only contain alphanumerics or underscores',
},
INVALID_MODULE_DEFINITION_FUNCTION: {
number: 202,
message: "Module definition function must be a function",
},
ASYNC_MODULE_DEFINITION_FUNCTION: {
number: 203,
message:
"The callback passed to 'buildModule' for %moduleDefinitionId% returns a Promise; async callbacks are not allowed in 'buildModule'.",
},
},
SERIALIZATION: {
INVALID_FUTURE_ID: {
number: 300,
message: "Unable to lookup future during deserialization: %futureId%",
},
INVALID_FUTURE_TYPE: {
number: 301,
message: "Invalid FutureType %type% as serialized argument",
},
LOOKAHEAD_NOT_FOUND: {
number: 302,
message: "Lookahead value %key% missing",
},
},
EXECUTION: {
FUTURE_NOT_FOUND: {
number: 400,
message: "Could not locate future id from batching",
},
DROPPED_TRANSACTION: {
number: 401,
message:
"Error while executing %futureId%: all the transactions of its network interaction %networkInteractionId% were dropped. Please try rerunning Ignition.",
},
INVALID_JSON_RPC_RESPONSE: {
number: 402,
message: "Invalid JSON-RPC response for %method%: %response%",
},
WAITING_FOR_CONFIRMATIONS: {
number: 403,
message:
"You have sent transactions from %sender%. Please wait until they get %requiredConfirmations% confirmations before running Ignition again.",
},
WAITING_FOR_NONCE: {
number: 404,
message:
"You have sent transactions from %sender% with nonce %nonce%. Please wait until they get %requiredConfirmations% confirmations before running Ignition again.",
},
INVALID_NONCE: {
number: 405,
message:
"The next nonce for %sender% should be %expectedNonce%, but is %pendingCount%. Please make sure not to send transactions from %sender% while running this deployment and try again.",
},
},
RECONCILIATION: {
INVALID_EXECUTION_STATUS: {
number: 500,
message: "Unsupported execution status: %status%",
},
},
WIPE: {
UNINITIALIZED_DEPLOYMENT: {
number: 600,
message:
"Cannot wipe %futureId% as the deployment hasn't been intialialized yet",
},
NO_STATE_FOR_FUTURE: {
number: 601,
message: "Cannot wipe %futureId% as no state recorded against it",
},
DEPENDENT_FUTURES: {
number: 602,
message: `Cannot wipe %futureId% as there are dependent futures that have already started: %dependents%`,
},
},
VALIDATION: {
INVALID_DEFAULT_SENDER: {
number: 700,
message:
"Default sender %defaultSender% is not part of the provided accounts",
},
MISSING_EMITTER: {
number: 701,
message:
"`options.emitter` must be provided when reading an event from a SendDataFuture",
},
INVALID_MODULE: {
number: 702,
message: "Module validation failed with reason: %message%",
},
INVALID_CONSTRUCTOR_ARGS_LENGTH: {
number: 703,
message:
"The constructor of the contract '%contractName%' expects %expectedArgsLength% arguments but %argsLength% were given",
},
INVALID_FUNCTION_ARGS_LENGTH: {
number: 704,
message:
"Function %functionName% in contract %contractName% expects %expectedLength% arguments but %argsLength% were given",
},
INVALID_STATIC_CALL: {
number: 705,
message:
"Function %functionName% in contract %contractName% is not 'pure' or 'view' and cannot be statically called",
},
INDEXED_EVENT_ARG: {
number: 706,
message:
"Indexed argument %argument% of event %eventName% of contract %contractName% is not stored in the receipt, but its hash is, so you can't read it.",
},
INVALID_OVERLOAD_NAME: {
number: 707,
message: "Invalid %eventOrFunction% name '%name%'",
},
OVERLOAD_NOT_FOUND: {
number: 708,
message:
"%eventOrFunction% '%name%' not found in contract %contractName%",
},
REQUIRE_BARE_NAME: {
number: 709,
message:
"%eventOrFunction% name '%name%' used for contract %contractName%, but it's not overloaded. Use '%bareName%' instead.",
},
OVERLOAD_NAME_REQUIRED: {
number: 710,
message: `%eventOrFunction% '%name%' is overloaded in contract %contractName%. Please use one of these names instead:
%normalizedNameList%`,
},
INVALID_OVERLOAD_GIVEN: {
number: 711,
message: `%eventOrFunction% '%name%' is not a valid overload of '%bareName%' in contract %contractName%. Please use one of these names instead:
%normalizedNameList%`,
},
EVENT_ARG_NOT_FOUND: {
number: 712,
message:
"Event %eventName% of contract %contractName% has no argument named %argument%",
},
INVALID_EVENT_ARG_INDEX: {
number: 713,
message:
"Event %eventName% of contract %contractName% has only %expectedLength% arguments, but argument %argument% was requested",
},
FUNCTION_ARG_NOT_FOUND: {
number: 714,
message:
"Function %functionName% of contract %contractName% has no return value named %argument%",
},
INVALID_FUNCTION_ARG_INDEX: {
number: 715,
message:
"Function %functionName% of contract %contractName% has only %expectedLength% return values, but value %argument% was requested",
},
MISSING_LIBRARIES: {
number: 716,
message:
"Invalid libraries for contract %contractName%: The following libraries are missing: %fullyQualifiedNames%",
},
CONFLICTING_LIBRARY_NAMES: {
number: 717,
message:
"Invalid libraries for contract %contractName%: The names '%inputName%' and '%libName%' clash with each other, please use qualified names for both.",
},
INVALID_LIBRARY_NAME: {
number: 718,
message: "Invalid library name %libraryName% for contract %contractName%",
},
LIBRARY_NOT_NEEDED: {
number: 719,
message:
"Invalid library %libraryName% for contract %contractName%: this library is not needed by this contract.",
},
AMBIGUOUS_LIBRARY_NAME: {
number: 720,
message: `Invalid libraries for contract %contractName%: The name "%libraryName%" is ambiguous, please use one of the following fully qualified names: %fullyQualifiedNames%`,
},
INVALID_LIBRARY_ADDRESS: {
number: 721,
message: `Invalid address %address% for library %libraryName% of contract %contractName%`,
},
NEGATIVE_ACCOUNT_INDEX: {
number: 722,
message: "Account index cannot be a negative number",
},
ACCOUNT_INDEX_TOO_HIGH: {
number: 723,
message:
"Requested account index '%accountIndex%' is greater than the total number of available accounts '%accountsLength%'",
},
INVALID_ARTIFACT: {
number: 724,
message: "Artifact for contract '%contractName%' is invalid",
},
MISSING_MODULE_PARAMETER: {
number: 725,
message: "Module parameter '%name%' requires a value but was given none",
},
INVALID_MODULE_PARAMETER_TYPE: {
number: 726,
message: `Module parameter '%name%' must be of type '%expectedType%' but is '%actualType%'`,
},
},
};

/**
* Setting the type of ERRORS to a map let us access undefined ones. Letting it
* be a literal doesn't enforce that its values are of type ErrorDescriptor.
*
* We let it be a literal, and use this variable to enforce the types
*/
const _PHONY_VARIABLE_TO_FORCE_ERRORS_TO_BE_OF_TYPE_ERROR_DESCRIPTOR: {
[category: string]: {
[name: string]: ErrorDescriptor;
};
} = ERRORS;
Loading

0 comments on commit ec86c8f

Please sign in to comment.