Skip to content

Commit 85a93c7

Browse files
committed
feat(gql_dio_link): support per-request timeouts via DioLinkTimeoutContextEntry
1 parent 03c210f commit 85a93c7

4 files changed

Lines changed: 90 additions & 8 deletions

File tree

links/gql_dio_link/lib/gql_dio_link.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export "package:gql_exec/gql_exec.dart"
44
show HttpLinkResponseContext, HttpLinkHeaders;
55
export "src/dio_cancel_token_context_entry.dart";
66
export "src/dio_link.dart";
7+
export "src/dio_timeout_context_entry.dart";
78
export "src/exceptions.dart";

links/gql_dio_link/lib/src/dio_link.dart

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "package:gql_link/gql_link.dart";
66

77
import "_utils.dart";
88
import "dio_cancel_token_context_entry.dart";
9+
import "dio_timeout_context_entry.dart";
910
import "exceptions.dart";
1011

1112
@Deprecated("Use HttpLinkResponseContext instead")
@@ -177,6 +178,14 @@ class DioLink extends Link {
177178
dio.Response<dynamic> res;
178179
final dio.CancelToken? cancelToken =
179180
request.context.entry<DioLinkCancelTokenContextEntry>()?.token;
181+
final DioLinkTimeoutContextEntry? timeout =
182+
request.context.entry<DioLinkTimeoutContextEntry>();
183+
final dio.Options requestOptions = dio.Options(
184+
responseType: dio.ResponseType.json,
185+
headers: headers,
186+
sendTimeout: timeout?.sendTimeout,
187+
receiveTimeout: timeout?.receiveTimeout,
188+
);
180189

181190
final useGet =
182191
useGETForQueries && body is Map<String, dynamic> && isQuery;
@@ -189,20 +198,14 @@ class DioLink extends Link {
189198
)(body as Map<String, dynamic>),
190199
),
191200
cancelToken: cancelToken,
192-
options: dio.Options(
193-
responseType: dio.ResponseType.json,
194-
headers: headers,
195-
),
201+
options: requestOptions,
196202
);
197203
} else {
198204
res = await client.post<dynamic>(
199205
endpoint,
200206
cancelToken: cancelToken,
201207
data: body,
202-
options: dio.Options(
203-
responseType: dio.ResponseType.json,
204-
headers: headers,
205-
),
208+
options: requestOptions,
206209
);
207210
}
208211
if (res.data is Map<String, dynamic> == false) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import "package:gql_exec/gql_exec.dart";
2+
3+
/// Per-request send/receive timeouts for the underlying Dio call, e.g. for
4+
/// a single long-running GraphQL operation. Fields left `null` keep the
5+
/// timeouts configured on the Dio client.
6+
class DioLinkTimeoutContextEntry extends ContextEntry {
7+
final Duration? sendTimeout;
8+
final Duration? receiveTimeout;
9+
10+
const DioLinkTimeoutContextEntry({this.sendTimeout, this.receiveTimeout});
11+
12+
@override
13+
List<Object?> get fieldsForEquality => [sendTimeout, receiveTimeout];
14+
}

links/gql_dio_link/test/gql_dio_link_test.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,70 @@ void main() {
187187
).called(1);
188188
});
189189

190+
test("applies timeouts from context", () async {
191+
when(
192+
client.post<dynamic>(
193+
any,
194+
data: anyNamed("data"),
195+
options: anyNamed("options"),
196+
),
197+
).thenAnswer(
198+
(invocation) {
199+
final options = invocation.namedArguments.entries
200+
.firstWhere((element) => element.key == Symbol("options"))
201+
.value as dio.Options;
202+
return Future.value(
203+
dio.Response<Map<String, dynamic>>(
204+
data: <String, dynamic>{
205+
"data": <String, dynamic>{},
206+
},
207+
statusCode: 200,
208+
requestOptions: dio.RequestOptions(
209+
path: path,
210+
headers: options.headers,
211+
),
212+
),
213+
);
214+
},
215+
);
216+
217+
await execute(
218+
Request(
219+
operation: Operation(
220+
document: parseString("query MyQuery {}"),
221+
),
222+
variables: const <String, dynamic>{"i": 12},
223+
context: Context.fromList(
224+
const [
225+
DioLinkTimeoutContextEntry(
226+
sendTimeout: Duration(seconds: 20),
227+
receiveTimeout: Duration(seconds: 20),
228+
),
229+
],
230+
),
231+
),
232+
).first;
233+
234+
verify(
235+
client.post<dynamic>(
236+
any,
237+
data: anyNamed("data"),
238+
options: argThat(
239+
predicate((dio.Options o) => o.extEqual(dio.Options(
240+
sendTimeout: const Duration(seconds: 20),
241+
receiveTimeout: const Duration(seconds: 20),
242+
responseType: dio.ResponseType.json,
243+
headers: <String, dynamic>{
244+
dio.Headers.contentTypeHeader: dio.Headers.jsonContentType,
245+
dio.Headers.acceptHeader: "*/*",
246+
},
247+
))),
248+
named: "options",
249+
),
250+
),
251+
).called(1);
252+
});
253+
190254
test("adds headers from context", () async {
191255
when(
192256
client.post<dynamic>(

0 commit comments

Comments
 (0)