diff --git a/lib/codegen/dart/dio.dart b/lib/codegen/dart/dio.dart index e9590f790..eedc6d623 100644 --- a/lib/codegen/dart/dio.dart +++ b/lib/codegen/dart/dio.dart @@ -83,7 +83,7 @@ class DartDioCodeGen { case ContentType.text: dataExp = declareFinal('data').assign(strContent); // when add new type of [ContentType], need update [dataExp]. - case ContentType.formdata: + default: dataExp = declareFinal('data').assign(refer('dio.FormData()')); } } diff --git a/lib/consts.dart b/lib/consts.dart index 2ad9d1852..a33507923 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -348,7 +348,8 @@ const kSubTypeDefaultViewOptions = 'all'; enum ContentType { json("$kTypeApplication/$kSubTypeJson"), text("$kTypeText/$kSubTypePlain"), - formdata("$kTypeMultipart/$kSubTypeFormData"); + formdata("$kTypeMultipart/$kSubTypeFormData"), + file("$kTypeApplication/$kSubTypeOctetStream"); const ContentType(this.header); final String header; diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index acf6a3eb9..00547775b 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -26,6 +26,7 @@ class RequestModel { this.isParamEnabledList, this.requestBodyContentType = ContentType.json, this.requestBody, + this.requestFile, this.requestFormDataList, this.responseStatus, this.message, @@ -45,6 +46,7 @@ class RequestModel { final List? isParamEnabledList; final ContentType requestBodyContentType; final String? requestBody; + final String? requestFile; final List? requestFormDataList; final int? responseStatus; final String? message; @@ -67,8 +69,11 @@ class RequestModel { requestBodyContentType == ContentType.formdata; bool get hasJsonContentType => requestBodyContentType == ContentType.json; bool get hasTextContentType => requestBodyContentType == ContentType.text; + bool get hasFileContentType => requestBodyContentType == ContentType.file; int get contentLength => utf8.encode(requestBody ?? "").length; - bool get hasBody => hasJsonData || hasTextData || hasFormData; + int get fileContentLength => utf8.encode(requestFile ?? "").length; + bool get hasBody => + hasJsonData || hasTextData || hasFormData || hasFileContentType; bool get hasJsonData => kMethodsWithBody.contains(method) && hasJsonContentType && @@ -81,6 +86,10 @@ class RequestModel { kMethodsWithBody.contains(method) && hasFormDataContentType && formDataMapList.isNotEmpty; + bool get hasFileData => + kMethodsWithBody.contains(method) && + hasFileContentType && + fileContentLength > 0; List get formDataList => requestFormDataList ?? []; List> get formDataMapList => @@ -112,6 +121,7 @@ class RequestModel { isParamEnabledList != null ? [...isParamEnabledList!] : null, requestBodyContentType: requestBodyContentType, requestBody: requestBody, + requestFile: requestFile, requestFormDataList: requestFormDataList != null ? [...requestFormDataList!] : null, ); @@ -130,6 +140,7 @@ class RequestModel { List? isParamEnabledList, ContentType? requestBodyContentType, String? requestBody, + String? requestFile, List? requestFormDataList, int? responseStatus, String? message, @@ -155,6 +166,7 @@ class RequestModel { requestBodyContentType: requestBodyContentType ?? this.requestBodyContentType, requestBody: requestBody ?? this.requestBody, + requestFile: requestFile ?? this.requestFile, requestFormDataList: formDataList != null ? [...formDataList] : null, responseStatus: responseStatus ?? this.responseStatus, message: message ?? this.message, @@ -188,6 +200,7 @@ class RequestModel { requestBodyContentType = kDefaultContentType; } final requestBody = data["requestBody"] as String?; + final requestFile = data["requestFile"] as String?; final requestFormDataList = data["requestFormDataList"]; final responseStatus = data["responseStatus"] as int?; final message = data["message"] as String?; @@ -217,6 +230,7 @@ class RequestModel { isParamEnabledList: isParamEnabledList, requestBodyContentType: requestBodyContentType, requestBody: requestBody, + requestFile: requestFile, requestFormDataList: requestFormDataList != null ? mapListToFormDataModelRows(List.from(requestFormDataList)) : null, @@ -239,6 +253,7 @@ class RequestModel { "isParamEnabledList": isParamEnabledList, "requestBodyContentType": requestBodyContentType.name, "requestBody": requestBody, + "requestFile": requestFile, "requestFormDataList": rowsToFormDataMapList(requestFormDataList), "responseStatus": includeResponse ? responseStatus : null, "message": includeResponse ? message : null, @@ -261,6 +276,7 @@ class RequestModel { "Enabled Params: ${isParamEnabledList.toString()}", "Request Body Content Type: ${requestBodyContentType.toString()}", "Request Body: ${requestBody.toString()}", + "Request File: ${requestFile.toString()}", "Request FormData: ${requestFormDataList.toString()}", "Response Status: $responseStatus", "Response Message: $message", @@ -284,6 +300,7 @@ class RequestModel { listEquals(other.isParamEnabledList, isParamEnabledList) && other.requestBodyContentType == requestBodyContentType && other.requestBody == requestBody && + other.requestFile == requestFile && other.requestFormDataList == requestFormDataList && other.responseStatus == responseStatus && other.message == message && @@ -306,6 +323,7 @@ class RequestModel { isParamEnabledList, requestBodyContentType, requestBody, + requestFile, requestFormDataList, responseStatus, message, diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 75e9d7c58..ca657801c 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -141,6 +141,7 @@ class CollectionStateNotifier List? isParamEnabledList, ContentType? requestBodyContentType, String? requestBody, + String? requestFile, List? requestFormDataList, int? responseStatus, String? message, @@ -158,6 +159,7 @@ class CollectionStateNotifier isParamEnabledList: isParamEnabledList, requestBodyContentType: requestBodyContentType, requestBody: requestBody, + requestFile: requestFile, requestFormDataList: requestFormDataList, responseStatus: responseStatus, message: message, diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart index f667f6b5a..a0579a21a 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart @@ -1,3 +1,4 @@ +import 'package:apidash/utils/file_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; @@ -50,6 +51,52 @@ class EditRequestBody extends ConsumerWidget { .update(selectedId, requestBody: value); }, ), + ContentType.file => Align( + alignment: Alignment.centerLeft, + child: Row( + children: [ + Expanded( + child: Theme( + data: Theme.of(context), + child: ElevatedButton.icon( + icon: const Icon( + Icons.snippet_folder_rounded, + size: 20, + ), + style: ButtonStyle( + shape: MaterialStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + ), + ), + onPressed: () async { + var pickedResult = await pickFile(); + if (pickedResult != null && + pickedResult.files.isNotEmpty && + pickedResult.files.first.path != null) { + ref + .read(collectionStateNotifierProvider + .notifier) + .update(selectedId, + requestFile: + pickedResult.files.first.path!); + } + }, + label: Text( + ref.watch(selectedRequestModelProvider + .select((value) => value?.requestFile)) ?? + kLabelSelectFile, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: kFormDataButtonLabelTextStyle, + ), + ), + ), + ), + ], + ), + ), _ => TextFieldEditor( key: Key("$selectedId-body"), fieldKey: "$selectedId-body-editor", diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart index dec0b5163..401a21328 100644 --- a/lib/services/http_service.dart +++ b/lib/services/http_service.dart @@ -19,17 +19,28 @@ Future<(http.Response?, Duration?, String?)> request( Uri requestUrl = uriRec.$1!; Map headers = requestModel.enabledHeadersMap; http.Response response; - String? body; + Object? body; try { Stopwatch stopwatch = Stopwatch()..start(); var isMultiPartRequest = requestModel.requestBodyContentType == ContentType.formdata; if (kMethodsWithBody.contains(requestModel.method)) { var requestBody = requestModel.requestBody; - if (requestBody != null && !isMultiPartRequest) { - var contentLength = utf8.encode(requestBody).length; + var requestFile = (requestModel.requestFile != null && + requestModel.requestFile!.isNotEmpty) + ? File(requestModel.requestFile!) + : null; + if ((requestBody != null || requestFile != null) && + !isMultiPartRequest) { + var requestFileBytes = + requestBody == null ? await requestFile!.readAsBytes() : null; + + var contentLength = requestBody != null + ? utf8.encode(requestBody).length + : requestFileBytes!.length; + if (contentLength > 0) { - body = requestBody; + body = requestBody ?? requestFileBytes; headers[HttpHeaders.contentLengthHeader] = contentLength.toString(); if (!requestModel.hasContentTypeHeader) { headers[HttpHeaders.contentTypeHeader] = diff --git a/test/models/request_model_test.dart b/test/models/request_model_test.dart index b7dbb413d..746b63a2b 100644 --- a/test/models/request_model_test.dart +++ b/test/models/request_model_test.dart @@ -113,6 +113,7 @@ void main() { "requestBody": '''{ "text":"WORLD" }''', + 'requestFile': null, 'requestFormDataList': null, 'responseStatus': null, 'message': null, @@ -148,6 +149,7 @@ void main() { "Enabled Params: null", "Request Body Content Type: ContentType.json", 'Request Body: {\n"text":"WORLD"\n}', + 'Request File: null', 'Request FormData: null', "Response Status: null", "Response Message: null",