Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c25a42d
Start adding sealed exception hierarchy for api plugin
jvh-aws Apr 16, 2026
33376a2
feat(api): add sealed exception hierarchy for api plugin
jvh-aws Apr 27, 2026
eabc5f2
fix checkstyle violations
jvh-aws Apr 27, 2026
e53603d
fix ktlint violations
jvh-aws Apr 27, 2026
49a5364
Merge remote-tracking branch 'origin/main' into jv/add-sealed-excepti…
jvh-aws Apr 27, 2026
e0d15ba
update core api dump for non-final ApiAuthException and NonRetryableE…
jvh-aws Apr 27, 2026
03e9e54
update aws-api api dump for new sealed exception types
jvh-aws Apr 27, 2026
4c35ddb
preserve original messages, fix api dump, remove fixed messages from …
jvh-aws Apr 27, 2026
784a09b
add AuthExhaustedException, InvalidStateException; fix remaining mess…
jvh-aws Apr 27, 2026
6eb4d5e
fix: remove unused TokenExpiredException, make NetworkException.cause…
jvh-aws Apr 28, 2026
5872e61
chore: mark exception hierarchy as experimental, remove unused except…
jvh-aws Apr 28, 2026
c06260d
fix: revert NonRetryableException to final
jvh-aws Apr 28, 2026
b728e4b
fix: preserve original exception messages
jvh-aws Apr 28, 2026
3111279
fix: add AppSyncAuthException.UnknownException, revert REST to plain …
jvh-aws Apr 28, 2026
f051723
flatten exception hierarchy to top-level classes
jvh-aws May 7, 2026
63b18db
fix: restore comment in AuthRuleRequestDecorator
jvh-aws May 7, 2026
17e5b69
fix: add missing exception types to exhaustiveness test
jvh-aws May 7, 2026
158ac05
Merge remote-tracking branch 'origin/main' into jv/add-sealed-excepti…
jvh-aws May 7, 2026
b44c44e
fix: checkstyle import order violations
jvh-aws May 7, 2026
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
72 changes: 72 additions & 0 deletions aws-api/api/aws-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ public final class com/amplifyframework/api/aws/ApiAuthProviders$Builder {
public fun oidcAuthProvider (Lcom/amplifyframework/api/aws/sigv4/OidcAuthProvider;)Lcom/amplifyframework/api/aws/ApiAuthProviders$Builder;
}

public abstract class com/amplifyframework/api/aws/AppSyncAuthException : com/amplifyframework/api/ApiException$ApiAuthException {
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}

public final class com/amplifyframework/api/aws/AppSyncAuthExhaustedException : com/amplifyframework/api/aws/AppSyncAuthException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncAuthUnknownException : com/amplifyframework/api/aws/AppSyncAuthException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncAuthorizationClaimException : com/amplifyframework/api/aws/AppSyncAuthException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncDeserializationException : com/amplifyframework/api/aws/AppSyncException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncEndpointResolutionException : com/amplifyframework/api/aws/AppSyncException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public abstract class com/amplifyframework/api/aws/AppSyncException : com/amplifyframework/api/ApiException {
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}

public final class com/amplifyframework/api/aws/AppSyncGraphQLOperation : com/amplifyframework/api/aws/AWSGraphQLOperation {
public fun cancel ()V
public fun start ()V
Expand All @@ -86,6 +114,50 @@ public final class com/amplifyframework/api/aws/AppSyncGraphQLRequestFactory {
public static final fun buildSubscription (Ljava/lang/Class;Lcom/amplifyframework/api/graphql/SubscriptionType;Lkotlin/jvm/functions/Function1;)Lcom/amplifyframework/api/graphql/GraphQLRequest;
}

public final class com/amplifyframework/api/aws/AppSyncInvalidConfigException : com/amplifyframework/api/aws/AppSyncException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncInvalidStateException : com/amplifyframework/api/aws/AppSyncException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncNetworkException : com/amplifyframework/api/aws/AppSyncException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncProviderNotConfiguredException : com/amplifyframework/api/aws/AppSyncAuthException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncRequestValidationException : com/amplifyframework/api/aws/AppSyncException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncSigningException : com/amplifyframework/api/aws/AppSyncAuthException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncSubscriptionConnectionException : com/amplifyframework/api/aws/AppSyncException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncSubscriptionTimeoutException : com/amplifyframework/api/aws/AppSyncException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncTokenFetchException : com/amplifyframework/api/aws/AppSyncAuthException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncTokenParsingException : com/amplifyframework/api/aws/AppSyncAuthException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/AppSyncUnknownException : com/amplifyframework/api/aws/AppSyncException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
}

