diff --git a/packages/functions_client/lib/src/functions_client.dart b/packages/functions_client/lib/src/functions_client.dart index 99d7c7a5..967eac3d 100644 --- a/packages/functions_client/lib/src/functions_client.dart +++ b/packages/functions_client/lib/src/functions_client.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:functions_client/src/constants.dart'; import 'package:functions_client/src/types.dart'; import 'package:http/http.dart' as http; +import 'package:http/http.dart' show MultipartRequest; import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; class FunctionsClient { @@ -38,11 +39,17 @@ class FunctionsClient { /// Invokes a function /// - /// [functionName] - the name of the function to invoke + /// [functionName] is the name of the function to invoke /// - /// [headers]: object representing the headers to send with the request + /// [headers] to send with the request + /// + /// [body] of the request when [files] is null and can be of type String + /// or an Object that is encodable to JSON with `jsonEncode`. + /// If [files] is not null, [body] represents the fields of the + /// [MultipartRequest] and must be be of type `Map`. + /// + /// [files] to send in a `MultipartRequest`. [body] is used for the fields. /// - /// [body]: the body of the request /// /// ```dart /// // Call a standard function @@ -70,12 +77,11 @@ class FunctionsClient { Future invoke( String functionName, { Map? headers, - Map? body, + Object? body, + Iterable? files, Map? queryParameters, HttpMethod method = HttpMethod.post, }) async { - final bodyStr = body == null ? null : await _isolate.encode(body); - final uri = Uri.parse('$_url/$functionName') .replace(queryParameters: queryParameters); @@ -84,12 +90,36 @@ class FunctionsClient { if (headers != null) ...headers }; - final request = http.Request(method.name, uri); + final http.BaseRequest request; + if (files != null) { + assert( + body == null || body is Map, + 'body must be of type Map', + ); + final fields = body as Map?; + + request = http.MultipartRequest(method.name, uri) + ..fields.addAll(fields ?? {}) + ..files.addAll(files); + } else { + final bodyRequest = http.Request(method.name, uri); + + final String? bodyStr; + if (body == null) { + bodyStr = null; + } else if (body is String) { + bodyStr = body; + } else { + bodyStr = await _isolate.encode(body); + } + if (bodyStr != null) bodyRequest.body = bodyStr; + request = bodyRequest; + } finalHeaders.forEach((key, value) { request.headers[key] = value; }); - if (bodyStr != null) request.body = bodyStr; + final response = await (_httpClient?.send(request) ?? request.send()); final responseType = (response.headers['Content-Type'] ?? response.headers['content-type'] ?? diff --git a/packages/functions_client/test/custom_http_client.dart b/packages/functions_client/test/custom_http_client.dart index 247d6095..02d075f1 100644 --- a/packages/functions_client/test/custom_http_client.dart +++ b/packages/functions_client/test/custom_http_client.dart @@ -32,8 +32,22 @@ class CustomHttpClient extends BaseClient { "Content-Type": "text/event-stream", }); } else { + final Stream> stream; + if (request is MultipartRequest) { + stream = Stream.value( + utf8.encode(jsonEncode([ + for (final file in request.files) + { + "name": file.field, + "content": await file.finalize().bytesToString() + } + ])), + ); + } else { + stream = Stream.value(utf8.encode(jsonEncode({"key": "Hello World"}))); + } return StreamedResponse( - Stream.value(utf8.encode(jsonEncode({"key": "Hello World"}))), + stream, 200, request: request, headers: { diff --git a/packages/functions_client/test/functions_dart_test.dart b/packages/functions_client/test/functions_dart_test.dart index 7637b8f0..0ea98b50 100644 --- a/packages/functions_client/test/functions_dart_test.dart +++ b/packages/functions_client/test/functions_dart_test.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:functions_client/src/functions_client.dart'; import 'package:functions_client/src/types.dart'; +import 'package:http/http.dart'; import 'package:test/test.dart'; import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; @@ -43,6 +44,26 @@ void main() { expect(res.status, 200); }); + test('function call with files', () async { + final fileName = "file.txt"; + final fileContent = "Hello World"; + final res = await functionsCustomHttpClient.invoke( + 'function1', + queryParameters: {'key': 'value'}, + files: [ + MultipartFile.fromString(fileName, fileContent), + ], + ); + + final request = customHttpClient.receivedRequests.last; + + expect(request.url.queryParameters, {'key': 'value'}); + expect(res.data, [ + {'name': fileName, 'content': fileContent} + ]); + expect(res.status, 200); + }); + test('dispose isolate', () async { await functionsCustomHttpClient.dispose(); expect(functionsCustomHttpClient.invoke('function'), throwsStateError);