From e1ae22bdcd85ddcf5510f8a08acdce91949662b9 Mon Sep 17 00:00:00 2001 From: vidya-hub Date: Tue, 19 Dec 2023 20:55:44 +0530 Subject: [PATCH 01/30] wip: added basic methods --- lib/consts.dart | 14 +- lib/models/form_data_model.dart | 23 +++ lib/models/form_data_model.freezed.dart | 188 ++++++++++++++++++ lib/models/form_data_model.g.dart | 26 +++ lib/models/name_value_model.freezed.dart | 43 ++-- lib/models/name_value_model.g.dart | 7 +- lib/models/request_model.dart | 16 +- .../request_pane/request_body.dart | 121 +++++++++-- lib/utils/convert_utils.dart | 33 ++- lib/widgets/dropdowns.dart | 4 +- 10 files changed, 429 insertions(+), 46 deletions(-) create mode 100644 lib/models/form_data_model.dart create mode 100644 lib/models/form_data_model.freezed.dart create mode 100644 lib/models/form_data_model.g.dart diff --git a/lib/consts.dart b/lib/consts.dart index 2dcb12ab..ef7ed6f4 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -1,9 +1,10 @@ -import 'dart:io'; import 'dart:convert'; +import 'dart:io'; + +import 'package:davi/davi.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:davi/davi.dart'; const kDiscordUrl = "https://bit.ly/heyfoss"; const kGitUrl = "https://github.com/foss42/apidash"; @@ -224,7 +225,9 @@ enum RequestItemMenuOption { edit, delete, duplicate } enum HTTPVerb { get, head, post, put, patch, delete } -enum ContentType { json, text } +enum ContentType { json, text, formdata } + +enum FormDataType { text, file } const kSupportedUriSchemes = ["https", "http"]; const kDefaultUriScheme = "https"; @@ -294,6 +297,11 @@ const kSubTypeDefaultViewOptions = 'all'; const kContentTypeMap = { ContentType.json: "$kTypeApplication/$kSubTypeJson", ContentType.text: "$kTypeText/$kSubTypePlain", + ContentType.formdata: "multipart/form-data", +}; +const kFormDataTypeMap = { + FormDataType.file: "File", + FormDataType.text: "Text", }; enum ResponseBodyView { preview, code, raw, none } diff --git a/lib/models/form_data_model.dart b/lib/models/form_data_model.dart new file mode 100644 index 00000000..edbcd113 --- /dev/null +++ b/lib/models/form_data_model.dart @@ -0,0 +1,23 @@ +import 'package:apidash/consts.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'form_data_model.freezed.dart'; +part 'form_data_model.g.dart'; + +@freezed +class FormDataModel with _$FormDataModel { + const factory FormDataModel({ + required String name, + required dynamic value, + required FormDataType type, + }) = _FormDataModel; + + factory FormDataModel.fromJson(Map json) => + _$FormDataModelFromJson(json); +} + +const kNameValueEmptyModel = FormDataModel( + name: "", + value: "", + type: FormDataType.text, +); diff --git a/lib/models/form_data_model.freezed.dart b/lib/models/form_data_model.freezed.dart new file mode 100644 index 00000000..e1babd78 --- /dev/null +++ b/lib/models/form_data_model.freezed.dart @@ -0,0 +1,188 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'form_data_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +FormDataModel _$FormDataModelFromJson(Map json) { + return _FormDataModel.fromJson(json); +} + +/// @nodoc +mixin _$FormDataModel { + String get name => throw _privateConstructorUsedError; + dynamic get value => throw _privateConstructorUsedError; + FormDataType get type => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $FormDataModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FormDataModelCopyWith<$Res> { + factory $FormDataModelCopyWith( + FormDataModel value, $Res Function(FormDataModel) then) = + _$FormDataModelCopyWithImpl<$Res, FormDataModel>; + @useResult + $Res call({String name, dynamic value, FormDataType type}); +} + +/// @nodoc +class _$FormDataModelCopyWithImpl<$Res, $Val extends FormDataModel> + implements $FormDataModelCopyWith<$Res> { + _$FormDataModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? value = freezed, + Object? type = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as dynamic, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as FormDataType, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$FormDataModelImplCopyWith<$Res> + implements $FormDataModelCopyWith<$Res> { + factory _$$FormDataModelImplCopyWith( + _$FormDataModelImpl value, $Res Function(_$FormDataModelImpl) then) = + __$$FormDataModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, dynamic value, FormDataType type}); +} + +/// @nodoc +class __$$FormDataModelImplCopyWithImpl<$Res> + extends _$FormDataModelCopyWithImpl<$Res, _$FormDataModelImpl> + implements _$$FormDataModelImplCopyWith<$Res> { + __$$FormDataModelImplCopyWithImpl( + _$FormDataModelImpl _value, $Res Function(_$FormDataModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? value = freezed, + Object? type = null, + }) { + return _then(_$FormDataModelImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as dynamic, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as FormDataType, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$FormDataModelImpl implements _FormDataModel { + const _$FormDataModelImpl( + {required this.name, required this.value, required this.type}); + + factory _$FormDataModelImpl.fromJson(Map json) => + _$$FormDataModelImplFromJson(json); + + @override + final String name; + @override + final dynamic value; + @override + final FormDataType type; + + @override + String toString() { + return 'FormDataModel(name: $name, value: $value, type: $type)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FormDataModelImpl && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals(other.value, value) && + (identical(other.type, type) || other.type == type)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, name, const DeepCollectionEquality().hash(value), type); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$FormDataModelImplCopyWith<_$FormDataModelImpl> get copyWith => + __$$FormDataModelImplCopyWithImpl<_$FormDataModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$FormDataModelImplToJson( + this, + ); + } +} + +abstract class _FormDataModel implements FormDataModel { + const factory _FormDataModel( + {required final String name, + required final dynamic value, + required final FormDataType type}) = _$FormDataModelImpl; + + factory _FormDataModel.fromJson(Map json) = + _$FormDataModelImpl.fromJson; + + @override + String get name; + @override + dynamic get value; + @override + FormDataType get type; + @override + @JsonKey(ignore: true) + _$$FormDataModelImplCopyWith<_$FormDataModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/form_data_model.g.dart b/lib/models/form_data_model.g.dart new file mode 100644 index 00000000..7d9d353b --- /dev/null +++ b/lib/models/form_data_model.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'form_data_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$FormDataModelImpl _$$FormDataModelImplFromJson(Map json) => + _$FormDataModelImpl( + name: json['name'] as String, + value: json['value'], + type: $enumDecode(_$FormDataTypeEnumMap, json['type']), + ); + +Map _$$FormDataModelImplToJson(_$FormDataModelImpl instance) => + { + 'name': instance.name, + 'value': instance.value, + 'type': _$FormDataTypeEnumMap[instance.type]!, + }; + +const _$FormDataTypeEnumMap = { + FormDataType.text: 'text', + FormDataType.file: 'file', +}; diff --git a/lib/models/name_value_model.freezed.dart b/lib/models/name_value_model.freezed.dart index e7d9e8e0..b224f596 100644 --- a/lib/models/name_value_model.freezed.dart +++ b/lib/models/name_value_model.freezed.dart @@ -68,22 +68,22 @@ class _$NameValueModelCopyWithImpl<$Res, $Val extends NameValueModel> } /// @nodoc -abstract class _$$_NameValueModelCopyWith<$Res> +abstract class _$$NameValueModelImplCopyWith<$Res> implements $NameValueModelCopyWith<$Res> { - factory _$$_NameValueModelCopyWith( - _$_NameValueModel value, $Res Function(_$_NameValueModel) then) = - __$$_NameValueModelCopyWithImpl<$Res>; + factory _$$NameValueModelImplCopyWith(_$NameValueModelImpl value, + $Res Function(_$NameValueModelImpl) then) = + __$$NameValueModelImplCopyWithImpl<$Res>; @override @useResult $Res call({String name, dynamic value}); } /// @nodoc -class __$$_NameValueModelCopyWithImpl<$Res> - extends _$NameValueModelCopyWithImpl<$Res, _$_NameValueModel> - implements _$$_NameValueModelCopyWith<$Res> { - __$$_NameValueModelCopyWithImpl( - _$_NameValueModel _value, $Res Function(_$_NameValueModel) _then) +class __$$NameValueModelImplCopyWithImpl<$Res> + extends _$NameValueModelCopyWithImpl<$Res, _$NameValueModelImpl> + implements _$$NameValueModelImplCopyWith<$Res> { + __$$NameValueModelImplCopyWithImpl( + _$NameValueModelImpl _value, $Res Function(_$NameValueModelImpl) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -92,7 +92,7 @@ class __$$_NameValueModelCopyWithImpl<$Res> Object? name = null, Object? value = freezed, }) { - return _then(_$_NameValueModel( + return _then(_$NameValueModelImpl( name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -107,13 +107,13 @@ class __$$_NameValueModelCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$_NameValueModel +class _$NameValueModelImpl with DiagnosticableTreeMixin implements _NameValueModel { - const _$_NameValueModel({required this.name, required this.value}); + const _$NameValueModelImpl({required this.name, required this.value}); - factory _$_NameValueModel.fromJson(Map json) => - _$$_NameValueModelFromJson(json); + factory _$NameValueModelImpl.fromJson(Map json) => + _$$NameValueModelImplFromJson(json); @override final String name; @@ -138,7 +138,7 @@ class _$_NameValueModel bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$_NameValueModel && + other is _$NameValueModelImpl && (identical(other.name, name) || other.name == name) && const DeepCollectionEquality().equals(other.value, value)); } @@ -151,12 +151,13 @@ class _$_NameValueModel @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$_NameValueModelCopyWith<_$_NameValueModel> get copyWith => - __$$_NameValueModelCopyWithImpl<_$_NameValueModel>(this, _$identity); + _$$NameValueModelImplCopyWith<_$NameValueModelImpl> get copyWith => + __$$NameValueModelImplCopyWithImpl<_$NameValueModelImpl>( + this, _$identity); @override Map toJson() { - return _$$_NameValueModelToJson( + return _$$NameValueModelImplToJson( this, ); } @@ -165,10 +166,10 @@ class _$_NameValueModel abstract class _NameValueModel implements NameValueModel { const factory _NameValueModel( {required final String name, - required final dynamic value}) = _$_NameValueModel; + required final dynamic value}) = _$NameValueModelImpl; factory _NameValueModel.fromJson(Map json) = - _$_NameValueModel.fromJson; + _$NameValueModelImpl.fromJson; @override String get name; @@ -176,6 +177,6 @@ abstract class _NameValueModel implements NameValueModel { dynamic get value; @override @JsonKey(ignore: true) - _$$_NameValueModelCopyWith<_$_NameValueModel> get copyWith => + _$$NameValueModelImplCopyWith<_$NameValueModelImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/name_value_model.g.dart b/lib/models/name_value_model.g.dart index 25867df8..5fb91b66 100644 --- a/lib/models/name_value_model.g.dart +++ b/lib/models/name_value_model.g.dart @@ -6,13 +6,14 @@ part of 'name_value_model.dart'; // JsonSerializableGenerator // ************************************************************************** -_$_NameValueModel _$$_NameValueModelFromJson(Map json) => - _$_NameValueModel( +_$NameValueModelImpl _$$NameValueModelImplFromJson(Map json) => + _$NameValueModelImpl( name: json['name'] as String, value: json['value'], ); -Map _$$_NameValueModelToJson(_$_NameValueModel instance) => +Map _$$NameValueModelImplToJson( + _$NameValueModelImpl instance) => { 'name': instance.name, 'value': instance.value, diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index a928dac1..fda03ea1 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -1,6 +1,8 @@ -import 'package:flutter/foundation.dart'; import 'package:apidash/consts.dart'; -import 'package:apidash/utils/utils.dart' show mapToRows, rowsToMap; +import 'package:apidash/models/form_data_model.dart'; +import 'package:apidash/utils/convert_utils.dart'; +import 'package:flutter/foundation.dart'; + import 'name_value_model.dart'; import 'response_model.dart'; @@ -20,6 +22,7 @@ class RequestModel { this.responseStatus, this.message, this.responseModel, + this.formDataList, }); final String id; @@ -30,6 +33,7 @@ class RequestModel { final int requestTabIndex; final List? requestHeaders; final List? requestParams; + final List? formDataList; final ContentType requestBodyContentType; final String? requestBody; final int? responseStatus; @@ -52,6 +56,7 @@ class RequestModel { requestParams: requestParams != null ? [...requestParams!] : null, requestBodyContentType: requestBodyContentType, requestBody: requestBody, + formDataList: formDataList != null ? [...formDataList!] : null, ); } @@ -69,6 +74,7 @@ class RequestModel { int? responseStatus, String? message, ResponseModel? responseModel, + List? formDataList, }) { var headers = requestHeaders ?? this.requestHeaders; var params = requestParams ?? this.requestParams; @@ -87,6 +93,7 @@ class RequestModel { responseStatus: responseStatus ?? this.responseStatus, message: message ?? this.message, responseModel: responseModel ?? this.responseModel, + formDataList: formDataList ?? this.formDataList, ); } @@ -116,6 +123,8 @@ class RequestModel { final responseStatus = data["responseStatus"] as int?; final message = data["message"] as String?; final responseModelJson = data["responseModel"]; + final formDataList = data["formDataList"]; + if (responseModelJson != null) { responseModel = ResponseModel.fromJson(Map.from(responseModelJson)); @@ -141,6 +150,9 @@ class RequestModel { responseStatus: responseStatus, message: message, responseModel: responseModel, + formDataList: formDataList != null + ? mapToFormRows(Map.from(formDataList)) + : null, ); } 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 07c5bf53..07a3eee4 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,8 +1,12 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'dart:math'; + +import 'package:apidash/consts.dart'; +import 'package:apidash/models/name_value_model.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; -import 'package:apidash/consts.dart'; +import 'package:davi/davi.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; class EditRequestBody extends ConsumerStatefulWidget { const EditRequestBody({super.key}); @@ -12,12 +16,99 @@ class EditRequestBody extends ConsumerStatefulWidget { } class _EditRequestBodyState extends ConsumerState { + late List rows; + final random = Random.secure(); + late int seed; + @override + void initState() { + super.initState(); + seed = random.nextInt(kRandMax); + } + @override Widget build(BuildContext context) { final activeId = ref.watch(activeIdStateProvider); final requestModel = ref .read(collectionStateNotifierProvider.notifier) .getRequestModel(activeId!); + ContentType? requestBodyStateWatcher = (ref + .watch(collectionStateNotifierProvider)![activeId] + ?.requestBodyContentType) ?? + ContentType.values.first; + DaviModel model = DaviModel( + rows: rows, + columns: [ + DaviColumn( + name: 'Header Name', + grow: 1, + cellBuilder: (_, row) { + int idx = row.index; + return HeaderField( + keyId: "$activeId-$idx-headers-k-$seed", + initialValue: rows[idx].name, + hintText: "Add Header Name", + onChanged: (value) { + rows[idx] = rows[idx].copyWith(name: value); + // _onFieldChange(activeId); + }, + colorScheme: Theme.of(context).colorScheme, + ); + }, + sortable: false, + ), + DaviColumn( + width: 30, + cellBuilder: (_, row) { + return Text( + "=", + style: kCodeStyle, + ); + }, + ), + DaviColumn( + name: 'Header Value', + grow: 1, + cellBuilder: (_, row) { + int idx = row.index; + return CellField( + keyId: "$activeId-$idx-headers-v-$seed", + initialValue: rows[idx].value, + hintText: " Add Header Value", + onChanged: (value) { + rows[idx] = rows[idx].copyWith(value: value); + // _onFieldChange(activeId); + }, + colorScheme: Theme.of(context).colorScheme, + ); + }, + sortable: false, + ), + DaviColumn( + pinStatus: PinStatus.none, + width: 30, + cellBuilder: (_, row) { + return InkWell( + child: Theme.of(context).brightness == Brightness.dark + ? kIconRemoveDark + : kIconRemoveLight, + onTap: () { + seed = random.nextInt(kRandMax); + if (rows.length == 1) { + setState(() { + rows = [ + kNameValueEmptyModel, + ]; + }); + } else { + rows.removeAt(row.index); + } + // _onFieldChange(activeId); + }, + ); + }, + ), + ], + ); return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.background, @@ -38,16 +129,20 @@ class _EditRequestBodyState extends ConsumerState { ), ), Expanded( - child: TextFieldEditor( - key: Key("$activeId-body"), - fieldKey: "$activeId-body-editor", - initialValue: requestModel?.requestBody, - onChanged: (String value) { - ref - .read(collectionStateNotifierProvider.notifier) - .update(activeId, requestBody: value); - }, - ), + child: requestBodyStateWatcher == ContentType.formdata + ? Container( + child: const Text("Vidya"), + ) + : TextFieldEditor( + key: Key("$activeId-body"), + fieldKey: "$activeId-body-editor", + initialValue: requestModel?.requestBody, + onChanged: (String value) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(activeId, requestBody: value); + }, + ), ) ], ), diff --git a/lib/utils/convert_utils.dart b/lib/utils/convert_utils.dart index e12f2dcc..97d8cc4d 100644 --- a/lib/utils/convert_utils.dart +++ b/lib/utils/convert_utils.dart @@ -1,7 +1,10 @@ -import 'dart:typed_data'; import 'dart:convert'; -import '../models/models.dart'; +import 'dart:typed_data'; + +import 'package:apidash/models/form_data_model.dart'; + import '../consts.dart'; +import '../models/models.dart'; String humanizeDuration(Duration? duration) { if (duration == null) { @@ -78,6 +81,17 @@ Map? rowsToMap(List? kvRows, return finalMap; } +Map? rowsToFormDataMap( + List? kvRows, +) { + if (kvRows == null) { + return null; + } + Map finalMap = {}; + for (var row in kvRows) {} + return finalMap; +} + List? mapToRows(Map? kvMap) { if (kvMap == null) { return null; @@ -89,6 +103,21 @@ List? mapToRows(Map? kvMap) { return finalRows; } +List? mapToFormRows(Map? kvMap) { + if (kvMap == null) { + return null; + } + List finalRows = []; + for (var k in kvMap.keys) { + finalRows.add(FormDataModel( + name: k, + value: kvMap[k], + type: FormDataType.text, + )); + } + return finalRows; +} + Uint8List? stringToBytes(String? text) { if (text == null) { return null; diff --git a/lib/widgets/dropdowns.dart b/lib/widgets/dropdowns.dart index e8290714..29f18b0a 100644 --- a/lib/widgets/dropdowns.dart +++ b/lib/widgets/dropdowns.dart @@ -1,6 +1,6 @@ -import 'package:flutter/material.dart'; -import 'package:apidash/utils/utils.dart'; import 'package:apidash/consts.dart'; +import 'package:apidash/utils/utils.dart'; +import 'package:flutter/material.dart'; class DropdownButtonHttpMethod extends StatefulWidget { const DropdownButtonHttpMethod({ From ffb9681958dccd721886958b36c5e9596866acc2 Mon Sep 17 00:00:00 2001 From: vidya-hub Date: Tue, 19 Dec 2023 23:29:47 +0530 Subject: [PATCH 02/30] wip: added necessary function to pick the files --- lib/consts.dart | 5 + lib/models/form_data_model.dart | 2 +- lib/models/request_model.dart | 11 +- lib/providers/collection_providers.dart | 35 ++-- lib/screens/home_page/collection_pane.dart | 70 ++++---- .../request_pane/request_body.dart | 165 ++++++++++++++---- lib/services/hive_services.dart | 5 +- lib/utils/convert_utils.dart | 28 +-- lib/utils/extensions/file_extension.dart | 5 + lib/widgets/dropdowns.dart | 51 ++++++ lib/widgets/form_data_field.dart | 87 +++++++++ pubspec.yaml | 1 + 12 files changed, 368 insertions(+), 97 deletions(-) create mode 100644 lib/utils/extensions/file_extension.dart create mode 100644 lib/widgets/form_data_field.dart diff --git a/lib/consts.dart b/lib/consts.dart index ef7ed6f4..05d0e6e8 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -58,6 +58,7 @@ const kP1 = EdgeInsets.all(1); const kP5 = EdgeInsets.all(5); const kP8 = EdgeInsets.all(8); const kPs8 = EdgeInsets.only(left: 8); +const kPs2 = EdgeInsets.only(left: 2); const kPh20v5 = EdgeInsets.symmetric(horizontal: 20, vertical: 5); const kPh20v10 = EdgeInsets.symmetric(horizontal: 20, vertical: 10); const kP10 = EdgeInsets.all(10); @@ -303,6 +304,10 @@ const kFormDataTypeMap = { FormDataType.file: "File", FormDataType.text: "Text", }; +const kMapFormDataType = { + "File": FormDataType.file, + "Text": FormDataType.text, +}; enum ResponseBodyView { preview, code, raw, none } diff --git a/lib/models/form_data_model.dart b/lib/models/form_data_model.dart index edbcd113..90991419 100644 --- a/lib/models/form_data_model.dart +++ b/lib/models/form_data_model.dart @@ -16,7 +16,7 @@ class FormDataModel with _$FormDataModel { _$FormDataModelFromJson(json); } -const kNameValueEmptyModel = FormDataModel( +const kFormDataEmptyModel = FormDataModel( name: "", value: "", type: FormDataType.text, diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index fda03ea1..2e58015a 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -150,9 +150,8 @@ class RequestModel { responseStatus: responseStatus, message: message, responseModel: responseModel, - formDataList: formDataList != null - ? mapToFormRows(Map.from(formDataList)) - : null, + formDataList: + formDataList != null ? listToFormDataModel(formDataList) : null, ); } @@ -170,6 +169,7 @@ class RequestModel { "responseStatus": includeResponse ? responseStatus : null, "message": includeResponse ? message : null, "responseModel": includeResponse ? responseModel?.toJson() : null, + "formDataList": rowsToFormDataMap(formDataList) }; } @@ -189,6 +189,7 @@ class RequestModel { "Response Status: $responseStatus", "Response Message: $message", "Response: ${responseModel.toString()}" + "FormData: ${formDataList.toString()}" ].join("\n"); } @@ -208,7 +209,8 @@ class RequestModel { other.requestBody == requestBody && other.responseStatus == responseStatus && other.message == message && - other.responseModel == responseModel; + other.responseModel == responseModel && + other.formDataList == formDataList; } @override @@ -228,6 +230,7 @@ class RequestModel { responseStatus, message, responseModel, + formDataList, ); } } diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index e0273a34..cd905e79 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -1,10 +1,12 @@ +import 'package:apidash/models/form_data_model.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'settings_providers.dart'; -import 'ui_providers.dart'; + +import '../consts.dart'; import '../models/models.dart'; import '../services/services.dart' show hiveHandler, HiveHandler, request; import '../utils/utils.dart' show uuid, collectionToHAR; -import '../consts.dart'; +import 'settings_providers.dart'; +import 'ui_providers.dart'; final activeIdStateProvider = StateProvider((ref) => null); @@ -128,20 +130,23 @@ class CollectionStateNotifier int? responseStatus, String? message, ResponseModel? responseModel, + List? formDataList, }) { final newModel = state![id]!.copyWith( - method: method, - url: url, - name: name, - description: description, - requestTabIndex: requestTabIndex, - requestHeaders: requestHeaders, - requestParams: requestParams, - requestBodyContentType: requestBodyContentType, - requestBody: requestBody, - responseStatus: responseStatus, - message: message, - responseModel: responseModel); + method: method, + url: url, + name: name, + description: description, + requestTabIndex: requestTabIndex, + requestHeaders: requestHeaders, + requestParams: requestParams, + requestBodyContentType: requestBodyContentType, + requestBody: requestBody, + responseStatus: responseStatus, + message: message, + responseModel: responseModel, + formDataList: formDataList, + ); //print(newModel); var map = {...state!}; map[id] = newModel; diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index 0ea875fb..6f0b0bde 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -1,9 +1,9 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/consts.dart'; +import 'package:apidash/models/models.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; -import 'package:apidash/models/models.dart'; -import 'package:apidash/consts.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; class CollectionPane extends ConsumerWidget { const CollectionPane({ @@ -108,36 +108,40 @@ class _RequestListState extends ConsumerState { controller: controller, thumbVisibility: alwaysShowCollectionPaneScrollbar ? true : null, radius: const Radius.circular(12), - child: ReorderableListView.builder( - padding: kPr8CollectionPane, - scrollController: controller, - buildDefaultDragHandles: false, - itemCount: requestSequence.length, - onReorder: (int oldIndex, int newIndex) { - if (oldIndex < newIndex) { - newIndex -= 1; - } - if (oldIndex != newIndex) { - ref - .read(collectionStateNotifierProvider.notifier) - .reorder(oldIndex, newIndex); - } - }, - itemBuilder: (context, index) { - var id = requestSequence[index]; - return ReorderableDragStartListener( - key: ValueKey(id), - index: index, - child: Padding( - padding: kP1, - child: RequestItem( - id: id, - requestModel: requestItems[id]!, - ), + child: requestSequence.isNotEmpty + ? ReorderableListView.builder( + padding: kPr8CollectionPane, + scrollController: controller, + buildDefaultDragHandles: false, + itemCount: requestSequence.length, + onReorder: (int oldIndex, int newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + if (oldIndex != newIndex) { + ref + .read(collectionStateNotifierProvider.notifier) + .reorder(oldIndex, newIndex); + } + }, + itemBuilder: (context, index) { + var id = requestSequence[index]; + return ReorderableDragStartListener( + key: ValueKey(id), + index: index, + child: Padding( + padding: kP1, + child: RequestItem( + id: id, + requestModel: requestItems[id]!, + ), + ), + ); + }, + ) + : const Center( + child: Text("No Requests found"), ), - ); - }, - ), ); } } 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 07a3eee4..da1d15d7 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,10 +1,13 @@ import 'dart:math'; import 'package:apidash/consts.dart'; -import 'package:apidash/models/name_value_model.dart'; +import 'package:apidash/models/form_data_model.dart'; import 'package:apidash/providers/providers.dart'; +import 'package:apidash/utils/extensions/file_extension.dart'; +import 'package:apidash/widgets/form_data_field.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:davi/davi.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -16,13 +19,15 @@ class EditRequestBody extends ConsumerStatefulWidget { } class _EditRequestBodyState extends ConsumerState { - late List rows; + List rows = []; final random = Random.secure(); late int seed; + late FilePicker filePicker; @override void initState() { super.initState(); seed = random.nextInt(kRandMax); + filePicker = FilePicker.platform; } @override @@ -35,29 +40,41 @@ class _EditRequestBodyState extends ConsumerState { .watch(collectionStateNotifierProvider)![activeId] ?.requestBodyContentType) ?? ContentType.values.first; - DaviModel model = DaviModel( + DaviModel model = DaviModel( rows: rows, columns: [ DaviColumn( - name: 'Header Name', + name: 'Key', grow: 1, cellBuilder: (_, row) { int idx = row.index; - return HeaderField( - keyId: "$activeId-$idx-headers-k-$seed", - initialValue: rows[idx].name, - hintText: "Add Header Name", - onChanged: (value) { - rows[idx] = rows[idx].copyWith(name: value); - // _onFieldChange(activeId); - }, - colorScheme: Theme.of(context).colorScheme, + return SizedBox( + child: FormDataField( + keyId: "$activeId-$idx-form-v-$seed", + initialValue: rows[idx].value, + hintText: " Key", + onChanged: (value) { + rows[idx] = rows[idx].copyWith( + name: value, + ); + _onFieldChange(activeId); + }, + colorScheme: Theme.of(context).colorScheme, + formDataType: rows[idx].type, + onFormDataTypeChanged: (value) { + rows[idx] = rows[idx].copyWith( + type: value ?? FormDataType.text, + ); + rows[idx] = rows[idx].copyWith(value: ""); + _onFieldChange(activeId); + }, + ), ); }, sortable: false, ), DaviColumn( - width: 30, + width: 10, cellBuilder: (_, row) { return Text( "=", @@ -66,20 +83,66 @@ class _EditRequestBodyState extends ConsumerState { }, ), DaviColumn( - name: 'Header Value', - grow: 1, + name: 'Value', + grow: 4, cellBuilder: (_, row) { int idx = row.index; - return CellField( - keyId: "$activeId-$idx-headers-v-$seed", - initialValue: rows[idx].value, - hintText: " Add Header Value", - onChanged: (value) { - rows[idx] = rows[idx].copyWith(value: value); - // _onFieldChange(activeId); - }, - colorScheme: Theme.of(context).colorScheme, - ); + return rows[idx].type == FormDataType.file + ? Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: kPs2, + child: Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () async { + FilePickerResult? pickedResult = + await filePicker.pickFiles(); + if (pickedResult != null && + pickedResult.files.isNotEmpty) { + rows[idx] = rows[idx].copyWith( + value: pickedResult.files.first.path, + ); + _onFieldChange(activeId); + } + }, + child: Text( + "Select File", + style: kTextStyleButton.copyWith( + fontSize: 10, + ), + ), + ), + ), + Expanded( + child: Padding( + padding: kPs2, + child: Text( + rows[idx].type == FormDataType.file + ? (rows[idx].value != null + ? rows[idx].value.toString().fileName + : "") + : "", + style: kTextStyleButton, + overflow: TextOverflow.ellipsis, + ), + ), + ) + ], + ), + ), + ) + : CellField( + keyId: "$activeId-$idx-form-v-$seed", + initialValue: rows[idx].value, + hintText: " Value", + onChanged: (value) { + rows[idx] = rows[idx].copyWith(value: value); + _onFieldChange(activeId); + }, + colorScheme: Theme.of(context).colorScheme, + ); }, sortable: false, ), @@ -96,13 +159,13 @@ class _EditRequestBodyState extends ConsumerState { if (rows.length == 1) { setState(() { rows = [ - kNameValueEmptyModel, + kFormDataEmptyModel, ]; }); } else { rows.removeAt(row.index); } - // _onFieldChange(activeId); + _onFieldChange(activeId); }, ); }, @@ -130,8 +193,43 @@ class _EditRequestBodyState extends ConsumerState { ), Expanded( child: requestBodyStateWatcher == ContentType.formdata - ? Container( - child: const Text("Vidya"), + ? Stack( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: kBorderRadius12, + ), + margin: kP10, + child: Column( + children: [ + Expanded( + child: DaviTheme( + data: kTableThemeData, + child: Davi(model), + ), + ), + ], + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.only(bottom: 30), + child: ElevatedButton.icon( + onPressed: () { + rows.add(kFormDataEmptyModel); + _onFieldChange(activeId); + }, + icon: const Icon(Icons.add), + label: const Text( + "Add Form Data", + style: kTextStyleButton, + ), + ), + ), + ), + ], ) : TextFieldEditor( key: Key("$activeId-body"), @@ -148,6 +246,13 @@ class _EditRequestBodyState extends ConsumerState { ), ); } + + void _onFieldChange(String activeId) { + ref.read(collectionStateNotifierProvider.notifier).update( + activeId, + formDataList: rows, + ); + } } class DropdownButtonBodyContentType extends ConsumerStatefulWidget { diff --git a/lib/services/hive_services.dart b/lib/services/hive_services.dart index 3052024e..9d7b108c 100644 --- a/lib/services/hive_services.dart +++ b/lib/services/hive_services.dart @@ -48,8 +48,9 @@ class HiveHandler { dynamic getRequestModel(String id) => dataBox.get(id); Future setRequestModel( - String id, Map? requestModelJson) => - dataBox.put(id, requestModelJson); + String id, Map? requestModelJson) { + return dataBox.put(id, requestModelJson); + } Future clear() => dataBox.clear(); diff --git a/lib/utils/convert_utils.dart b/lib/utils/convert_utils.dart index 97d8cc4d..81c5bff8 100644 --- a/lib/utils/convert_utils.dart +++ b/lib/utils/convert_utils.dart @@ -81,14 +81,19 @@ Map? rowsToMap(List? kvRows, return finalMap; } -Map? rowsToFormDataMap( +List>? rowsToFormDataMap( List? kvRows, ) { if (kvRows == null) { return null; } - Map finalMap = {}; - for (var row in kvRows) {} + List> finalMap = kvRows + .map((FormDataModel formData) => { + "name": formData.name, + "value": formData.value, + "type": formData.type.name, + }) + .toList(); return finalMap; } @@ -103,18 +108,17 @@ List? mapToRows(Map? kvMap) { return finalRows; } -List? mapToFormRows(Map? kvMap) { +List? listToFormDataModel(List? kvMap) { if (kvMap == null) { return null; } - List finalRows = []; - for (var k in kvMap.keys) { - finalRows.add(FormDataModel( - name: k, - value: kvMap[k], - type: FormDataType.text, - )); - } + List finalRows = kvMap + .map((formData) => FormDataModel( + name: formData["name"], + value: formData["value"], + type: kMapFormDataType[formData["type"]] ?? FormDataType.text, + )) + .toList(); return finalRows; } diff --git a/lib/utils/extensions/file_extension.dart b/lib/utils/extensions/file_extension.dart new file mode 100644 index 00000000..797a2ec9 --- /dev/null +++ b/lib/utils/extensions/file_extension.dart @@ -0,0 +1,5 @@ +extension FileExtension on String { + String get fileName { + return split(r'\').last; + } +} diff --git a/lib/widgets/dropdowns.dart b/lib/widgets/dropdowns.dart index 29f18b0a..98bd10d4 100644 --- a/lib/widgets/dropdowns.dart +++ b/lib/widgets/dropdowns.dart @@ -110,6 +110,57 @@ class _DropdownButtonContentTypeState extends State { } } +class DropdownButtonFormData extends StatefulWidget { + const DropdownButtonFormData({ + super.key, + this.formDataType, + this.onChanged, + }); + + final FormDataType? formDataType; + final void Function(FormDataType?)? onChanged; + + @override + State createState() => _DropdownButtonFormData(); +} + +class _DropdownButtonFormData extends State { + @override + Widget build(BuildContext context) { + final surfaceColor = Theme.of(context).colorScheme.surface; + return DropdownButton( + focusColor: surfaceColor, + value: widget.formDataType, + icon: const Icon( + Icons.unfold_more_rounded, + size: 16, + ), + elevation: 4, + style: kCodeStyle.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + underline: Container( + height: 0, + ), + onChanged: widget.onChanged, + borderRadius: kBorderRadius12, + items: FormDataType.values + .map>((FormDataType value) { + return DropdownMenuItem( + value: value, + child: Padding( + padding: kPs8, + child: Text( + value.name, + style: kTextStyleButton, + ), + ), + ); + }).toList(), + ); + } +} + class DropdownButtonCodegenLanguage extends StatefulWidget { const DropdownButtonCodegenLanguage({ super.key, diff --git a/lib/widgets/form_data_field.dart b/lib/widgets/form_data_field.dart new file mode 100644 index 00000000..d00dd70b --- /dev/null +++ b/lib/widgets/form_data_field.dart @@ -0,0 +1,87 @@ +import 'package:apidash/consts.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'package:flutter/material.dart'; + +class FormDataField extends StatefulWidget { + const FormDataField({ + super.key, + required this.keyId, + this.initialValue, + this.hintText, + this.onChanged, + this.colorScheme, + this.formDataType, + this.onFormDataTypeChanged, + }); + + final String keyId; + final String? initialValue; + final String? hintText; + final void Function(String)? onChanged; + final ColorScheme? colorScheme; + final FormDataType? formDataType; + final void Function(FormDataType?)? onFormDataTypeChanged; + + @override + State createState() => _FormDataFieldState(); +} + +class _FormDataFieldState extends State { + TextEditingController valueController = TextEditingController(); + @override + void initState() { + valueController.text = widget.initialValue ?? ""; + super.initState(); + } + + @override + Widget build(BuildContext context) { + var colorScheme = widget.colorScheme ?? Theme.of(context).colorScheme; + return Row( + children: [ + Expanded( + flex: 1, + child: TextFormField( + controller: valueController, + key: Key(widget.keyId), + style: kCodeStyle.copyWith( + color: colorScheme.onSurface, + ), + decoration: InputDecoration( + hintStyle: kCodeStyle.copyWith( + color: colorScheme.outline.withOpacity( + kHintOpacity, + ), + ), + hintText: widget.hintText, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: colorScheme.primary.withOpacity( + kHintOpacity, + ), + ), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: colorScheme.surfaceVariant, + ), + ), + ), + onChanged: widget.onChanged, + ), + ), + Expanded( + child: DropdownButtonFormData( + formDataType: widget.formDataType, + onChanged: (p0) { + if (widget.onFormDataTypeChanged != null) { + widget.onFormDataTypeChanged!(p0); + valueController.clear(); + } + }, + ), + ) + ], + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 96ab5fbf..0d696d1d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,7 @@ dependencies: url: https://github.com/foss42/json_data_explorer.git version: ^0.1.1 scrollable_positioned_list: ^0.2.3 + file_picker: ^6.1.1 dev_dependencies: flutter_test: From c04507f93a76bfaed6603b247597f726e83ed306 Mon Sep 17 00:00:00 2001 From: vidya-hub Date: Wed, 20 Dec 2023 21:51:27 +0530 Subject: [PATCH 03/30] feat: Multi Part Request Feature Added --- lib/consts.dart | 8 +- lib/providers/collection_providers.dart | 19 +- .../request_pane/request_body.dart | 190 +--------------- lib/services/http_service.dart | 64 +++++- lib/utils/convert_utils.dart | 18 ++ lib/widgets/form_data_widget.dart | 212 ++++++++++++++++++ 6 files changed, 316 insertions(+), 195 deletions(-) create mode 100644 lib/widgets/form_data_widget.dart diff --git a/lib/consts.dart b/lib/consts.dart index 05d0e6e8..cba29ab6 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -301,12 +301,12 @@ const kContentTypeMap = { ContentType.formdata: "multipart/form-data", }; const kFormDataTypeMap = { - FormDataType.file: "File", - FormDataType.text: "Text", + FormDataType.file: "file", + FormDataType.text: "text", }; const kMapFormDataType = { - "File": FormDataType.file, - "Text": FormDataType.text, + "file": FormDataType.file, + "text": FormDataType.text, }; enum ResponseBodyView { preview, code, raw, none } diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index cd905e79..dc059913 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -1,5 +1,7 @@ import 'package:apidash/models/form_data_model.dart'; +import 'package:apidash/services/http_service.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; import '../consts.dart'; import '../models/models.dart'; @@ -158,10 +160,20 @@ class CollectionStateNotifier ref.read(codePaneVisibleStateProvider.notifier).state = false; final defaultUriScheme = ref.read(settingsProvider.select((value) => value.defaultUriScheme)); - + (http.Response?, Duration?, String?)? responseRec; RequestModel requestModel = state![id]!; - var responseRec = - await request(requestModel, defaultUriScheme: defaultUriScheme); + if (requestModel.formDataList != null && + requestModel.formDataList!.isNotEmpty) { + responseRec = await multiPartRequest( + requestModel, + defaultUriScheme: defaultUriScheme, + ); + } else { + responseRec = await request( + requestModel, + defaultUriScheme: defaultUriScheme, + ); + } late final RequestModel newRequestModel; if (responseRec.$1 == null) { newRequestModel = requestModel.copyWith( @@ -180,7 +192,6 @@ class CollectionStateNotifier responseModel: responseModel, ); } - //print(newRequestModel); ref.read(sentRequestIdStateProvider.notifier).state = null; var map = {...state!}; map[id] = newRequestModel; 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 da1d15d7..21dd63c6 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,13 +1,9 @@ import 'dart:math'; import 'package:apidash/consts.dart'; -import 'package:apidash/models/form_data_model.dart'; import 'package:apidash/providers/providers.dart'; -import 'package:apidash/utils/extensions/file_extension.dart'; -import 'package:apidash/widgets/form_data_field.dart'; +import 'package:apidash/widgets/form_data_widget.dart'; import 'package:apidash/widgets/widgets.dart'; -import 'package:davi/davi.dart'; -import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -19,15 +15,12 @@ class EditRequestBody extends ConsumerStatefulWidget { } class _EditRequestBodyState extends ConsumerState { - List rows = []; final random = Random.secure(); late int seed; - late FilePicker filePicker; @override void initState() { super.initState(); seed = random.nextInt(kRandMax); - filePicker = FilePicker.platform; } @override @@ -40,138 +33,6 @@ class _EditRequestBodyState extends ConsumerState { .watch(collectionStateNotifierProvider)![activeId] ?.requestBodyContentType) ?? ContentType.values.first; - DaviModel model = DaviModel( - rows: rows, - columns: [ - DaviColumn( - name: 'Key', - grow: 1, - cellBuilder: (_, row) { - int idx = row.index; - return SizedBox( - child: FormDataField( - keyId: "$activeId-$idx-form-v-$seed", - initialValue: rows[idx].value, - hintText: " Key", - onChanged: (value) { - rows[idx] = rows[idx].copyWith( - name: value, - ); - _onFieldChange(activeId); - }, - colorScheme: Theme.of(context).colorScheme, - formDataType: rows[idx].type, - onFormDataTypeChanged: (value) { - rows[idx] = rows[idx].copyWith( - type: value ?? FormDataType.text, - ); - rows[idx] = rows[idx].copyWith(value: ""); - _onFieldChange(activeId); - }, - ), - ); - }, - sortable: false, - ), - DaviColumn( - width: 10, - cellBuilder: (_, row) { - return Text( - "=", - style: kCodeStyle, - ); - }, - ), - DaviColumn( - name: 'Value', - grow: 4, - cellBuilder: (_, row) { - int idx = row.index; - return rows[idx].type == FormDataType.file - ? Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: kPs2, - child: Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: () async { - FilePickerResult? pickedResult = - await filePicker.pickFiles(); - if (pickedResult != null && - pickedResult.files.isNotEmpty) { - rows[idx] = rows[idx].copyWith( - value: pickedResult.files.first.path, - ); - _onFieldChange(activeId); - } - }, - child: Text( - "Select File", - style: kTextStyleButton.copyWith( - fontSize: 10, - ), - ), - ), - ), - Expanded( - child: Padding( - padding: kPs2, - child: Text( - rows[idx].type == FormDataType.file - ? (rows[idx].value != null - ? rows[idx].value.toString().fileName - : "") - : "", - style: kTextStyleButton, - overflow: TextOverflow.ellipsis, - ), - ), - ) - ], - ), - ), - ) - : CellField( - keyId: "$activeId-$idx-form-v-$seed", - initialValue: rows[idx].value, - hintText: " Value", - onChanged: (value) { - rows[idx] = rows[idx].copyWith(value: value); - _onFieldChange(activeId); - }, - colorScheme: Theme.of(context).colorScheme, - ); - }, - sortable: false, - ), - DaviColumn( - pinStatus: PinStatus.none, - width: 30, - cellBuilder: (_, row) { - return InkWell( - child: Theme.of(context).brightness == Brightness.dark - ? kIconRemoveDark - : kIconRemoveLight, - onTap: () { - seed = random.nextInt(kRandMax); - if (rows.length == 1) { - setState(() { - rows = [ - kFormDataEmptyModel, - ]; - }); - } else { - rows.removeAt(row.index); - } - _onFieldChange(activeId); - }, - ); - }, - ), - ], - ); return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.background, @@ -193,43 +54,11 @@ class _EditRequestBodyState extends ConsumerState { ), Expanded( child: requestBodyStateWatcher == ContentType.formdata - ? Stack( - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: kBorderRadius12, - ), - margin: kP10, - child: Column( - children: [ - Expanded( - child: DaviTheme( - data: kTableThemeData, - child: Davi(model), - ), - ), - ], - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: const EdgeInsets.only(bottom: 30), - child: ElevatedButton.icon( - onPressed: () { - rows.add(kFormDataEmptyModel); - _onFieldChange(activeId); - }, - icon: const Icon(Icons.add), - label: const Text( - "Add Form Data", - style: kTextStyleButton, - ), - ), - ), - ), - ], + ? FormDataWidget( + seed: seed, + onFormDataRemove: () { + seed = random.nextInt(kRandMax); + }, ) : TextFieldEditor( key: Key("$activeId-body"), @@ -246,13 +75,6 @@ class _EditRequestBodyState extends ConsumerState { ), ); } - - void _onFieldChange(String activeId) { - ref.read(collectionStateNotifierProvider.notifier).update( - activeId, - formDataList: rows, - ); - } } class DropdownButtonBodyContentType extends ConsumerStatefulWidget { diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart index 8265a299..d49656a0 100644 --- a/lib/services/http_service.dart +++ b/lib/services/http_service.dart @@ -1,10 +1,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:http/http.dart' as http; -import 'package:apidash/utils/utils.dart'; -import 'package:apidash/models/models.dart'; + import 'package:apidash/consts.dart'; +import 'package:apidash/models/form_data_model.dart'; +import 'package:apidash/models/models.dart'; +import 'package:apidash/utils/utils.dart'; +import 'package:http/http.dart' as http; Future<(http.Response?, Duration?, String?)> request( RequestModel requestModel, { @@ -63,3 +65,59 @@ Future<(http.Response?, Duration?, String?)> request( return (null, null, uriRec.$2); } } + +Future<(http.Response?, Duration?, String?)> multiPartRequest( + RequestModel requestModel, { + String defaultUriScheme = kDefaultUriScheme, +}) async { + (Uri?, String?) uriRec = getValidRequestUri( + requestModel.url, + requestModel.requestParams, + defaultUriScheme: defaultUriScheme, + ); + if (uriRec.$1 != null) { + Uri requestUrl = uriRec.$1!; + Map headers = requestModel.headersMap; + try { + var requestBody = requestModel.requestBody; + if (kMethodsWithBody.contains(requestModel.method) && + requestBody != null) { + var contentLength = utf8.encode(requestBody).length; + if (contentLength > 0) { + headers[HttpHeaders.contentLengthHeader] = contentLength.toString(); + headers[HttpHeaders.contentTypeHeader] = + kContentTypeMap[requestModel.requestBodyContentType] ?? ""; + } + } + Stopwatch stopwatch = Stopwatch()..start(); + + var request = http.MultipartRequest( + requestModel.method.name.toUpperCase(), + requestUrl, + ); + for (FormDataModel formData in (requestModel.formDataList ?? [])) { + if (formData.type == FormDataType.text) { + request.fields.addAll({formData.name: formData.value}); + } else { + request.files.add( + await http.MultipartFile.fromPath( + formData.name, + formData.value, + ), + ); + } + } + + http.StreamedResponse response = await request.send(); + + stopwatch.stop(); + http.Response convertedHttpResponse = + await convertStreamedResponse(response); + return (convertedHttpResponse, stopwatch.elapsed, null); + } catch (e) { + return (null, null, e.toString()); + } + } else { + return (null, null, uriRec.$2); + } +} diff --git a/lib/utils/convert_utils.dart b/lib/utils/convert_utils.dart index 81c5bff8..63b1f3eb 100644 --- a/lib/utils/convert_utils.dart +++ b/lib/utils/convert_utils.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:apidash/models/form_data_model.dart'; +import 'package:http/http.dart' as http; import '../consts.dart'; import '../models/models.dart'; @@ -142,3 +143,20 @@ Uint8List jsonMapToBytes(Map? map) { return bytes; } } + +Future convertStreamedResponse( + http.StreamedResponse streamedResponse, +) async { + Uint8List bodyBytes = await streamedResponse.stream.toBytes(); + + http.Response response = http.Response.bytes( + bodyBytes, + streamedResponse.statusCode, + headers: streamedResponse.headers, + persistentConnection: streamedResponse.persistentConnection, + reasonPhrase: streamedResponse.reasonPhrase, + request: streamedResponse.request, + ); + + return response; +} diff --git a/lib/widgets/form_data_widget.dart b/lib/widgets/form_data_widget.dart new file mode 100644 index 00000000..85318d70 --- /dev/null +++ b/lib/widgets/form_data_widget.dart @@ -0,0 +1,212 @@ +import 'package:apidash/consts.dart'; +import 'package:apidash/models/form_data_model.dart'; +import 'package:apidash/providers/collection_providers.dart'; +import 'package:apidash/widgets/form_data_field.dart'; +import 'package:apidash/widgets/textfields.dart'; +import 'package:davi/davi.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class FormDataWidget extends ConsumerStatefulWidget { + const FormDataWidget({ + super.key, + required this.seed, + required this.onFormDataRemove, + }); + final int seed; + final Function onFormDataRemove; + @override + ConsumerState createState() => _FormDataBodyState(); +} + +class _FormDataBodyState extends ConsumerState { + @override + Widget build(BuildContext context) { + final activeId = ref.watch(activeIdStateProvider); + final requestModel = ref + .read(collectionStateNotifierProvider.notifier) + .getRequestModel(activeId!); + List rows = requestModel?.formDataList ?? []; + DaviModel model = DaviModel( + rows: rows, + columns: [ + DaviColumn( + name: 'Key', + grow: 1, + cellBuilder: (_, row) { + int idx = row.index; + return SizedBox( + child: FormDataField( + keyId: "$activeId-$idx-form-v-${widget.seed}", + initialValue: rows[idx].name, + hintText: " Key", + onChanged: (value) { + rows[idx] = rows[idx].copyWith( + name: value, + ); + _onFieldChange(activeId); + }, + colorScheme: Theme.of(context).colorScheme, + formDataType: rows[idx].type, + onFormDataTypeChanged: (value) { + rows[idx] = rows[idx].copyWith( + type: value ?? FormDataType.text, + ); + rows[idx] = rows[idx].copyWith(value: ""); + _onFieldChange(activeId); + }, + ), + ); + }, + sortable: false, + ), + DaviColumn( + width: 10, + cellBuilder: (_, row) { + return Text( + "=", + style: kCodeStyle, + ); + }, + ), + DaviColumn( + name: 'Value', + grow: 4, + cellBuilder: (_, row) { + int idx = row.index; + return rows[idx].type == FormDataType.file + ? Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: kPs8, + child: Row( + children: [ + Expanded( + child: ElevatedButtonTheme( + data: const ElevatedButtonThemeData(), + child: ElevatedButton.icon( + onPressed: () async { + FilePickerResult? pickedResult = + await FilePicker.platform.pickFiles(); + if (pickedResult != null && + pickedResult.files.isNotEmpty) { + rows[idx] = rows[idx].copyWith( + value: pickedResult.files.first.path, + ); + _onFieldChange(activeId); + } + }, + icon: const Icon( + Icons.snippet_folder_rounded, + size: 18, + ), + label: Text( + rows[idx].type == FormDataType.file + ? (rows[idx].value != null + ? rows[idx].value.toString() + : "Select File") + : "Select File", + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: kTextStyleButton.copyWith( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ), + ) + : CellField( + keyId: "$activeId-$idx-form-v-${widget.seed}", + initialValue: rows[idx].value, + hintText: " Value", + onChanged: (value) { + rows[idx] = rows[idx].copyWith(value: value); + _onFieldChange(activeId); + }, + colorScheme: Theme.of(context).colorScheme, + ); + }, + sortable: false, + ), + DaviColumn( + pinStatus: PinStatus.none, + width: 30, + cellBuilder: (_, row) { + return InkWell( + child: Theme.of(context).brightness == Brightness.dark + ? kIconRemoveDark + : kIconRemoveLight, + onTap: () { + widget.onFormDataRemove(); + if (rows.length == 1) { + setState(() { + rows = [ + kFormDataEmptyModel, + ]; + }); + } else { + rows.removeAt(row.index); + } + _onFieldChange(activeId); + }, + ); + }, + ), + ], + ); + return Stack( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: kBorderRadius12, + ), + margin: kP10, + child: Column( + children: [ + Expanded( + child: DaviTheme( + data: kTableThemeData, + child: Davi(model), + ), + ), + ], + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.only(bottom: 30), + child: ElevatedButton.icon( + onPressed: () { + rows.add(kFormDataEmptyModel); + _onFieldChange(activeId); + }, + icon: const Icon(Icons.add), + label: const Text( + "Add Form Data", + style: kTextStyleButton, + ), + ), + ), + ), + ], + ); + } + + void _onFieldChange(String activeId) { + List formDataList = + ref.read(collectionStateNotifierProvider)?[activeId]?.formDataList ?? + []; + ref.read(collectionStateNotifierProvider.notifier).update( + activeId, + formDataList: formDataList, + ); + } +} From a9bbe208d461abc0bc75be832d82fd240cabede4 Mon Sep 17 00:00:00 2001 From: vidya-hub Date: Wed, 20 Dec 2023 22:10:14 +0530 Subject: [PATCH 04/30] chore: added imports for multi part request --- lib/providers/collection_providers.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index dc059913..d5acd609 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -1,11 +1,11 @@ import 'package:apidash/models/form_data_model.dart'; -import 'package:apidash/services/http_service.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import '../consts.dart'; import '../models/models.dart'; -import '../services/services.dart' show hiveHandler, HiveHandler, request; +import '../services/services.dart' + show hiveHandler, HiveHandler, request, multiPartRequest; import '../utils/utils.dart' show uuid, collectionToHAR; import 'settings_providers.dart'; import 'ui_providers.dart'; From 906cf8384709d36e6d443785a2d0d2718c092331 Mon Sep 17 00:00:00 2001 From: vidya-hub Date: Wed, 20 Dec 2023 22:28:03 +0530 Subject: [PATCH 05/30] chore: reverted name value model files --- lib/models/name_value_model.dart | 3 +- lib/models/name_value_model.freezed.dart | 43 ++++++++++++------------ lib/models/name_value_model.g.dart | 7 ++-- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/models/name_value_model.dart b/lib/models/name_value_model.dart index 886fbbe8..f99b7a60 100644 --- a/lib/models/name_value_model.dart +++ b/lib/models/name_value_model.dart @@ -1,8 +1,7 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; part 'name_value_model.freezed.dart'; - part 'name_value_model.g.dart'; @freezed diff --git a/lib/models/name_value_model.freezed.dart b/lib/models/name_value_model.freezed.dart index b224f596..e7d9e8e0 100644 --- a/lib/models/name_value_model.freezed.dart +++ b/lib/models/name_value_model.freezed.dart @@ -68,22 +68,22 @@ class _$NameValueModelCopyWithImpl<$Res, $Val extends NameValueModel> } /// @nodoc -abstract class _$$NameValueModelImplCopyWith<$Res> +abstract class _$$_NameValueModelCopyWith<$Res> implements $NameValueModelCopyWith<$Res> { - factory _$$NameValueModelImplCopyWith(_$NameValueModelImpl value, - $Res Function(_$NameValueModelImpl) then) = - __$$NameValueModelImplCopyWithImpl<$Res>; + factory _$$_NameValueModelCopyWith( + _$_NameValueModel value, $Res Function(_$_NameValueModel) then) = + __$$_NameValueModelCopyWithImpl<$Res>; @override @useResult $Res call({String name, dynamic value}); } /// @nodoc -class __$$NameValueModelImplCopyWithImpl<$Res> - extends _$NameValueModelCopyWithImpl<$Res, _$NameValueModelImpl> - implements _$$NameValueModelImplCopyWith<$Res> { - __$$NameValueModelImplCopyWithImpl( - _$NameValueModelImpl _value, $Res Function(_$NameValueModelImpl) _then) +class __$$_NameValueModelCopyWithImpl<$Res> + extends _$NameValueModelCopyWithImpl<$Res, _$_NameValueModel> + implements _$$_NameValueModelCopyWith<$Res> { + __$$_NameValueModelCopyWithImpl( + _$_NameValueModel _value, $Res Function(_$_NameValueModel) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -92,7 +92,7 @@ class __$$NameValueModelImplCopyWithImpl<$Res> Object? name = null, Object? value = freezed, }) { - return _then(_$NameValueModelImpl( + return _then(_$_NameValueModel( name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -107,13 +107,13 @@ class __$$NameValueModelImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$NameValueModelImpl +class _$_NameValueModel with DiagnosticableTreeMixin implements _NameValueModel { - const _$NameValueModelImpl({required this.name, required this.value}); + const _$_NameValueModel({required this.name, required this.value}); - factory _$NameValueModelImpl.fromJson(Map json) => - _$$NameValueModelImplFromJson(json); + factory _$_NameValueModel.fromJson(Map json) => + _$$_NameValueModelFromJson(json); @override final String name; @@ -138,7 +138,7 @@ class _$NameValueModelImpl bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$NameValueModelImpl && + other is _$_NameValueModel && (identical(other.name, name) || other.name == name) && const DeepCollectionEquality().equals(other.value, value)); } @@ -151,13 +151,12 @@ class _$NameValueModelImpl @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$NameValueModelImplCopyWith<_$NameValueModelImpl> get copyWith => - __$$NameValueModelImplCopyWithImpl<_$NameValueModelImpl>( - this, _$identity); + _$$_NameValueModelCopyWith<_$_NameValueModel> get copyWith => + __$$_NameValueModelCopyWithImpl<_$_NameValueModel>(this, _$identity); @override Map toJson() { - return _$$NameValueModelImplToJson( + return _$$_NameValueModelToJson( this, ); } @@ -166,10 +165,10 @@ class _$NameValueModelImpl abstract class _NameValueModel implements NameValueModel { const factory _NameValueModel( {required final String name, - required final dynamic value}) = _$NameValueModelImpl; + required final dynamic value}) = _$_NameValueModel; factory _NameValueModel.fromJson(Map json) = - _$NameValueModelImpl.fromJson; + _$_NameValueModel.fromJson; @override String get name; @@ -177,6 +176,6 @@ abstract class _NameValueModel implements NameValueModel { dynamic get value; @override @JsonKey(ignore: true) - _$$NameValueModelImplCopyWith<_$NameValueModelImpl> get copyWith => + _$$_NameValueModelCopyWith<_$_NameValueModel> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/name_value_model.g.dart b/lib/models/name_value_model.g.dart index 5fb91b66..25867df8 100644 --- a/lib/models/name_value_model.g.dart +++ b/lib/models/name_value_model.g.dart @@ -6,14 +6,13 @@ part of 'name_value_model.dart'; // JsonSerializableGenerator // ************************************************************************** -_$NameValueModelImpl _$$NameValueModelImplFromJson(Map json) => - _$NameValueModelImpl( +_$_NameValueModel _$$_NameValueModelFromJson(Map json) => + _$_NameValueModel( name: json['name'] as String, value: json['value'], ); -Map _$$NameValueModelImplToJson( - _$NameValueModelImpl instance) => +Map _$$_NameValueModelToJson(_$_NameValueModel instance) => { 'name': instance.name, 'value': instance.value, From 1444317d914fd412609483b13650889f7fcbdea6 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:47:53 +0530 Subject: [PATCH 06/30] fix: PR fixes with few enhancements --- lib/consts.dart | 19 ++-- lib/models/form_data_model.dart | 2 +- lib/models/form_data_model.freezed.dart | 29 +++-- lib/models/form_data_model.g.dart | 2 +- lib/models/models.dart | 1 + .../request_pane/request_body.dart | 12 +- lib/utils/convert_utils.dart | 29 +++-- lib/widgets/dropdowns.dart | 1 + lib/widgets/form_data_field.dart | 52 ++++----- lib/widgets/form_data_widget.dart | 104 ++++++++++-------- 10 files changed, 122 insertions(+), 129 deletions(-) diff --git a/lib/consts.dart b/lib/consts.dart index d5201956..967a20b7 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -1,10 +1,9 @@ -import 'dart:convert'; import 'dart:io'; - -import 'package:davi/davi.dart'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:davi/davi.dart'; const kDiscordUrl = "https://bit.ly/heyfoss"; const kGitUrl = "https://github.com/foss42/apidash"; @@ -49,6 +48,10 @@ const kHintOpacity = 0.6; const kForegroundOpacity = 0.05; const kTextStyleButton = TextStyle(fontWeight: FontWeight.bold); +const kFormDataButton = TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, +); const kBorderRadius8 = BorderRadius.all(Radius.circular(8)); final kBorderRadius10 = BorderRadius.circular(10); @@ -74,7 +77,7 @@ const kPh60 = EdgeInsets.symmetric(horizontal: 60); const kP24CollectionPane = EdgeInsets.only(top: 24, left: 8.0, bottom: 8.0); const kP8CollectionPane = EdgeInsets.only(top: 8.0, left: 8.0, bottom: 8.0); const kPr8CollectionPane = EdgeInsets.only(right: 8.0); - +const kpsV5 = EdgeInsets.symmetric(vertical: 2); const kHSpacer4 = SizedBox(width: 4); const kHSpacer5 = SizedBox(width: 5); const kHSpacer10 = SizedBox(width: 10); @@ -300,14 +303,6 @@ const kContentTypeMap = { ContentType.text: "$kTypeText/$kSubTypePlain", ContentType.formdata: "multipart/form-data", }; -const kFormDataTypeMap = { - FormDataType.file: "file", - FormDataType.text: "text", -}; -const kMapFormDataType = { - "file": FormDataType.file, - "text": FormDataType.text, -}; enum ResponseBodyView { preview, code, raw, none } diff --git a/lib/models/form_data_model.dart b/lib/models/form_data_model.dart index 90991419..add20833 100644 --- a/lib/models/form_data_model.dart +++ b/lib/models/form_data_model.dart @@ -8,7 +8,7 @@ part 'form_data_model.g.dart'; class FormDataModel with _$FormDataModel { const factory FormDataModel({ required String name, - required dynamic value, + required String value, required FormDataType type, }) = _FormDataModel; diff --git a/lib/models/form_data_model.freezed.dart b/lib/models/form_data_model.freezed.dart index e1babd78..0958bd4e 100644 --- a/lib/models/form_data_model.freezed.dart +++ b/lib/models/form_data_model.freezed.dart @@ -21,7 +21,7 @@ FormDataModel _$FormDataModelFromJson(Map json) { /// @nodoc mixin _$FormDataModel { String get name => throw _privateConstructorUsedError; - dynamic get value => throw _privateConstructorUsedError; + String get value => throw _privateConstructorUsedError; FormDataType get type => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @@ -36,7 +36,7 @@ abstract class $FormDataModelCopyWith<$Res> { FormDataModel value, $Res Function(FormDataModel) then) = _$FormDataModelCopyWithImpl<$Res, FormDataModel>; @useResult - $Res call({String name, dynamic value, FormDataType type}); + $Res call({String name, String value, FormDataType type}); } /// @nodoc @@ -53,7 +53,7 @@ class _$FormDataModelCopyWithImpl<$Res, $Val extends FormDataModel> @override $Res call({ Object? name = null, - Object? value = freezed, + Object? value = null, Object? type = null, }) { return _then(_value.copyWith( @@ -61,10 +61,10 @@ class _$FormDataModelCopyWithImpl<$Res, $Val extends FormDataModel> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, - value: freezed == value + value: null == value ? _value.value : value // ignore: cast_nullable_to_non_nullable - as dynamic, + as String, type: null == type ? _value.type : type // ignore: cast_nullable_to_non_nullable @@ -81,7 +81,7 @@ abstract class _$$FormDataModelImplCopyWith<$Res> __$$FormDataModelImplCopyWithImpl<$Res>; @override @useResult - $Res call({String name, dynamic value, FormDataType type}); + $Res call({String name, String value, FormDataType type}); } /// @nodoc @@ -96,7 +96,7 @@ class __$$FormDataModelImplCopyWithImpl<$Res> @override $Res call({ Object? name = null, - Object? value = freezed, + Object? value = null, Object? type = null, }) { return _then(_$FormDataModelImpl( @@ -104,10 +104,10 @@ class __$$FormDataModelImplCopyWithImpl<$Res> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, - value: freezed == value + value: null == value ? _value.value : value // ignore: cast_nullable_to_non_nullable - as dynamic, + as String, type: null == type ? _value.type : type // ignore: cast_nullable_to_non_nullable @@ -128,7 +128,7 @@ class _$FormDataModelImpl implements _FormDataModel { @override final String name; @override - final dynamic value; + final String value; @override final FormDataType type; @@ -143,14 +143,13 @@ class _$FormDataModelImpl implements _FormDataModel { (other.runtimeType == runtimeType && other is _$FormDataModelImpl && (identical(other.name, name) || other.name == name) && - const DeepCollectionEquality().equals(other.value, value) && + (identical(other.value, value) || other.value == value) && (identical(other.type, type) || other.type == type)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash( - runtimeType, name, const DeepCollectionEquality().hash(value), type); + int get hashCode => Object.hash(runtimeType, name, value, type); @JsonKey(ignore: true) @override @@ -169,7 +168,7 @@ class _$FormDataModelImpl implements _FormDataModel { abstract class _FormDataModel implements FormDataModel { const factory _FormDataModel( {required final String name, - required final dynamic value, + required final String value, required final FormDataType type}) = _$FormDataModelImpl; factory _FormDataModel.fromJson(Map json) = @@ -178,7 +177,7 @@ abstract class _FormDataModel implements FormDataModel { @override String get name; @override - dynamic get value; + String get value; @override FormDataType get type; @override diff --git a/lib/models/form_data_model.g.dart b/lib/models/form_data_model.g.dart index 7d9d353b..f539458f 100644 --- a/lib/models/form_data_model.g.dart +++ b/lib/models/form_data_model.g.dart @@ -9,7 +9,7 @@ part of 'form_data_model.dart'; _$FormDataModelImpl _$$FormDataModelImplFromJson(Map json) => _$FormDataModelImpl( name: json['name'] as String, - value: json['value'], + value: json['value'] as String, type: $enumDecode(_$FormDataTypeEnumMap, json['type']), ); diff --git a/lib/models/models.dart b/lib/models/models.dart index 3820e3c2..66d6f6ce 100644 --- a/lib/models/models.dart +++ b/lib/models/models.dart @@ -2,3 +2,4 @@ export 'name_value_model.dart'; export 'request_model.dart'; export 'response_model.dart'; export 'settings_model.dart'; +export 'form_data_model.dart'; 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 21dd63c6..f6e06332 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,5 +1,3 @@ -import 'dart:math'; - import 'package:apidash/consts.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/form_data_widget.dart'; @@ -15,12 +13,9 @@ class EditRequestBody extends ConsumerStatefulWidget { } class _EditRequestBodyState extends ConsumerState { - final random = Random.secure(); - late int seed; @override void initState() { super.initState(); - seed = random.nextInt(kRandMax); } @override @@ -54,12 +49,7 @@ class _EditRequestBodyState extends ConsumerState { ), Expanded( child: requestBodyStateWatcher == ContentType.formdata - ? FormDataWidget( - seed: seed, - onFormDataRemove: () { - seed = random.nextInt(kRandMax); - }, - ) + ? const FormDataWidget() : TextFieldEditor( key: Key("$activeId-body"), fieldKey: "$activeId-body-editor", diff --git a/lib/utils/convert_utils.dart b/lib/utils/convert_utils.dart index 63b1f3eb..72a88520 100644 --- a/lib/utils/convert_utils.dart +++ b/lib/utils/convert_utils.dart @@ -1,12 +1,9 @@ import 'dart:convert'; import 'dart:typed_data'; - -import 'package:apidash/models/form_data_model.dart'; +import 'package:apidash/consts.dart'; +import 'package:apidash/models/models.dart'; import 'package:http/http.dart' as http; -import '../consts.dart'; -import '../models/models.dart'; - String humanizeDuration(Duration? duration) { if (duration == null) { return ""; @@ -113,16 +110,24 @@ List? listToFormDataModel(List? kvMap) { if (kvMap == null) { return null; } - List finalRows = kvMap - .map((formData) => FormDataModel( - name: formData["name"], - value: formData["value"], - type: kMapFormDataType[formData["type"]] ?? FormDataType.text, - )) - .toList(); + List finalRows = kvMap.map( + (formData) { + return FormDataModel( + name: formData["name"], + value: formData["value"], + type: getFormDataType(formData["type"]), + ); + }, + ).toList(); return finalRows; } +FormDataType getFormDataType(String? type) { + List formData = FormDataType.values; + return formData.firstWhere((element) => element.name == type, + orElse: () => FormDataType.text); +} + Uint8List? stringToBytes(String? text) { if (text == null) { return null; diff --git a/lib/widgets/dropdowns.dart b/lib/widgets/dropdowns.dart index 98bd10d4..6ca7a698 100644 --- a/lib/widgets/dropdowns.dart +++ b/lib/widgets/dropdowns.dart @@ -129,6 +129,7 @@ class _DropdownButtonFormData extends State { Widget build(BuildContext context) { final surfaceColor = Theme.of(context).colorScheme.surface; return DropdownButton( + dropdownColor: surfaceColor, focusColor: surfaceColor, value: widget.formDataType, icon: const Icon( diff --git a/lib/widgets/form_data_field.dart b/lib/widgets/form_data_field.dart index d00dd70b..0353d033 100644 --- a/lib/widgets/form_data_field.dart +++ b/lib/widgets/form_data_field.dart @@ -27,10 +27,8 @@ class FormDataField extends StatefulWidget { } class _FormDataFieldState extends State { - TextEditingController valueController = TextEditingController(); @override void initState() { - valueController.text = widget.initialValue ?? ""; super.initState(); } @@ -42,45 +40,41 @@ class _FormDataFieldState extends State { Expanded( flex: 1, child: TextFormField( - controller: valueController, + initialValue: widget.initialValue, key: Key(widget.keyId), style: kCodeStyle.copyWith( color: colorScheme.onSurface, ), decoration: InputDecoration( - hintStyle: kCodeStyle.copyWith( - color: colorScheme.outline.withOpacity( - kHintOpacity, - ), - ), - hintText: widget.hintText, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: colorScheme.primary.withOpacity( + hintStyle: kCodeStyle.copyWith( + color: colorScheme.outline.withOpacity( kHintOpacity, ), ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: colorScheme.surfaceVariant, + hintText: widget.hintText, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: colorScheme.primary.withOpacity( + kHintOpacity, + ), + ), ), - ), - ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: colorScheme.surfaceVariant, + ), + ), + suffixIcon: DropdownButtonFormData( + formDataType: widget.formDataType, + onChanged: (p0) { + if (widget.onFormDataTypeChanged != null) { + widget.onFormDataTypeChanged!(p0); + } + }, + )), onChanged: widget.onChanged, ), ), - Expanded( - child: DropdownButtonFormData( - formDataType: widget.formDataType, - onChanged: (p0) { - if (widget.onFormDataTypeChanged != null) { - widget.onFormDataTypeChanged!(p0); - valueController.clear(); - } - }, - ), - ) ], ); } diff --git a/lib/widgets/form_data_widget.dart b/lib/widgets/form_data_widget.dart index 85318d70..e28c2fc3 100644 --- a/lib/widgets/form_data_widget.dart +++ b/lib/widgets/form_data_widget.dart @@ -1,5 +1,7 @@ +import 'dart:math'; import 'package:apidash/consts.dart'; import 'package:apidash/models/form_data_model.dart'; +import 'package:apidash/models/models.dart'; import 'package:apidash/providers/collection_providers.dart'; import 'package:apidash/widgets/form_data_field.dart'; import 'package:apidash/widgets/textfields.dart'; @@ -9,43 +11,48 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class FormDataWidget extends ConsumerStatefulWidget { - const FormDataWidget({ - super.key, - required this.seed, - required this.onFormDataRemove, - }); - final int seed; - final Function onFormDataRemove; + const FormDataWidget({super.key}); @override ConsumerState createState() => _FormDataBodyState(); } class _FormDataBodyState extends ConsumerState { + late int seed; + final random = Random.secure(); + late List rows; + @override + void initState() { + super.initState(); + seed = random.nextInt(kRandMax); + } + @override Widget build(BuildContext context) { final activeId = ref.watch(activeIdStateProvider); - final requestModel = ref - .read(collectionStateNotifierProvider.notifier) - .getRequestModel(activeId!); - List rows = requestModel?.formDataList ?? []; - DaviModel model = DaviModel( + var formRows = ref.read(activeRequestModelProvider)?.formDataList; + rows = + formRows == null || formRows.isEmpty ? [kFormDataEmptyModel] : formRows; + + DaviModel daviModelRows = DaviModel( rows: rows, columns: [ DaviColumn( + cellPadding: kpsV5, name: 'Key', grow: 1, cellBuilder: (_, row) { int idx = row.index; - return SizedBox( + return Theme( + data: Theme.of(context), child: FormDataField( - keyId: "$activeId-$idx-form-v-${widget.seed}", + keyId: "$activeId-$idx-form-v-$seed", initialValue: rows[idx].name, hintText: " Key", onChanged: (value) { rows[idx] = rows[idx].copyWith( name: value, ); - _onFieldChange(activeId); + _onFieldChange(activeId!); }, colorScheme: Theme.of(context).colorScheme, formDataType: rows[idx].type, @@ -54,7 +61,8 @@ class _FormDataBodyState extends ConsumerState { type: value ?? FormDataType.text, ); rows[idx] = rows[idx].copyWith(value: ""); - _onFieldChange(activeId); + setState(() {}); + _onFieldChange(activeId!); }, ), ); @@ -62,7 +70,9 @@ class _FormDataBodyState extends ConsumerState { sortable: false, ), DaviColumn( - width: 10, + width: 30, + cellPadding: kpsV5, + cellAlignment: Alignment.center, cellBuilder: (_, row) { return Text( "=", @@ -73,6 +83,7 @@ class _FormDataBodyState extends ConsumerState { DaviColumn( name: 'Value', grow: 4, + cellPadding: kpsV5, cellBuilder: (_, row) { int idx = row.index; return rows[idx].type == FormDataType.file @@ -83,36 +94,35 @@ class _FormDataBodyState extends ConsumerState { child: Row( children: [ Expanded( - child: ElevatedButtonTheme( - data: const ElevatedButtonThemeData(), + child: Theme( + data: Theme.of(context), child: ElevatedButton.icon( + icon: const Icon( + Icons.snippet_folder_rounded, + size: 20, + ), + style: const ButtonStyle(), onPressed: () async { FilePickerResult? pickedResult = await FilePicker.platform.pickFiles(); if (pickedResult != null && - pickedResult.files.isNotEmpty) { + pickedResult.files.isNotEmpty && + pickedResult.files.first.path != null) { rows[idx] = rows[idx].copyWith( - value: pickedResult.files.first.path, + value: pickedResult.files.first.path!, ); - _onFieldChange(activeId); + setState(() {}); + _onFieldChange(activeId!); } }, - icon: const Icon( - Icons.snippet_folder_rounded, - size: 18, - ), label: Text( - rows[idx].type == FormDataType.file - ? (rows[idx].value != null - ? rows[idx].value.toString() - : "Select File") + (rows[idx].type == FormDataType.file && + rows[idx].value.isNotEmpty) + ? rows[idx].value.toString() : "Select File", textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, - style: kTextStyleButton.copyWith( - fontSize: 12, - fontWeight: FontWeight.w500, - ), + style: kFormDataButton, ), ), ), @@ -122,12 +132,12 @@ class _FormDataBodyState extends ConsumerState { ), ) : CellField( - keyId: "$activeId-$idx-form-v-${widget.seed}", + keyId: "$activeId-$idx-form-v-$seed", initialValue: rows[idx].value, hintText: " Value", onChanged: (value) { rows[idx] = rows[idx].copyWith(value: value); - _onFieldChange(activeId); + _onFieldChange(activeId!); }, colorScheme: Theme.of(context).colorScheme, ); @@ -143,17 +153,16 @@ class _FormDataBodyState extends ConsumerState { ? kIconRemoveDark : kIconRemoveLight, onTap: () { - widget.onFormDataRemove(); + seed = random.nextInt(kRandMax); if (rows.length == 1) { setState(() { - rows = [ - kFormDataEmptyModel, - ]; + rows = [kFormDataEmptyModel]; }); } else { rows.removeAt(row.index); } - _onFieldChange(activeId); + _onFieldChange(activeId!); + setState(() {}); }, ); }, @@ -173,7 +182,7 @@ class _FormDataBodyState extends ConsumerState { Expanded( child: DaviTheme( data: kTableThemeData, - child: Davi(model), + child: Davi(daviModelRows), ), ), ], @@ -185,8 +194,10 @@ class _FormDataBodyState extends ConsumerState { padding: const EdgeInsets.only(bottom: 30), child: ElevatedButton.icon( onPressed: () { - rows.add(kFormDataEmptyModel); - _onFieldChange(activeId); + setState(() { + rows.add(kFormDataEmptyModel); + }); + _onFieldChange(activeId!); }, icon: const Icon(Icons.add), label: const Text( @@ -201,12 +212,9 @@ class _FormDataBodyState extends ConsumerState { } void _onFieldChange(String activeId) { - List formDataList = - ref.read(collectionStateNotifierProvider)?[activeId]?.formDataList ?? - []; ref.read(collectionStateNotifierProvider.notifier).update( activeId, - formDataList: formDataList, + formDataList: rows, ); } } From 35c0ed32c66f198f300dc748ade7185544d8e51e Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:49:50 +0530 Subject: [PATCH 07/30] chore: reverted name view model --- lib/models/name_value_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/name_value_model.dart b/lib/models/name_value_model.dart index f99b7a60..3249fd6f 100644 --- a/lib/models/name_value_model.dart +++ b/lib/models/name_value_model.dart @@ -1,5 +1,5 @@ -import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:flutter/foundation.dart'; part 'name_value_model.freezed.dart'; part 'name_value_model.g.dart'; From 57aca1b9c4158c9c9346499edeba642324ef25bb Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:55:41 +0530 Subject: [PATCH 08/30] chore: imports reorder --- lib/models/request_model.dart | 5 +---- lib/providers/collection_providers.dart | 10 ++++------ lib/screens/home_page/collection_pane.dart | 8 ++++---- .../details_card/request_pane/request_body.dart | 8 ++++---- lib/services/http_service.dart | 8 +++----- lib/utils/convert_utils.dart | 6 +++--- lib/widgets/dropdowns.dart | 4 ++-- 7 files changed, 21 insertions(+), 28 deletions(-) diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index 2e58015a..0203ea89 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -1,10 +1,7 @@ import 'package:apidash/consts.dart'; -import 'package:apidash/models/form_data_model.dart'; import 'package:apidash/utils/convert_utils.dart'; import 'package:flutter/foundation.dart'; - -import 'name_value_model.dart'; -import 'response_model.dart'; +import 'package:apidash/models/models.dart'; @immutable class RequestModel { diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index d5acd609..94735616 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -1,14 +1,12 @@ -import 'package:apidash/models/form_data_model.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:http/http.dart' as http; - -import '../consts.dart'; +import 'settings_providers.dart'; +import 'ui_providers.dart'; import '../models/models.dart'; import '../services/services.dart' show hiveHandler, HiveHandler, request, multiPartRequest; import '../utils/utils.dart' show uuid, collectionToHAR; -import 'settings_providers.dart'; -import 'ui_providers.dart'; +import '../consts.dart'; +import 'package:http/http.dart' as http; final activeIdStateProvider = StateProvider((ref) => null); diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index 6f0b0bde..43e4713f 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -1,9 +1,9 @@ -import 'package:apidash/consts.dart'; -import 'package:apidash/models/models.dart'; -import 'package:apidash/providers/providers.dart'; -import 'package:apidash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'package:apidash/models/models.dart'; +import 'package:apidash/consts.dart'; class CollectionPane extends ConsumerWidget { const CollectionPane({ 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 f6e06332..91d2c97f 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,9 +1,9 @@ -import 'package:apidash/consts.dart'; -import 'package:apidash/providers/providers.dart'; -import 'package:apidash/widgets/form_data_widget.dart'; -import 'package:apidash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'package:apidash/consts.dart'; +import 'package:apidash/widgets/form_data_widget.dart'; class EditRequestBody extends ConsumerStatefulWidget { const EditRequestBody({super.key}); diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart index d49656a0..2c63145b 100644 --- a/lib/services/http_service.dart +++ b/lib/services/http_service.dart @@ -1,12 +1,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; - -import 'package:apidash/consts.dart'; -import 'package:apidash/models/form_data_model.dart'; -import 'package:apidash/models/models.dart'; -import 'package:apidash/utils/utils.dart'; import 'package:http/http.dart' as http; +import 'package:apidash/utils/utils.dart'; +import 'package:apidash/models/models.dart'; +import 'package:apidash/consts.dart'; Future<(http.Response?, Duration?, String?)> request( RequestModel requestModel, { diff --git a/lib/utils/convert_utils.dart b/lib/utils/convert_utils.dart index 72a88520..b8829cfc 100644 --- a/lib/utils/convert_utils.dart +++ b/lib/utils/convert_utils.dart @@ -1,7 +1,7 @@ -import 'dart:convert'; import 'dart:typed_data'; -import 'package:apidash/consts.dart'; -import 'package:apidash/models/models.dart'; +import 'dart:convert'; +import '../models/models.dart'; +import '../consts.dart'; import 'package:http/http.dart' as http; String humanizeDuration(Duration? duration) { diff --git a/lib/widgets/dropdowns.dart b/lib/widgets/dropdowns.dart index 6ca7a698..0c897ea0 100644 --- a/lib/widgets/dropdowns.dart +++ b/lib/widgets/dropdowns.dart @@ -1,6 +1,6 @@ -import 'package:apidash/consts.dart'; -import 'package:apidash/utils/utils.dart'; import 'package:flutter/material.dart'; +import 'package:apidash/utils/utils.dart'; +import 'package:apidash/consts.dart'; class DropdownButtonHttpMethod extends StatefulWidget { const DropdownButtonHttpMethod({ From 22677d523f70bf1c52dd5dfd716614c0cc46dd92 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:57:31 +0530 Subject: [PATCH 09/30] chore: name value revert --- lib/models/name_value_model.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/models/name_value_model.dart b/lib/models/name_value_model.dart index 3249fd6f..886fbbe8 100644 --- a/lib/models/name_value_model.dart +++ b/lib/models/name_value_model.dart @@ -2,6 +2,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:flutter/foundation.dart'; part 'name_value_model.freezed.dart'; + part 'name_value_model.g.dart'; @freezed From 784b8947aeb4e68df8fb20a62eea49567a40f650 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sat, 23 Dec 2023 23:00:22 +0530 Subject: [PATCH 10/30] req model imports --- lib/models/request_model.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index 0203ea89..97a240f5 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -1,5 +1,6 @@ import 'package:apidash/consts.dart'; -import 'package:apidash/utils/convert_utils.dart'; +import 'package:apidash/utils/utils.dart' + show listToFormDataModel, mapToRows, rowsToFormDataMap, rowsToMap; import 'package:flutter/foundation.dart'; import 'package:apidash/models/models.dart'; From 7df09235c42c8ccb44586551e034219c1f56d37a Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sat, 23 Dec 2023 23:02:12 +0530 Subject: [PATCH 11/30] hive services revert --- lib/services/hive_services.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/services/hive_services.dart b/lib/services/hive_services.dart index 9d7b108c..3052024e 100644 --- a/lib/services/hive_services.dart +++ b/lib/services/hive_services.dart @@ -48,9 +48,8 @@ class HiveHandler { dynamic getRequestModel(String id) => dataBox.get(id); Future setRequestModel( - String id, Map? requestModelJson) { - return dataBox.put(id, requestModelJson); - } + String id, Map? requestModelJson) => + dataBox.put(id, requestModelJson); Future clear() => dataBox.clear(); From a4765e0a64b5f5bf02b5d8f1f4070716c2845690 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sat, 23 Dec 2023 23:03:44 +0530 Subject: [PATCH 12/30] file ext deleted --- lib/utils/extensions/file_extension.dart | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 lib/utils/extensions/file_extension.dart diff --git a/lib/utils/extensions/file_extension.dart b/lib/utils/extensions/file_extension.dart deleted file mode 100644 index 797a2ec9..00000000 --- a/lib/utils/extensions/file_extension.dart +++ /dev/null @@ -1,5 +0,0 @@ -extension FileExtension on String { - String get fileName { - return split(r'\').last; - } -} From 880f445c31ae2cff769344147091da03d3e6fedb Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Wed, 27 Dec 2023 21:25:58 +0530 Subject: [PATCH 13/30] fix: fixes for alignment issues and request handling methods --- lib/consts.dart | 2 +- lib/providers/collection_providers.dart | 22 +++---- lib/services/http_service.dart | 80 +++++++----------------- lib/widgets/dropdowns.dart | 4 +- lib/widgets/form_data_widget.dart | 83 +++++++++++++------------ 5 files changed, 76 insertions(+), 115 deletions(-) diff --git a/lib/consts.dart b/lib/consts.dart index 967a20b7..ea2c5325 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -48,7 +48,7 @@ const kHintOpacity = 0.6; const kForegroundOpacity = 0.05; const kTextStyleButton = TextStyle(fontWeight: FontWeight.bold); -const kFormDataButton = TextStyle( +const kFormDataButtonLabelTextStyle = TextStyle( fontSize: 12, fontWeight: FontWeight.w600, ); diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 94735616..10317b15 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -2,8 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'settings_providers.dart'; import 'ui_providers.dart'; import '../models/models.dart'; -import '../services/services.dart' - show hiveHandler, HiveHandler, request, multiPartRequest; +import '../services/services.dart' show hiveHandler, HiveHandler, request; import '../utils/utils.dart' show uuid, collectionToHAR; import '../consts.dart'; import 'package:http/http.dart' as http; @@ -158,20 +157,13 @@ class CollectionStateNotifier ref.read(codePaneVisibleStateProvider.notifier).state = false; final defaultUriScheme = ref.read(settingsProvider.select((value) => value.defaultUriScheme)); - (http.Response?, Duration?, String?)? responseRec; RequestModel requestModel = state![id]!; - if (requestModel.formDataList != null && - requestModel.formDataList!.isNotEmpty) { - responseRec = await multiPartRequest( - requestModel, - defaultUriScheme: defaultUriScheme, - ); - } else { - responseRec = await request( - requestModel, - defaultUriScheme: defaultUriScheme, - ); - } + (http.Response?, Duration?, String?)? responseRec = await request( + requestModel, + defaultUriScheme: defaultUriScheme, + isMultiPartRequest: requestModel.formDataList != null && + requestModel.formDataList!.isNotEmpty, + ); late final RequestModel newRequestModel; if (responseRec.$1 == null) { newRequestModel = requestModel.copyWith( diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart index 2c63145b..8d6e70b9 100644 --- a/lib/services/http_service.dart +++ b/lib/services/http_service.dart @@ -9,6 +9,7 @@ import 'package:apidash/consts.dart'; Future<(http.Response?, Duration?, String?)> request( RequestModel requestModel, { String defaultUriScheme = kDefaultUriScheme, + bool isMultiPartRequest = false, }) async { (Uri?, String?) uriRec = getValidRequestUri( requestModel.url, @@ -33,6 +34,29 @@ Future<(http.Response?, Duration?, String?)> request( } } Stopwatch stopwatch = Stopwatch()..start(); + if (isMultiPartRequest) { + var multiPartRequest = http.MultipartRequest( + requestModel.method.name.toUpperCase(), + requestUrl, + ); + for (FormDataModel formData in (requestModel.formDataList ?? [])) { + if (formData.type == FormDataType.text) { + multiPartRequest.fields.addAll({formData.name: formData.value}); + } else { + multiPartRequest.files.add( + await http.MultipartFile.fromPath( + formData.name, + formData.value, + ), + ); + } + } + http.StreamedResponse multiPartResponse = await multiPartRequest.send(); + stopwatch.stop(); + http.Response convertedMultiPartResponse = + await convertStreamedResponse(multiPartResponse); + return (convertedMultiPartResponse, stopwatch.elapsed, null); + } switch (requestModel.method) { case HTTPVerb.get: response = await http.get(requestUrl, headers: headers); @@ -63,59 +87,3 @@ Future<(http.Response?, Duration?, String?)> request( return (null, null, uriRec.$2); } } - -Future<(http.Response?, Duration?, String?)> multiPartRequest( - RequestModel requestModel, { - String defaultUriScheme = kDefaultUriScheme, -}) async { - (Uri?, String?) uriRec = getValidRequestUri( - requestModel.url, - requestModel.requestParams, - defaultUriScheme: defaultUriScheme, - ); - if (uriRec.$1 != null) { - Uri requestUrl = uriRec.$1!; - Map headers = requestModel.headersMap; - try { - var requestBody = requestModel.requestBody; - if (kMethodsWithBody.contains(requestModel.method) && - requestBody != null) { - var contentLength = utf8.encode(requestBody).length; - if (contentLength > 0) { - headers[HttpHeaders.contentLengthHeader] = contentLength.toString(); - headers[HttpHeaders.contentTypeHeader] = - kContentTypeMap[requestModel.requestBodyContentType] ?? ""; - } - } - Stopwatch stopwatch = Stopwatch()..start(); - - var request = http.MultipartRequest( - requestModel.method.name.toUpperCase(), - requestUrl, - ); - for (FormDataModel formData in (requestModel.formDataList ?? [])) { - if (formData.type == FormDataType.text) { - request.fields.addAll({formData.name: formData.value}); - } else { - request.files.add( - await http.MultipartFile.fromPath( - formData.name, - formData.value, - ), - ); - } - } - - http.StreamedResponse response = await request.send(); - - stopwatch.stop(); - http.Response convertedHttpResponse = - await convertStreamedResponse(response); - return (convertedHttpResponse, stopwatch.elapsed, null); - } catch (e) { - return (null, null, e.toString()); - } - } else { - return (null, null, uriRec.$2); - } -} diff --git a/lib/widgets/dropdowns.dart b/lib/widgets/dropdowns.dart index 0c897ea0..f88642c5 100644 --- a/lib/widgets/dropdowns.dart +++ b/lib/widgets/dropdowns.dart @@ -140,9 +140,7 @@ class _DropdownButtonFormData extends State { style: kCodeStyle.copyWith( color: Theme.of(context).colorScheme.primary, ), - underline: Container( - height: 0, - ), + underline: const IgnorePointer(), onChanged: widget.onChanged, borderRadius: kBorderRadius12, items: FormDataType.values diff --git a/lib/widgets/form_data_widget.dart b/lib/widgets/form_data_widget.dart index e28c2fc3..9a90262a 100644 --- a/lib/widgets/form_data_widget.dart +++ b/lib/widgets/form_data_widget.dart @@ -39,7 +39,7 @@ class _FormDataBodyState extends ConsumerState { DaviColumn( cellPadding: kpsV5, name: 'Key', - grow: 1, + grow: 4, cellBuilder: (_, row) { int idx = row.index; return Theme( @@ -47,7 +47,7 @@ class _FormDataBodyState extends ConsumerState { child: FormDataField( keyId: "$activeId-$idx-form-v-$seed", initialValue: rows[idx].name, - hintText: " Key", + hintText: " Add Key", onChanged: (value) { rows[idx] = rows[idx].copyWith( name: value, @@ -70,7 +70,7 @@ class _FormDataBodyState extends ConsumerState { sortable: false, ), DaviColumn( - width: 30, + width: 40, cellPadding: kpsV5, cellAlignment: Alignment.center, cellBuilder: (_, row) { @@ -89,52 +89,55 @@ class _FormDataBodyState extends ConsumerState { return rows[idx].type == FormDataType.file ? Align( alignment: Alignment.centerLeft, - child: Padding( - padding: kPs8, - child: Row( - children: [ - Expanded( - child: Theme( - data: Theme.of(context), - child: ElevatedButton.icon( - icon: const Icon( - Icons.snippet_folder_rounded, - size: 20, - ), - style: const ButtonStyle(), - onPressed: () async { - FilePickerResult? pickedResult = - await FilePicker.platform.pickFiles(); - if (pickedResult != null && - pickedResult.files.isNotEmpty && - pickedResult.files.first.path != null) { - rows[idx] = rows[idx].copyWith( - value: pickedResult.files.first.path!, - ); - setState(() {}); - _onFieldChange(activeId!); - } - }, - label: Text( - (rows[idx].type == FormDataType.file && - rows[idx].value.isNotEmpty) - ? rows[idx].value.toString() - : "Select File", - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - style: kFormDataButton, + 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 { + FilePickerResult? pickedResult = + await FilePicker.platform.pickFiles(); + if (pickedResult != null && + pickedResult.files.isNotEmpty && + pickedResult.files.first.path != null) { + rows[idx] = rows[idx].copyWith( + value: pickedResult.files.first.path!, + ); + setState(() {}); + _onFieldChange(activeId!); + } + }, + label: Text( + (rows[idx].type == FormDataType.file && + rows[idx].value.isNotEmpty) + ? rows[idx].value.toString() + : "Select File", + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: kFormDataButtonLabelTextStyle, + ), ), ), - ], - ), + ), + ], ), ) : CellField( keyId: "$activeId-$idx-form-v-$seed", initialValue: rows[idx].value, - hintText: " Value", + hintText: " Add Value", onChanged: (value) { rows[idx] = rows[idx].copyWith(value: value); _onFieldChange(activeId!); From 80e13fc625622a7e5a37d18ceb9627f79d3da0ab Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Wed, 27 Dec 2023 21:38:23 +0530 Subject: [PATCH 14/30] fix: fixed code gen issues in latest code --- lib/codegen/dart/dio.dart | 2 ++ lib/widgets/dropdowns.dart | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/codegen/dart/dio.dart b/lib/codegen/dart/dio.dart index fcaa8314..1b4a8820 100644 --- a/lib/codegen/dart/dio.dart +++ b/lib/codegen/dart/dio.dart @@ -68,6 +68,8 @@ class DartDioCodeGen { case ContentType.text: dataExp = declareFinal('data').assign(strContent); // when add new type of [ContentType], need update [dataExp]. + case ContentType.formdata: + // TODO: Need to Handle this case. } } final responseExp = declareFinal('response').assign(InvokeExpression.newOf( diff --git a/lib/widgets/dropdowns.dart b/lib/widgets/dropdowns.dart index be25737d..69008a6b 100644 --- a/lib/widgets/dropdowns.dart +++ b/lib/widgets/dropdowns.dart @@ -143,7 +143,7 @@ class _DropdownButtonFormData extends State { } } -class DropdownButtonCodegenLanguage extends StatefulWidget { +class DropdownButtonCodegenLanguage extends StatelessWidget { const DropdownButtonCodegenLanguage({ super.key, this.codegenLanguage, From 2b41f54015eef432541c1bb5d50ae8e7e0016534 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Wed, 27 Dec 2023 21:49:14 +0530 Subject: [PATCH 15/30] fix: pr fixes --- lib/providers/collection_providers.dart | 4 +- lib/screens/home_page/collection_pane.dart | 62 +++++++++---------- .../request_pane/request_body.dart | 6 -- lib/widgets/widgets.dart | 1 + 4 files changed, 32 insertions(+), 41 deletions(-) diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 10317b15..9212ea35 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -161,8 +161,8 @@ class CollectionStateNotifier (http.Response?, Duration?, String?)? responseRec = await request( requestModel, defaultUriScheme: defaultUriScheme, - isMultiPartRequest: requestModel.formDataList != null && - requestModel.formDataList!.isNotEmpty, + isMultiPartRequest: + requestModel.requestBodyContentType == ContentType.formdata, ); late final RequestModel newRequestModel; if (responseRec.$1 == null) { diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index 43e4713f..0ea875fb 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -108,40 +108,36 @@ class _RequestListState extends ConsumerState { controller: controller, thumbVisibility: alwaysShowCollectionPaneScrollbar ? true : null, radius: const Radius.circular(12), - child: requestSequence.isNotEmpty - ? ReorderableListView.builder( - padding: kPr8CollectionPane, - scrollController: controller, - buildDefaultDragHandles: false, - itemCount: requestSequence.length, - onReorder: (int oldIndex, int newIndex) { - if (oldIndex < newIndex) { - newIndex -= 1; - } - if (oldIndex != newIndex) { - ref - .read(collectionStateNotifierProvider.notifier) - .reorder(oldIndex, newIndex); - } - }, - itemBuilder: (context, index) { - var id = requestSequence[index]; - return ReorderableDragStartListener( - key: ValueKey(id), - index: index, - child: Padding( - padding: kP1, - child: RequestItem( - id: id, - requestModel: requestItems[id]!, - ), - ), - ); - }, - ) - : const Center( - child: Text("No Requests found"), + child: ReorderableListView.builder( + padding: kPr8CollectionPane, + scrollController: controller, + buildDefaultDragHandles: false, + itemCount: requestSequence.length, + onReorder: (int oldIndex, int newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + if (oldIndex != newIndex) { + ref + .read(collectionStateNotifierProvider.notifier) + .reorder(oldIndex, newIndex); + } + }, + itemBuilder: (context, index) { + var id = requestSequence[index]; + return ReorderableDragStartListener( + key: ValueKey(id), + index: index, + child: Padding( + padding: kP1, + child: RequestItem( + id: id, + requestModel: requestItems[id]!, + ), ), + ); + }, + ), ); } } 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 91d2c97f..94ff858c 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 @@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/consts.dart'; -import 'package:apidash/widgets/form_data_widget.dart'; class EditRequestBody extends ConsumerStatefulWidget { const EditRequestBody({super.key}); @@ -13,11 +12,6 @@ class EditRequestBody extends ConsumerStatefulWidget { } class _EditRequestBodyState extends ConsumerState { - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) { final activeId = ref.watch(activeIdStateProvider); diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index a4921bdc..69598ca8 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -21,3 +21,4 @@ export 'textfields.dart'; export 'texts.dart'; export 'uint8_audio_player.dart'; export 'window_caption.dart'; +export 'form_data_widget.dart'; From 68c69d37fe3465ed0d737cd2da313411dfbb2419 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:38:06 +0530 Subject: [PATCH 16/30] wip: curl and python code gen tested and added --- lib/codegen/others/curl.dart | 20 +++++ lib/codegen/python/http_client.dart | 73 +++++++++++++++++-- lib/codegen/python/requests.dart | 72 ++++++++++++++++-- .../extensions/request_model_extension.dart | 8 ++ lib/utils/har_utils.dart | 8 +- 5 files changed, 167 insertions(+), 14 deletions(-) create mode 100644 lib/utils/extensions/request_model_extension.dart diff --git a/lib/codegen/others/curl.dart b/lib/codegen/others/curl.dart index 5405ee1a..6c60193f 100644 --- a/lib/codegen/others/curl.dart +++ b/lib/codegen/others/curl.dart @@ -10,6 +10,9 @@ class cURLCodeGen { String kTemplateHeader = """ \\ --header '{{name}}: {{value}}' """; + String kTemplateFormData = """ \\ + --form '{{name}}: {{value}}' +"""; String kTemplateBody = """ \\ --data '{{body}}' @@ -48,6 +51,23 @@ class cURLCodeGen { .render({"name": item["name"], "value": item["value"]}); } } + if (harJson['formData'] != null) { + var formDataList = harJson['formData'] as List>; + for (var formData in formDataList) { + var templateFormData = jj.Template(kTemplateFormData); + if (formData['type'] != null && + formData['name'] != null && + formData['value'] != null && + formData['name']!.isNotEmpty && + formData['value']!.isNotEmpty) { + result += templateFormData.render({ + "name": formData["name"], + "value": + "${formData['type'] == 'file' ? '@' : ''}${formData["value"]}", + }); + } + } + } if (harJson["postData"]?["text"] != null) { var templateBody = jj.Template(kTemplateBody); diff --git a/lib/codegen/python/http_client.dart b/lib/codegen/python/http_client.dart index 83d02676..a00038d5 100644 --- a/lib/codegen/python/http_client.dart +++ b/lib/codegen/python/http_client.dart @@ -1,13 +1,23 @@ import 'dart:io'; import 'dart:convert'; +import 'package:apidash/utils/convert_utils.dart'; +import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/consts.dart'; import 'package:apidash/utils/utils.dart' show getValidRequestUri, padMultilineString; -import 'package:apidash/models/models.dart' show RequestModel; +import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; class PythonHttpClientCodeGen { final String kTemplateStart = """import http.client +{% if isFormDataRequest %} +import mimetypes +from codecs import encode +import uuid + +headers = {} +boundary = str(uuid.uuid4()) +{% endif %} """; String kTemplateParams = """ @@ -41,6 +51,10 @@ conn = http.client.HTTP{{isHttps}}Connection("{{authority}}")"""; conn.request("{{method}}", "{{path}}"{{queryParamsStr}}"""; + String kTemplateFormDataRequest = """ +conn.request("{{method}}", "{{path}}"{{queryParamsStr}},body=payload, headers=headers +"""; + String kStringRequestBody = """, body= body"""; @@ -55,10 +69,43 @@ data = res.read() print(data.decode("utf-8")) """; + final String kStringFormDataBody = r''' +def build_data_list(fields): + dataList = [] + for field in fields: + name = field.get('name', '') + value = field.get('value', '') + type_ = field.get('type', 'text') + + dataList.append(encode(f'--{boundary}')) + if type_ == 'text': + dataList.append(encode(f'Content-Disposition: form-data; name="{name}"')) + dataList.append(encode('Content-Type: text/plain')) + dataList.append(encode('')) + dataList.append(encode(value)) + elif type_ == 'file': + dataList.append(encode(f'Content-Disposition: form-data; name="{name}"; filename="{value}"')) + dataList.append(encode(f'Content-Type: {mimetypes.guess_type(value)[0] or "application/octet-stream"}')) + dataList.append(encode('')) + dataList.append(open(value, 'rb').read()) + + dataList.append(encode(f'--{boundary}--')) + dataList.append(encode('')) + return dataList +dataList = build_data_list({{fields_list}}) +body = b'\r\n'.join(dataList) +'''; + + String kFormDataHeaders = ''' +headers['Content-type'] = f'multipart/form-data; boundary={boundary}'; +'''; + String? getCode( RequestModel requestModel, String defaultUriScheme, ) { + List formDataList = requestModel.formDataList ?? []; + try { String result = ""; bool hasHeaders = false; @@ -69,8 +116,12 @@ print(data.decode("utf-8")) if (!url.contains("://") && url.isNotEmpty) { url = "$defaultUriScheme://$url"; } - - result += kTemplateStart; + var templateStartUrl = jj.Template(kTemplateStart); + result += templateStartUrl.render( + { + "isFormDataRequest": requestModel.isFormDataRequest, + }, + ); var rec = getValidRequestUri(url, requestModel.requestParams); Uri? uri = rec.$1; @@ -112,13 +163,20 @@ print(data.decode("utf-8")) result += templateHeaders.render({"headers": headersString}); } } - + if (requestModel.isFormDataRequest) { + result += kFormDataHeaders; + var formDataBodyData = jj.Template(kStringFormDataBody); + result += formDataBodyData.render( + { + "fields_list": json.encode(rowsToFormDataMap(formDataList)), + }, + ); + } var templateConnection = jj.Template(kTemplateConnection); result += templateConnection.render({ "isHttps": uri.scheme == "https" ? "S" : "", "authority": uri.authority }); - var templateRequest = jj.Template(kTemplateRequest); result += templateRequest.render({ "method": method.name.toUpperCase(), @@ -126,11 +184,11 @@ print(data.decode("utf-8")) "queryParamsStr": hasQuery ? " + queryParamsStr" : "", }); - if (hasBody) { + if (hasBody || requestModel.isFormDataRequest) { result += kStringRequestBody; } - if (hasHeaders) { + if (hasHeaders || requestModel.isFormDataRequest) { result += kStringRequestHeaders; } @@ -138,6 +196,7 @@ print(data.decode("utf-8")) } return result; } catch (e) { + print(e); return null; } } diff --git a/lib/codegen/python/requests.dart b/lib/codegen/python/requests.dart index af8b3fbe..821f45ed 100644 --- a/lib/codegen/python/requests.dart +++ b/lib/codegen/python/requests.dart @@ -1,16 +1,35 @@ import 'dart:io'; import 'dart:convert'; +import 'package:apidash/utils/convert_utils.dart'; +import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/consts.dart'; import 'package:apidash/utils/utils.dart' show getValidRequestUri, padMultilineString, stripUriParams; -import 'package:apidash/models/models.dart' show RequestModel; +import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; class PythonRequestsCodeGen { final String kTemplateStart = """import requests +{% if isFormDataRequest %} +import mimetypes +from codecs import encode +import uuid +headers = {} +boundary = str(uuid.uuid4()) +{% endif %} url = '{{url}}' +"""; + + final String kTemplateFormDataImports = """ +import mimetypes +from codecs import encode +import uuid + +headers = {} +boundary = str(uuid.uuid4()) + """; String kTemplateParams = """ @@ -45,6 +64,32 @@ headers = {{headers}} response = requests.{{method}}(url """; + final String kStringFormDataBody = r''' +def build_data_list(fields): + dataList = [] + for field in fields: + name = field.get('name', '') + value = field.get('value', '') + type_ = field.get('type', 'text') + + dataList.append(encode(f'--{boundary}')) + if type_ == 'text': + dataList.append(encode(f'Content-Disposition: form-data; name="{name}"')) + dataList.append(encode('Content-Type: text/plain')) + dataList.append(encode('')) + dataList.append(encode(value)) + elif type_ == 'file': + dataList.append(encode(f'Content-Disposition: form-data; name="{name}"; filename="{value}"')) + dataList.append(encode(f'Content-Type: {mimetypes.guess_type(value)[0] or "application/octet-stream"}')) + dataList.append(encode('')) + dataList.append(open(value, 'rb').read()) + + dataList.append(encode(f'--{boundary}--')) + dataList.append(encode('')) + return dataList +dataList = build_data_list({{fields_list}}) +payload = b'\r\n'.join(dataList) +'''; String kStringRequestParams = """, params=params"""; String kStringRequestBody = """, data=payload"""; @@ -52,6 +97,9 @@ response = requests.{{method}}(url String kStringRequestJson = """, json=payload"""; String kStringRequestHeaders = """, headers=headers"""; + String kFormDataHeaders = ''' +headers['Content-type'] = f'multipart/form-data; boundary={boundary}'; +'''; final String kStringRequestEnd = """) @@ -69,6 +117,7 @@ print('Response Body:', response.text) bool hasHeaders = false; bool hasBody = false; bool hasJsonBody = false; + List formDataList = requestModel.formDataList ?? []; String url = requestModel.url; if (!url.contains("://") && url.isNotEmpty) { @@ -79,7 +128,10 @@ print('Response Body:', response.text) Uri? uri = rec.$1; if (uri != null) { var templateStartUrl = jj.Template(kTemplateStart); - result += templateStartUrl.render({"url": stripUriParams(uri)}); + result += templateStartUrl.render({ + "url": stripUriParams(uri), + 'isFormDataRequest': requestModel.isFormDataRequest + }); if (uri.hasQuery) { var params = uri.queryParameters; @@ -124,7 +176,15 @@ print('Response Body:', response.text) result += templateHeaders.render({"headers": headersString}); } } - + if (requestModel.isFormDataRequest) { + result += kFormDataHeaders; + var formDataBodyData = jj.Template(kStringFormDataBody); + result += formDataBodyData.render( + { + "fields_list": json.encode(rowsToFormDataMap(formDataList)), + }, + ); + } var templateRequest = jj.Template(kTemplateRequest); result += templateRequest.render({ "method": method.name.toLowerCase(), @@ -134,15 +194,15 @@ print('Response Body:', response.text) result += kStringRequestParams; } - if (hasBody) { + if (hasBody || requestModel.isFormDataRequest) { result += kStringRequestBody; } - if (hasJsonBody) { + if (hasJsonBody || requestModel.isFormDataRequest) { result += kStringRequestJson; } - if (hasHeaders) { + if (hasHeaders || requestModel.isFormDataRequest) { result += kStringRequestHeaders; } diff --git a/lib/utils/extensions/request_model_extension.dart b/lib/utils/extensions/request_model_extension.dart new file mode 100644 index 00000000..95563a05 --- /dev/null +++ b/lib/utils/extensions/request_model_extension.dart @@ -0,0 +1,8 @@ +import 'package:apidash/consts.dart'; +import 'package:apidash/models/models.dart'; + +extension RequestModelExtension on RequestModel { + bool get isFormDataRequest { + return requestBodyContentType == ContentType.formdata; + } +} diff --git a/lib/utils/har_utils.dart b/lib/utils/har_utils.dart index f0838807..c3581f86 100644 --- a/lib/utils/har_utils.dart +++ b/lib/utils/har_utils.dart @@ -1,5 +1,7 @@ import 'dart:convert'; import 'package:apidash/consts.dart'; +import 'package:apidash/models/form_data_model.dart'; +import 'package:apidash/utils/convert_utils.dart'; import 'package:apidash/utils/utils.dart' show getValidRequestUri; import 'package:apidash/models/models.dart' show RequestModel; import 'package:package_info_plus/package_info_plus.dart'; @@ -148,7 +150,11 @@ Map requestModelToHARJsonRequest( } } } - + List formDataList = requestModel.formDataList ?? []; + if (requestModel.requestBodyContentType == ContentType.formdata) { + var formListMap = rowsToFormDataMap(formDataList); + json["formData"] = formListMap; + } if (exportMode) { json["comment"] = ""; json["cookies"] = []; From f0047aa087f6f1ca49ca9fa3e9568a08d0586395 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sun, 31 Dec 2023 23:25:35 +0530 Subject: [PATCH 17/30] wip: updated http and requests header handle --- lib/codegen/python/http_client.dart | 38 ++++++++++++-------------- lib/codegen/python/requests.dart | 42 ++++++++++++++--------------- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/lib/codegen/python/http_client.dart b/lib/codegen/python/http_client.dart index a00038d5..0225ff38 100644 --- a/lib/codegen/python/http_client.dart +++ b/lib/codegen/python/http_client.dart @@ -1,11 +1,10 @@ import 'dart:io'; import 'dart:convert'; -import 'package:apidash/utils/convert_utils.dart'; import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/consts.dart'; import 'package:apidash/utils/utils.dart' - show getValidRequestUri, padMultilineString; + show getNewUuid, getValidRequestUri, padMultilineString, rowsToFormDataMap; import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; class PythonHttpClientCodeGen { @@ -13,11 +12,8 @@ class PythonHttpClientCodeGen { {% if isFormDataRequest %} import mimetypes from codecs import encode -import uuid - -headers = {} -boundary = str(uuid.uuid4()) {% endif %} + """; String kTemplateParams = """ @@ -40,6 +36,8 @@ body = r'''{{body}}''' headers = {{headers}} """; + String kTemplateFormHeaderContentType = ''' +multipart/form-data; boundary={{boundary}}'''; int kHeadersPadding = 10; @@ -51,10 +49,6 @@ conn = http.client.HTTP{{isHttps}}Connection("{{authority}}")"""; conn.request("{{method}}", "{{path}}"{{queryParamsStr}}"""; - String kTemplateFormDataRequest = """ -conn.request("{{method}}", "{{path}}"{{queryParamsStr}},body=payload, headers=headers -"""; - String kStringRequestBody = """, body= body"""; @@ -76,8 +70,7 @@ def build_data_list(fields): name = field.get('name', '') value = field.get('value', '') type_ = field.get('type', 'text') - - dataList.append(encode(f'--{boundary}')) + dataList.append(encode('--{{boundary}}')) if type_ == 'text': dataList.append(encode(f'Content-Disposition: form-data; name="{name}"')) dataList.append(encode('Content-Type: text/plain')) @@ -88,23 +81,18 @@ def build_data_list(fields): dataList.append(encode(f'Content-Type: {mimetypes.guess_type(value)[0] or "application/octet-stream"}')) dataList.append(encode('')) dataList.append(open(value, 'rb').read()) - - dataList.append(encode(f'--{boundary}--')) + dataList.append(encode(f'--{{boundary}}--')) dataList.append(encode('')) return dataList dataList = build_data_list({{fields_list}}) body = b'\r\n'.join(dataList) '''; - - String kFormDataHeaders = ''' -headers['Content-type'] = f'multipart/form-data; boundary={boundary}'; -'''; - String? getCode( RequestModel requestModel, String defaultUriScheme, ) { List formDataList = requestModel.formDataList ?? []; + String uuid = getNewUuid(); try { String result = ""; @@ -116,6 +104,7 @@ headers['Content-type'] = f'multipart/form-data; boundary={boundary}'; if (!url.contains("://") && url.isNotEmpty) { url = "$defaultUriScheme://$url"; } + var templateStartUrl = jj.Template(kTemplateStart); result += templateStartUrl.render( { @@ -151,6 +140,13 @@ headers['Content-type'] = f'multipart/form-data; boundary={boundary}'; var headersList = requestModel.requestHeaders; if (headersList != null || hasBody) { var headers = requestModel.headersMap; + if (requestModel.isFormDataRequest) { + var formHeaderTemplate = + jj.Template(kTemplateFormHeaderContentType); + headers[HttpHeaders.contentTypeHeader] = formHeaderTemplate.render({ + "boundary": uuid, + }); + } if (headers.isNotEmpty || hasBody) { hasHeaders = true; if (hasBody) { @@ -164,11 +160,11 @@ headers['Content-type'] = f'multipart/form-data; boundary={boundary}'; } } if (requestModel.isFormDataRequest) { - result += kFormDataHeaders; var formDataBodyData = jj.Template(kStringFormDataBody); result += formDataBodyData.render( { "fields_list": json.encode(rowsToFormDataMap(formDataList)), + "boundary": uuid, }, ); } @@ -177,6 +173,7 @@ headers['Content-type'] = f'multipart/form-data; boundary={boundary}'; "isHttps": uri.scheme == "https" ? "S" : "", "authority": uri.authority }); + var templateRequest = jj.Template(kTemplateRequest); result += templateRequest.render({ "method": method.name.toUpperCase(), @@ -196,7 +193,6 @@ headers['Content-type'] = f'multipart/form-data; boundary={boundary}'; } return result; } catch (e) { - print(e); return null; } } diff --git a/lib/codegen/python/requests.dart b/lib/codegen/python/requests.dart index 821f45ed..6f40fa2e 100644 --- a/lib/codegen/python/requests.dart +++ b/lib/codegen/python/requests.dart @@ -1,11 +1,15 @@ import 'dart:io'; import 'dart:convert'; -import 'package:apidash/utils/convert_utils.dart'; import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/consts.dart'; import 'package:apidash/utils/utils.dart' - show getValidRequestUri, padMultilineString, stripUriParams; + show + getNewUuid, + getValidRequestUri, + padMultilineString, + rowsToFormDataMap, + stripUriParams; import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; class PythonRequestsCodeGen { @@ -13,23 +17,9 @@ class PythonRequestsCodeGen { {% if isFormDataRequest %} import mimetypes from codecs import encode -import uuid - -headers = {} -boundary = str(uuid.uuid4()) {% endif %} url = '{{url}}' -"""; - - final String kTemplateFormDataImports = """ -import mimetypes -from codecs import encode -import uuid - -headers = {} -boundary = str(uuid.uuid4()) - """; String kTemplateParams = """ @@ -56,6 +46,8 @@ payload = {{body}} headers = {{headers}} """; + String kTemplateFormHeaderContentType = ''' +multipart/form-data; boundary={{boundary}}'''; int kHeadersPadding = 10; @@ -72,7 +64,7 @@ def build_data_list(fields): value = field.get('value', '') type_ = field.get('type', 'text') - dataList.append(encode(f'--{boundary}')) + dataList.append(encode('--{{boundary}}')) if type_ == 'text': dataList.append(encode(f'Content-Disposition: form-data; name="{name}"')) dataList.append(encode('Content-Type: text/plain')) @@ -84,12 +76,13 @@ def build_data_list(fields): dataList.append(encode('')) dataList.append(open(value, 'rb').read()) - dataList.append(encode(f'--{boundary}--')) + dataList.append(encode('--{{boundary}}--')) dataList.append(encode('')) return dataList dataList = build_data_list({{fields_list}}) payload = b'\r\n'.join(dataList) '''; + String kStringRequestParams = """, params=params"""; String kStringRequestBody = """, data=payload"""; @@ -97,9 +90,6 @@ payload = b'\r\n'.join(dataList) String kStringRequestJson = """, json=payload"""; String kStringRequestHeaders = """, headers=headers"""; - String kFormDataHeaders = ''' -headers['Content-type'] = f'multipart/form-data; boundary={boundary}'; -'''; final String kStringRequestEnd = """) @@ -118,6 +108,7 @@ print('Response Body:', response.text) bool hasBody = false; bool hasJsonBody = false; List formDataList = requestModel.formDataList ?? []; + String uuid = getNewUuid(); String url = requestModel.url; if (!url.contains("://") && url.isNotEmpty) { @@ -164,6 +155,13 @@ print('Response Body:', response.text) var headersList = requestModel.requestHeaders; if (headersList != null || hasBody) { var headers = requestModel.headersMap; + if (requestModel.isFormDataRequest) { + var formHeaderTemplate = + jj.Template(kTemplateFormHeaderContentType); + headers[HttpHeaders.contentTypeHeader] = formHeaderTemplate.render({ + "boundary": uuid, + }); + } if (headers.isNotEmpty || hasBody) { hasHeaders = true; if (hasBody) { @@ -177,11 +175,11 @@ print('Response Body:', response.text) } } if (requestModel.isFormDataRequest) { - result += kFormDataHeaders; var formDataBodyData = jj.Template(kStringFormDataBody); result += formDataBodyData.render( { "fields_list": json.encode(rowsToFormDataMap(formDataList)), + "boundary": uuid, }, ); } From f39f30a085bb33217c47d9718a2c1ea755f5de1f Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sun, 31 Dec 2023 23:25:55 +0530 Subject: [PATCH 18/30] wip: working on fetch api for js --- lib/codegen/js/fetch.dart | 104 ++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/lib/codegen/js/fetch.dart b/lib/codegen/js/fetch.dart index 2d57d290..aeaa9c81 100644 --- a/lib/codegen/js/fetch.dart +++ b/lib/codegen/js/fetch.dart @@ -1,15 +1,22 @@ +import 'dart:convert'; + import 'package:apidash/consts.dart'; +import 'package:apidash/utils/convert_utils.dart'; +import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/utils/utils.dart' - show requestModelToHARJsonRequest, padMultilineString; -import 'package:apidash/models/models.dart' show RequestModel; + show getNewUuid, padMultilineString, requestModelToHARJsonRequest; +import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; class FetchCodeGen { FetchCodeGen({this.isNodeJs = false}); final bool isNodeJs; - String kStringImportNode = """import fetch from 'node-fetch'; + String kStringImportNode = """ +import fetch from 'node-fetch'; +{% if isFormDataRequest %}const fs = require('fs');{% endif %} + """; @@ -24,10 +31,55 @@ let options = { """; String kTemplateBody = """, - body: -{{body}} + body: {{body}} """; + String kMultiPartFileReader = ''' +function readFile(file) { + {% if isNodeJs %} return fs.readFile(file, 'binary'); {% else %} return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = reject; + reader.readAsArrayBuffer(file); + }); +{% endif %} +} +'''; + String kMultiPartBodyTemplate = r''' +async function buildDataList(fields) { + const dataList = []; + for (const field of fields) { + const name = field.name || ''; + const value = field.value || ''; + const type = field.type || 'text'; + + dataList.push(`--{{boundary}}`); + if (type === 'text') { + dataList.push(`Content-Disposition: form-data; name="${name}"`); + dataList.push('Content-Type: text/plain'); + dataList.push(''); + dataList.push(value); + } else if (type === 'file') { + const fileContent = await readFile(value); + dataList.push(`Content-Disposition: form-data; name="$name"; filename="${value.name}"`); + dataList.push(`Content-Type: ${value.type}`); + dataList.push(''); + {% if isNodeJs %}dataList.push(fileContent); + {% else %} + dataList.push(fileContent.content);{% endif %}} + } + + dataList.push(`--{{boundary}}--`); + dataList.push(''); + return dataList.join('\r\n'); +} +var dataList = []; +buildDataList({{fields_list}}) + .then(data => dataList = data) + .catch(err => console.error(err)); +const payload = dataList.join('\r\n'); + +'''; String kStringRequest = """ }; @@ -53,8 +105,28 @@ fetch(url, options) String defaultUriScheme, ) { try { - String result = isNodeJs ? kStringImportNode : ""; + List formDataList = requestModel.formDataList ?? []; + String uuid = getNewUuid(); + jj.Template kNodejsImportTemplate = jj.Template(kStringImportNode); + String importsData = kNodejsImportTemplate.render({ + "isFormDataRequest": requestModel.isFormDataRequest, + }); + String result = isNodeJs ? importsData : ""; + if (requestModel.isFormDataRequest) { + jj.Template kMultiPartFileReaderTemplate = + jj.Template(kMultiPartFileReader); + result += kMultiPartFileReaderTemplate.render({ + "isNodeJs": isNodeJs, + }); + var boundary = uuid; + var templateMultiPartBody = jj.Template(kMultiPartBodyTemplate); + result += templateMultiPartBody.render({ + "boundary": boundary, + "isNodeJs": isNodeJs, + "fields_list": json.encode(rowsToFormDataMap(formDataList)), + }); + } String url = requestModel.url; if (!url.contains("://") && url.isNotEmpty) { url = "$defaultUriScheme://$url"; @@ -70,21 +142,33 @@ fetch(url, options) }); var headers = harJson["headers"]; + if (headers.isNotEmpty) { var templateHeader = jj.Template(kTemplateHeader); var m = {}; + if (requestModel.isFormDataRequest) { + m["Content-Type"] = "multipart/form-data; boundary=$uuid"; + } for (var i in headers) { m[i["name"]] = i["value"]; } - result += templateHeader - .render({"headers": padMultilineString(kEncoder.convert(m), 2)}); + result += templateHeader.render({ + "headers": padMultilineString(kEncoder.convert(m), 2), + }); } if (harJson["postData"]?["text"] != null) { var templateBody = jj.Template(kTemplateBody); - result += templateBody - .render({"body": kEncoder.convert(harJson["postData"]["text"])}); + result += templateBody.render({ + "body": kEncoder.convert(harJson["postData"]["text"]), + }); + } else if (requestModel.isFormDataRequest) { + var templateBody = jj.Template(kTemplateBody); + result += templateBody.render({ + "body": 'payload', + }); } + result += kStringRequest; return result; } catch (e) { From 8d7f745bd59d8e7c86efd26a4d0263d9f6556861 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Wed, 3 Jan 2024 21:02:32 +0530 Subject: [PATCH 19/30] feat: added js amd node-fetch code gen --- lib/codegen/js/fetch.dart | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/lib/codegen/js/fetch.dart b/lib/codegen/js/fetch.dart index aeaa9c81..adbc2eec 100644 --- a/lib/codegen/js/fetch.dart +++ b/lib/codegen/js/fetch.dart @@ -47,37 +47,21 @@ function readFile(file) { '''; String kMultiPartBodyTemplate = r''' async function buildDataList(fields) { - const dataList = []; + var formdata = new FormData(); for (const field of fields) { const name = field.name || ''; const value = field.value || ''; const type = field.type || 'text'; - dataList.push(`--{{boundary}}`); if (type === 'text') { - dataList.push(`Content-Disposition: form-data; name="${name}"`); - dataList.push('Content-Type: text/plain'); - dataList.push(''); - dataList.push(value); + formdata.append(name, value); } else if (type === 'file') { - const fileContent = await readFile(value); - dataList.push(`Content-Disposition: form-data; name="$name"; filename="${value.name}"`); - dataList.push(`Content-Type: ${value.type}`); - dataList.push(''); - {% if isNodeJs %}dataList.push(fileContent); - {% else %} - dataList.push(fileContent.content);{% endif %}} + formdata.append(name,{% if isNodeJs %} fs.createReadStream(value){% else %} fileInput.files[0],value{% endif %}); } - dataList.push(`--{{boundary}}--`); - dataList.push(''); - return dataList.join('\r\n'); } -var dataList = []; -buildDataList({{fields_list}}) - .then(data => dataList = data) - .catch(err => console.error(err)); -const payload = dataList.join('\r\n'); + +const payload = buildDataList({{fields_list}}); '''; String kStringRequest = """ From e98ee3d2b1180e63d13246f3cafe47b368879c37 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Wed, 3 Jan 2024 21:27:17 +0530 Subject: [PATCH 20/30] fix: axios is added and fetch issues fixed --- lib/codegen/js/axios.dart | 62 +++++++++++++++++++++++++++++++++++---- lib/codegen/js/fetch.dart | 38 ++++++++---------------- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/lib/codegen/js/axios.dart b/lib/codegen/js/axios.dart index 21c99c8b..4c67f4f7 100644 --- a/lib/codegen/js/axios.dart +++ b/lib/codegen/js/axios.dart @@ -1,8 +1,14 @@ +import 'dart:convert'; import 'package:apidash/consts.dart'; +import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/utils/utils.dart' - show requestModelToHARJsonRequest, padMultilineString, stripUrlParams; -import 'package:apidash/models/models.dart' show RequestModel; + show + padMultilineString, + requestModelToHARJsonRequest, + rowsToFormDataMap, + stripUrlParams; +import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; class AxiosCodeGen { AxiosCodeGen({this.isNodeJs = false}); @@ -10,6 +16,8 @@ class AxiosCodeGen { final bool isNodeJs; String kStringImportNode = """import axios from 'axios'; +{% if isFormDataRequest and isNodeJs %}const fs = require('fs'); +{% endif %} """; @@ -46,13 +54,45 @@ axios(config) console.log(error); }); """; + String kMultiPartBodyTemplate = r''' +async function buildFormData(fields) { + var formdata = new FormData(); + for (const field of fields) { + const name = field.name || ''; + const value = field.value || ''; + const type = field.type || 'text'; + if (type === 'text') { + formdata.append(name, value); + } else if (type === 'file') { + formdata.append(name,{% if isNodeJs %} fs.createReadStream(value){% else %} fileInput.files[0],value{% endif %}); + } + } + return formdata; +} + + +'''; + var kGetFormDataTemplate = '''buildFormData({{fields_list}}); +'''; String? getCode( RequestModel requestModel, String defaultUriScheme, ) { try { - String result = isNodeJs ? kStringImportNode : ""; + List formDataList = requestModel.formDataList ?? []; + jj.Template kNodejsImportTemplate = jj.Template(kStringImportNode); + String importsData = kNodejsImportTemplate.render({ + "isFormDataRequest": requestModel.isFormDataRequest, + "isNodeJs": isNodeJs, + }); + + String result = importsData; + var templateMultiPartBody = jj.Template(kMultiPartBodyTemplate); + var renderedMultiPartBody = templateMultiPartBody.render({ + "isNodeJs": isNodeJs, + }); + result += renderedMultiPartBody; String url = requestModel.url; if (!url.contains("://") && url.isNotEmpty) { @@ -80,18 +120,30 @@ axios(config) } var headers = harJson["headers"]; - if (headers.isNotEmpty) { + if (headers.isNotEmpty || requestModel.isFormDataRequest) { var templateHeader = jj.Template(kTemplateHeader); var m = {}; for (var i in headers) { m[i["name"]] = i["value"]; } + if (requestModel.isFormDataRequest) { + m['Content-Type'] = 'multipart/form-data'; + } result += templateHeader .render({"headers": padMultilineString(kEncoder.convert(m), 2)}); } + var templateBody = jj.Template(kTemplateBody); + if (requestModel.isFormDataRequest) { + var getFieldDataTemplate = jj.Template(kGetFormDataTemplate); + + result += templateBody.render({ + "body": getFieldDataTemplate.render({ + "fields_list": json.encode(rowsToFormDataMap(formDataList)), + }) + }); + } if (harJson["postData"]?["text"] != null) { - var templateBody = jj.Template(kTemplateBody); result += templateBody .render({"body": kEncoder.convert(harJson["postData"]["text"])}); } diff --git a/lib/codegen/js/fetch.dart b/lib/codegen/js/fetch.dart index adbc2eec..715b5e6d 100644 --- a/lib/codegen/js/fetch.dart +++ b/lib/codegen/js/fetch.dart @@ -5,7 +5,7 @@ import 'package:apidash/utils/convert_utils.dart'; import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/utils/utils.dart' - show getNewUuid, padMultilineString, requestModelToHARJsonRequest; + show padMultilineString, requestModelToHARJsonRequest; import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; class FetchCodeGen { @@ -13,14 +13,16 @@ class FetchCodeGen { final bool isNodeJs; - String kStringImportNode = """ + String kStringImportNode = + """ import fetch from 'node-fetch'; {% if isFormDataRequest %}const fs = require('fs');{% endif %} """; - String kTemplateStart = """let url = '{{url}}'; + String kTemplateStart = + """let url = '{{url}}'; let options = { method: '{{method}}' @@ -33,19 +35,9 @@ let options = { String kTemplateBody = """, body: {{body}} """; - String kMultiPartFileReader = ''' -function readFile(file) { - {% if isNodeJs %} return fs.readFile(file, 'binary'); {% else %} return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result); - reader.onerror = reject; - reader.readAsArrayBuffer(file); - }); -{% endif %} -} -'''; - String kMultiPartBodyTemplate = r''' + String kMultiPartBodyTemplate = + r''' async function buildDataList(fields) { var formdata = new FormData(); for (const field of fields) { @@ -58,13 +50,15 @@ async function buildDataList(fields) { } else if (type === 'file') { formdata.append(name,{% if isNodeJs %} fs.createReadStream(value){% else %} fileInput.files[0],value{% endif %}); } - + } + return formdata; } const payload = buildDataList({{fields_list}}); '''; - String kStringRequest = """ + String kStringRequest = + """ }; @@ -90,7 +84,6 @@ fetch(url, options) ) { try { List formDataList = requestModel.formDataList ?? []; - String uuid = getNewUuid(); jj.Template kNodejsImportTemplate = jj.Template(kStringImportNode); String importsData = kNodejsImportTemplate.render({ "isFormDataRequest": requestModel.isFormDataRequest, @@ -98,15 +91,8 @@ fetch(url, options) String result = isNodeJs ? importsData : ""; if (requestModel.isFormDataRequest) { - jj.Template kMultiPartFileReaderTemplate = - jj.Template(kMultiPartFileReader); - result += kMultiPartFileReaderTemplate.render({ - "isNodeJs": isNodeJs, - }); - var boundary = uuid; var templateMultiPartBody = jj.Template(kMultiPartBodyTemplate); result += templateMultiPartBody.render({ - "boundary": boundary, "isNodeJs": isNodeJs, "fields_list": json.encode(rowsToFormDataMap(formDataList)), }); @@ -131,7 +117,7 @@ fetch(url, options) var templateHeader = jj.Template(kTemplateHeader); var m = {}; if (requestModel.isFormDataRequest) { - m["Content-Type"] = "multipart/form-data; boundary=$uuid"; + m["Content-Type"] = "multipart/form-data"; } for (var i in headers) { m[i["name"]] = i["value"]; From 9fa72af06bc171e752f3812fc67102e3b86df9bc Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Wed, 3 Jan 2024 23:10:51 +0530 Subject: [PATCH 21/30] feat: added http and dio code gen --- lib/codegen/dart/dio.dart | 30 ++++++++++-- lib/codegen/dart/http.dart | 83 +++++++++++++++++++++++++++++----- lib/services/http_service.dart | 1 + 3 files changed, 99 insertions(+), 15 deletions(-) diff --git a/lib/codegen/dart/dio.dart b/lib/codegen/dart/dio.dart index 1b4a8820..7453b8ca 100644 --- a/lib/codegen/dart/dio.dart +++ b/lib/codegen/dart/dio.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; + import 'package:apidash/consts.dart'; import 'package:apidash/models/request_model.dart' show RequestModel; +import 'package:apidash/utils/convert_utils.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; @@ -22,6 +25,7 @@ class DartDioCodeGen { headers: requestModel.headersMap, body: requestModel.requestBody, contentType: requestModel.requestBodyContentType, + formData: rowsToFormDataMap(requestModel.formDataList) ?? [], ); return next; } catch (e) { @@ -36,6 +40,7 @@ class DartDioCodeGen { required Map headers, required String? body, required ContentType contentType, + required List> formData, }) { final sbf = StringBuffer(); final emitter = DartEmitter(); @@ -54,9 +59,22 @@ class DartDioCodeGen { literalMap(headers.map((key, value) => MapEntry(key, value))), ); } - + final multiPartList = Code(''' + final List> formDataList = ${json.encode(formData)}; + for (var formField in formDataList) { + if (formField['type'] == 'file') { + formData.files.add(MapEntry( + formField['name'], + await MultipartFile.fromFile(formField['value'], filename: formField['value']), + )); + } else { + formData.fields.add(MapEntry(formField['name'], formField['value'])); + } + } + '''); Expression? dataExp; - if (kMethodsWithBody.contains(method) && (body?.isNotEmpty ?? false)) { + if ((kMethodsWithBody.contains(method) && (body?.isNotEmpty ?? false) || + contentType == ContentType.formdata)) { final strContent = CodeExpression(Code('r\'\'\'$body\'\'\'')); switch (contentType) { // dio dosen't need pass `content-type` header when body is json or plain text @@ -69,7 +87,7 @@ class DartDioCodeGen { dataExp = declareFinal('data').assign(strContent); // when add new type of [ContentType], need update [dataExp]. case ContentType.formdata: - // TODO: Need to Handle this case. + dataExp = declareFinal('data').assign(refer('FormData()')); } } final responseExp = declareFinal('response').assign(InvokeExpression.newOf( @@ -95,6 +113,8 @@ class DartDioCodeGen { if (queryParamExp != null) queryParamExp, if (headerExp != null) headerExp, if (dataExp != null) dataExp, + if ((contentType == ContentType.formdata && formData.isNotEmpty)) + multiPartList, responseExp, refer('print').call([refer('response').property('statusCode')]), refer('print').call([refer('response').property('data')]), @@ -124,6 +144,8 @@ class DartDioCodeGen { sbf.writeln(mainFunction.accept(emitter)); - return DartFormatter(pageWidth: 160).format(sbf.toString()); + return DartFormatter( + pageWidth: contentType == ContentType.formdata ? 70 : 160) + .format(sbf.toString()); } } diff --git a/lib/codegen/dart/http.dart b/lib/codegen/dart/http.dart index 67184c25..9f3ce219 100644 --- a/lib/codegen/dart/http.dart +++ b/lib/codegen/dart/http.dart @@ -1,7 +1,9 @@ +import 'dart:convert'; import 'dart:io'; import 'package:apidash/consts.dart'; import 'package:apidash/models/models.dart' show RequestModel; +import 'package:apidash/utils/convert_utils.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; @@ -24,6 +26,7 @@ class DartHttpCodeGen { headers: requestModel.headersMap, body: requestModel.requestBody, contentType: requestModel.requestBodyContentType, + formData: rowsToFormDataMap(requestModel.formDataList) ?? [], ); return next; } catch (e) { @@ -38,6 +41,7 @@ class DartHttpCodeGen { required Map headers, required String? body, required ContentType contentType, + required List> formData, }) { final uri = Uri.parse(url); @@ -99,7 +103,9 @@ class DartHttpCodeGen { ); } final responseExp = declareFinal('response').assign(InvokeExpression.newOf( - refer('http.${method.name}'), + refer( + 'http.${method.name}', + ), [refer('uri')], { if (headerExp != null) 'headers': refer('headers'), @@ -107,7 +113,36 @@ class DartHttpCodeGen { }, [], ).awaited); + final multiPartRequest = + declareFinal('request').assign(InvokeExpression.newOf( + refer( + 'http.MultipartRequest', + ), + [refer(jsonEncode(method.name.toUpperCase())), refer('uri')], + )); + final multiPartFiles = declareFinal('formDataList').assign(refer( + jsonEncode(formData), + )); + final addHeaders = refer('request.headers.addAll').call([refer('headers')]); + const multiPartList = Code(''' + for (Map formData in formDataList){ + if (formData['type'] == 'text') { + request.fields.addAll({formData['name']: formData['value']}); + } else { + request.files.add( + await http.MultipartFile.fromPath( + formData['name'], + formData['value'], + ), + ); + } + } +'''); + var multiPartRequestSend = + declareFinal('response').assign(refer('request.send()').awaited); + var multiPartResponseBody = declareFinal('responseBody') + .assign(refer('response.stream.bytesToString()').awaited); final mainFunction = Method((m) { final statusRef = refer('statusCode'); m @@ -132,25 +167,49 @@ class DartHttpCodeGen { b.statements.add(headerExp.statement); } b.statements.add(const Code('\n')); - b.statements.add(responseExp.statement); - b.statements.add(const Code('\n')); - b.statements.add(declareVar('statusCode', type: refer('int')) - .assign(refer('response').property('statusCode')) - .statement); + if (contentType == ContentType.formdata) { + if (formData.isNotEmpty) { + b.statements.add(multiPartFiles.statement); + } + b.statements.add(multiPartRequest.statement); + if (formData.isNotEmpty) { + b.statements.add(multiPartList); + } + if (headerExp != null) { + b.statements.add(addHeaders.statement); + } + b.statements.add(multiPartRequestSend.statement); + b.statements.add(multiPartResponseBody.statement); + b.statements.add(declareVar('statusCode', type: refer('int')) + .assign(refer('response').property('statusCode')) + .statement); + b.statements.add(const Code('\n')); + } else { + b.statements.add(responseExp.statement); + b.statements.add(const Code('\n')); + b.statements.add(declareVar('statusCode', type: refer('int')) + .assign(refer('response').property('statusCode')) + .statement); + } + b.statements.add(declareIfElse( condition: statusRef .greaterOrEqualTo(literalNum(200)) .and(statusRef.lessThan(literalNum(300))), body: [ refer('print').call([literalString(r'Status Code: $statusCode')]), - refer('print') - .call([literalString(r'Response Body: ${response.body}')]), + refer('print').call([ + literalString( + 'Response Body: ${contentType == ContentType.formdata ? ':\$responseBody' : '\${response.body}'}') + ]), ], elseBody: [ refer('print') .call([literalString(r'Error Status Code: $statusCode')]), - refer('print').call( - [literalString(r'Error Response Body: ${response.body}')]), + refer('print').call([ + literalString( + 'Error Response Body: ${contentType == ContentType.formdata ? ':\$responseBody' : '\${response.body}'}') + ]), ], )); }); @@ -158,6 +217,8 @@ class DartHttpCodeGen { sbf.writeln(mainFunction.accept(emitter)); - return DartFormatter(pageWidth: 160).format(sbf.toString()); + return DartFormatter( + pageWidth: contentType == ContentType.formdata ? 70 : 160) + .format(sbf.toString()); } } diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart index 8d6e70b9..a88f060d 100644 --- a/lib/services/http_service.dart +++ b/lib/services/http_service.dart @@ -39,6 +39,7 @@ Future<(http.Response?, Duration?, String?)> request( requestModel.method.name.toUpperCase(), requestUrl, ); + multiPartRequest.headers.addAll(headers); for (FormDataModel formData in (requestModel.formDataList ?? [])) { if (formData.type == FormDataType.text) { multiPartRequest.fields.addAll({formData.name: formData.value}); From 06b5eaff22e5dbff6d81793d5b024d7fec30db96 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Thu, 4 Jan 2024 20:50:37 +0530 Subject: [PATCH 22/30] fix: fixed okhttp3 code gen --- lib/codegen/kotlin/okhttp.dart | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/codegen/kotlin/okhttp.dart b/lib/codegen/kotlin/okhttp.dart index 09cd2ce9..995dcaa4 100644 --- a/lib/codegen/kotlin/okhttp.dart +++ b/lib/codegen/kotlin/okhttp.dart @@ -1,5 +1,7 @@ import 'dart:convert'; import 'package:apidash/consts.dart'; +import 'package:apidash/utils/convert_utils.dart'; +import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/utils/utils.dart' show getValidRequestUri, stripUriParams; @@ -61,6 +63,13 @@ import okhttp3.MediaType.Companion.toMediaType"""; } """; +// Converting list of form data objects to kolin multi part data + String kFormDataBody = ''' + val body = MultipartBody.Builder().setType(MultipartBody.FORM){% for item in formDataList %}{% if item.type == 'file' %} + .addFormDataPart("{{item.name}}",null,File("{{item.value}}").asRequestBody("application/octet-stream".toMediaType())) + {% else %}.addFormDataPart("{{item.name}}","{{item.value}}") + {% endif %}{% endfor %}.build() +'''; String? getCode( RequestModel requestModel, @@ -68,7 +77,6 @@ import okhttp3.MediaType.Companion.toMediaType"""; ) { try { String result = ""; - bool hasHeaders = false; bool hasQuery = false; bool hasBody = false; @@ -99,7 +107,13 @@ import okhttp3.MediaType.Companion.toMediaType"""; var method = requestModel.method; var requestBody = requestModel.requestBody; - if (kMethodsWithBody.contains(method) && requestBody != null) { + if (requestModel.isFormDataRequest) { + var formDataTemplate = jj.Template(kFormDataBody); + + result += formDataTemplate.render({ + "formDataList": rowsToFormDataMap(requestModel.formDataList), + }); + } else if (kMethodsWithBody.contains(method) && requestBody != null) { var contentLength = utf8.encode(requestBody).length; if (contentLength > 0) { hasBody = true; @@ -124,7 +138,6 @@ import okhttp3.MediaType.Companion.toMediaType"""; if (headersList != null) { var headers = requestModel.headersMap; if (headers.isNotEmpty) { - hasHeaders = true; result += getHeaders(headers); } } @@ -132,7 +145,7 @@ import okhttp3.MediaType.Companion.toMediaType"""; var templateRequestEnd = jj.Template(kTemplateRequestEnd); result += templateRequestEnd.render({ "method": method.name.toLowerCase(), - "hasBody": hasBody ? "body" : "", + "hasBody": (hasBody || requestModel.isFormDataRequest) ? "body" : "", }); } return result; From 4cf36b645015194c33907e9f8c12499464e2152b Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sun, 7 Jan 2024 14:36:30 +0530 Subject: [PATCH 23/30] Update model & codegen --- lib/codegen/dart/dio.dart | 9 ++-- lib/codegen/dart/http.dart | 14 +++---- lib/codegen/js/axios.dart | 14 ++----- lib/codegen/js/fetch.dart | 22 ++++------ lib/codegen/kotlin/okhttp.dart | 6 +-- lib/codegen/python/http_client.dart | 13 +++--- lib/codegen/python/requests.dart | 13 ++---- lib/models/request_model.dart | 41 ++++++++++++------- lib/utils/convert_utils.dart | 29 +++++++------ .../extensions/request_model_extension.dart | 8 ---- 10 files changed, 71 insertions(+), 98 deletions(-) delete mode 100644 lib/utils/extensions/request_model_extension.dart diff --git a/lib/codegen/dart/dio.dart b/lib/codegen/dart/dio.dart index 72b62ac3..c4a7ed8e 100644 --- a/lib/codegen/dart/dio.dart +++ b/lib/codegen/dart/dio.dart @@ -1,11 +1,8 @@ import 'dart:convert'; - -import 'package:apidash/consts.dart'; -import 'package:apidash/models/request_model.dart' show RequestModel; -import 'package:apidash/utils/convert_utils.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; - +import 'package:apidash/models/request_model.dart' show RequestModel; +import 'package:apidash/consts.dart'; import 'shared.dart'; class DartDioCodeGen { @@ -25,7 +22,7 @@ class DartDioCodeGen { headers: requestModel.enabledHeadersMap, body: requestModel.requestBody, contentType: requestModel.requestBodyContentType, - formData: rowsToFormDataMap(requestModel.formDataList) ?? [], + formData: requestModel.formDataMapList, ); return next; } catch (e) { diff --git a/lib/codegen/dart/http.dart b/lib/codegen/dart/http.dart index b257c662..f301db19 100644 --- a/lib/codegen/dart/http.dart +++ b/lib/codegen/dart/http.dart @@ -1,12 +1,9 @@ import 'dart:convert'; import 'dart:io'; - -import 'package:apidash/consts.dart'; -import 'package:apidash/models/models.dart' show RequestModel; -import 'package:apidash/utils/convert_utils.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; - +import 'package:apidash/models/models.dart' show RequestModel; +import 'package:apidash/consts.dart'; import 'shared.dart'; class DartHttpCodeGen { @@ -26,7 +23,7 @@ class DartHttpCodeGen { headers: requestModel.enabledHeadersMap, body: requestModel.requestBody, contentType: requestModel.requestBodyContentType, - formData: rowsToFormDataMap(requestModel.formDataList) ?? [], + formData: requestModel.formDataMapList, ); return next; } catch (e) { @@ -59,10 +56,11 @@ class DartHttpCodeGen { final strContent = CodeExpression(Code('r\'\'\'$body\'\'\'')); dataExp = declareVar('body', type: refer('String')).assign(strContent); - final hasContentTypeHeader = composeHeaders.keys.any((k) => k.toLowerCase() == HttpHeaders.contentTypeHeader); + final hasContentTypeHeader = composeHeaders.keys + .any((k) => k.toLowerCase() == HttpHeaders.contentTypeHeader); if (!hasContentTypeHeader) { composeHeaders.putIfAbsent(HttpHeaders.contentTypeHeader, - () => kContentTypeMap[contentType] ?? ''); + () => kContentTypeMap[contentType] ?? ''); } } diff --git a/lib/codegen/js/axios.dart b/lib/codegen/js/axios.dart index cd9ee2a4..fc65905b 100644 --- a/lib/codegen/js/axios.dart +++ b/lib/codegen/js/axios.dart @@ -1,14 +1,9 @@ import 'dart:convert'; -import 'package:apidash/consts.dart'; -import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/utils/utils.dart' - show - padMultilineString, - requestModelToHARJsonRequest, - rowsToFormDataMap, - stripUrlParams; -import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; + show padMultilineString, requestModelToHARJsonRequest, stripUrlParams; +import 'package:apidash/models/models.dart' show RequestModel; +import 'package:apidash/consts.dart'; class AxiosCodeGen { AxiosCodeGen({this.isNodeJs = false}); @@ -80,7 +75,6 @@ async function buildFormData(fields) { String defaultUriScheme, ) { try { - List formDataList = requestModel.formDataList ?? []; jj.Template kNodejsImportTemplate = jj.Template(kStringImportNode); String importsData = kNodejsImportTemplate.render({ "isFormDataRequest": requestModel.isFormDataRequest, @@ -139,7 +133,7 @@ async function buildFormData(fields) { result += templateBody.render({ "body": getFieldDataTemplate.render({ - "fields_list": json.encode(rowsToFormDataMap(formDataList)), + "fields_list": json.encode(requestModel.formDataMapList), }) }); } diff --git a/lib/codegen/js/fetch.dart b/lib/codegen/js/fetch.dart index f959a33b..31f7a211 100644 --- a/lib/codegen/js/fetch.dart +++ b/lib/codegen/js/fetch.dart @@ -1,28 +1,23 @@ import 'dart:convert'; - -import 'package:apidash/consts.dart'; -import 'package:apidash/utils/convert_utils.dart'; -import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/utils/utils.dart' show padMultilineString, requestModelToHARJsonRequest; -import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; +import 'package:apidash/models/models.dart' show RequestModel; +import 'package:apidash/consts.dart'; class FetchCodeGen { FetchCodeGen({this.isNodeJs = false}); final bool isNodeJs; - String kStringImportNode = - """ + String kStringImportNode = """ import fetch from 'node-fetch'; {% if isFormDataRequest %}const fs = require('fs');{% endif %} """; - String kTemplateStart = - """let url = '{{url}}'; + String kTemplateStart = """let url = '{{url}}'; let options = { method: '{{method}}' @@ -36,8 +31,7 @@ let options = { body: {{body}} """; - String kMultiPartBodyTemplate = - r''' + String kMultiPartBodyTemplate = r''' async function buildDataList(fields) { var formdata = new FormData(); for (const field of fields) { @@ -57,8 +51,7 @@ async function buildDataList(fields) { const payload = buildDataList({{fields_list}}); '''; - String kStringRequest = - """ + String kStringRequest = """ }; @@ -83,7 +76,6 @@ fetch(url, options) String defaultUriScheme, ) { try { - List formDataList = requestModel.formDataList ?? []; jj.Template kNodejsImportTemplate = jj.Template(kStringImportNode); String importsData = kNodejsImportTemplate.render({ "isFormDataRequest": requestModel.isFormDataRequest, @@ -94,7 +86,7 @@ fetch(url, options) var templateMultiPartBody = jj.Template(kMultiPartBodyTemplate); result += templateMultiPartBody.render({ "isNodeJs": isNodeJs, - "fields_list": json.encode(rowsToFormDataMap(formDataList)), + "fields_list": json.encode(requestModel.formDataMapList), }); } String url = requestModel.url; diff --git a/lib/codegen/kotlin/okhttp.dart b/lib/codegen/kotlin/okhttp.dart index df7774a9..becfdbe4 100644 --- a/lib/codegen/kotlin/okhttp.dart +++ b/lib/codegen/kotlin/okhttp.dart @@ -1,11 +1,9 @@ import 'dart:convert'; -import 'package:apidash/consts.dart'; -import 'package:apidash/utils/convert_utils.dart'; -import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/utils/utils.dart' show getValidRequestUri, stripUriParams; import '../../models/request_model.dart'; +import 'package:apidash/consts.dart'; class KotlinOkHttpCodeGen { final String kTemplateStart = """import okhttp3.OkHttpClient @@ -114,7 +112,7 @@ import okhttp3.MediaType.Companion.toMediaType"""; var formDataTemplate = jj.Template(kFormDataBody); result += formDataTemplate.render({ - "formDataList": rowsToFormDataMap(requestModel.formDataList), + "formDataList": requestModel.formDataMapList, }); } else if (kMethodsWithBody.contains(method) && requestBody != null) { var contentLength = utf8.encode(requestBody).length; diff --git a/lib/codegen/python/http_client.dart b/lib/codegen/python/http_client.dart index 625c3937..cc34cb70 100644 --- a/lib/codegen/python/http_client.dart +++ b/lib/codegen/python/http_client.dart @@ -1,11 +1,10 @@ import 'dart:io'; import 'dart:convert'; -import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; -import 'package:apidash/consts.dart'; import 'package:apidash/utils/utils.dart' - show getNewUuid, getValidRequestUri, padMultilineString, rowsToFormDataMap; -import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; + show getNewUuid, getValidRequestUri, padMultilineString; +import 'package:apidash/models/models.dart' show RequestModel; +import 'package:apidash/consts.dart'; class PythonHttpClientCodeGen { final String kTemplateStart = """import http.client @@ -91,7 +90,6 @@ body = b'\r\n'.join(dataList) RequestModel requestModel, String defaultUriScheme, ) { - List formDataList = requestModel.formDataList ?? []; String uuid = getNewUuid(); try { @@ -154,7 +152,8 @@ body = b'\r\n'.join(dataList) if (headers.isNotEmpty || hasBody) { hasHeaders = true; - bool hasContentTypeHeader = headers.keys.any((k) => k.toLowerCase() == HttpHeaders.contentTypeHeader); + bool hasContentTypeHeader = headers.keys + .any((k) => k.toLowerCase() == HttpHeaders.contentTypeHeader); if (hasBody && !hasContentTypeHeader) { headers[HttpHeaders.contentTypeHeader] = @@ -170,7 +169,7 @@ body = b'\r\n'.join(dataList) var formDataBodyData = jj.Template(kStringFormDataBody); result += formDataBodyData.render( { - "fields_list": json.encode(rowsToFormDataMap(formDataList)), + "fields_list": json.encode(requestModel.formDataMapList), "boundary": uuid, }, ); diff --git a/lib/codegen/python/requests.dart b/lib/codegen/python/requests.dart index 5825584b..a2f359a1 100644 --- a/lib/codegen/python/requests.dart +++ b/lib/codegen/python/requests.dart @@ -1,16 +1,10 @@ import 'dart:io'; import 'dart:convert'; -import 'package:apidash/utils/extensions/request_model_extension.dart'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/consts.dart'; import 'package:apidash/utils/utils.dart' - show - getNewUuid, - getValidRequestUri, - padMultilineString, - rowsToFormDataMap, - stripUriParams; -import 'package:apidash/models/models.dart' show FormDataModel, RequestModel; + show getNewUuid, getValidRequestUri, padMultilineString, stripUriParams; +import 'package:apidash/models/models.dart' show RequestModel; class PythonRequestsCodeGen { final String kTemplateStart = """import requests @@ -107,7 +101,6 @@ print('Response Body:', response.text) bool hasHeaders = false; bool hasBody = false; bool hasJsonBody = false; - List formDataList = requestModel.formDataList ?? []; String uuid = getNewUuid(); String url = requestModel.url; @@ -181,7 +174,7 @@ print('Response Body:', response.text) var formDataBodyData = jj.Template(kStringFormDataBody); result += formDataBodyData.render( { - "fields_list": json.encode(rowsToFormDataMap(formDataList)), + "fields_list": json.encode(requestModel.formDataMapList), "boundary": uuid, }, ); diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index f1dfb492..49277d1f 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -1,6 +1,11 @@ import 'package:flutter/foundation.dart'; -import '../utils/utils.dart'; - show listToFormDataModel, rowsToFormDataMap, mapToRows, rowsToMap, getEnabledRows; +import '../utils/utils.dart' + show + mapListToFormDataModelRows, + rowsToFormDataMapList, + mapToRows, + rowsToMap, + getEnabledRows; import '../consts.dart'; import 'models.dart'; @@ -19,10 +24,10 @@ class RequestModel { this.isParamEnabledList, this.requestBodyContentType = ContentType.json, this.requestBody, + this.requestFormDataList, this.responseStatus, this.message, this.responseModel, - this.formDataList, }); final String id; @@ -35,9 +40,9 @@ class RequestModel { final List? requestParams; final List? isHeaderEnabledList; final List? isParamEnabledList; - final List? requestFormDataList; final ContentType requestBodyContentType; final String? requestBody; + final List? requestFormDataList; final int? responseStatus; final String? message; final ResponseModel? responseModel; @@ -54,6 +59,10 @@ class RequestModel { Map get headersMap => rowsToMap(requestHeaders) ?? {}; Map get paramsMap => rowsToMap(requestParams) ?? {}; + List> get formDataMapList => + rowsToFormDataMapList(requestFormDataList) ?? []; + bool get isFormDataRequest => requestBodyContentType == ContentType.formdata; + RequestModel duplicate({ required String id, }) { @@ -71,7 +80,8 @@ class RequestModel { isParamEnabledList != null ? [...isParamEnabledList!] : null, requestBodyContentType: requestBodyContentType, requestBody: requestBody, - formDataList: formDataList != null ? [...formDataList!] : null, + requestFormDataList: + requestFormDataList != null ? [...requestFormDataList!] : null, ); } @@ -88,10 +98,10 @@ class RequestModel { List? isParamEnabledList, ContentType? requestBodyContentType, String? requestBody, + List? requestFormDataList, int? responseStatus, String? message, ResponseModel? responseModel, - List? formDataList, }) { var headers = requestHeaders ?? this.requestHeaders; var params = requestParams ?? this.requestParams; @@ -111,10 +121,10 @@ class RequestModel { requestBodyContentType: requestBodyContentType ?? this.requestBodyContentType, requestBody: requestBody ?? this.requestBody, + requestFormDataList: requestFormDataList ?? this.requestFormDataList, responseStatus: responseStatus ?? this.responseStatus, message: message ?? this.message, responseModel: responseModel ?? this.responseModel, - formDataList: formDataList ?? this.formDataList, ); } @@ -143,10 +153,10 @@ class RequestModel { requestBodyContentType = kDefaultContentType; } final requestBody = data["requestBody"] as String?; + final requestFormDataList = data["requestFormDataList"]; final responseStatus = data["responseStatus"] as int?; final message = data["message"] as String?; final responseModelJson = data["responseModel"]; - final formDataList = data["formDataList"]; if (responseModelJson != null) { responseModel = @@ -172,11 +182,12 @@ class RequestModel { isParamEnabledList: isParamEnabledList, requestBodyContentType: requestBodyContentType, requestBody: requestBody, + requestFormDataList: requestFormDataList != null + ? mapListToFormDataModelRows(List.from(requestFormDataList)) + : null, responseStatus: responseStatus, message: message, responseModel: responseModel, - formDataList: - formDataList != null ? listToFormDataModel(formDataList) : null, ); } @@ -193,10 +204,10 @@ class RequestModel { "isParamEnabledList": isParamEnabledList, "requestBodyContentType": requestBodyContentType.name, "requestBody": requestBody, + "requestFormDataList": rowsToFormDataMapList(requestFormDataList), "responseStatus": includeResponse ? responseStatus : null, "message": includeResponse ? message : null, "responseModel": includeResponse ? responseModel?.toJson() : null, - "formDataList": rowsToFormDataMap(formDataList) }; } @@ -215,10 +226,10 @@ class RequestModel { "Enabled Params: ${isParamEnabledList.toString()}", "Request Body Content Type: ${requestBodyContentType.toString()}", "Request Body: ${requestBody.toString()}", + "Request FormData: ${requestFormDataList.toString()}", "Response Status: $responseStatus", "Response Message: $message", "Response: ${responseModel.toString()}" - "FormData: ${formDataList.toString()}" ].join("\n"); } @@ -238,10 +249,10 @@ class RequestModel { listEquals(other.isParamEnabledList, isParamEnabledList) && other.requestBodyContentType == requestBodyContentType && other.requestBody == requestBody && + other.requestFormDataList == requestFormDataList && other.responseStatus == responseStatus && other.message == message && - other.responseModel == responseModel && - other.formDataList == formDataList; + other.responseModel == responseModel; } @override @@ -260,10 +271,10 @@ class RequestModel { isParamEnabledList, requestBodyContentType, requestBody, + requestFormDataList, responseStatus, message, responseModel, - formDataList, ); } } diff --git a/lib/utils/convert_utils.dart b/lib/utils/convert_utils.dart index 10aec7a0..bc704af9 100644 --- a/lib/utils/convert_utils.dart +++ b/lib/utils/convert_utils.dart @@ -79,7 +79,18 @@ Map? rowsToMap(List? kvRows, return finalMap; } -List>? rowsToFormDataMap( +List? mapToRows(Map? kvMap) { + if (kvMap == null) { + return null; + } + List finalRows = []; + for (var k in kvMap.keys) { + finalRows.add(NameValueModel(name: k, value: kvMap[k])); + } + return finalRows; +} + +List>? rowsToFormDataMapList( List? kvRows, ) { if (kvRows == null) { @@ -95,18 +106,7 @@ List>? rowsToFormDataMap( return finalMap; } -List? mapToRows(Map? kvMap) { - if (kvMap == null) { - return null; - } - List finalRows = []; - for (var k in kvMap.keys) { - finalRows.add(NameValueModel(name: k, value: kvMap[k])); - } - return finalRows; -} - -List? listToFormDataModel(List? kvMap) { +List? mapListToFormDataModelRows(List? kvMap) { if (kvMap == null) { return null; } @@ -123,8 +123,7 @@ List? listToFormDataModel(List? kvMap) { } FormDataType getFormDataType(String? type) { - List formData = FormDataType.values; - return formData.firstWhere((element) => element.name == type, + return FormDataType.values.firstWhere((element) => element.name == type, orElse: () => FormDataType.text); } diff --git a/lib/utils/extensions/request_model_extension.dart b/lib/utils/extensions/request_model_extension.dart deleted file mode 100644 index 95563a05..00000000 --- a/lib/utils/extensions/request_model_extension.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:apidash/consts.dart'; -import 'package:apidash/models/models.dart'; - -extension RequestModelExtension on RequestModel { - bool get isFormDataRequest { - return requestBodyContentType == ContentType.formdata; - } -} From 462519610a9834794417ff2a85be1f2118f84a1e Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sun, 7 Jan 2024 14:45:18 +0530 Subject: [PATCH 24/30] fixes --- lib/providers/collection_providers.dart | 1 - lib/services/http_service.dart | 10 ++++++---- lib/utils/har_utils.dart | 11 +++++------ lib/widgets/form_data_widget.dart | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 6aee2c2e..415c9715 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -149,7 +149,6 @@ class CollectionStateNotifier responseStatus: responseStatus, message: message, responseModel: responseModel); - ); //print(newModel); var map = {...state!}; map[id] = newModel; diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart index aa46f010..1b8b8395 100644 --- a/lib/services/http_service.dart +++ b/lib/services/http_service.dart @@ -29,10 +29,11 @@ Future<(http.Response?, Duration?, String?)> request( if (contentLength > 0) { body = requestBody; headers[HttpHeaders.contentLengthHeader] = contentLength.toString(); - final hasContentTypeHeader = headers.keys.any((k) => k.toLowerCase() == HttpHeaders.contentTypeHeader); + final hasContentTypeHeader = headers.keys + .any((k) => k.toLowerCase() == HttpHeaders.contentTypeHeader); if (!hasContentTypeHeader) { - headers[HttpHeaders.contentTypeHeader] = - kContentTypeMap[requestModel.requestBodyContentType] ?? ""; + headers[HttpHeaders.contentTypeHeader] = + kContentTypeMap[requestModel.requestBodyContentType] ?? ""; } } } @@ -43,7 +44,8 @@ Future<(http.Response?, Duration?, String?)> request( requestUrl, ); multiPartRequest.headers.addAll(headers); - for (FormDataModel formData in (requestModel.formDataList ?? [])) { + for (FormDataModel formData + in (requestModel.requestFormDataList ?? [])) { if (formData.type == FormDataType.text) { multiPartRequest.fields.addAll({formData.name: formData.value}); } else { diff --git a/lib/utils/har_utils.dart b/lib/utils/har_utils.dart index 1d7c5073..da338a52 100644 --- a/lib/utils/har_utils.dart +++ b/lib/utils/har_utils.dart @@ -136,8 +136,9 @@ Map requestModelToHARJsonRequest( var headers = useEnabled ? requestModel.enabledHeadersMap : requestModel.headersMap; if (headers.isNotEmpty || hasBody) { - bool hasContentTypeHeader = headers.keys.any((k) => k.toLowerCase() == HttpHeaders.contentTypeHeader); - + bool hasContentTypeHeader = headers.keys + .any((k) => k.toLowerCase() == HttpHeaders.contentTypeHeader); + if (hasBody && !hasContentTypeHeader) { var m = { "name": "Content-Type", @@ -157,10 +158,8 @@ Map requestModelToHARJsonRequest( } } } - List formDataList = requestModel.formDataList ?? []; - if (requestModel.requestBodyContentType == ContentType.formdata) { - var formListMap = rowsToFormDataMap(formDataList); - json["formData"] = formListMap; + if (requestModel.isFormDataRequest) { + json["formData"] = requestModel.formDataMapList; } if (exportMode) { json["comment"] = ""; diff --git a/lib/widgets/form_data_widget.dart b/lib/widgets/form_data_widget.dart index 9a90262a..bb4782d2 100644 --- a/lib/widgets/form_data_widget.dart +++ b/lib/widgets/form_data_widget.dart @@ -29,7 +29,7 @@ class _FormDataBodyState extends ConsumerState { @override Widget build(BuildContext context) { final activeId = ref.watch(activeIdStateProvider); - var formRows = ref.read(activeRequestModelProvider)?.formDataList; + var formRows = ref.read(activeRequestModelProvider)?.requestFormDataList; rows = formRows == null || formRows.isEmpty ? [kFormDataEmptyModel] : formRows; @@ -217,7 +217,7 @@ class _FormDataBodyState extends ConsumerState { void _onFieldChange(String activeId) { ref.read(collectionStateNotifierProvider.notifier).update( activeId, - formDataList: rows, + requestFormDataList: rows, ); } } From 5153841a9796a62e7f070e477ec17d1185dcaedf Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sun, 7 Jan 2024 15:12:46 +0530 Subject: [PATCH 25/30] fix: curl gen issue fixes --- lib/codegen/others/curl.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codegen/others/curl.dart b/lib/codegen/others/curl.dart index 50dfb503..4ae610de 100644 --- a/lib/codegen/others/curl.dart +++ b/lib/codegen/others/curl.dart @@ -52,7 +52,7 @@ class cURLCodeGen { } } if (harJson['formData'] != null) { - var formDataList = harJson['formData'] as List>; + var formDataList = harJson['formData'] as List>; for (var formData in formDataList) { var templateFormData = jj.Template(kTemplateFormData); if (formData['type'] != null && From 30791df102e5c7504f071f6b9a2153848337176b Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sun, 7 Jan 2024 16:13:53 +0530 Subject: [PATCH 26/30] Fix tests --- lib/codegen/js/fetch.dart | 3 ++- lib/codegen/python/http_client.dart | 1 - test/models/request_model_test.dart | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/codegen/js/fetch.dart b/lib/codegen/js/fetch.dart index 31f7a211..ccebf616 100644 --- a/lib/codegen/js/fetch.dart +++ b/lib/codegen/js/fetch.dart @@ -28,7 +28,8 @@ let options = { """; String kTemplateBody = """, - body: {{body}} + body: +{{body}} """; String kMultiPartBodyTemplate = r''' diff --git a/lib/codegen/python/http_client.dart b/lib/codegen/python/http_client.dart index b95ebf4c..39c39864 100644 --- a/lib/codegen/python/http_client.dart +++ b/lib/codegen/python/http_client.dart @@ -12,7 +12,6 @@ class PythonHttpClientCodeGen { import mimetypes from codecs import encode {% endif %} - """; String kTemplateParams = """ diff --git a/test/models/request_model_test.dart b/test/models/request_model_test.dart index b1b56bfd..66064d31 100644 --- a/test/models/request_model_test.dart +++ b/test/models/request_model_test.dart @@ -113,6 +113,7 @@ void main() { "requestBody": '''{ "text":"WORLD" }''', + 'requestFormDataList': null, 'responseStatus': null, 'message': null, 'responseModel': null @@ -147,6 +148,7 @@ void main() { "Enabled Params: null", "Request Body Content Type: ContentType.json", 'Request Body: {\n"text":"WORLD"\n}', + 'Request FormData: null', "Response Status: null", "Response Message: null", "Response: null" From b532044981d12397522f1e42a7d062df7bddab38 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sun, 7 Jan 2024 19:32:09 +0530 Subject: [PATCH 27/30] chore: fixed code generation test cases --- lib/codegen/js/axios.dart | 22 ++++++++++++--------- lib/codegen/js/fetch.dart | 1 - lib/codegen/python/http_client.dart | 3 +-- test/codegen/js_fetch_codegen_test.dart | 18 ++++++----------- test/codegen/nodejs_fetch_codegen_test.dart | 18 ++++++----------- test/models/request_model_test.dart | 2 ++ 6 files changed, 28 insertions(+), 36 deletions(-) diff --git a/lib/codegen/js/axios.dart b/lib/codegen/js/axios.dart index fc65905b..ef73f646 100644 --- a/lib/codegen/js/axios.dart +++ b/lib/codegen/js/axios.dart @@ -10,10 +10,10 @@ class AxiosCodeGen { final bool isNodeJs; - String kStringImportNode = """import axios from 'axios'; -{% if isFormDataRequest and isNodeJs %}const fs = require('fs'); -{% endif %} + String kStringImportNode = """{% if isNodeJs %}import axios from 'axios'; +{% endif %}{% if isFormDataRequest and isNodeJs %} +const fs = require('fs');{% endif %} """; String kTemplateStart = """let config = { @@ -82,11 +82,14 @@ async function buildFormData(fields) { }); String result = importsData; - var templateMultiPartBody = jj.Template(kMultiPartBodyTemplate); - var renderedMultiPartBody = templateMultiPartBody.render({ - "isNodeJs": isNodeJs, - }); - result += renderedMultiPartBody; + if (requestModel.isFormDataRequest && + requestModel.formDataMapList.isNotEmpty) { + var templateMultiPartBody = jj.Template(kMultiPartBodyTemplate); + var renderedMultiPartBody = templateMultiPartBody.render({ + "isNodeJs": isNodeJs, + }); + result += renderedMultiPartBody; + } String url = requestModel.url; if (!url.contains("://") && url.isNotEmpty) { @@ -128,7 +131,8 @@ async function buildFormData(fields) { } var templateBody = jj.Template(kTemplateBody); - if (requestModel.isFormDataRequest) { + if (requestModel.isFormDataRequest && + requestModel.formDataMapList.isNotEmpty) { var getFieldDataTemplate = jj.Template(kGetFormDataTemplate); result += templateBody.render({ diff --git a/lib/codegen/js/fetch.dart b/lib/codegen/js/fetch.dart index 31f7a211..fb2b4e36 100644 --- a/lib/codegen/js/fetch.dart +++ b/lib/codegen/js/fetch.dart @@ -14,7 +14,6 @@ class FetchCodeGen { import fetch from 'node-fetch'; {% if isFormDataRequest %}const fs = require('fs');{% endif %} - """; String kTemplateStart = """let url = '{{url}}'; diff --git a/lib/codegen/python/http_client.dart b/lib/codegen/python/http_client.dart index cc34cb70..8efb368a 100644 --- a/lib/codegen/python/http_client.dart +++ b/lib/codegen/python/http_client.dart @@ -7,8 +7,7 @@ import 'package:apidash/models/models.dart' show RequestModel; import 'package:apidash/consts.dart'; class PythonHttpClientCodeGen { - final String kTemplateStart = """import http.client -{% if isFormDataRequest %} + final String kTemplateStart = """import http.client{% if isFormDataRequest %} import mimetypes from codecs import encode {% endif %} diff --git a/test/codegen/js_fetch_codegen_test.dart b/test/codegen/js_fetch_codegen_test.dart index 43d053f9..e7426bf1 100644 --- a/test/codegen/js_fetch_codegen_test.dart +++ b/test/codegen/js_fetch_codegen_test.dart @@ -398,8 +398,7 @@ let options = { headers: { "Content-Type": "text/plain" }, - body: -"{\n\"text\": \"I LOVE Flutter\"\n}" + body: "{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -428,8 +427,7 @@ let options = { headers: { "Content-Type": "application/json" }, - body: -"{\n\"text\": \"I LOVE Flutter\"\n}" + body: "{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -459,8 +457,7 @@ let options = { "Content-Type": "application/json", "User-Agent": "Test Agent" }, - body: -"{\n\"text\": \"I LOVE Flutter\"\n}" + body: "{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -491,8 +488,7 @@ let options = { headers: { "Content-Type": "application/json" }, - body: -"{\n\"name\": \"morpheus\",\n\"job\": \"zion resident\"\n}" + body: "{\n\"name\": \"morpheus\",\n\"job\": \"zion resident\"\n}" }; let status; @@ -523,8 +519,7 @@ let options = { headers: { "Content-Type": "application/json" }, - body: -"{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" + body: "{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" }; let status; @@ -580,8 +575,7 @@ let options = { headers: { "Content-Type": "application/json" }, - body: -"{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" + body: "{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" }; let status; diff --git a/test/codegen/nodejs_fetch_codegen_test.dart b/test/codegen/nodejs_fetch_codegen_test.dart index c06ceee5..feca786d 100644 --- a/test/codegen/nodejs_fetch_codegen_test.dart +++ b/test/codegen/nodejs_fetch_codegen_test.dart @@ -418,8 +418,7 @@ let options = { headers: { "Content-Type": "text/plain" }, - body: -"{\n\"text\": \"I LOVE Flutter\"\n}" + body: "{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -450,8 +449,7 @@ let options = { headers: { "Content-Type": "application/json" }, - body: -"{\n\"text\": \"I LOVE Flutter\"\n}" + body: "{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -483,8 +481,7 @@ let options = { "Content-Type": "application/json", "User-Agent": "Test Agent" }, - body: -"{\n\"text\": \"I LOVE Flutter\"\n}" + body: "{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -517,8 +514,7 @@ let options = { headers: { "Content-Type": "application/json" }, - body: -"{\n\"name\": \"morpheus\",\n\"job\": \"zion resident\"\n}" + body: "{\n\"name\": \"morpheus\",\n\"job\": \"zion resident\"\n}" }; let status; @@ -551,8 +547,7 @@ let options = { headers: { "Content-Type": "application/json" }, - body: -"{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" + body: "{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" }; let status; @@ -612,8 +607,7 @@ let options = { headers: { "Content-Type": "application/json" }, - body: -"{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" + body: "{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" }; let status; diff --git a/test/models/request_model_test.dart b/test/models/request_model_test.dart index b1b56bfd..66064d31 100644 --- a/test/models/request_model_test.dart +++ b/test/models/request_model_test.dart @@ -113,6 +113,7 @@ void main() { "requestBody": '''{ "text":"WORLD" }''', + 'requestFormDataList': null, 'responseStatus': null, 'message': null, 'responseModel': null @@ -147,6 +148,7 @@ void main() { "Enabled Params: null", "Request Body Content Type: ContentType.json", 'Request Body: {\n"text":"WORLD"\n}', + 'Request FormData: null', "Response Status: null", "Response Message: null", "Response: null" From 9d8379e9ac5cced7a03d6e67f419558548f836c5 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sun, 7 Jan 2024 19:50:25 +0530 Subject: [PATCH 28/30] chore: fixed spacing issues in nodejs axios codegen --- lib/codegen/js/axios.dart | 6 +++--- test/codegen/nodejs_axios_codegen_test.dart | 21 --------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/lib/codegen/js/axios.dart b/lib/codegen/js/axios.dart index ef73f646..ee826e32 100644 --- a/lib/codegen/js/axios.dart +++ b/lib/codegen/js/axios.dart @@ -11,9 +11,7 @@ class AxiosCodeGen { final bool isNodeJs; String kStringImportNode = """{% if isNodeJs %}import axios from 'axios'; - -{% endif %}{% if isFormDataRequest and isNodeJs %} -const fs = require('fs');{% endif %} +{% endif %}{% if isFormDataRequest and isNodeJs %}const fs = require('fs');{% endif %} """; String kTemplateStart = """let config = { @@ -50,6 +48,8 @@ axios(config) }); """; String kMultiPartBodyTemplate = r''' + + async function buildFormData(fields) { var formdata = new FormData(); for (const field of fields) { diff --git a/test/codegen/nodejs_axios_codegen_test.dart b/test/codegen/nodejs_axios_codegen_test.dart index 519f5ab5..974b410e 100644 --- a/test/codegen/nodejs_axios_codegen_test.dart +++ b/test/codegen/nodejs_axios_codegen_test.dart @@ -8,7 +8,6 @@ void main() { group('GET Request', () { test('GET 1', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com', method: 'get' @@ -31,7 +30,6 @@ axios(config) test('GET 2', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/country/data', method: 'get', @@ -57,7 +55,6 @@ axios(config) test('GET 3', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/country/data', method: 'get', @@ -83,7 +80,6 @@ axios(config) test('GET 4', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/humanize/social', method: 'get', @@ -113,7 +109,6 @@ axios(config) test('GET 5', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.github.com/repos/foss42/apidash', method: 'get', @@ -139,7 +134,6 @@ axios(config) test('GET 6', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.github.com/repos/foss42/apidash', method: 'get', @@ -168,7 +162,6 @@ axios(config) test('GET 7', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com', method: 'get' @@ -191,7 +184,6 @@ axios(config) test('GET 8', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.github.com/repos/foss42/apidash', method: 'get', @@ -220,7 +212,6 @@ axios(config) test('GET 9', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/humanize/social', method: 'get', @@ -247,7 +238,6 @@ axios(config) test('GET 10', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/humanize/social', method: 'get', @@ -278,7 +268,6 @@ axios(config) test('GET 11', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/humanize/social', method: 'get', @@ -308,7 +297,6 @@ axios(config) test('GET 12', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/humanize/social', method: 'get' @@ -333,7 +321,6 @@ axios(config) group('HEAD Request', () { test('HEAD 1', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com', method: 'head' @@ -356,7 +343,6 @@ axios(config) test('HEAD 2', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'http://api.foss42.com', method: 'head' @@ -381,7 +367,6 @@ axios(config) group('POST Request', () { test('POST 1', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/case/lower', method: 'post', @@ -408,7 +393,6 @@ axios(config) test('POST 2', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/case/lower', method: 'post', @@ -435,7 +419,6 @@ axios(config) test('POST 3', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://api.foss42.com/case/lower', method: 'post', @@ -465,7 +448,6 @@ axios(config) group('PUT Request', () { test('PUT 1', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://reqres.in/api/users/2', method: 'put', @@ -494,7 +476,6 @@ axios(config) group('PATCH Request', () { test('PATCH 1', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://reqres.in/api/users/2', method: 'patch', @@ -523,7 +504,6 @@ axios(config) group('DELETE Request', () { test('DELETE 1', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://reqres.in/api/users/2', method: 'delete' @@ -546,7 +526,6 @@ axios(config) test('DELETE 2', () { const expectedCode = r"""import axios from 'axios'; - let config = { url: 'https://reqres.in/api/users/2', method: 'delete', From fe3bea31bd2dd25250a3703dc0d3ec7841ae70b2 Mon Sep 17 00:00:00 2001 From: Vidya Sagar <59490902+vidya-hub@users.noreply.github.com> Date: Sun, 7 Jan 2024 20:21:37 +0530 Subject: [PATCH 29/30] rev: reverted changes in js code gen --- lib/codegen/js/axios.dart | 1 + lib/codegen/js/fetch.dart | 3 ++- test/codegen/js_fetch_codegen_test.dart | 18 ++++++++++++------ test/codegen/nodejs_axios_codegen_test.dart | 21 +++++++++++++++++++++ test/codegen/nodejs_fetch_codegen_test.dart | 18 ++++++++++++------ 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/lib/codegen/js/axios.dart b/lib/codegen/js/axios.dart index ee826e32..1aeee285 100644 --- a/lib/codegen/js/axios.dart +++ b/lib/codegen/js/axios.dart @@ -11,6 +11,7 @@ class AxiosCodeGen { final bool isNodeJs; String kStringImportNode = """{% if isNodeJs %}import axios from 'axios'; + {% endif %}{% if isFormDataRequest and isNodeJs %}const fs = require('fs');{% endif %} """; diff --git a/lib/codegen/js/fetch.dart b/lib/codegen/js/fetch.dart index fb2b4e36..4ef2e551 100644 --- a/lib/codegen/js/fetch.dart +++ b/lib/codegen/js/fetch.dart @@ -27,7 +27,8 @@ let options = { """; String kTemplateBody = """, - body: {{body}} + body: +{{body}} """; String kMultiPartBodyTemplate = r''' diff --git a/test/codegen/js_fetch_codegen_test.dart b/test/codegen/js_fetch_codegen_test.dart index e7426bf1..43d053f9 100644 --- a/test/codegen/js_fetch_codegen_test.dart +++ b/test/codegen/js_fetch_codegen_test.dart @@ -398,7 +398,8 @@ let options = { headers: { "Content-Type": "text/plain" }, - body: "{\n\"text\": \"I LOVE Flutter\"\n}" + body: +"{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -427,7 +428,8 @@ let options = { headers: { "Content-Type": "application/json" }, - body: "{\n\"text\": \"I LOVE Flutter\"\n}" + body: +"{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -457,7 +459,8 @@ let options = { "Content-Type": "application/json", "User-Agent": "Test Agent" }, - body: "{\n\"text\": \"I LOVE Flutter\"\n}" + body: +"{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -488,7 +491,8 @@ let options = { headers: { "Content-Type": "application/json" }, - body: "{\n\"name\": \"morpheus\",\n\"job\": \"zion resident\"\n}" + body: +"{\n\"name\": \"morpheus\",\n\"job\": \"zion resident\"\n}" }; let status; @@ -519,7 +523,8 @@ let options = { headers: { "Content-Type": "application/json" }, - body: "{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" + body: +"{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" }; let status; @@ -575,7 +580,8 @@ let options = { headers: { "Content-Type": "application/json" }, - body: "{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" + body: +"{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" }; let status; diff --git a/test/codegen/nodejs_axios_codegen_test.dart b/test/codegen/nodejs_axios_codegen_test.dart index 974b410e..519f5ab5 100644 --- a/test/codegen/nodejs_axios_codegen_test.dart +++ b/test/codegen/nodejs_axios_codegen_test.dart @@ -8,6 +8,7 @@ void main() { group('GET Request', () { test('GET 1', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com', method: 'get' @@ -30,6 +31,7 @@ axios(config) test('GET 2', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/country/data', method: 'get', @@ -55,6 +57,7 @@ axios(config) test('GET 3', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/country/data', method: 'get', @@ -80,6 +83,7 @@ axios(config) test('GET 4', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/humanize/social', method: 'get', @@ -109,6 +113,7 @@ axios(config) test('GET 5', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.github.com/repos/foss42/apidash', method: 'get', @@ -134,6 +139,7 @@ axios(config) test('GET 6', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.github.com/repos/foss42/apidash', method: 'get', @@ -162,6 +168,7 @@ axios(config) test('GET 7', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com', method: 'get' @@ -184,6 +191,7 @@ axios(config) test('GET 8', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.github.com/repos/foss42/apidash', method: 'get', @@ -212,6 +220,7 @@ axios(config) test('GET 9', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/humanize/social', method: 'get', @@ -238,6 +247,7 @@ axios(config) test('GET 10', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/humanize/social', method: 'get', @@ -268,6 +278,7 @@ axios(config) test('GET 11', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/humanize/social', method: 'get', @@ -297,6 +308,7 @@ axios(config) test('GET 12', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/humanize/social', method: 'get' @@ -321,6 +333,7 @@ axios(config) group('HEAD Request', () { test('HEAD 1', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com', method: 'head' @@ -343,6 +356,7 @@ axios(config) test('HEAD 2', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'http://api.foss42.com', method: 'head' @@ -367,6 +381,7 @@ axios(config) group('POST Request', () { test('POST 1', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/case/lower', method: 'post', @@ -393,6 +408,7 @@ axios(config) test('POST 2', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/case/lower', method: 'post', @@ -419,6 +435,7 @@ axios(config) test('POST 3', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://api.foss42.com/case/lower', method: 'post', @@ -448,6 +465,7 @@ axios(config) group('PUT Request', () { test('PUT 1', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://reqres.in/api/users/2', method: 'put', @@ -476,6 +494,7 @@ axios(config) group('PATCH Request', () { test('PATCH 1', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://reqres.in/api/users/2', method: 'patch', @@ -504,6 +523,7 @@ axios(config) group('DELETE Request', () { test('DELETE 1', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://reqres.in/api/users/2', method: 'delete' @@ -526,6 +546,7 @@ axios(config) test('DELETE 2', () { const expectedCode = r"""import axios from 'axios'; + let config = { url: 'https://reqres.in/api/users/2', method: 'delete', diff --git a/test/codegen/nodejs_fetch_codegen_test.dart b/test/codegen/nodejs_fetch_codegen_test.dart index feca786d..c06ceee5 100644 --- a/test/codegen/nodejs_fetch_codegen_test.dart +++ b/test/codegen/nodejs_fetch_codegen_test.dart @@ -418,7 +418,8 @@ let options = { headers: { "Content-Type": "text/plain" }, - body: "{\n\"text\": \"I LOVE Flutter\"\n}" + body: +"{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -449,7 +450,8 @@ let options = { headers: { "Content-Type": "application/json" }, - body: "{\n\"text\": \"I LOVE Flutter\"\n}" + body: +"{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -481,7 +483,8 @@ let options = { "Content-Type": "application/json", "User-Agent": "Test Agent" }, - body: "{\n\"text\": \"I LOVE Flutter\"\n}" + body: +"{\n\"text\": \"I LOVE Flutter\"\n}" }; let status; @@ -514,7 +517,8 @@ let options = { headers: { "Content-Type": "application/json" }, - body: "{\n\"name\": \"morpheus\",\n\"job\": \"zion resident\"\n}" + body: +"{\n\"name\": \"morpheus\",\n\"job\": \"zion resident\"\n}" }; let status; @@ -547,7 +551,8 @@ let options = { headers: { "Content-Type": "application/json" }, - body: "{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" + body: +"{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" }; let status; @@ -607,7 +612,8 @@ let options = { headers: { "Content-Type": "application/json" }, - body: "{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" + body: +"{\n\"name\": \"marfeus\",\n\"job\": \"accountant\"\n}" }; let status; From b92ac78424374cd53b56107294de20b6f74d9b63 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sun, 7 Jan 2024 20:42:23 +0530 Subject: [PATCH 30/30] Fix python codegens --- lib/codegen/python/http_client.dart | 6 ++++-- lib/codegen/python/requests.dart | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/codegen/python/http_client.dart b/lib/codegen/python/http_client.dart index 086151bb..2d002ca7 100644 --- a/lib/codegen/python/http_client.dart +++ b/lib/codegen/python/http_client.dart @@ -7,8 +7,8 @@ import 'package:apidash/models/models.dart' show RequestModel; import 'package:apidash/consts.dart'; class PythonHttpClientCodeGen { - final String kTemplateStart = """import http.client{% if isFormDataRequest %} -import mimetypes + final String kTemplateStart = """import http.client +{% if isFormDataRequest %}import mimetypes from codecs import encode {% endif %} """; @@ -61,6 +61,7 @@ print(data.decode("utf-8")) """; final String kStringFormDataBody = r''' + def build_data_list(fields): dataList = [] for field in fields: @@ -81,6 +82,7 @@ def build_data_list(fields): dataList.append(encode(f'--{{boundary}}--')) dataList.append(encode('')) return dataList + dataList = build_data_list({{fields_list}}) body = b'\r\n'.join(dataList) '''; diff --git a/lib/codegen/python/requests.dart b/lib/codegen/python/requests.dart index a2f359a1..4d9523e4 100644 --- a/lib/codegen/python/requests.dart +++ b/lib/codegen/python/requests.dart @@ -8,8 +8,7 @@ import 'package:apidash/models/models.dart' show RequestModel; class PythonRequestsCodeGen { final String kTemplateStart = """import requests -{% if isFormDataRequest %} -import mimetypes +{% if isFormDataRequest %}import mimetypes from codecs import encode {% endif %} url = '{{url}}' @@ -51,6 +50,7 @@ response = requests.{{method}}(url """; final String kStringFormDataBody = r''' + def build_data_list(fields): dataList = [] for field in fields: @@ -69,10 +69,10 @@ def build_data_list(fields): dataList.append(encode(f'Content-Type: {mimetypes.guess_type(value)[0] or "application/octet-stream"}')) dataList.append(encode('')) dataList.append(open(value, 'rb').read()) - dataList.append(encode('--{{boundary}}--')) dataList.append(encode('')) return dataList + dataList = build_data_list({{fields_list}}) payload = b'\r\n'.join(dataList) ''';