mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 18:49:37 +00:00
Add debug adapter to DAP
Change-Id: I5bacd460175a5b2e86326f7501d7f250bbe3ab0c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292060 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Helin Shiah <helinx@google.com>
This commit is contained in:
parent
86d754690a
commit
5ef021b116
|
@ -11,6 +11,7 @@ import 'package:web_socket_channel/web_socket_channel.dart';
|
|||
|
||||
import '../dds.dart';
|
||||
import 'constants.dart';
|
||||
import 'dap/adapters/dds_hosted_adapter.dart';
|
||||
import 'dds_impl.dart';
|
||||
import 'rpc_error_codes.dart';
|
||||
import 'stream_manager.dart';
|
||||
|
@ -252,6 +253,11 @@ class DartDevelopmentServiceClient {
|
|||
dds.packageUriConverter.convert,
|
||||
);
|
||||
|
||||
_clientPeer.registerMethod(
|
||||
'handleDap',
|
||||
(parameters) => dds.dapHandler.handle(adapter, parameters),
|
||||
);
|
||||
|
||||
// When invoked within a fallback, the next fallback will start executing.
|
||||
// The final fallback forwards the request to the VM service directly.
|
||||
Never nextFallback() => throw json_rpc.RpcException.methodNotFound('');
|
||||
|
@ -334,4 +340,5 @@ class DartDevelopmentServiceClient {
|
|||
final Set<String> profilerUserTagFilters = {};
|
||||
final json_rpc.Peer _vmServicePeer;
|
||||
late json_rpc.Peer _clientPeer;
|
||||
final DdsHostedAdapter adapter = DdsHostedAdapter();
|
||||
}
|
||||
|
|
|
@ -631,7 +631,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
|
|||
}
|
||||
|
||||
logger?.call('Connecting to debugger at $uri');
|
||||
sendOutput('console', 'Connecting to VM Service at $uri\n');
|
||||
sendConsoleOutput('Connecting to VM Service at $uri\n');
|
||||
final vmService = await _vmServiceConnectUri(uri.toString());
|
||||
logger?.call('Connected to debugger at $uri!');
|
||||
|
||||
|
@ -1272,6 +1272,11 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
|
|||
sendResponse(ScopesResponseBody(scopes: scopes));
|
||||
}
|
||||
|
||||
/// Sends an OutputEvent with a newline to the console.
|
||||
void sendConsoleOutput(String message) {
|
||||
sendOutput('console', '\n$message');
|
||||
}
|
||||
|
||||
/// Sends an OutputEvent (without a newline, since calls to this method
|
||||
/// may be using buffered data that is not split cleanly on newlines).
|
||||
///
|
||||
|
|
124
pkg/dds/lib/src/dap/adapters/dds_hosted_adapter.dart
Normal file
124
pkg/dds/lib/src/dap/adapters/dds_hosted_adapter.dart
Normal file
|
@ -0,0 +1,124 @@
|
|||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:vm_service/vm_service.dart' as vm;
|
||||
|
||||
import '../exceptions.dart';
|
||||
import '../protocol_generated.dart';
|
||||
import '../protocol_stream.dart';
|
||||
import 'dart.dart';
|
||||
import 'mixins.dart';
|
||||
|
||||
/// A DAP Debug Adapter for attaching to already-running Dart and Flutter applications.
|
||||
class DdsHostedAdapter extends DartDebugAdapter<DartLaunchRequestArguments,
|
||||
DartAttachRequestArguments>
|
||||
with PidTracker, VmServiceInfoFileUtils, PackageConfigUtils, TestAdapter {
|
||||
Uri? ddsUri;
|
||||
|
||||
@override
|
||||
final parseLaunchArgs = DartLaunchRequestArguments.fromJson;
|
||||
|
||||
@override
|
||||
final parseAttachArgs = DartAttachRequestArguments.fromJson;
|
||||
|
||||
DdsHostedAdapter()
|
||||
: super(
|
||||
// TODO(helin24): Make channel optional for base adapter class.
|
||||
ByteStreamServerChannel(
|
||||
Stream.empty(),
|
||||
NullStreamSink(),
|
||||
(message) {},
|
||||
),
|
||||
ipv6: true,
|
||||
enableDds: false,
|
||||
);
|
||||
|
||||
/// Whether the VM Service closing should be used as a signal to terminate the
|
||||
/// debug session.
|
||||
///
|
||||
/// True here because we no longer need this adapter once the VM service has closed.
|
||||
@override
|
||||
bool get terminateOnVmServiceClose => true;
|
||||
|
||||
@override
|
||||
Future<void> debuggerConnected(vm.VM vmInfo) async {}
|
||||
|
||||
/// Called by [disconnectRequest] to request that we forcefully shut down the
|
||||
/// app being run (or in the case of an attach, disconnect).
|
||||
@override
|
||||
Future<void> disconnectImpl() async {
|
||||
await handleDetach();
|
||||
}
|
||||
|
||||
/// Called by [launchRequest] to request that we actually start the app to be
|
||||
/// run/debugged.
|
||||
///
|
||||
/// For debugging, this should start paused, connect to the VM Service, set
|
||||
/// breakpoints, and resume.
|
||||
@override
|
||||
Future<void> launchImpl() async {
|
||||
sendConsoleOutput(
|
||||
'Launch is not supported for the attach only adapter',
|
||||
);
|
||||
handleSessionTerminate();
|
||||
}
|
||||
|
||||
/// Called by [attachRequest] to request that we actually connect to the app
|
||||
/// to be debugged.
|
||||
@override
|
||||
Future<void> attachImpl() async {
|
||||
final args = this.args as DartAttachRequestArguments;
|
||||
final vmServiceUri = args.vmServiceUri;
|
||||
|
||||
if (vmServiceUri == null) {
|
||||
sendConsoleOutput(
|
||||
'To attach, provide vmServiceUri',
|
||||
);
|
||||
handleSessionTerminate();
|
||||
return;
|
||||
}
|
||||
if (vmServiceUri != ddsUri.toString()) {
|
||||
sendConsoleOutput(
|
||||
'To use the attach-only adapter, VM service URI must match DDS URI',
|
||||
);
|
||||
handleSessionTerminate();
|
||||
}
|
||||
|
||||
// TODO(helin24): In this method, we only need to verify that the DDS URI
|
||||
// matches the VM service URI. The DDS URI isn't really needed because this
|
||||
// adapter is running in the same process. We need to refactor so that we
|
||||
// call DDS/VM service methods directly instead of using the websocket.
|
||||
unawaited(connectDebugger(ddsUri!));
|
||||
}
|
||||
|
||||
/// Called by [terminateRequest] to request that we gracefully shut down the
|
||||
/// app being run (or in the case of an attach, disconnect).
|
||||
@override
|
||||
Future<void> terminateImpl() async {
|
||||
await handleDetach();
|
||||
terminatePids(ProcessSignal.sigterm);
|
||||
}
|
||||
|
||||
Future<Response> handleMessage(String message) async {
|
||||
final potentialException =
|
||||
DebugAdapterException('Message does not conform to DAP spec: $message');
|
||||
|
||||
try {
|
||||
final Map<String, Object?> json = jsonDecode(message);
|
||||
final type = json['type'] as String;
|
||||
if (type == 'request') {
|
||||
return handleIncomingRequest(Request.fromJson(json));
|
||||
// TODO(helin24): Handle event and response?
|
||||
}
|
||||
throw potentialException;
|
||||
} catch (e) {
|
||||
throw potentialException;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -107,6 +107,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
|||
Request request,
|
||||
RequestHandler<TArg, TResp> handler,
|
||||
TArg Function(Map<String, Object?>) fromJson,
|
||||
Completer<Response> completer,
|
||||
) async {
|
||||
try {
|
||||
final args = request.arguments != null
|
||||
|
@ -130,7 +131,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
|||
command: request.command,
|
||||
body: responseBody,
|
||||
);
|
||||
_channel.sendResponse(response);
|
||||
completer.complete(response);
|
||||
}
|
||||
|
||||
await handler(request, args, sendResponse);
|
||||
|
@ -145,7 +146,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
|||
message: e is DebugAdapterException ? e.message : '$e',
|
||||
body: '$s',
|
||||
);
|
||||
_channel.sendResponse(response);
|
||||
completer.complete(response);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,7 +271,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
|||
/// Handles incoming messages from the client editor.
|
||||
void _handleIncomingMessage(ProtocolMessage message) {
|
||||
if (message is Request) {
|
||||
_handleIncomingRequest(message);
|
||||
handleIncomingRequest(message).then(_channel.sendResponse);
|
||||
} else if (message is Response) {
|
||||
_handleIncomingResponse(message);
|
||||
} else {
|
||||
|
@ -279,80 +280,152 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
|||
}
|
||||
|
||||
/// Handles an incoming request, calling the appropriate method to handle it.
|
||||
void _handleIncomingRequest(Request request) {
|
||||
Future<Response> handleIncomingRequest(Request request) {
|
||||
final completer = Completer<Response>();
|
||||
|
||||
if (request.command == 'initialize') {
|
||||
handle(request, initializeRequest, InitializeRequestArguments.fromJson);
|
||||
handle(
|
||||
request,
|
||||
initializeRequest,
|
||||
InitializeRequestArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'launch') {
|
||||
handle(request, _withVoidResponse(launchRequest), parseLaunchArgs);
|
||||
handle(
|
||||
request,
|
||||
_withVoidResponse(launchRequest),
|
||||
parseLaunchArgs,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'attach') {
|
||||
handle(request, _withVoidResponse(attachRequest), parseAttachArgs);
|
||||
handle(
|
||||
request,
|
||||
_withVoidResponse(attachRequest),
|
||||
parseAttachArgs,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'restart') {
|
||||
handle(
|
||||
request,
|
||||
_withVoidResponse(restartRequest),
|
||||
_allowNullArg(RestartArguments.fromJson),
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'terminate') {
|
||||
handle(
|
||||
request,
|
||||
_withVoidResponse(terminateRequest),
|
||||
_allowNullArg(TerminateArguments.fromJson),
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'disconnect') {
|
||||
handle(
|
||||
request,
|
||||
_withVoidResponse(disconnectRequest),
|
||||
_allowNullArg(DisconnectArguments.fromJson),
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'configurationDone') {
|
||||
handle(
|
||||
request,
|
||||
_withVoidResponse(configurationDoneRequest),
|
||||
_allowNullArg(ConfigurationDoneArguments.fromJson),
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'setBreakpoints') {
|
||||
handle(request, setBreakpointsRequest, SetBreakpointsArguments.fromJson);
|
||||
handle(
|
||||
request,
|
||||
setBreakpointsRequest,
|
||||
SetBreakpointsArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'setExceptionBreakpoints') {
|
||||
handle(
|
||||
request,
|
||||
setExceptionBreakpointsRequest,
|
||||
SetExceptionBreakpointsArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'continue') {
|
||||
handle(request, continueRequest, ContinueArguments.fromJson);
|
||||
handle(
|
||||
request,
|
||||
continueRequest,
|
||||
ContinueArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'next') {
|
||||
handle(request, _withVoidResponse(nextRequest), NextArguments.fromJson);
|
||||
handle(
|
||||
request,
|
||||
_withVoidResponse(nextRequest),
|
||||
NextArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'stepIn') {
|
||||
handle(
|
||||
request,
|
||||
_withVoidResponse(stepInRequest),
|
||||
StepInArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'stepOut') {
|
||||
handle(
|
||||
request,
|
||||
_withVoidResponse(stepOutRequest),
|
||||
StepOutArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'threads') {
|
||||
handle(request, threadsRequest, _voidArgs);
|
||||
handle(
|
||||
request,
|
||||
threadsRequest,
|
||||
_voidArgs,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'stackTrace') {
|
||||
handle(request, stackTraceRequest, StackTraceArguments.fromJson);
|
||||
handle(
|
||||
request,
|
||||
stackTraceRequest,
|
||||
StackTraceArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'source') {
|
||||
handle(request, sourceRequest, SourceArguments.fromJson);
|
||||
handle(
|
||||
request,
|
||||
sourceRequest,
|
||||
SourceArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'scopes') {
|
||||
handle(request, scopesRequest, ScopesArguments.fromJson);
|
||||
handle(
|
||||
request,
|
||||
scopesRequest,
|
||||
ScopesArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'variables') {
|
||||
handle(request, variablesRequest, VariablesArguments.fromJson);
|
||||
handle(
|
||||
request,
|
||||
variablesRequest,
|
||||
VariablesArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else if (request.command == 'evaluate') {
|
||||
handle(request, evaluateRequest, EvaluateArguments.fromJson);
|
||||
handle(
|
||||
request,
|
||||
evaluateRequest,
|
||||
EvaluateArguments.fromJson,
|
||||
completer,
|
||||
);
|
||||
} else {
|
||||
handle(
|
||||
request,
|
||||
customRequest,
|
||||
_allowNullArg(RawRequestArguments.fromJson),
|
||||
completer,
|
||||
);
|
||||
}
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void _handleIncomingResponse(Response response) {
|
||||
|
|
9
pkg/dds/lib/src/dap/constants.dart
Normal file
9
pkg/dds/lib/src/dap/constants.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
class Command {
|
||||
static const initialize = 'initialize';
|
||||
static const configurationDone = 'configurationDone';
|
||||
static const attach = 'attach';
|
||||
}
|
|
@ -114,7 +114,7 @@ class ByteStreamServerChannel {
|
|||
|
||||
void _sendParseError(String data) {
|
||||
// TODO(dantup): Review LSP implementation of this when consolidating classes.
|
||||
throw DebugAdapterException('Message does not confirm to DAP spec: $data');
|
||||
throw DebugAdapterException('Message does not conform to DAP spec: $data');
|
||||
}
|
||||
|
||||
/// Send [bytes] to [_output].
|
||||
|
|
80
pkg/dds/lib/src/dap_handler.dart
Normal file
80
pkg/dds/lib/src/dap_handler.dart
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
|
||||
|
||||
import '../dap.dart';
|
||||
import 'dap/adapters/dds_hosted_adapter.dart';
|
||||
import 'dap/constants.dart';
|
||||
import 'dds_impl.dart';
|
||||
|
||||
/// Responds to incoming DAP messages using a debug adapter connected to DDS.
|
||||
class DapHandler {
|
||||
DapHandler(this.dds);
|
||||
|
||||
Future<Map<String, dynamic>> handle(
|
||||
DdsHostedAdapter adapter,
|
||||
json_rpc.Parameters parameters,
|
||||
) async {
|
||||
if (adapter.ddsUri == null) {
|
||||
_startAdapter(adapter);
|
||||
}
|
||||
|
||||
// TODO(helin24): Consider a sequence offset for incoming messages to avoid
|
||||
// overlapping sequence numbers with startup requests.
|
||||
final message = parameters['message'].asString;
|
||||
|
||||
final result = await adapter.handleMessage(message);
|
||||
|
||||
return <String, dynamic>{
|
||||
'type': 'DapResponse',
|
||||
'message': result.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> _startAdapter(DdsHostedAdapter adapter) async {
|
||||
adapter.ddsUri = dds.uri;
|
||||
|
||||
// TODO(helin24): Most likely we'll want the client to do these
|
||||
// initialization steps so that clients can differentiate capabilities. This
|
||||
// may require a custom stream for the debug adapter.
|
||||
int seq = 1;
|
||||
// TODO(helin24): Add waiting for `InitializedEvent`.
|
||||
await adapter.initializeRequest(
|
||||
Request(
|
||||
command: Command.initialize,
|
||||
seq: seq,
|
||||
),
|
||||
InitializeRequestArguments(
|
||||
adapterID: 'dds-dap-handler',
|
||||
),
|
||||
(capabilities) {},
|
||||
);
|
||||
await adapter.configurationDoneRequest(
|
||||
Request(
|
||||
arguments: const {},
|
||||
command: Command.configurationDone,
|
||||
seq: seq++,
|
||||
),
|
||||
ConfigurationDoneArguments(),
|
||||
noopCallback,
|
||||
);
|
||||
await adapter.attachRequest(
|
||||
Request(
|
||||
arguments: const {},
|
||||
command: Command.attach,
|
||||
seq: seq++,
|
||||
),
|
||||
DartAttachRequestArguments(
|
||||
vmServiceUri: dds.remoteVmServiceUri.toString(),
|
||||
),
|
||||
noopCallback,
|
||||
);
|
||||
}
|
||||
|
||||
final DartDevelopmentServiceImpl dds;
|
||||
}
|
||||
|
||||
void noopCallback() {}
|
|
@ -23,6 +23,7 @@ import 'binary_compatible_peer.dart';
|
|||
import 'client.dart';
|
||||
import 'client_manager.dart';
|
||||
import 'constants.dart';
|
||||
import 'dap_handler.dart';
|
||||
import 'devtools/handler.dart';
|
||||
import 'expression_evaluator.dart';
|
||||
import 'isolate_manager.dart';
|
||||
|
@ -68,6 +69,7 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
|||
_isolateManager = IsolateManager(this);
|
||||
_streamManager = StreamManager(this);
|
||||
_packageUriConverter = PackageUriConverter(this);
|
||||
_dapHandler = DapHandler(this);
|
||||
_authCode = _authCodesEnabled ? _makeAuthToken() : '';
|
||||
}
|
||||
|
||||
|
@ -486,6 +488,9 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
|||
PackageUriConverter get packageUriConverter => _packageUriConverter;
|
||||
late PackageUriConverter _packageUriConverter;
|
||||
|
||||
DapHandler get dapHandler => _dapHandler;
|
||||
late DapHandler _dapHandler;
|
||||
|
||||
ClientManager get clientManager => _clientManager;
|
||||
late ClientManager _clientManager;
|
||||
|
||||
|
|
64
pkg/dds/test/dap_handler_test.dart
Normal file
64
pkg/dds/test/dap_handler_test.dart
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dds/dds.dart';
|
||||
import 'package:dds/src/dap/protocol_generated.dart';
|
||||
import 'package:dds_service_extensions/src/dap.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service_io.dart';
|
||||
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
void main() {
|
||||
late Process process;
|
||||
DartDevelopmentService? dds;
|
||||
|
||||
setUp(() async {
|
||||
process = await spawnDartProcess(
|
||||
'get_cached_cpu_samples_script.dart',
|
||||
disableServiceAuthCodes: true,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await dds?.shutdown();
|
||||
process.kill();
|
||||
});
|
||||
|
||||
test('DDS responds to DAP message', () async {
|
||||
Uri serviceUri = remoteVmServiceUri;
|
||||
dds = await DartDevelopmentService.startDartDevelopmentService(
|
||||
remoteVmServiceUri,
|
||||
);
|
||||
serviceUri = dds!.wsUri!;
|
||||
expect(dds!.isRunning, true);
|
||||
final service = await vmServiceConnectUri(serviceUri.toString());
|
||||
|
||||
final setBreakpointsRequest = Request(
|
||||
command: 'setBreakpoints',
|
||||
seq: 9,
|
||||
arguments: SetBreakpointsArguments(
|
||||
breakpoints: [
|
||||
SourceBreakpoint(line: 20),
|
||||
SourceBreakpoint(line: 30),
|
||||
],
|
||||
source: Source(
|
||||
name: 'main.dart',
|
||||
path: '/file/to/main.dart',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// TODO(helinx): Check result format after using better typing from JSON.
|
||||
final result = await service.handleDap(jsonEncode(setBreakpointsRequest));
|
||||
expect(result.message, isNotNull);
|
||||
expect(result.message['type'], 'response');
|
||||
expect(result.message['success'], true);
|
||||
expect(result.message['command'], 'setBreakpoints');
|
||||
expect(result.message['body'], isNotNull);
|
||||
});
|
||||
}
|
50
pkg/dds_service_extensions/lib/src/dap.dart
Normal file
50
pkg/dds_service_extensions/lib/src/dap.dart
Normal file
|
@ -0,0 +1,50 @@
|
|||
// ignore: implementation_imports
|
||||
import 'package:vm_service/src/vm_service.dart';
|
||||
|
||||
extension DapExtension on VmService {
|
||||
static bool _factoriesRegistered = false;
|
||||
Future<DapResponse> handleDap(String message) async {
|
||||
return _callHelper<DapResponse>(
|
||||
'handleDap',
|
||||
args: {'message': message},
|
||||
);
|
||||
}
|
||||
|
||||
Future<T> _callHelper<T>(String method,
|
||||
{String? isolateId, Map args = const {}}) {
|
||||
if (!_factoriesRegistered) {
|
||||
_registerFactories();
|
||||
}
|
||||
return callMethod(
|
||||
method,
|
||||
args: {
|
||||
if (isolateId != null) 'isolateId': isolateId,
|
||||
...args,
|
||||
},
|
||||
).then((e) => e as T);
|
||||
}
|
||||
|
||||
static void _registerFactories() {
|
||||
addTypeFactory('DapResponse', DapResponse.parse);
|
||||
_factoriesRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
class DapResponse extends Response {
|
||||
static DapResponse? parse(Map<String, dynamic>? json) =>
|
||||
json == null ? null : DapResponse._fromJson(json);
|
||||
|
||||
DapResponse({
|
||||
required this.message,
|
||||
});
|
||||
|
||||
DapResponse._fromJson(Map<String, dynamic> json) : message = json['message'];
|
||||
|
||||
@override
|
||||
String get type => 'DapResponse';
|
||||
|
||||
@override
|
||||
String toString() => '[DapResponse]';
|
||||
|
||||
final Map<String, Object?> message;
|
||||
}
|
Loading…
Reference in a new issue