Skip to content

Commit 22fde31

Browse files
committed
Implement REST API retry mechanism
1 parent 9093dbd commit 22fde31

File tree

8 files changed

+1265
-170
lines changed

8 files changed

+1265
-170
lines changed

packages/dart/lib/parse_server_sdk.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ part 'src/network/options.dart';
3636
part 'src/network/parse_client.dart';
3737
part 'src/network/parse_connectivity.dart';
3838
part 'src/network/parse_live_query.dart';
39+
part 'src/network/parse_network_retry.dart';
3940
part 'src/network/parse_query.dart';
4041
part 'src/objects/parse_acl.dart';
4142
part 'src/objects/parse_array.dart';
@@ -118,6 +119,7 @@ class Parse {
118119
Map<String, ParseObjectConstructor>? registeredSubClassMap,
119120
ParseUserConstructor? parseUserConstructor,
120121
ParseFileConstructor? parseFileConstructor,
122+
List<int>? restRetryIntervals,
121123
List<int>? liveListRetryIntervals,
122124
ParseConnectivityProvider? connectivityProvider,
123125
String? fileDirectory,
@@ -144,6 +146,7 @@ class Parse {
144146
registeredSubClassMap: registeredSubClassMap,
145147
parseUserConstructor: parseUserConstructor,
146148
parseFileConstructor: parseFileConstructor,
149+
restRetryIntervals: restRetryIntervals,
147150
liveListRetryIntervals: liveListRetryIntervals,
148151
connectivityProvider: connectivityProvider,
149152
fileDirectory: fileDirectory,

packages/dart/lib/src/data/parse_core_data.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class ParseCoreData {
3232
Map<String, ParseObjectConstructor>? registeredSubClassMap,
3333
ParseUserConstructor? parseUserConstructor,
3434
ParseFileConstructor? parseFileConstructor,
35+
List<int>? restRetryIntervals,
3536
List<int>? liveListRetryIntervals,
3637
ParseConnectivityProvider? connectivityProvider,
3738
String? fileDirectory,
@@ -52,6 +53,8 @@ class ParseCoreData {
5253
_instance.sessionId = sessionId;
5354
_instance.autoSendSessionId = autoSendSessionId;
5455
_instance.securityContext = securityContext;
56+
_instance.restRetryIntervals =
57+
restRetryIntervals ?? <int>[0, 250, 500, 1000, 2000];
5558
_instance.liveListRetryIntervals =
5659
liveListRetryIntervals ??
5760
(parseIsWeb
@@ -89,6 +92,7 @@ class ParseCoreData {
8992
late bool debug;
9093
late CoreStore storage;
9194
late ParseSubClassHandler _subClassHandler;
95+
late List<int> restRetryIntervals;
9296
late List<int> liveListRetryIntervals;
9397
ParseConnectivityProvider? connectivityProvider;
9498
String? fileDirectory;

packages/dart/lib/src/network/parse_dio_client.dart

Lines changed: 155 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@ import 'package:parse_server_sdk/parse_server_sdk.dart';
33

44
import 'dio_adapter_io.dart' if (dart.library.js) 'dio_adapter_js.dart';
55

6+
/// HTTP client implementation for Parse Server using the Dio package.
7+
///
8+
/// Coverage Note:
9+
///
10+
/// This file typically shows low test coverage (4-5%) in LCOV reports because:
11+
/// - Integration tests use MockParseClient which bypasses actual HTTP operations
12+
/// - The retry logic (tested at 100% in parse_network_retry_test.dart) wraps
13+
/// these HTTP methods but isn't exercised when using mocks
14+
/// - This is architecturally correct: retry operates at the HTTP layer,
15+
/// while mocks operate at the ParseClient interface layer above it
16+
///
17+
/// The core retry mechanism has 100% coverage in its dedicated unit tests.
18+
/// This file's primary responsibility is thin wrapper code around executeWithRetry().
19+
620
class ParseDioClient extends ParseClient {
721
// securityContext is SecurityContext
822
ParseDioClient({bool sendSessionId = false, dynamic securityContext}) {
@@ -22,22 +36,26 @@ class ParseDioClient extends ParseClient {
2236
ParseNetworkOptions? options,
2337
ProgressCallback? onReceiveProgress,
2438
}) async {
25-
try {
26-
final dio.Response<String> dioResponse = await _client.get<String>(
27-
path,
28-
options: _Options(headers: options?.headers),
29-
);
39+
return executeWithRetry(
40+
operation: () async {
41+
try {
42+
final dio.Response<String> dioResponse = await _client.get<String>(
43+
path,
44+
options: _Options(headers: options?.headers),
45+
);
3046

31-
return ParseNetworkResponse(
32-
data: dioResponse.data!,
33-
statusCode: dioResponse.statusCode!,
34-
);
35-
} on dio.DioException catch (error) {
36-
return ParseNetworkResponse(
37-
data: error.response?.data ?? _fallbackErrorData,
38-
statusCode: error.response?.statusCode ?? ParseError.otherCause,
39-
);
40-
}
47+
return ParseNetworkResponse(
48+
data: dioResponse.data!,
49+
statusCode: dioResponse.statusCode!,
50+
);
51+
} on dio.DioException catch (error) {
52+
return ParseNetworkResponse(
53+
data: error.response?.data ?? _fallbackErrorData,
54+
statusCode: error.response?.statusCode ?? ParseError.otherCause,
55+
);
56+
}
57+
},
58+
);
4159
}
4260

4361
@override
@@ -47,34 +65,39 @@ class ParseDioClient extends ParseClient {
4765
ProgressCallback? onReceiveProgress,
4866
dynamic cancelToken,
4967
}) async {
50-
try {
51-
final dio.Response<List<int>> dioResponse = await _client.get<List<int>>(
52-
path,
53-
cancelToken: cancelToken,
54-
onReceiveProgress: onReceiveProgress,
55-
options: _Options(
56-
headers: options?.headers,
57-
responseType: dio.ResponseType.bytes,
58-
),
59-
);
60-
return ParseNetworkByteResponse(
61-
bytes: dioResponse.data,
62-
statusCode: dioResponse.statusCode!,
63-
);
64-
} on dio.DioException catch (error) {
65-
if (error.response != null) {
66-
return ParseNetworkByteResponse(
67-
data: error.response?.data ?? _fallbackErrorData,
68-
statusCode: error.response?.statusCode ?? ParseError.otherCause,
69-
);
70-
} else {
71-
return ParseNetworkByteResponse(
72-
data:
73-
"{\"code\":${ParseError.otherCause},\"error\":\"${error.error.toString()}\"}",
74-
statusCode: ParseError.otherCause,
75-
);
76-
}
77-
}
68+
return executeWithRetry(
69+
operation: () async {
70+
try {
71+
final dio.Response<List<int>> dioResponse = await _client
72+
.get<List<int>>(
73+
path,
74+
cancelToken: cancelToken,
75+
onReceiveProgress: onReceiveProgress,
76+
options: _Options(
77+
headers: options?.headers,
78+
responseType: dio.ResponseType.bytes,
79+
),
80+
);
81+
return ParseNetworkByteResponse(
82+
bytes: dioResponse.data,
83+
statusCode: dioResponse.statusCode!,
84+
);
85+
} on dio.DioException catch (error) {
86+
if (error.response != null) {
87+
return ParseNetworkByteResponse(
88+
data: error.response?.data ?? _fallbackErrorData,
89+
statusCode: error.response?.statusCode ?? ParseError.otherCause,
90+
);
91+
} else {
92+
return ParseNetworkByteResponse(
93+
data:
94+
"{\"code\":${ParseError.otherCause},\"error\":\"${error.error.toString()}\"}",
95+
statusCode: ParseError.otherCause,
96+
);
97+
}
98+
}
99+
},
100+
);
78101
}
79102

80103
@override
@@ -83,23 +106,27 @@ class ParseDioClient extends ParseClient {
83106
String? data,
84107
ParseNetworkOptions? options,
85108
}) async {
86-
try {
87-
final dio.Response<String> dioResponse = await _client.put<String>(
88-
path,
89-
data: data,
90-
options: _Options(headers: options?.headers),
91-
);
109+
return executeWithRetry(
110+
operation: () async {
111+
try {
112+
final dio.Response<String> dioResponse = await _client.put<String>(
113+
path,
114+
data: data,
115+
options: _Options(headers: options?.headers),
116+
);
92117

93-
return ParseNetworkResponse(
94-
data: dioResponse.data!,
95-
statusCode: dioResponse.statusCode!,
96-
);
97-
} on dio.DioException catch (error) {
98-
return ParseNetworkResponse(
99-
data: error.response?.data ?? _fallbackErrorData,
100-
statusCode: error.response?.statusCode ?? ParseError.otherCause,
101-
);
102-
}
118+
return ParseNetworkResponse(
119+
data: dioResponse.data!,
120+
statusCode: dioResponse.statusCode!,
121+
);
122+
} on dio.DioException catch (error) {
123+
return ParseNetworkResponse(
124+
data: error.response?.data ?? _fallbackErrorData,
125+
statusCode: error.response?.statusCode ?? ParseError.otherCause,
126+
);
127+
}
128+
},
129+
);
103130
}
104131

105132
@override
@@ -108,23 +135,27 @@ class ParseDioClient extends ParseClient {
108135
String? data,
109136
ParseNetworkOptions? options,
110137
}) async {
111-
try {
112-
final dio.Response<String> dioResponse = await _client.post<String>(
113-
path,
114-
data: data,
115-
options: _Options(headers: options?.headers),
116-
);
138+
return executeWithRetry(
139+
operation: () async {
140+
try {
141+
final dio.Response<String> dioResponse = await _client.post<String>(
142+
path,
143+
data: data,
144+
options: _Options(headers: options?.headers),
145+
);
117146

118-
return ParseNetworkResponse(
119-
data: dioResponse.data!,
120-
statusCode: dioResponse.statusCode!,
121-
);
122-
} on dio.DioException catch (error) {
123-
return ParseNetworkResponse(
124-
data: error.response?.data ?? _fallbackErrorData,
125-
statusCode: error.response?.statusCode ?? ParseError.otherCause,
126-
);
127-
}
147+
return ParseNetworkResponse(
148+
data: dioResponse.data!,
149+
statusCode: dioResponse.statusCode!,
150+
);
151+
} on dio.DioException catch (error) {
152+
return ParseNetworkResponse(
153+
data: error.response?.data ?? _fallbackErrorData,
154+
statusCode: error.response?.statusCode ?? ParseError.otherCause,
155+
);
156+
}
157+
},
158+
);
128159
}
129160

130161
@override
@@ -135,31 +166,35 @@ class ParseDioClient extends ParseClient {
135166
ProgressCallback? onSendProgress,
136167
dynamic cancelToken,
137168
}) async {
138-
try {
139-
final dio.Response<String> dioResponse = await _client.post<String>(
140-
path,
141-
data: data,
142-
cancelToken: cancelToken,
143-
options: _Options(headers: options?.headers),
144-
onSendProgress: onSendProgress,
145-
);
169+
return executeWithRetry(
170+
operation: () async {
171+
try {
172+
final dio.Response<String> dioResponse = await _client.post<String>(
173+
path,
174+
data: data,
175+
cancelToken: cancelToken,
176+
options: _Options(headers: options?.headers),
177+
onSendProgress: onSendProgress,
178+
);
146179

147-
return ParseNetworkResponse(
148-
data: dioResponse.data!,
149-
statusCode: dioResponse.statusCode!,
150-
);
151-
} on dio.DioException catch (error) {
152-
if (error.response != null) {
153-
return ParseNetworkResponse(
154-
data: error.response?.data ?? _fallbackErrorData,
155-
statusCode: error.response?.statusCode ?? ParseError.otherCause,
156-
);
157-
} else {
158-
return _getOtherCaseErrorForParseNetworkResponse(
159-
error.error.toString(),
160-
);
161-
}
162-
}
180+
return ParseNetworkResponse(
181+
data: dioResponse.data!,
182+
statusCode: dioResponse.statusCode!,
183+
);
184+
} on dio.DioException catch (error) {
185+
if (error.response != null) {
186+
return ParseNetworkResponse(
187+
data: error.response?.data ?? _fallbackErrorData,
188+
statusCode: error.response?.statusCode ?? ParseError.otherCause,
189+
);
190+
} else {
191+
return _getOtherCaseErrorForParseNetworkResponse(
192+
error.error.toString(),
193+
);
194+
}
195+
}
196+
},
197+
);
163198
}
164199

165200
ParseNetworkResponse _getOtherCaseErrorForParseNetworkResponse(String error) {
@@ -174,25 +209,30 @@ class ParseDioClient extends ParseClient {
174209
String path, {
175210
ParseNetworkOptions? options,
176211
}) async {
177-
try {
178-
final dio.Response<String> dioResponse = await _client.delete<String>(
179-
path,
180-
options: _Options(headers: options?.headers),
181-
);
212+
return executeWithRetry(
213+
operation: () async {
214+
try {
215+
final dio.Response<String> dioResponse = await _client.delete<String>(
216+
path,
217+
options: _Options(headers: options?.headers),
218+
);
182219

183-
return ParseNetworkResponse(
184-
data: dioResponse.data!,
185-
statusCode: dioResponse.statusCode!,
186-
);
187-
} on dio.DioException catch (error) {
188-
return ParseNetworkResponse(
189-
data: error.response?.data ?? _fallbackErrorData,
190-
statusCode: error.response?.statusCode ?? ParseError.otherCause,
191-
);
192-
}
220+
return ParseNetworkResponse(
221+
data: dioResponse.data!,
222+
statusCode: dioResponse.statusCode!,
223+
);
224+
} on dio.DioException catch (error) {
225+
return ParseNetworkResponse(
226+
data: error.response?.data ?? _fallbackErrorData,
227+
statusCode: error.response?.statusCode ?? ParseError.otherCause,
228+
);
229+
}
230+
},
231+
);
193232
}
194233

195-
String get _fallbackErrorData => '{"$keyError":"NetworkError"}';
234+
String get _fallbackErrorData =>
235+
'{"code":${ParseError.otherCause},"error":"NetworkError"}';
196236
}
197237

198238
/// Creates a custom version of HTTP Client that has Parse Data Preset

0 commit comments

Comments
 (0)