Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions packages/errors/src/__typetests__/error-typetest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as SolanaErrorCodeModule from '../codes';
import { SolanaErrorCode, SolanaErrorCodeWithCause } from '../codes';
import { SolanaErrorContext } from '../context';
import { isSolanaError, SolanaError } from '../error';
import { isSolanaError, SolanaError, SolanaErrorWithDeprecatedCause } from '../error';

const { SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING, SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING } =
SolanaErrorCodeModule;
Expand Down Expand Up @@ -55,14 +55,30 @@ if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING)) {
// `SolanaErrorContext` must not contain any keys reserved by `ErrorOptions` (eg. `cause`)
null as unknown as SolanaErrorContext satisfies {
[Code in keyof SolanaErrorContext]: SolanaErrorContext[Code] extends undefined
? undefined
: {
[PP in keyof SolanaErrorContext[Code]]: PP extends keyof ErrorOptions
? never
: SolanaErrorContext[Code][PP];
};
? undefined
: {
[PP in keyof SolanaErrorContext[Code]]: PP extends keyof ErrorOptions
? never
: SolanaErrorContext[Code][PP];
};
};

// Special errors have a nested `cause` property that is an optional `SolanaError`
const errorWithCause = null as unknown as SolanaError<SolanaErrorCodeWithCause>;
errorWithCause.cause satisfies SolanaError | undefined;

// Errors that have a deprecated `cause` property should satisfy `SolanaErrorWithDeprecatedCause`
// when narrowed with `isSolanaError`
{
const e = null as unknown;
if (isSolanaError(e)) {
// @ts-expect-error Not all SolanaErrors have deprecated cause
e satisfies SolanaErrorWithDeprecatedCause;
}
// This one does
if (isSolanaError(e, SolanaErrorCodeModule.SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN)) {
e satisfies SolanaErrorWithDeprecatedCause;
// context still works
e.context.transactionPlanResult satisfies unknown;
Comment thread
mcintyre94 marked this conversation as resolved.
Dismissed
}
}
7 changes: 7 additions & 0 deletions packages/errors/src/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,10 @@ export type SolanaErrorCode =
* `cause`.
*/
export type SolanaErrorCodeWithCause = typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE;

/**
* Errors of this type have a deprecated `cause` property. Consumers should use the error's
* `context` instead to access relevant error information.
*/
export type SolanaErrorCodeWithDeprecatedCause =
typeof SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN;
29 changes: 24 additions & 5 deletions packages/errors/src/error.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import { SolanaErrorCode, SolanaErrorCodeWithCause } from './codes';
import { SolanaErrorCode, SolanaErrorCodeWithCause, SolanaErrorCodeWithDeprecatedCause } from './codes';
import { SolanaErrorContext } from './context';
import { getErrorMessage } from './message-formatter';

/**
* A variant of {@link SolanaError} where the `cause` property is deprecated.
*
* This type is returned by {@link isSolanaError} when checking for error codes in
* {@link SolanaErrorCodeWithDeprecatedCause}. Accessing `cause` on these errors will show
* a deprecation warning in IDEs that support JSDoc `@deprecated` tags.
*/
export interface SolanaErrorWithDeprecatedCause<
TErrorCode extends SolanaErrorCodeWithDeprecatedCause = SolanaErrorCodeWithDeprecatedCause,
> extends Omit<SolanaError<TErrorCode>, 'cause'> {
/**
* @deprecated The `cause` property is deprecated for this error code.
* Use the error's `context` property instead to access relevant error information.
*/
readonly cause?: unknown;
}

/**
* A type guard that returns `true` if the input is a {@link SolanaError}, optionally with a
* particular error code.
Expand Down Expand Up @@ -51,7 +68,9 @@ export function isSolanaError<TErrorCode extends SolanaErrorCode>(
* its error code is exactly this value.
*/
code?: TErrorCode,
): e is SolanaError<TErrorCode> {
): e is TErrorCode extends SolanaErrorCodeWithDeprecatedCause
? SolanaErrorWithDeprecatedCause<SolanaErrorCodeWithDeprecatedCause & TErrorCode>
: SolanaError<TErrorCode> {
const isSolanaError = e instanceof Error && e.name === 'SolanaError';
if (isSolanaError) {
if (code !== undefined) {
Expand All @@ -66,7 +85,7 @@ type SolanaErrorCodedContext = {
[P in SolanaErrorCode]: Readonly<{
__code: P;
}> &
(SolanaErrorContext[P] extends undefined ? object : SolanaErrorContext[P]);
(SolanaErrorContext[P] extends undefined ? object : SolanaErrorContext[P]);
};

/**
Expand Down Expand Up @@ -112,8 +131,8 @@ export class SolanaError<TErrorCode extends SolanaErrorCode = SolanaErrorCode> e
this.context = Object.freeze(
context === undefined
? {
__code: code,
}
__code: code,
}
: context,
) as SolanaErrorCodedContext[TErrorCode];
// This is necessary so that `isSolanaError()` can identify a `SolanaError` without having
Expand Down
2 changes: 1 addition & 1 deletion packages/errors/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ export const SolanaErrorMessages: Readonly<{
[SOLANA_ERROR__INSTRUCTION_PLANS__NON_DIVISIBLE_TRANSACTION_PLANS_NOT_SUPPORTED]:
'This transaction plan executor does not support non-divisible sequential plans. To support them, you may create your own executor such that multi-transaction atomicity is preserved — e.g. by targetting RPCs that support transaction bundles.',
[SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN]:
'The provided transaction plan failed to execute. See the `transactionPlanResult` attribute and the `cause` error for more details.',
'The provided transaction plan failed to execute. See the `transactionPlanResult` attribute for more details. Note that the `cause` property is deprecated, and a future version will not set it.',
[SOLANA_ERROR__INSTRUCTION_PLANS__MESSAGE_CANNOT_ACCOMMODATE_PLAN]:
'The provided message has insufficient capacity to accommodate the next instruction(s) in this plan. Expected at least $numBytesRequired free byte(s), got $numFreeBytes byte(s).',
[SOLANA_ERROR__INVARIANT_VIOLATION__INVALID_TRANSACTION_PLAN_KIND]: 'Invalid transaction plan kind: $kind.',
Expand Down
Loading