Skip to content

Commit ef60bea

Browse files
author
Travis Sheppard
authored
fix(amplify_api): prevent some fatal REST errors in Android
Throw ApiException in android when PUT, POST, and PATCH REST requests have no body to prevent fatal error (#661).
1 parent e95be4d commit ef60bea

File tree

3 files changed

+75
-15
lines changed

3 files changed

+75
-15
lines changed

packages/amplify_api/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/FlutterApiRequest.kt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
package com.amazonaws.amplify.amplify_api
1717

18+
import com.amazonaws.amplify.amplify_api.rest_api.FlutterRestApi
1819
import com.amplifyframework.AmplifyException
20+
import com.amplifyframework.api.ApiException
1921
import com.amplifyframework.api.rest.RestOptions
2022
import io.flutter.plugin.common.MethodChannel
2123

@@ -33,7 +35,7 @@ class FlutterApiRequest {
3335
private val HEADERS_KEY = "headers"
3436

3537
// ====== Rest API ======
36-
fun getCancelToken(request: Map<String, Any>) : String {
38+
fun getCancelToken(request: Map<String, Any>): String {
3739
try {
3840
return request[CANCEL_TOKEN_KEY] as String
3941
} catch (cause: Exception) {
@@ -45,7 +47,7 @@ class FlutterApiRequest {
4547
return request[CANCEL_TOKEN_KEY] as String
4648
}
4749

48-
fun getApiPath(request: Map<String, Any>) : String? {
50+
fun getApiPath(request: Map<String, Any>): String? {
4951
try {
5052
val restOptionsMap = request[REST_OPTIONS_KEY] as Map<String, Any>
5153
return restOptionsMap[API_NAME_KEY] as String?
@@ -58,7 +60,7 @@ class FlutterApiRequest {
5860
return request[CANCEL_TOKEN_KEY] as String
5961
}
6062

61-
fun getRestOptions(request: Map<String, Any>) : RestOptions {
63+
fun getRestOptions(request: Map<String, Any>): RestOptions {
6264

6365
try {
6466
val builder: RestOptions.Builder = RestOptions.builder()
@@ -90,16 +92,23 @@ class FlutterApiRequest {
9092
}
9193
}
9294

95+
@JvmStatic
96+
fun checkForEmptyBodyIfRequired(options: RestOptions, methodName: String) {
97+
if ((methodName == FlutterRestApi.PUT || methodName == FlutterRestApi.POST || methodName == FlutterRestApi.PATCH) && !options.hasData()) {
98+
throw ApiException("$methodName request must have a body", "Add a body to the request.")
99+
}
100+
}
101+
93102
// ====== GraphQL ======
94103
@JvmStatic
95104
fun getGraphQLDocument(request: Map<String, Any>): String {
96105
try {
97106
return request["document"] as String
98107
} catch (cause: Exception) {
99-
throw AmplifyException(
100-
"The graphQL document request argument was not passed as a String",
101-
cause,
102-
"The request should include the graphQL document as a String")
108+
throw AmplifyException(
109+
"The graphQL document request argument was not passed as a String",
110+
cause,
111+
"The request should include the graphQL document as a String")
103112
}
104113
}
105114

packages/amplify_api/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/rest_api/FlutterRestApi.kt

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ class FlutterRestApi {
3333
companion object {
3434
private val LOG = Amplify.Logging.forNamespace("amplify:flutter:api")
3535
private val handler = Handler(Looper.getMainLooper())
36+
const val GET = "get"
37+
const val POST = "post"
38+
const val PUT = "put"
39+
const val DELETE = "delete"
40+
const val HEAD = "head"
41+
const val PATCH = "patch"
3642

3743
private fun restFunctionHelper(
3844
methodName: String,
@@ -41,14 +47,23 @@ class FlutterRestApi {
4147
functionWithoutApiName: FunctionWithoutApiName,
4248
functionWithApiName: FunctionWithApiName
4349
) {
44-
var cancelToken : String
50+
var cancelToken: String
4551
var apiName: String?
4652
var options: RestOptions
4753

4854
try {
4955
cancelToken = FlutterApiRequest.getCancelToken(request)
5056
apiName = FlutterApiRequest.getApiPath(request)
5157
options = FlutterApiRequest.getRestOptions(request)
58+
59+
// Needed to prevent Android library from throwing a fatal error when body not present in some methods. https://github.com/aws-amplify/amplify-android/issues/1355
60+
FlutterApiRequest.checkForEmptyBodyIfRequired(options, methodName)
61+
} catch (e: ApiException) {
62+
handler.post {
63+
ExceptionUtil.postExceptionToFlutterChannel(flutterResult, "ApiException",
64+
ExceptionUtil.createSerializedError(e))
65+
}
66+
return
5267
} catch (e: Exception) {
5368
handler.post {
5469
ExceptionUtil.postExceptionToFlutterChannel(flutterResult, "ApiException",
@@ -92,7 +107,7 @@ class FlutterRestApi {
92107
}
93108
)
94109
}
95-
if(operation != null) {
110+
if (operation != null) {
96111
OperationsManager.addOperation(cancelToken, operation)
97112
}
98113

@@ -116,27 +131,27 @@ class FlutterRestApi {
116131
}
117132

118133
fun get(flutterResult: Result, arguments: Map<String, Any>) {
119-
restFunctionHelper("get", flutterResult, arguments, this::get, this::get)
134+
restFunctionHelper(GET, flutterResult, arguments, this::get, this::get)
120135
}
121136

122137
fun post(flutterResult: Result, arguments: Map<String, Any>) {
123-
restFunctionHelper("post", flutterResult, arguments, this::post, this::post)
138+
restFunctionHelper(POST, flutterResult, arguments, this::post, this::post)
124139
}
125140

126141
fun put(flutterResult: Result, arguments: Map<String, Any>) {
127-
restFunctionHelper("put", flutterResult, arguments, this::put, this::put)
142+
restFunctionHelper(PUT, flutterResult, arguments, this::put, this::put)
128143
}
129144

130145
fun delete(flutterResult: Result, arguments: Map<String, Any>) {
131-
restFunctionHelper("delete", flutterResult, arguments, this::delete, this::delete)
146+
restFunctionHelper(DELETE, flutterResult, arguments, this::delete, this::delete)
132147
}
133148

134149
fun head(flutterResult: Result, arguments: Map<String, Any>) {
135-
restFunctionHelper("head", flutterResult, arguments, this::head, this::head)
150+
restFunctionHelper(HEAD, flutterResult, arguments, this::head, this::head)
136151
}
137152

138153
fun patch(flutterResult: Result, arguments: Map<String, Any>) {
139-
restFunctionHelper("patch", flutterResult, arguments, this::patch, this::patch)
154+
restFunctionHelper(PATCH, flutterResult, arguments, this::patch, this::patch)
140155
}
141156

142157

packages/amplify_api/android/src/test/kotlin/com/amazonaws/amplify/amplify_api/AmplifyApiRestTest.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package com.amazonaws.amplify.amplify_api
1717

1818

1919
import com.amazonaws.amplify.amplify_core.exception.ExceptionMessages
20+
import com.amazonaws.amplify.amplify_api.AmplifyApiPlugin
21+
import com.amazonaws.amplify.amplify_api.rest_api.FlutterRestApi
2022
import com.amplifyframework.api.ApiCategory
2123
import com.amplifyframework.api.ApiException
2224
import com.amplifyframework.api.rest.RestOperation
@@ -352,6 +354,40 @@ class AmplifyApiRestTest {
352354
)
353355
}
354356

357+
// PUT PATCH and POST with no body throws error
358+
@Test
359+
fun required_body_methods_error() {
360+
Mockito.doAnswer { _ ->
361+
mockRestOperation
362+
}.`when`(mockApi).get(
363+
any(RestOptions::class.java),
364+
any(),
365+
any())
366+
367+
var arguments: Map<String, Any> = mapOf(
368+
"restOptions" to mapOf(
369+
"path" to "/items"
370+
),
371+
"cancelToken" to "someCode"
372+
)
373+
var methods = arrayOf<String>(FlutterRestApi.PUT, FlutterRestApi.POST, FlutterRestApi.PATCH)
374+
for (method in methods) {
375+
flutterPlugin.onMethodCall(
376+
MethodCall(method, arguments),
377+
mockResult
378+
)
379+
380+
verify(mockResult, times(1)).error(
381+
"ApiException",
382+
ExceptionMessages.defaultFallbackExceptionMessage,
383+
mapOf(
384+
"recoverySuggestion" to "Add a body to the request.",
385+
"message" to "$method request must have a body"
386+
)
387+
)
388+
}
389+
}
390+
355391
// Cancellation of ongoing rest operation succeeds
356392
@Test
357393
fun test_cancel_get_returns_success(){

0 commit comments

Comments
 (0)