public final class com/amplifyframework/api/aws/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -589,21 +589,21 @@ String getSelectedApiName(EndpointType endpointType) throws ApiException {
case GRAPHQL:
return selectApiName(gqlApis);
default:
throw new ApiException(endpointType.name() + " is not a " +
"supported endpoint type.",
throw new AppSyncInvalidConfigException(endpointType.name() + " is not a " +
"supported endpoint type.", null,
"Please use REST or GraphQL as endpoint type.");
}
}

private String selectApiName(Set<String> apiClients) throws ApiException {
if (apiClients.isEmpty()) {
throw new ApiException("There is no API configured for this " +
"plugin with matching endpoint type.",
throw new AppSyncInvalidConfigException("There is no API configured for this " +
"plugin with matching endpoint type.", null,
"Please add at least one API in amplifyconfiguration.json.");
}
if (apiClients.size() > 1) {
throw new ApiException("There is more than one API configured " +
"for this plugin with matching endpoint type.",
throw new AppSyncInvalidConfigException("There is more than one API configured " +
"for this plugin with matching endpoint type.", null,
"Please specify the name of API to invoke in the API method.");
}
return apiClients.iterator().next();
Expand All @@ -619,8 +619,8 @@ private <R> GraphQLOperation<R> buildSubscriptionOperation(

final ClientDetails clientDetails = apiDetails.get(apiName);
if (clientDetails == null) {
throw new ApiException(
"No client information for API named " + apiName,
throw new AppSyncInvalidConfigException(
"No client information for API named " + apiName, null,
"Check your amplify configuration to make sure there " +
"is a correctly configured section for " + apiName
);
Expand Down Expand Up @@ -676,8 +676,8 @@ private <R> GraphQLOperation<R> buildAppSyncGraphQLOperation(
throws ApiException {
final ClientDetails clientDetails = apiDetails.get(apiName);
if (clientDetails == null) {
throw new ApiException(
"No client information for API named " + apiName,
throw new AppSyncInvalidConfigException(
"No client information for API named " + apiName, null,
"Check your amplify configuration to make sure there " +
"is a correctly configured section for " + apiName
);
Expand Down Expand Up @@ -727,8 +727,8 @@ private RestOperation createRestOperation(
Consumer<ApiException> onFailure) throws ApiException {
final ClientDetails clientDetails = apiDetails.get(apiName);
if (clientDetails == null) {
throw new ApiException(
"No client information for API named " + apiName,
throw new AppSyncInvalidConfigException(
"No client information for API named " + apiName, null,
"Check your amplify configuration to make sure there " +
"is a correctly configured section for " + apiName
);
Expand All @@ -739,8 +739,9 @@ private RestOperation createRestOperation(
case HEAD:
case GET:
if (options.hasData()) {
throw new ApiException("HTTP method does not support data object! " + type,
"Try sending the request without any data in the options.");
throw new AppSyncRequestValidationException(
"HTTP method does not support data object! " + type,
null, "Try sending the request without any data in the options.");
}
operationRequest = new RestOperationRequest(
type,
Expand Down Expand Up @@ -775,8 +776,8 @@ private RestOperation createRestOperation(
options.getQueryParameters());
break;
default:
throw new ApiException("Unknown REST operation type: " + type,
"Send support type for the request.");
throw new AppSyncRequestValidationException("Unknown REST operation type: " + type,
null, "Send support type for the request.");
}
AWSRestOperation operation = new AWSRestOperation(operationRequest,
clientDetails.apiConfiguration.getEndpoint(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ static AWSApiPluginConfiguration readFrom(JSONObject configurationJson)
throws ApiException {

if (configurationJson == null) {
throw new ApiException(
"Null configuration JSON provided to AWS API plugin.",
throw new AppSyncInvalidConfigException(
"Null configuration JSON provided to AWS API plugin.", null,
"Check that the content of the AWS API Plugin section of the amplifyconfiguration.json file hasn't " +
"been accidentally deleted."
);
Expand All @@ -62,8 +62,8 @@ static AWSApiPluginConfiguration from(AmplifyOutputsData outputs) throws ApiExce
final AmplifyOutputsData.Data data = outputs.getData();

if (data == null) {
throw new ApiException(
"Missing data configuration in Amplify Outputs",
throw new AppSyncInvalidConfigException(
"Missing data configuration in Amplify Outputs", null,
"Check that your amplify_outputs.json file contains a \"data\" section"
);
}
Expand Down Expand Up @@ -117,9 +117,9 @@ private static AWSApiPluginConfiguration parseConfigurationJson(JSONObject confi

for (final String requiredKey : ConfigKey.requiredKeys()) {
if (!apiSpec.has(requiredKey)) {
throw new ApiException(
throw new AppSyncInvalidConfigException(
"Failed to parse configuration, missing required key: " + requiredKey,
AmplifyException.TODO_RECOVERY_SUGGESTION
null, AmplifyException.TODO_RECOVERY_SUGGESTION
);
}
}
Expand All @@ -140,7 +140,7 @@ private static AWSApiPluginConfiguration parseConfigurationJson(JSONObject confi
configBuilder.addApi(apiName, apiConfigBuilder.build());
}
} catch (JSONException | ApiException exception) {
throw new ApiException(
throw new AppSyncInvalidConfigException(
"Failed to parse configuration JSON for AWS API Plugin",
exception,
"Check amplifyconfiguration.json to make sure the AWS API configuration section hasn't been " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.amplifyframework.api.aws
import com.amplifyframework.AmplifyException
import com.amplifyframework.annotations.InternalAmplifyApi
import com.amplifyframework.api.ApiException
import com.amplifyframework.api.aws.AppSyncDeserializationException
import com.amplifyframework.api.graphql.GraphQLOperation
import com.amplifyframework.api.graphql.GraphQLRequest
import com.amplifyframework.api.graphql.GraphQLResponse
Expand Down Expand Up @@ -44,14 +45,16 @@ abstract class AWSGraphQLOperation<R>(
@Throws(ApiException::class)
private fun buildResponse(jsonResponse: String): GraphQLResponse<R> = try {
(responseFactory as? GsonGraphQLResponseFactory)?.buildResponse(request, jsonResponse, apiName)
?: throw ApiException(
?: throw AppSyncDeserializationException(
"Amplify encountered an error while deserializing an object. " +
"GraphQLResponse.Factory was not of type GsonGraphQLResponseFactory",
null,
AmplifyException.REPORT_BUG_TO_AWS_SUGGESTION
)
} catch (cce: ClassCastException) {
throw ApiException(
throw AppSyncDeserializationException(
"Amplify encountered an error while deserializing an object",
cce,
AmplifyException.TODO_RECOVERY_SUGGESTION
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amplifyframework.api.aws

import com.amplifyframework.annotations.ExperimentalAmplifyApi
import com.amplifyframework.api.ApiException
import com.amplifyframework.api.ApiException.ApiAuthException

/**
* Sealed auth exception hierarchy for the AppSync client.
* Extends [ApiAuthException] so existing `catch (ApiAuthException)` and
* `throws ApiAuthException` declarations work without changes.
*/
@ExperimentalAmplifyApi
sealed class AppSyncAuthException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : ApiAuthException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncTokenFetchException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncAuthException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncProviderNotConfiguredException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncAuthException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncSigningException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncAuthException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncTokenParsingException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncAuthException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncAuthorizationClaimException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncAuthException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncAuthExhaustedException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncAuthException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncAuthUnknownException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncAuthException(message, cause, recoverySuggestion)

/**
* Sealed non-auth exception hierarchy for the AppSync client.
* Extends [ApiException] for backward compatibility.
*/
@ExperimentalAmplifyApi
sealed class AppSyncException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : ApiException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncInvalidConfigException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncEndpointResolutionException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncDeserializationException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncSubscriptionConnectionException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncSubscriptionTimeoutException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncRequestValidationException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncInvalidStateException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncNetworkException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncException(message, cause, recoverySuggestion)

@ExperimentalAmplifyApi
class AppSyncUnknownException(
message: String,
cause: Throwable?,
recoverySuggestion: String
) : AppSyncException(message, cause, recoverySuggestion)
Loading
Loading