Files
apidash/lib/providers/collection_providers.dart
Udhay-Adithya fe4858ecd0 feat: update pre-request and post-response script handling in CollectionStateNotifier
- Changed return type of handlePreRequestScript and handlePostResponseScript to Future<RequestModel> for better type safety.
- Updated the logic to return the modified request model after executing scripts.
- Ensured that the request model is updated correctly in the sendRequest method after executing the pre-request script.
2025-05-19 16:54:32 +05:30

574 lines
18 KiB
Dart

import 'package:apidash/services/flutter_js_service.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/consts.dart';
import 'providers.dart';
import '../models/models.dart';
import '../services/services.dart' show hiveHandler, HiveHandler;
import '../utils/utils.dart'
show getNewUuid, collectionToHAR, substituteHttpRequestModel;
final selectedIdStateProvider = StateProvider<String?>((ref) => null);
final selectedRequestModelProvider = StateProvider<RequestModel?>((ref) {
final selectedId = ref.watch(selectedIdStateProvider);
final collection = ref.watch(collectionStateNotifierProvider);
if (selectedId == null || collection == null) {
return null;
} else {
return collection[selectedId];
}
});
final requestSequenceProvider = StateProvider<List<String>>((ref) {
var ids = hiveHandler.getIds();
return ids ?? [];
});
final StateNotifierProvider<CollectionStateNotifier, Map<String, RequestModel>?>
collectionStateNotifierProvider =
StateNotifierProvider((ref) => CollectionStateNotifier(
ref,
hiveHandler,
));
class CollectionStateNotifier
extends StateNotifier<Map<String, RequestModel>?> {
CollectionStateNotifier(
this.ref,
this.hiveHandler,
) : super(null) {
var status = loadData();
Future.microtask(() {
if (status) {
ref.read(requestSequenceProvider.notifier).state = [
state!.keys.first,
];
}
ref.read(selectedIdStateProvider.notifier).state =
ref.read(requestSequenceProvider)[0];
});
}
final Ref ref;
final HiveHandler hiveHandler;
final baseHttpResponseModel = const HttpResponseModel();
bool hasId(String id) => state?.keys.contains(id) ?? false;
RequestModel? getRequestModel(String id) {
return state?[id];
}
void unsave() {
ref.read(hasUnsavedChangesProvider.notifier).state = true;
}
void add() {
final id = getNewUuid();
final newRequestModel = RequestModel(
id: id,
httpRequestModel: const HttpRequestModel(),
);
var map = {...state!};
map[id] = newRequestModel;
state = map;
ref
.read(requestSequenceProvider.notifier)
.update((state) => [id, ...state]);
ref.read(selectedIdStateProvider.notifier).state = newRequestModel.id;
unsave();
}
void addRequestModel(
HttpRequestModel httpRequestModel, {
String? name,
}) {
final id = getNewUuid();
final newRequestModel = RequestModel(
id: id,
name: name ?? "",
httpRequestModel: httpRequestModel,
);
var map = {...state!};
map[id] = newRequestModel;
state = map;
ref
.read(requestSequenceProvider.notifier)
.update((state) => [id, ...state]);
ref.read(selectedIdStateProvider.notifier).state = newRequestModel.id;
unsave();
}
void reorder(int oldIdx, int newIdx) {
var itemIds = ref.read(requestSequenceProvider);
final itemId = itemIds.removeAt(oldIdx);
itemIds.insert(newIdx, itemId);
ref.read(requestSequenceProvider.notifier).state = [...itemIds];
unsave();
}
void remove({String? id}) {
final rId = id ?? ref.read(selectedIdStateProvider);
var itemIds = ref.read(requestSequenceProvider);
int idx = itemIds.indexOf(rId!);
cancelHttpRequest(rId);
itemIds.remove(rId);
ref.read(requestSequenceProvider.notifier).state = [...itemIds];
String? newId;
if (idx == 0 && itemIds.isNotEmpty) {
newId = itemIds[0];
} else if (itemIds.length > 1) {
newId = itemIds[idx - 1];
} else {
newId = null;
}
ref.read(selectedIdStateProvider.notifier).state = newId;
var map = {...state!};
map.remove(rId);
state = map;
unsave();
}
void clearResponse({String? id}) {
final rId = id ?? ref.read(selectedIdStateProvider);
if (rId == null || state?[rId] == null) return;
var currentModel = state![rId]!;
final newModel = currentModel.copyWith(
responseStatus: null,
message: null,
httpResponseModel: null,
isWorking: false,
sendingTime: null,
);
var map = {...state!};
map[rId] = newModel;
state = map;
unsave();
}
void duplicate({String? id}) {
final rId = id ?? ref.read(selectedIdStateProvider);
final newId = getNewUuid();
var itemIds = ref.read(requestSequenceProvider);
int idx = itemIds.indexOf(rId!);
var currentModel = state![rId]!;
final newModel = currentModel.copyWith(
id: newId,
name: "${currentModel.name} (copy)",
requestTabIndex: 0,
responseStatus: null,
message: null,
httpResponseModel: null,
isWorking: false,
sendingTime: null,
);
itemIds.insert(idx + 1, newId);
var map = {...state!};
map[newId] = newModel;
state = map;
ref.read(requestSequenceProvider.notifier).state = [...itemIds];
ref.read(selectedIdStateProvider.notifier).state = newId;
unsave();
}
void duplicateFromHistory(HistoryRequestModel historyRequestModel) {
final newId = getNewUuid();
var itemIds = ref.read(requestSequenceProvider);
var currentModel = historyRequestModel;
final newModel = RequestModel(
id: newId,
name: "${currentModel.metaData.name} (history)",
httpRequestModel: currentModel.httpRequestModel,
responseStatus: currentModel.metaData.responseStatus,
message: kResponseCodeReasons[currentModel.metaData.responseStatus],
httpResponseModel: currentModel.httpResponseModel,
isWorking: false,
sendingTime: null,
);
itemIds.insert(0, newId);
var map = {...state!};
map[newId] = newModel;
state = map;
ref.read(requestSequenceProvider.notifier).state = [...itemIds];
ref.read(selectedIdStateProvider.notifier).state = newId;
unsave();
}
void update({
String? id,
HTTPVerb? method,
APIType? apiType,
String? url,
String? name,
String? description,
int? requestTabIndex,
List<NameValueModel>? headers,
List<NameValueModel>? params,
List<bool>? isHeaderEnabledList,
List<bool>? isParamEnabledList,
ContentType? bodyContentType,
String? body,
String? query,
List<FormDataModel>? formData,
int? responseStatus,
String? message,
HttpResponseModel? httpResponseModel,
String? preRequestScript,
String? postRequestScript,
}) {
final rId = id ?? ref.read(selectedIdStateProvider);
if (rId == null) {
debugPrint("Unable to update as Request Id is null");
return;
}
var currentModel = state![rId]!;
var currentHttpRequestModel = currentModel.httpRequestModel;
final newModel = currentModel.copyWith(
apiType: apiType ?? currentModel.apiType,
name: name ?? currentModel.name,
description: description ?? currentModel.description,
requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex,
httpRequestModel: currentHttpRequestModel?.copyWith(
method: method ?? currentHttpRequestModel.method,
url: url ?? currentHttpRequestModel.url,
headers: headers ?? currentHttpRequestModel.headers,
params: params ?? currentHttpRequestModel.params,
isHeaderEnabledList:
isHeaderEnabledList ?? currentHttpRequestModel.isHeaderEnabledList,
isParamEnabledList:
isParamEnabledList ?? currentHttpRequestModel.isParamEnabledList,
bodyContentType:
bodyContentType ?? currentHttpRequestModel.bodyContentType,
body: body ?? currentHttpRequestModel.body,
query: query ?? currentHttpRequestModel.query,
formData: formData ?? currentHttpRequestModel.formData,
),
responseStatus: responseStatus ?? currentModel.responseStatus,
message: message ?? currentModel.message,
httpResponseModel: httpResponseModel ?? currentModel.httpResponseModel,
preRequestScript: preRequestScript ?? currentModel.preRequestScript,
postRequestScript: postRequestScript ?? currentModel.postRequestScript,
);
var map = {...state!};
map[rId] = newModel;
state = map;
unsave();
}
Future<RequestModel> handlePreRequestScript(
RequestModel requestModel,
EnvironmentModel? originalEnvironmentModel,
) async {
final scriptResult = await executePreRequestScript(
currentRequestModel: requestModel,
activeEnvironment: originalEnvironmentModel?.toJson() ?? {},
);
final newRequestModel =
requestModel.copyWith(httpRequestModel: scriptResult.updatedRequest);
if (originalEnvironmentModel != null) {
final updatedEnvironmentMap = scriptResult.updatedEnvironment;
final List<EnvironmentVariableModel> newValues = [];
final Map<String, dynamic> mutableUpdatedEnv =
Map.from(updatedEnvironmentMap);
for (final originalVariable in originalEnvironmentModel.values) {
if (mutableUpdatedEnv.containsKey(originalVariable.key)) {
final dynamic newValue = mutableUpdatedEnv[originalVariable.key];
newValues.add(
originalVariable.copyWith(
value: newValue == null ? '' : newValue.toString(),
enabled: true,
),
);
mutableUpdatedEnv.remove(originalVariable.key);
} else {
// Variable was removed by the script (unset/clear), don't add it to newValues.
// Alternatively, you could keep it but set enabled = false:
// newValues.add(originalVariable.copyWith(enabled: false));
}
}
for (final entry in mutableUpdatedEnv.entries) {
final dynamic newValue = entry.value;
newValues.add(
EnvironmentVariableModel(
key: entry.key,
value: newValue == null ? '' : newValue.toString(),
enabled: true,
type: EnvironmentVariableType.variable,
),
);
}
ref.read(environmentsStateNotifierProvider.notifier).updateEnvironment(
originalEnvironmentModel.id,
name: originalEnvironmentModel.name,
values: newValues);
} else {
debugPrint(
"Skipped environment update as originalEnvironmentModel was null.");
if (scriptResult.updatedEnvironment.isNotEmpty) {
debugPrint(
"Warning: Pre-request script updated environment variables, but no active environment was selected to save them to.");
}
return requestModel;
}
return newRequestModel;
}
Future<RequestModel> handlePostResponseScript(
RequestModel requestModel,
EnvironmentModel? originalEnvironmentModel,
) async {
final scriptResult = await executePostResponseScript(
currentRequestModel: requestModel,
activeEnvironment: originalEnvironmentModel?.toJson() ?? {},
);
final newRequestModel =
requestModel.copyWith(httpResponseModel: scriptResult.updatedResponse);
if (originalEnvironmentModel != null) {
final updatedEnvironmentMap = scriptResult.updatedEnvironment;
final List<EnvironmentVariableModel> newValues = [];
final Map<String, dynamic> mutableUpdatedEnv =
Map.from(updatedEnvironmentMap);
for (final originalVariable in originalEnvironmentModel.values) {
if (mutableUpdatedEnv.containsKey(originalVariable.key)) {
final dynamic newValue = mutableUpdatedEnv[originalVariable.key];
newValues.add(
originalVariable.copyWith(
value: newValue == null ? '' : newValue.toString(),
enabled: true,
),
);
mutableUpdatedEnv.remove(originalVariable.key);
} else {
// Variable was removed by the script (unset/clear), don't add it to newValues.
// Alternatively, you could keep it but set enabled = false:
// newValues.add(originalVariable.copyWith(enabled: false));
}
}
for (final entry in mutableUpdatedEnv.entries) {
final dynamic newValue = entry.value;
newValues.add(
EnvironmentVariableModel(
key: entry.key,
value: newValue == null ? '' : newValue.toString(),
enabled: true,
type: EnvironmentVariableType.variable,
),
);
}
ref.read(environmentsStateNotifierProvider.notifier).updateEnvironment(
originalEnvironmentModel.id,
name: originalEnvironmentModel.name,
values: newValues);
} else {
debugPrint(
"Skipped environment update as originalEnvironmentModel was null.");
if (scriptResult.updatedEnvironment.isNotEmpty) {
debugPrint(
"Warning: Post-response script updated environment variables, but no active environment was selected to save them to.");
}
return requestModel;
}
return newRequestModel;
}
Future<void> sendRequest() async {
final requestId = ref.read(selectedIdStateProvider);
ref.read(codePaneVisibleStateProvider.notifier).state = false;
final defaultUriScheme = ref.read(settingsProvider).defaultUriScheme;
final EnvironmentModel? originalEnvironmentModel =
ref.read(selectedEnvironmentModelProvider);
if (requestId == null || state == null) {
return;
}
RequestModel? requestModel = state![requestId];
if (requestModel?.httpRequestModel == null) {
return;
}
if (requestModel != null && requestModel.preRequestScript.isNotEmpty) {
requestModel =
await handlePreRequestScript(requestModel, originalEnvironmentModel);
}
APIType apiType = requestModel!.apiType;
HttpRequestModel substitutedHttpRequestModel =
getSubstitutedHttpRequestModel(requestModel.httpRequestModel!);
// set current model's isWorking to true and update state
var map = {...state!};
map[requestId] = requestModel.copyWith(
isWorking: true,
sendingTime: DateTime.now(),
);
state = map;
bool noSSL = ref.read(settingsProvider).isSSLDisabled;
var responseRec = await sendHttpRequest(
requestId,
apiType,
substitutedHttpRequestModel,
defaultUriScheme: defaultUriScheme,
noSSL: noSSL,
);
late RequestModel newRequestModel;
if (responseRec.$1 == null) {
newRequestModel = requestModel.copyWith(
responseStatus: -1,
message: responseRec.$3,
isWorking: false,
);
} else {
final httpResponseModel = baseHttpResponseModel.fromResponse(
response: responseRec.$1!,
time: responseRec.$2!,
);
int statusCode = responseRec.$1!.statusCode;
newRequestModel = requestModel.copyWith(
responseStatus: statusCode,
message: kResponseCodeReasons[statusCode],
httpResponseModel: httpResponseModel,
isWorking: false,
);
String newHistoryId = getNewUuid();
HistoryRequestModel model = HistoryRequestModel(
historyId: newHistoryId,
metaData: HistoryMetaModel(
historyId: newHistoryId,
requestId: requestId,
apiType: requestModel.apiType,
name: requestModel.name,
url: substitutedHttpRequestModel.url,
method: substitutedHttpRequestModel.method,
responseStatus: statusCode,
timeStamp: DateTime.now(),
),
httpRequestModel: substitutedHttpRequestModel,
httpResponseModel: httpResponseModel,
);
if (requestModel.postRequestScript.isNotEmpty) {
newRequestModel = await handlePostResponseScript(
newRequestModel, originalEnvironmentModel);
}
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(model);
}
// update state with response data
map = {...state!};
map[requestId] = newRequestModel;
state = map;
unsave();
}
void cancelRequest() {
final id = ref.read(selectedIdStateProvider);
cancelHttpRequest(id);
unsave();
}
Future<void> clearData() async {
ref.read(clearDataStateProvider.notifier).state = true;
ref.read(selectedIdStateProvider.notifier).state = null;
await hiveHandler.clear();
ref.read(clearDataStateProvider.notifier).state = false;
ref.read(requestSequenceProvider.notifier).state = [];
state = {};
unsave();
}
bool loadData() {
var ids = hiveHandler.getIds();
if (ids == null || ids.length == 0) {
String newId = getNewUuid();
state = {
newId: RequestModel(
id: newId,
httpRequestModel: const HttpRequestModel(),
),
};
return true;
} else {
Map<String, RequestModel> data = {};
for (var id in ids) {
var jsonModel = hiveHandler.getRequestModel(id);
if (jsonModel != null) {
var jsonMap = Map<String, Object?>.from(jsonModel);
var requestModel = RequestModel.fromJson(jsonMap);
if (requestModel.httpRequestModel == null) {
requestModel = requestModel.copyWith(
httpRequestModel: const HttpRequestModel(),
);
}
data[id] = requestModel;
}
}
state = data;
return false;
}
}
Future<void> saveData() async {
ref.read(saveDataStateProvider.notifier).state = true;
final saveResponse = ref.read(settingsProvider).saveResponses;
final ids = ref.read(requestSequenceProvider);
await hiveHandler.setIds(ids);
for (var id in ids) {
await hiveHandler.setRequestModel(
id,
saveResponse
? (state?[id])?.toJson()
: (state?[id]?.copyWith(httpResponseModel: null))?.toJson(),
);
}
await hiveHandler.removeUnused();
ref.read(saveDataStateProvider.notifier).state = false;
ref.read(hasUnsavedChangesProvider.notifier).state = false;
}
Future<Map<String, dynamic>> exportDataToHAR() async {
var result = await collectionToHAR(state?.values.toList());
return result;
// return {
// "data": state!.map((e) => e.toJson(includeResponse: false)).toList()
// };
}
HttpRequestModel getSubstitutedHttpRequestModel(
HttpRequestModel httpRequestModel) {
var envMap = ref.read(availableEnvironmentVariablesStateProvider);
var activeEnvId = ref.read(activeEnvironmentIdStateProvider);
return substituteHttpRequestModel(
httpRequestModel,
envMap,
activeEnvId,
);
}
}