diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 00000000..aea62bd7 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,30 @@ +import { Metadata } from 'nice-grpc'; + +export class ApiError extends Error { + metadata: Metadata; + + constructor(error: Error, metadata: Metadata) { + super(error.message); + this.name = 'ApiError'; + this.stack = error.stack; + this.metadata = metadata; + } + + /** + * Getter for the request ID from the metadata. + * Will provide additional information in case of opening a support ticket. + * @returns {string | undefined} The request ID if it exists, undefined otherwise. + */ + get requestId(): string | undefined { + return this.metadata.get('x-request-id'); + } + + /** + * Getter for the server trace ID from the metadata. + * Will provide additional information in case of opening a support ticket. + * @returns {string | undefined} The server trace ID if it exists, undefined otherwise. + */ + get serverTraceId(): string | undefined { + return this.metadata.get('x-server-trace-id'); + } +} diff --git a/src/index.ts b/src/index.ts index 0bd23339..250727e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,4 +3,5 @@ export * as cloudApi from './generated/yandex/cloud'; export * from './session'; export * from './utils/operation'; export * from './utils/decode-message'; +export * as errors from './errors'; export { WrappedServiceClientType } from './types'; diff --git a/src/middleware/error-metadata.ts b/src/middleware/error-metadata.ts new file mode 100644 index 00000000..75c4c40c --- /dev/null +++ b/src/middleware/error-metadata.ts @@ -0,0 +1,31 @@ +import { CallOptions, ClientMiddleware, Metadata } from 'nice-grpc'; +import { rethrowAbortError } from 'abort-controller-x'; +import { ApiError } from '../errors'; + +export const errorMetadataMiddleware: ClientMiddleware = async function* errorMetadataMiddleware( + call, + options, +) { + let md: Metadata | undefined; + const { onHeader } = options; + const callOptions: CallOptions = { + ...options, + onHeader: (header: Metadata) => { + md = header; + if (onHeader !== undefined) { + onHeader(header); + } + }, + }; + + try { + return yield* call.next(call.request, callOptions); + } catch (error: unknown) { + rethrowAbortError(error); + if (error instanceof Error && md !== undefined) { + throw new ApiError(error, md); + } + + throw error; + } +}; diff --git a/src/utils/client-factory.ts b/src/utils/client-factory.ts index e74b0824..7cb3c940 100644 --- a/src/utils/client-factory.ts +++ b/src/utils/client-factory.ts @@ -1,7 +1,9 @@ import { createClientFactory } from 'nice-grpc'; import { deadlineMiddleware } from 'nice-grpc-client-middleware-deadline'; import { retryMiddleware } from '../middleware/retry'; +import { errorMetadataMiddleware } from '../middleware/error-metadata'; export const clientFactory = createClientFactory() + .use(errorMetadataMiddleware) .use(retryMiddleware) .use(deadlineMiddleware);