2024-05-19 23:09:23 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
import 'package:gql/language.dart' as gql;
|
|
|
|
import 'package:graphql/client.dart' as gql_client;
|
2024-04-20 09:53:55 +00:00
|
|
|
import 'package:intl/intl.dart';
|
|
|
|
|
|
|
|
enum ConsoleLogSeverity {
|
|
|
|
normal,
|
|
|
|
warning,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Base entity for console logs.
|
|
|
|
sealed class ConsoleLog {
|
|
|
|
ConsoleLog({
|
|
|
|
final String? customTitle,
|
|
|
|
this.severity = ConsoleLogSeverity.normal,
|
|
|
|
}) : title = customTitle ??
|
|
|
|
(severity == ConsoleLogSeverity.warning ? 'Error' : 'Log'),
|
|
|
|
time = DateTime.now();
|
|
|
|
|
|
|
|
final DateTime time;
|
|
|
|
final ConsoleLogSeverity severity;
|
|
|
|
bool get isError => severity == ConsoleLogSeverity.warning;
|
|
|
|
|
|
|
|
/// title for both in listing and in dialog
|
|
|
|
final String title;
|
|
|
|
|
|
|
|
/// formatted data to be shown in listing
|
|
|
|
String get content;
|
|
|
|
|
|
|
|
/// data available for copy in dialog
|
2024-06-19 11:20:15 +00:00
|
|
|
String? get shareableData => '{"title": "$title",\n'
|
2024-05-19 23:09:23 +00:00
|
|
|
'"timestamp": "$fullUTCString",\n'
|
|
|
|
'"data":{\n$content\n}'
|
|
|
|
'\n}';
|
2024-04-20 09:53:55 +00:00
|
|
|
|
|
|
|
static final DateFormat _formatter = DateFormat('hh:mm:ss');
|
|
|
|
String get timeString => _formatter.format(time);
|
2024-05-19 23:09:23 +00:00
|
|
|
|
|
|
|
String get fullUTCString => time.toUtc().toIso8601String();
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class LogWithRawResponse {
|
|
|
|
String get rawResponse;
|
2024-04-20 09:53:55 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 23:09:23 +00:00
|
|
|
/// entity for manually created logs, as opposed to automated ones coming
|
|
|
|
/// from requests / responses
|
2024-04-20 09:53:55 +00:00
|
|
|
class ManualConsoleLog extends ConsoleLog {
|
|
|
|
ManualConsoleLog({
|
|
|
|
required this.content,
|
|
|
|
super.customTitle,
|
|
|
|
super.severity,
|
|
|
|
});
|
|
|
|
|
|
|
|
ManualConsoleLog.warning({
|
|
|
|
required this.content,
|
|
|
|
super.customTitle,
|
|
|
|
}) : super(severity: ConsoleLogSeverity.warning);
|
|
|
|
|
|
|
|
@override
|
|
|
|
String content;
|
|
|
|
}
|
|
|
|
|
|
|
|
class RestApiRequestConsoleLog extends ConsoleLog {
|
|
|
|
RestApiRequestConsoleLog({
|
|
|
|
this.method,
|
|
|
|
this.uri,
|
|
|
|
this.headers,
|
|
|
|
this.data,
|
|
|
|
super.severity,
|
|
|
|
});
|
|
|
|
|
2024-06-19 14:01:13 +00:00
|
|
|
/// headers thath should not be included into clipboard buffer, as opposed to
|
|
|
|
/// `[[ConsoleLogItemDialog]]` `_KeyValueRow.hideList` which filters values,
|
|
|
|
/// that should be accessible from UI, but hidden in screenshots
|
2024-06-13 17:53:06 +00:00
|
|
|
static const blacklistedHeaders = ['Authorization'];
|
|
|
|
|
2024-04-20 09:53:55 +00:00
|
|
|
final String? method;
|
|
|
|
final Uri? uri;
|
|
|
|
final Map<String, dynamic>? headers;
|
|
|
|
final String? data;
|
|
|
|
|
|
|
|
@override
|
|
|
|
String get title => 'Rest API Request';
|
2024-06-13 17:53:06 +00:00
|
|
|
|
|
|
|
Map<String, dynamic> get filteredHeaders => Map.fromEntries(
|
|
|
|
headers?.entries.where(
|
|
|
|
(final entry) => !blacklistedHeaders.contains(entry.key),
|
|
|
|
) ??
|
|
|
|
const [],
|
|
|
|
);
|
|
|
|
|
2024-04-20 09:53:55 +00:00
|
|
|
@override
|
2024-05-19 23:09:23 +00:00
|
|
|
String get content => '"method": "$method",\n'
|
|
|
|
'"uri": "$uri",\n'
|
2024-06-13 17:53:06 +00:00
|
|
|
'"headers": ${jsonEncode(filteredHeaders)},\n' // censor header to not expose API keys
|
2024-05-19 23:09:23 +00:00
|
|
|
'"data": $data';
|
2024-04-20 09:53:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class RestApiResponseConsoleLog extends ConsoleLog {
|
|
|
|
RestApiResponseConsoleLog({
|
|
|
|
this.method,
|
|
|
|
this.uri,
|
|
|
|
this.statusCode,
|
|
|
|
this.data,
|
|
|
|
super.severity,
|
|
|
|
});
|
|
|
|
|
|
|
|
final String? method;
|
|
|
|
final Uri? uri;
|
|
|
|
final int? statusCode;
|
|
|
|
final String? data;
|
|
|
|
|
|
|
|
@override
|
|
|
|
String get title => 'Rest API Response';
|
|
|
|
@override
|
2024-05-19 23:09:23 +00:00
|
|
|
String get content => '"method": "$method",\n'
|
|
|
|
'"status_code": $statusCode,\n'
|
|
|
|
'"uri": "$uri",\n'
|
|
|
|
'"data": $data';
|
2024-04-20 09:53:55 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 23:09:23 +00:00
|
|
|
/// there is no actual getter for context fields outside of its class
|
|
|
|
/// one can extract unique entries by their type, which implements
|
|
|
|
/// `ContextEntry` class, I'll leave the code here if in the future
|
|
|
|
/// some entries will actually be needed.
|
|
|
|
// extension ContextEncoder on gql_client.Context {
|
|
|
|
// String get encode {
|
|
|
|
// return '""';
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
2024-04-20 09:53:55 +00:00
|
|
|
class GraphQlRequestConsoleLog extends ConsoleLog {
|
|
|
|
GraphQlRequestConsoleLog({
|
2024-05-19 23:09:23 +00:00
|
|
|
required this.operationType,
|
|
|
|
required this.operation,
|
|
|
|
required this.variables,
|
|
|
|
// this.context,
|
2024-04-20 09:53:55 +00:00
|
|
|
super.severity,
|
|
|
|
});
|
|
|
|
|
2024-05-19 23:09:23 +00:00
|
|
|
// final gql_client.Context? context;
|
|
|
|
final String operationType;
|
|
|
|
final gql_client.Operation? operation;
|
|
|
|
String get operationDocument =>
|
|
|
|
operation != null ? gql.printNode(operation!.document) : 'null';
|
2024-04-20 09:53:55 +00:00
|
|
|
final Map<String, dynamic>? variables;
|
|
|
|
|
|
|
|
@override
|
|
|
|
String get title => 'GraphQL Request';
|
|
|
|
@override
|
2024-05-19 23:09:23 +00:00
|
|
|
String get content =>
|
|
|
|
// '"context": ${context?.encode},\n'
|
|
|
|
'"variables": ${jsonEncode(variables)},\n'
|
|
|
|
'"type": "$operationType",\n'
|
|
|
|
'"name": "${operation?.operationName}",\n'
|
|
|
|
'"document": ${jsonEncode(operationDocument)}';
|
2024-04-20 09:53:55 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 23:09:23 +00:00
|
|
|
class GraphQlResponseConsoleLog extends ConsoleLog
|
|
|
|
implements LogWithRawResponse {
|
2024-04-20 09:53:55 +00:00
|
|
|
GraphQlResponseConsoleLog({
|
2024-05-19 23:09:23 +00:00
|
|
|
required this.rawResponse,
|
|
|
|
// this.context,
|
2024-04-20 09:53:55 +00:00
|
|
|
this.data,
|
|
|
|
this.errors,
|
|
|
|
super.severity,
|
|
|
|
});
|
|
|
|
|
2024-05-19 23:09:23 +00:00
|
|
|
@override
|
|
|
|
final String rawResponse;
|
|
|
|
// final gql_client.Context? context;
|
2024-04-20 09:53:55 +00:00
|
|
|
final Map<String, dynamic>? data;
|
2024-05-19 23:09:23 +00:00
|
|
|
final List<gql_client.GraphQLError>? errors;
|
2024-04-20 09:53:55 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
String get title => 'GraphQL Response';
|
|
|
|
@override
|
2024-05-19 23:09:23 +00:00
|
|
|
String get content =>
|
|
|
|
// '"context": ${context?.encode},\n'
|
|
|
|
'"data": ${jsonEncode(data)},\n'
|
|
|
|
'"errors": $errors';
|
2024-04-20 09:53:55 +00:00
|
|
|
}
|