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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ firebase-admin-*.tgz
docgen/markdown/

# Dataconnect integration test artifacts should not be checked in
test/integration/dataconnect/dataconnect/.dataconnect
test/integration/dataconnect/dataconnect/.dataconnect
test/integration/dataconnect/dataconnect-debug.log
test/integration/dataconnect/firebase-debug.log
test/integration/dataconnect/pglite-debug.log
Comment thread
stephenarosaj marked this conversation as resolved.
Outdated
24 changes: 16 additions & 8 deletions src/data-connect/data-connect-api-client-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import {
HttpRequestConfig, HttpClient, RequestResponseError, AuthorizedHttpClient
} from '../utils/api-request';
import { FirebaseError, toHttpResponse } from '../utils/error';
import { FirebaseDataConnectError, DataConnectErrorCode, DATA_CONNECT_ERROR_CODE_MAPPING } from './error';
import {
FirebaseDataConnectError,
DataConnectErrorCode,
DATA_CONNECT_ERROR_CODE_MAPPING,
GRPC_STATUS_CODE_TO_STRING
} from './error';
import * as utils from '../utils/index';
import * as validator from '../utils/validator';
import { ConnectorConfig, ExecuteGraphqlResponse, GraphqlOptions, OperationOptions } from './data-connect-api';
Expand Down Expand Up @@ -409,10 +414,17 @@ export class DataConnectApiClient {
});
}

const error: ServerError = (response.data as ErrorResponse).error || {};
const data = response.data as any;
const error: ServerError = (validator.isNonNullObject(data) && 'error' in data) ? data.error : data || {};
Comment thread
stephenarosaj marked this conversation as resolved.
Outdated

let status = error.status;
if (!status && validator.isNumber(error.code)) {
status = GRPC_STATUS_CODE_TO_STRING[error.code as number];
}

let code: DataConnectErrorCode = DATA_CONNECT_ERROR_CODE_MAPPING.UNKNOWN;
if (error.status && error.status in DATA_CONNECT_ERROR_CODE_MAPPING) {
code = DATA_CONNECT_ERROR_CODE_MAPPING[error.status];
if (status && status in DATA_CONNECT_ERROR_CODE_MAPPING) {
code = DATA_CONNECT_ERROR_CODE_MAPPING[status];
}
const message = error.message || 'Unknown server error';
return new FirebaseDataConnectError({
Expand Down Expand Up @@ -670,10 +682,6 @@ export function useEmulator(): boolean {
return !!emulatorHost();
}

interface ErrorResponse {
error?: ServerError;
}

interface ServerError {
code?: number;
message?: string;
Expand Down
23 changes: 23 additions & 0 deletions src/data-connect/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,26 @@ export class FirebaseDataConnectError extends FirebaseError {
});
}
}

/**
* Mappings from gRPC status codes to their string equivalents.
* @internal
*/
export const GRPC_STATUS_CODE_TO_STRING: Record<number, string> = {
Comment thread
stephenarosaj marked this conversation as resolved.
Outdated
1: 'CANCELLED',
2: 'UNKNOWN',
3: 'INVALID_ARGUMENT',
4: 'DEADLINE_EXCEEDED',
5: 'NOT_FOUND',
6: 'ALREADY_EXISTS',
7: 'PERMISSION_DENIED',
8: 'RESOURCE_EXHAUSTED',
9: 'FAILED_PRECONDITION',
10: 'ABORTED',
11: 'OUT_OF_RANGE',
12: 'UNIMPLEMENTED',
13: 'INTERNAL',
14: 'UNAVAILABLE',
15: 'DATA_LOSS',
16: 'UNAUTHENTICATED',
};
21 changes: 21 additions & 0 deletions test/unit/data-connect/data-connect-api-client-internal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,27 @@ describe('DataConnectApiClient', () => {
.and.have.property('cause', expected.cause);
});

it('should reject when a gRPC-to-HTTP transcoded error response is received', () => {
const grpcError = {
code: 7,
message: 'Permission denied',
};
const mockErr = utils.errorFrom(grpcError, 403);
sandbox
.stub(HttpClient.prototype, 'send')
.rejects(mockErr);
const expected = new FirebaseDataConnectError({
code: 'permission-denied',
message: 'Permission denied',
httpResponse: toHttpResponse(mockErr.response),
cause: mockErr
});
return apiClient.executeGraphql('query', {})
.should.eventually.be.rejected
.and.deep.include(expected)
.and.have.property('cause', expected.cause);
});

it('should reject with unknown-error when error code is not present', () => {
const mockErr = utils.errorFrom({}, 404);
sandbox
Expand Down
Loading