Skip to content

Commit 10d1107

Browse files
authored
feat(api): add sealed exception hierarchy for api plugin (#3279)
1 parent 1aca347 commit 10d1107

28 files changed

Lines changed: 638 additions & 187 deletions

aws-api/api/aws-api.api

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,34 @@ public final class com/amplifyframework/api/aws/ApiAuthProviders$Builder {
6565
public fun oidcAuthProvider (Lcom/amplifyframework/api/aws/sigv4/OidcAuthProvider;)Lcom/amplifyframework/api/aws/ApiAuthProviders$Builder;
6666
}
6767

68+
public abstract class com/amplifyframework/api/aws/AppSyncAuthException : com/amplifyframework/api/ApiException$ApiAuthException {
69+
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
70+
}
71+
72+
public final class com/amplifyframework/api/aws/AppSyncAuthExhaustedException : com/amplifyframework/api/aws/AppSyncAuthException {
73+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
74+
}
75+
76+
public final class com/amplifyframework/api/aws/AppSyncAuthUnknownException : com/amplifyframework/api/aws/AppSyncAuthException {
77+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
78+
}
79+
80+
public final class com/amplifyframework/api/aws/AppSyncAuthorizationClaimException : com/amplifyframework/api/aws/AppSyncAuthException {
81+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
82+
}
83+
84+
public final class com/amplifyframework/api/aws/AppSyncDeserializationException : com/amplifyframework/api/aws/AppSyncException {
85+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
86+
}
87+
88+
public final class com/amplifyframework/api/aws/AppSyncEndpointResolutionException : com/amplifyframework/api/aws/AppSyncException {
89+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
90+
}
91+
92+
public abstract class com/amplifyframework/api/aws/AppSyncException : com/amplifyframework/api/ApiException {
93+
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
94+
}
95+
6896
public final class com/amplifyframework/api/aws/AppSyncGraphQLOperation : com/amplifyframework/api/aws/AWSGraphQLOperation {
6997
public fun cancel ()V
7098
public fun start ()V
@@ -86,6 +114,50 @@ public final class com/amplifyframework/api/aws/AppSyncGraphQLRequestFactory {
86114
public static final fun buildSubscription (Ljava/lang/Class;Lcom/amplifyframework/api/graphql/SubscriptionType;Lkotlin/jvm/functions/Function1;)Lcom/amplifyframework/api/graphql/GraphQLRequest;
87115
}
88116

117+
public final class com/amplifyframework/api/aws/AppSyncInvalidConfigException : com/amplifyframework/api/aws/AppSyncException {
118+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
119+
}
120+
121+
public final class com/amplifyframework/api/aws/AppSyncInvalidStateException : com/amplifyframework/api/aws/AppSyncException {
122+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
123+
}
124+
125+
public final class com/amplifyframework/api/aws/AppSyncNetworkException : com/amplifyframework/api/aws/AppSyncException {
126+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
127+
}
128+
129+
public final class com/amplifyframework/api/aws/AppSyncProviderNotConfiguredException : com/amplifyframework/api/aws/AppSyncAuthException {
130+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
131+
}
132+
133+
public final class com/amplifyframework/api/aws/AppSyncRequestValidationException : com/amplifyframework/api/aws/AppSyncException {
134+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
135+
}
136+
137+
public final class com/amplifyframework/api/aws/AppSyncSigningException : com/amplifyframework/api/aws/AppSyncAuthException {
138+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
139+
}
140+
141+
public final class com/amplifyframework/api/aws/AppSyncSubscriptionConnectionException : com/amplifyframework/api/aws/AppSyncException {
142+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
143+
}
144+
145+
public final class com/amplifyframework/api/aws/AppSyncSubscriptionTimeoutException : com/amplifyframework/api/aws/AppSyncException {
146+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
147+
}
148+
149+
public final class com/amplifyframework/api/aws/AppSyncTokenFetchException : com/amplifyframework/api/aws/AppSyncAuthException {
150+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
151+
}
152+
153+
public final class com/amplifyframework/api/aws/AppSyncTokenParsingException : com/amplifyframework/api/aws/AppSyncAuthException {
154+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
155+
}
156+
157+
public final class com/amplifyframework/api/aws/AppSyncUnknownException : com/amplifyframework/api/aws/AppSyncException {
158+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V
159+
}
160+
89161
public final class com/amplifyframework/api/aws/BuildConfig {
90162
public static final field BUILD_TYPE Ljava/lang/String;
91163
public static final field DEBUG Z

aws-api/src/main/java/com/amplifyframework/api/aws/AWSApiPlugin.java

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -589,21 +589,21 @@ String getSelectedApiName(EndpointType endpointType) throws ApiException {
589589
case GRAPHQL:
590590
return selectApiName(gqlApis);
591591
default:
592-
throw new ApiException(endpointType.name() + " is not a " +
593-
"supported endpoint type.",
592+
throw new AppSyncInvalidConfigException(endpointType.name() + " is not a " +
593+
"supported endpoint type.", null,
594594
"Please use REST or GraphQL as endpoint type.");
595595
}
596596
}
597597

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

620620
final ClientDetails clientDetails = apiDetails.get(apiName);
621621
if (clientDetails == null) {
622-
throw new ApiException(
623-
"No client information for API named " + apiName,
622+
throw new AppSyncInvalidConfigException(
623+
"No client information for API named " + apiName, null,
624624
"Check your amplify configuration to make sure there " +
625625
"is a correctly configured section for " + apiName
626626
);
@@ -676,8 +676,8 @@ private <R> GraphQLOperation<R> buildAppSyncGraphQLOperation(
676676
throws ApiException {
677677
final ClientDetails clientDetails = apiDetails.get(apiName);
678678
if (clientDetails == null) {
679-
throw new ApiException(
680-
"No client information for API named " + apiName,
679+
throw new AppSyncInvalidConfigException(
680+
"No client information for API named " + apiName, null,
681681
"Check your amplify configuration to make sure there " +
682682
"is a correctly configured section for " + apiName
683683
);
@@ -727,8 +727,8 @@ private RestOperation createRestOperation(
727727
Consumer<ApiException> onFailure) throws ApiException {
728728
final ClientDetails clientDetails = apiDetails.get(apiName);
729729
if (clientDetails == null) {
730-
throw new ApiException(
731-
"No client information for API named " + apiName,
730+
throw new AppSyncInvalidConfigException(
731+
"No client information for API named " + apiName, null,
732732
"Check your amplify configuration to make sure there " +
733733
"is a correctly configured section for " + apiName
734734
);
@@ -739,8 +739,9 @@ private RestOperation createRestOperation(
739739
case HEAD:
740740
case GET:
741741
if (options.hasData()) {
742-
throw new ApiException("HTTP method does not support data object! " + type,
743-
"Try sending the request without any data in the options.");
742+
throw new AppSyncRequestValidationException(
743+
"HTTP method does not support data object! " + type,
744+
null, "Try sending the request without any data in the options.");
744745
}
745746
operationRequest = new RestOperationRequest(
746747
type,
@@ -775,8 +776,8 @@ private RestOperation createRestOperation(
775776
options.getQueryParameters());
776777
break;
777778
default:
778-
throw new ApiException("Unknown REST operation type: " + type,
779-
"Send support type for the request.");
779+
throw new AppSyncRequestValidationException("Unknown REST operation type: " + type,
780+
null, "Send support type for the request.");
780781
}
781782
AWSRestOperation operation = new AWSRestOperation(operationRequest,
782783
clientDetails.apiConfiguration.getEndpoint(),

aws-api/src/main/java/com/amplifyframework/api/aws/AWSApiPluginConfigurationReader.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ static AWSApiPluginConfiguration readFrom(JSONObject configurationJson)
4848
throws ApiException {
4949

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

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

118118
for (final String requiredKey : ConfigKey.requiredKeys()) {
119119
if (!apiSpec.has(requiredKey)) {
120-
throw new ApiException(
120+
throw new AppSyncInvalidConfigException(
121121
"Failed to parse configuration, missing required key: " + requiredKey,
122-
AmplifyException.TODO_RECOVERY_SUGGESTION
122+
null, AmplifyException.TODO_RECOVERY_SUGGESTION
123123
);
124124
}
125125
}
@@ -140,7 +140,7 @@ private static AWSApiPluginConfiguration parseConfigurationJson(JSONObject confi
140140
configBuilder.addApi(apiName, apiConfigBuilder.build());
141141
}
142142
} catch (JSONException | ApiException exception) {
143-
throw new ApiException(
143+
throw new AppSyncInvalidConfigException(
144144
"Failed to parse configuration JSON for AWS API Plugin",
145145
exception,
146146
"Check amplifyconfiguration.json to make sure the AWS API configuration section hasn't been " +

aws-api/src/main/java/com/amplifyframework/api/aws/AWSGraphQLOperation.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package com.amplifyframework.api.aws
1717
import com.amplifyframework.AmplifyException
1818
import com.amplifyframework.annotations.InternalAmplifyApi
1919
import com.amplifyframework.api.ApiException
20+
import com.amplifyframework.api.aws.AppSyncDeserializationException
2021
import com.amplifyframework.api.graphql.GraphQLOperation
2122
import com.amplifyframework.api.graphql.GraphQLRequest
2223
import com.amplifyframework.api.graphql.GraphQLResponse
@@ -44,14 +45,16 @@ abstract class AWSGraphQLOperation<R>(
4445
@Throws(ApiException::class)
4546
private fun buildResponse(jsonResponse: String): GraphQLResponse<R> = try {
4647
(responseFactory as? GsonGraphQLResponseFactory)?.buildResponse(request, jsonResponse, apiName)
47-
?: throw ApiException(
48+
?: throw AppSyncDeserializationException(
4849
"Amplify encountered an error while deserializing an object. " +
4950
"GraphQLResponse.Factory was not of type GsonGraphQLResponseFactory",
51+
null,
5052
AmplifyException.REPORT_BUG_TO_AWS_SUGGESTION
5153
)
5254
} catch (cce: ClassCastException) {
53-
throw ApiException(
55+
throw AppSyncDeserializationException(
5456
"Amplify encountered an error while deserializing an object",
57+
cce,
5558
AmplifyException.TODO_RECOVERY_SUGGESTION
5659
)
5760
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package com.amplifyframework.api.aws
16+
17+
import com.amplifyframework.annotations.ExperimentalAmplifyApi
18+
import com.amplifyframework.api.ApiException
19+
import com.amplifyframework.api.ApiException.ApiAuthException
20+
21+
/**
22+
* Sealed auth exception hierarchy for the AppSync client.
23+
* Extends [ApiAuthException] so existing `catch (ApiAuthException)` and
24+
* `throws ApiAuthException` declarations work without changes.
25+
*/
26+
@ExperimentalAmplifyApi
27+
sealed class AppSyncAuthException(
28+
message: String,
29+
cause: Throwable?,
30+
recoverySuggestion: String
31+
) : ApiAuthException(message, cause, recoverySuggestion)
32+
33+
@ExperimentalAmplifyApi
34+
class AppSyncTokenFetchException(
35+
message: String,
36+
cause: Throwable?,
37+
recoverySuggestion: String
38+
) : AppSyncAuthException(message, cause, recoverySuggestion)
39+
40+
@ExperimentalAmplifyApi
41+
class AppSyncProviderNotConfiguredException(
42+
message: String,
43+
cause: Throwable?,
44+
recoverySuggestion: String
45+
) : AppSyncAuthException(message, cause, recoverySuggestion)
46+
47+
@ExperimentalAmplifyApi
48+
class AppSyncSigningException(
49+
message: String,
50+
cause: Throwable?,
51+
recoverySuggestion: String
52+
) : AppSyncAuthException(message, cause, recoverySuggestion)
53+
54+
@ExperimentalAmplifyApi
55+
class AppSyncTokenParsingException(
56+
message: String,
57+
cause: Throwable?,
58+
recoverySuggestion: String
59+
) : AppSyncAuthException(message, cause, recoverySuggestion)
60+
61+
@ExperimentalAmplifyApi
62+
class AppSyncAuthorizationClaimException(
63+
message: String,
64+
cause: Throwable?,
65+
recoverySuggestion: String
66+
) : AppSyncAuthException(message, cause, recoverySuggestion)
67+
68+
@ExperimentalAmplifyApi
69+
class AppSyncAuthExhaustedException(
70+
message: String,
71+
cause: Throwable?,
72+
recoverySuggestion: String
73+
) : AppSyncAuthException(message, cause, recoverySuggestion)
74+
75+
@ExperimentalAmplifyApi
76+
class AppSyncAuthUnknownException(
77+
message: String,
78+
cause: Throwable?,
79+
recoverySuggestion: String
80+
) : AppSyncAuthException(message, cause, recoverySuggestion)
81+
82+
/**
83+
* Sealed non-auth exception hierarchy for the AppSync client.
84+
* Extends [ApiException] for backward compatibility.
85+
*/
86+
@ExperimentalAmplifyApi
87+
sealed class AppSyncException(
88+
message: String,
89+
cause: Throwable?,
90+
recoverySuggestion: String
91+
) : ApiException(message, cause, recoverySuggestion)
92+
93+
@ExperimentalAmplifyApi
94+
class AppSyncInvalidConfigException(
95+
message: String,
96+
cause: Throwable?,
97+
recoverySuggestion: String
98+
) : AppSyncException(message, cause, recoverySuggestion)
99+
100+
@ExperimentalAmplifyApi
101+
class AppSyncEndpointResolutionException(
102+
message: String,
103+
cause: Throwable?,
104+
recoverySuggestion: String
105+
) : AppSyncException(message, cause, recoverySuggestion)
106+
107+
@ExperimentalAmplifyApi
108+
class AppSyncDeserializationException(
109+
message: String,
110+
cause: Throwable?,
111+
recoverySuggestion: String
112+
) : AppSyncException(message, cause, recoverySuggestion)
113+
114+
@ExperimentalAmplifyApi
115+
class AppSyncSubscriptionConnectionException(
116+
message: String,
117+
cause: Throwable?,
118+
recoverySuggestion: String
119+
) : AppSyncException(message, cause, recoverySuggestion)
120+
121+
@ExperimentalAmplifyApi
122+
class AppSyncSubscriptionTimeoutException(
123+
message: String,
124+
cause: Throwable?,
125+
recoverySuggestion: String
126+
) : AppSyncException(message, cause, recoverySuggestion)
127+
128+
@ExperimentalAmplifyApi
129+
class AppSyncRequestValidationException(
130+
message: String,
131+
cause: Throwable?,
132+
recoverySuggestion: String
133+
) : AppSyncException(message, cause, recoverySuggestion)
134+
135+
@ExperimentalAmplifyApi
136+
class AppSyncInvalidStateException(
137+
message: String,
138+
cause: Throwable?,
139+
recoverySuggestion: String
140+
) : AppSyncException(message, cause, recoverySuggestion)
141+
142+
@ExperimentalAmplifyApi
143+
class AppSyncNetworkException(
144+
message: String,
145+
cause: Throwable?,
146+
recoverySuggestion: String
147+
) : AppSyncException(message, cause, recoverySuggestion)
148+
149+
@ExperimentalAmplifyApi
150+
class AppSyncUnknownException(
151+
message: String,
152+
cause: Throwable?,
153+
recoverySuggestion: String
154+
) : AppSyncException(message, cause, recoverySuggestion)

0 commit comments

Comments
 (0)