Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(storage-browser): missing error wrapping for s3 control responses #13779

Merged
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 7 additions & 7 deletions packages/aws-amplify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -461,43 +461,43 @@
"name": "[Storage] copy (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ copy }",
"limit": "15.42 kB"
"limit": "15.47 kB"
},
{
"name": "[Storage] downloadData (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ downloadData }",
"limit": "15.93 kB"
"limit": "15.98 kB"
},
{
"name": "[Storage] getProperties (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ getProperties }",
"limit": "15.20 kB"
"limit": "15.25 kB"
},
{
"name": "[Storage] getUrl (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ getUrl }",
"limit": "16.43 kB"
"limit": "16.47 kB"
},
{
"name": "[Storage] list (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ list }",
"limit": "15.82 kB"
"limit": "15.88 kB"
},
{
"name": "[Storage] remove (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ remove }",
"limit": "15.05 kB"
"limit": "15.13 kB"
},
{
"name": "[Storage] uploadData (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ uploadData }",
"limit": "21.68 kB"
"limit": "21.73 kB"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ const getDataAccessErrorCase: ApiFunctionalTestCase<typeof getDataAccess> = [
headers: DEFAULT_RESPONSE_HEADERS,
body: `
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<ErrorResponse>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
</Error>
<RequestId>656c76696e6727732072657175657374</RequestId>
<HostId>Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==</HostId>
</Error>
</ErrorResponse>
`,
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,14 @@ const listCallerAccessGrantsErrorCase: ApiFunctionalTestCase<
headers: DEFAULT_RESPONSE_HEADERS,
body: `
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<ErrorResponse>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
</Error>
<RequestId>656c76696e6727732072657175657374</RequestId>
<HostId>Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==</HostId>
</Error>
</ErrorResponse>
`,
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ import {
getRetryDecider as getDefaultRetryDecider,
} from '@aws-amplify/core/internals/aws-client-utils';

import { retryDecider } from '../../../../../../../src/providers/s3/utils/client/utils';
import { parseXmlError } from '../../../../../../../src/providers/s3/utils/client/utils/parsePayload';
import { createRetryDecider } from '../../../../../../../src/providers/s3/utils/client/utils';

jest.mock(
'../../../../../../../src/providers/s3/utils/client/utils/parsePayload',
);
jest.mock('@aws-amplify/core/internals/aws-client-utils');

const mockErrorParser = jest.mocked(parseXmlError);
const mockErrorParser = jest.fn();

describe('retryDecider', () => {
describe('createRetryDecider', () => {
const mockHttpResponse: HttpResponse = {
statusCode: 200,
headers: {},
Expand All @@ -34,6 +30,7 @@ describe('retryDecider', () => {

it('should invoke the default retry decider', async () => {
expect.assertions(3);
const retryDecider = createRetryDecider(mockErrorParser);
const { retryable, isCredentialsExpiredError } = await retryDecider(
mockHttpResponse,
undefined,
Expand All @@ -56,6 +53,7 @@ describe('retryDecider', () => {
$metadata: {},
};
mockErrorParser.mockResolvedValue(parsedError);
const retryDecider = createRetryDecider(mockErrorParser);
const { retryable, isCredentialsExpiredError } = await retryDecider(
{ ...mockHttpResponse, statusCode: 400 },
undefined,
Expand All @@ -74,6 +72,7 @@ describe('retryDecider', () => {
$metadata: {},
};
mockErrorParser.mockResolvedValue(parsedError);
const retryDecider = createRetryDecider(mockErrorParser);
const { retryable, isCredentialsExpiredError } = await retryDecider(
{ ...mockHttpResponse, statusCode: 400 },
undefined,
Expand All @@ -91,6 +90,7 @@ describe('retryDecider', () => {
$metadata: {},
};
mockErrorParser.mockResolvedValue(parsedError);
const retryDecider = createRetryDecider(mockErrorParser);
const { retryable, isCredentialsExpiredError } = await retryDecider(
{ ...mockHttpResponse, statusCode: 400 },
undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { HttpRequest } from '@aws-amplify/core/internals/aws-client-utils';

interface MockFetchResponse {
export interface MockFetchResponse {
body: BodyInit;
headers: HeadersInit;
status: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
jitteredBackoff,
} from '@aws-amplify/core/internals/aws-client-utils';

import { retryDecider } from '../utils';
import { createRetryDecider, createXmlErrorParser } from '../utils';

/**
* The service name used to sign requests if the API requires authentication.
Expand Down Expand Up @@ -57,6 +57,33 @@ const endpointResolver = (
return { url: endpoint };
};

/**
* Error parser for the XML payload of S3 control plane error response. The
* error's `Code` and `Message` locates at the nested `Error` element instead of
* the XML root element.
*
* @example
* ```
* <?xml version="1.0" encoding="UTF-8"?>
* <ErrorResponse>
* <Error>
* <Code>AccessDenied</Code>
* <Message>Access Denied</Message>
* </Error>
* <RequestId>656c76696e6727732072657175657374</RequestId>
* <HostId>Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==</HostId>
* </ErrorResponse>
* ```
*
* @internal
*/
export const parseXmlError = createXmlErrorParser();

/**
* @internal
*/
export const retryDecider = createRetryDecider(parseXmlError);

/**
* @internal
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,26 @@ import {
HttpResponse,
parseMetadata,
} from '@aws-amplify/core/internals/aws-client-utils';
import { composeServiceApi } from '@aws-amplify/core/internals/aws-client-utils/composers';
import {
AmplifyUrl,
AmplifyUrlSearchParams,
} from '@aws-amplify/core/internals/utils';
import { composeServiceApi } from '@aws-amplify/core/internals/aws-client-utils/composers';

import {
assignStringVariables,
buildStorageServiceError,
deserializeTimestamp,
map,
parseXmlBody,
parseXmlError,
s3TransferHandler,
} from '../utils';

import type {
GetDataAccessCommandInput,
GetDataAccessCommandOutput,
} from './types';
import { defaultConfig } from './base';
import { defaultConfig, parseXmlError } from './base';

export type GetDataAccessInput = GetDataAccessCommandInput;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
emptyArrayGuard,
map,
parseXmlBody,
parseXmlError,
s3TransferHandler,
} from '../utils';
import { createStringEnumDeserializer } from '../utils/deserializeHelpers';
Expand All @@ -28,7 +27,7 @@ import type {
ListCallerAccessGrantsCommandInput,
ListCallerAccessGrantsCommandOutput,
} from './types';
import { defaultConfig } from './base';
import { defaultConfig, parseXmlError } from './base';

export type ListCallerAccessGrantsInput = ListCallerAccessGrantsCommandInput;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ import { MetadataBearer } from '@aws-sdk/types';

import {
buildStorageServiceError,
parseXmlError,
s3TransferHandler,
serializePathnameObjectKey,
validateS3RequiredParameter,
} from '../utils';

import type { AbortMultipartUploadCommandInput } from './types';
import { defaultConfig } from './base';
import { defaultConfig, parseXmlError } from './base';

export type AbortMultipartUploadInput = Pick<
AbortMultipartUploadCommandInput,
Expand Down
26 changes: 25 additions & 1 deletion packages/storage/src/providers/s3/utils/client/s3data/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
jitteredBackoff,
} from '@aws-amplify/core/internals/aws-client-utils';

import { retryDecider } from '../utils';
import { createRetryDecider, createXmlErrorParser } from '../utils';

const DOMAIN_PATTERN = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
const IP_ADDRESS_PATTERN = /(\d+\.){3}\d+/;
Expand Down Expand Up @@ -99,6 +99,30 @@ export const isDnsCompatibleBucketName = (bucketName: string): boolean =>
!IP_ADDRESS_PATTERN.test(bucketName) &&
!DOTS_PATTERN.test(bucketName);

/**
* Error parser for the XML payload of S3 data plane error response. The error's
* `Code` and `Message` locates directly at the XML root element.
*
* @example
* ```
* <?xml version="1.0" encoding="UTF-8"?>
* <Error>
* <Code>NoSuchKey</Code>
* <Message>The resource you requested does not exist</Message>
* <Resource>/mybucket/myfoto.jpg</Resource>
* <RequestId>4442587FB7D0A2F9</RequestId>
* </Error>
* ```
*
* @internal
*/
export const parseXmlError = createXmlErrorParser({ noErrorWrapping: true });

/**
* @internal
*/
export const retryDecider = createRetryDecider(parseXmlError);

/**
* @internal
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import {
buildStorageServiceError,
map,
parseXmlBody,
parseXmlError,
retryDecider,
s3TransferHandler,
serializePathnameObjectKey,
validateS3RequiredParameter,
Expand All @@ -33,7 +31,7 @@ import type {
CompletedMultipartUpload,
CompletedPart,
} from './types';
import { defaultConfig } from './base';
import { defaultConfig, parseXmlError, retryDecider } from './base';

const INVALID_PARAMETER_ERROR_MSG =
'Invalid parameter for ComplteMultipartUpload API';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ import {
assignStringVariables,
buildStorageServiceError,
parseXmlBody,
parseXmlError,
s3TransferHandler,
serializeObjectConfigsToHeaders,
serializePathnameObjectKey,
validateS3RequiredParameter,
} from '../utils';

import type { CopyObjectCommandInput, CopyObjectCommandOutput } from './types';
import { defaultConfig } from './base';
import { defaultConfig, parseXmlError } from './base';

export type CopyObjectInput = Pick<
CopyObjectCommandInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
buildStorageServiceError,
map,
parseXmlBody,
parseXmlError,
s3TransferHandler,
serializeObjectConfigsToHeaders,
serializePathnameObjectKey,
Expand All @@ -27,7 +26,7 @@ import type {
CreateMultipartUploadCommandOutput,
} from './types';
import type { PutObjectInput } from './putObject';
import { defaultConfig } from './base';
import { defaultConfig, parseXmlError } from './base';

export type CreateMultipartUploadInput = Extract<
CreateMultipartUploadCommandInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
buildStorageServiceError,
deserializeBoolean,
map,
parseXmlError,
s3TransferHandler,
serializePathnameObjectKey,
validateS3RequiredParameter,
Expand All @@ -24,7 +23,7 @@ import type {
DeleteObjectCommandInput,
DeleteObjectCommandOutput,
} from './types';
import { defaultConfig } from './base';
import { defaultConfig, parseXmlError } from './base';

export type DeleteObjectInput = Pick<
DeleteObjectCommandInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
parseMetadata,
presignUrl,
} from '@aws-amplify/core/internals/aws-client-utils';
import { AmplifyUrl } from '@aws-amplify/core/internals/utils';
import { composeServiceApi } from '@aws-amplify/core/internals/aws-client-utils/composers';
import { AmplifyUrl } from '@aws-amplify/core/internals/utils';

import {
CONTENT_SHA256_HEADER,
Expand All @@ -22,13 +22,16 @@ import {
deserializeNumber,
deserializeTimestamp,
map,
parseXmlError,
s3TransferHandler,
serializePathnameObjectKey,
validateS3RequiredParameter,
} from '../utils';

import { S3EndpointResolverOptions, defaultConfig } from './base';
import {
S3EndpointResolverOptions,
defaultConfig,
parseXmlError,
} from './base';
import type {
CompatibleHttpResponse,
GetObjectCommandInput,
Expand Down
Loading
Loading