Skip to content

Commit

Permalink
fix: map Lambda exceptions correctly (#4804)
Browse files Browse the repository at this point in the history
fix: correctly map lambda exceptions
  • Loading branch information
Jordan-Nelson committed May 2, 2024
1 parent 67612bd commit 091df2a
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,27 @@ sealed class CognitoServiceException extends core.AuthServiceException {
/// {@endtemplate}
final class LambdaException extends CognitoServiceException {
/// {@macro amplify_auth_cognito_dart.sdk.lambda_exception}
factory LambdaException(
String message, {
String? recoverySuggestion,
Object? underlyingException,
}) {
final match = _errorRegex.firstMatch(message);
final lambdaName = match?.group(1);
final parsedMessage = match?.group(2);
if (parsedMessage != null) {
message = parsedMessage;
}
return LambdaException._(
message,
lambdaName: lambdaName,
recoverySuggestion: recoverySuggestion,
underlyingException: underlyingException,
);
}

const LambdaException._(
const LambdaException(
super.message, {
this.lambdaName,
super.recoverySuggestion,
super.underlyingException,
});
}) : _message = message;

final String _message;

@override
String get message {
final match = _errorRegex.firstMatch(_message);
final parsedMessage = match?.group(2);
return parsedMessage ?? _message;
}

/// The name of the lambda which triggered this exception.
String? get lambdaName {
final match = _errorRegex.firstMatch(_message);
final lambdaName = match?.group(1);
return lambdaName;
}

/// Whether [exception] originated in a user Lambda.
static bool isLambdaException(String exception) =>
Expand All @@ -65,9 +61,6 @@ final class LambdaException extends CognitoServiceException {
/// errors.
static final RegExp _errorRegex = RegExp(r'(\w+) failed with error (.*)\.');

/// The name of the lambda which triggered this exception.
final String? lambdaName;

@override
String get runtimeTypeName => 'LambdaException';
}
Expand Down Expand Up @@ -227,7 +220,7 @@ final class InvalidEmailRoleAccessPolicyException
/// {@template amplify_auth_cognito_dart.sdk_exception.invalid_lambda_response_exception}
/// This exception is thrown when Amazon Cognito encounters an invalid Lambda response.
/// {@endtemplate}
final class InvalidLambdaResponseException extends CognitoServiceException {
final class InvalidLambdaResponseException extends LambdaException {
/// {@macro amplify_auth_cognito_dart.sdk_exception.invalid_lambda_response_exception}
const InvalidLambdaResponseException(
super.message, {
Expand Down Expand Up @@ -456,7 +449,7 @@ final class UnauthorizedException extends CognitoServiceException {
/// {@template amplify_auth_cognito_dart.sdk_exception.unexpected_lambda_exception}
/// This exception is thrown when Amazon Cognito encounters an unexpected exception with Lambda.
/// {@endtemplate}
final class UnexpectedLambdaException extends CognitoServiceException {
final class UnexpectedLambdaException extends LambdaException {
/// {@macro amplify_auth_cognito_dart.sdk_exception.unexpected_lambda_exception}
const UnexpectedLambdaException(
super.message, {
Expand Down Expand Up @@ -501,7 +494,7 @@ final class UnsupportedTokenTypeException extends CognitoServiceException {
/// {@template amplify_auth_cognito_dart.sdk_exception.user_lambda_validation_exception}
/// This exception is thrown when the Amazon Cognito service encounters a user validation exception with the Lambda service.
/// {@endtemplate}
final class UserLambdaValidationException extends CognitoServiceException {
final class UserLambdaValidationException extends LambdaException {
/// {@macro amplify_auth_cognito_dart.sdk_exception.user_lambda_validation_exception}
const UserLambdaValidationException(
super.message, {
Expand Down Expand Up @@ -615,15 +608,6 @@ Object transformSdkException(Object e) {
final message = e.message ?? 'An unknown error occurred';
final shapeName = e.shapeId?.shape;

// Some exceptions are returned as non-Lambda exceptions even though they
// orginated in user-defined lambdas.
if (LambdaException.isLambdaException(message) ||
shapeName == 'InvalidLambdaResponseException' ||
shapeName == 'UnexpectedLambdaException' ||
shapeName == 'UserLambdaValidationException') {
return LambdaException(message, underlyingException: e);
}

return switch (shapeName) {
'AliasExistsException' => AliasExistsException(
message,
Expand Down Expand Up @@ -766,6 +750,13 @@ Object transformSdkException(Object e) {
message,
underlyingException: e,
),
_ => UnknownServiceException(message, underlyingException: e),
_ => (() {
// Some exceptions are returned as non-Lambda exceptions even though they
// originated in user-defined lambdas.
if (LambdaException.isLambdaException(message)) {
return LambdaException(message, underlyingException: e);
}
return UnknownServiceException(message, underlyingException: e);
})(),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@
// SPDX-License-Identifier: Apache-2.0

import 'package:amplify_auth_cognito_dart/src/sdk/sdk_exception.dart';
import 'package:amplify_auth_cognito_dart/src/sdk/src/cognito_identity_provider/model/user_lambda_validation_exception.dart'
as cognito;
import 'package:amplify_core/amplify_core.dart';
import 'package:smithy/smithy.dart';
import 'package:test/test.dart';

const lambdaName = 'Foo';
const lambdaMessage = 'Something went wrong';
// errors that originate from lambdas are in the format "<Lambda Name> failed with error <Error>."
const lambdaErrorMessage = '$lambdaName failed with error $lambdaMessage.';

void main() {
group('SDK exception', () {
group('LambdaException', () {
Expand All @@ -14,7 +22,7 @@ void main() {
test('matches string', () {
expect(LambdaException.isLambdaException(message), isTrue);

final exception = LambdaException(message);
const exception = LambdaException(message);
expect(exception.message, error);
expect(exception.lambdaName, 'PreConfirmation');
});
Expand All @@ -29,6 +37,58 @@ void main() {
});
});

group('transformSdkException', () {
test('maps SDK Lambda exceptions to the Amplify equivalent', () {
final exception = cognito.UserLambdaValidationException(
message: lambdaErrorMessage,
);
final transformed = transformSdkException(exception);
expect(
transformed,
// UserLambdaValidationException from the SDK should be mapped to
// UserLambdaValidationException from Amplify
isA<UserLambdaValidationException>()
.having(
(e) => e.lambdaName,
'lambdaName',
lambdaName,
)
.having(
(e) => e.message,
'message',
lambdaMessage,
),
);
});

test('maps all other Lambda exceptions to a generic LambdaException', () {
const exception = UnhandledException(lambdaErrorMessage);
final transformed = transformSdkException(exception);
expect(
transformed,
isA<LambdaException>()
.having(
(e) => e.lambdaName,
'lambdaName',
lambdaName,
)
.having(
(e) => e.message,
'message',
lambdaMessage,
),
);
});

test('maps to UnknownServiceException by default', () {
const exception = UnhandledException('error');
expect(
transformSdkException(exception),
isA<UnknownServiceException>(),
);
});
});

test('transforms network exceptions', () {
final networkException = AWSHttpException(
AWSHttpRequest.get(Uri.parse('https://example.com')),
Expand All @@ -40,3 +100,24 @@ void main() {
});
});
}

class UnhandledException implements SmithyException {
const UnhandledException(this._message);

final String _message;

@override
String? get message => _message;

@override
RetryConfig? get retryConfig => null;

@override
ShapeId? get shapeId => const ShapeId(
shape: 'UnhandledException',
namespace: '',
);

@override
Exception? get underlyingException => null;
}
67 changes: 32 additions & 35 deletions packages/auth/amplify_auth_cognito_dart/tool/generate_sdk_exceptions.dart
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -59,30 +59,27 @@ sealed class CognitoServiceException extends core.AuthServiceException {
/// {@endtemplate}
final class LambdaException extends CognitoServiceException {
/// {@macro amplify_auth_cognito_dart.sdk.lambda_exception}
factory LambdaException(String message, {
String? recoverySuggestion,
Object? underlyingException,
}) {
final match = _errorRegex.firstMatch(message);
final lambdaName = match?.group(1);
final parsedMessage = match?.group(2);
if (parsedMessage != null) {
message = parsedMessage;
}
return LambdaException._(
message,
lambdaName: lambdaName,
recoverySuggestion: recoverySuggestion,
underlyingException: underlyingException,
);
}
const LambdaException._(
const LambdaException(
super.message, {
this.lambdaName,
super.recoverySuggestion,
super.underlyingException,
});
}) : _message = message;
final String _message;
@override
String get message {
final match = _errorRegex.firstMatch(_message);
final parsedMessage = match?.group(2);
return parsedMessage ?? _message;
}
/// The name of the lambda which triggered this exception.
String? get lambdaName {
final match = _errorRegex.firstMatch(_message);
final lambdaName = match?.group(1);
return lambdaName;
}
/// Whether [exception] originated in a user Lambda.
static bool isLambdaException(String exception) =>
Expand All @@ -96,9 +93,6 @@ final class LambdaException extends CognitoServiceException {
/// errors.
static final RegExp _errorRegex = RegExp(r'(\w+) failed with error (.*)\.');
/// The name of the lambda which triggered this exception.
final String? lambdaName;
@override
String get runtimeTypeName => 'LambdaException';
}
Expand Down Expand Up @@ -143,14 +137,19 @@ final class UnknownServiceException extends CognitoServiceException

final hasCoreType = authExceptions.keys.contains(shapeName);
final className = authExceptions[shapeName] ?? shapeName.pascalCase;
final isLambdaException = [
'InvalidLambdaResponseException',
'UnexpectedLambdaException',
'UserLambdaValidationException',
].contains(shapeName);
final templateName =
'amplify_auth_cognito_dart.sdk_exception.${shapeName.snakeCase}';
final docs = shape.formattedDocs(context);
exceptions.writeln('''
/// {@template $templateName}
${docs.isEmpty ? '/// Cognito `$shapeName` exception' : docs}
/// {@endtemplate}
final class $className extends CognitoServiceException ${hasCoreType ? 'implements core.Auth$shapeName' : ''} {
final class $className extends ${isLambdaException ? 'LambdaException' : 'CognitoServiceException'} ${hasCoreType ? 'implements core.Auth$shapeName' : ''} {
/// {@macro $templateName}
const $className(
super.message, {
Expand All @@ -176,15 +175,6 @@ Object transformSdkException(Object e) {
final message = e.message ?? 'An unknown error occurred';
final shapeName = e.shapeId?.shape;
// Some exceptions are returned as non-Lambda exceptions even though they
// orginated in user-defined lambdas.
if (LambdaException.isLambdaException(message) ||
shapeName == 'InvalidLambdaResponseException' ||
shapeName == 'UnexpectedLambdaException' ||
shapeName == 'UserLambdaValidationException') {
return LambdaException(message, underlyingException: e);
}
return switch (shapeName) {
''');

Expand All @@ -196,7 +186,14 @@ Object transformSdkException(Object e) {
}

exceptions.write('''
_ => UnknownServiceException(message, underlyingException: e),
_ => (() {
// Some exceptions are returned as non-Lambda exceptions even though they
// originated in user-defined lambdas.
if (LambdaException.isLambdaException(message)) {
return LambdaException(message, underlyingException: e);
}
return UnknownServiceException(message, underlyingException: e);
})(),
};
}
''');
Expand Down

0 comments on commit 091df2a

Please sign in to comment.