blob: 0c00630e2f248433bea3438cd948892c331c9d9d [file] [log] [blame]
// Copyright (c) 2021, 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 'logging.dart';
import 'protocol_common.dart';
import 'protocol_generated.dart';
import 'protocol_stream.dart';
typedef _FromJsonHandler<T> = T Function(Map<String, Object?>);
typedef _NullableFromJsonHandler<T> = T? Function(Map<String, Object?>?);
/// A base class for debug adapters.
///
/// Communicates over a [ByteStreamServerChannel] and turns messages into
/// appropriate method calls/events.
///
/// This class does not implement any DA functionality, only message handling.
abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments> {
int _sequence = 1;
final ByteStreamServerChannel _channel;
final Logger? logger;
BaseDebugAdapter(this._channel, this.logger) {
_channel.listen(_handleIncomingMessage);
}
/// Parses arguments for [launchRequest] into a type of [TLaunchArgs].
///
/// This method must be implemented by the implementing class using a class
/// that corresponds to the arguments it expects (these may differ between
/// Dart CLI, Dart tests, Flutter, Flutter tests).
TLaunchArgs Function(Map<String, Object?>) get parseLaunchArgs;
FutureOr<void> attachRequest(
Request request,
TLaunchArgs args,
void Function(void) sendResponse,
);
FutureOr<void> configurationDoneRequest(
Request request,
ConfigurationDoneArguments? args,
void Function(void) sendResponse,
);
FutureOr<void> disconnectRequest(
Request request,
DisconnectArguments? args,
void Function(void) sendResponse,
);
/// Calls [handler] for an incoming request, using [fromJson] to parse its
/// arguments from the request.
///
/// [handler] will be provided a function [sendResponse] that it can use to
/// sends its response without needing to build a [Response] from fields on
/// the request.
///
/// [handler] must _always_ call [sendResponse], even if the response does not
/// require a body.
///
/// If [handler] throws, its exception will be sent as an error response.
FutureOr<void> handle<TArg, TResp>(
Request request,
FutureOr<void> Function(Request, TArg, void Function(TResp)) handler,
TArg Function(Map<String, Object?>) fromJson,
) async {
final args = request.arguments != null
? fromJson(request.arguments as Map<String, Object?>)
// arguments are only valid to be null then TArg is nullable.
: null as TArg;
// Because handlers may need to send responses before they have finished
// executing (for example, initializeRequest needs to send its response
// before sending InitializedEvent()), we pass in a function `sendResponse`
// rather than using a return value.
var sendResponseCalled = false;
void sendResponse(TResp responseBody) {
assert(!sendResponseCalled,
'sendResponse was called multiple times by ${request.command}');
sendResponseCalled = true;
final response = Response(
success: true,
requestSeq: request.seq,
seq: _sequence++,
command: request.command,
body: responseBody,
);
_channel.sendResponse(response);
}
try {
await handler(request, args, sendResponse);
assert(sendResponseCalled,
'sendResponse was not called in ${request.command}');
} catch (e, s) {
final response = Response(
success: false,
requestSeq: request.seq,
seq: _sequence++,
command: request.command,
message: '$e',
body: '$s',
);
_channel.sendResponse(response);
}
}
FutureOr<void> initializeRequest(
Request request,
InitializeRequestArguments args,
void Function(Capabilities) sendResponse,
);
FutureOr<void> launchRequest(
Request request, TLaunchArgs args, void Function(void) sendResponse);
/// Sends an event, lookup up the event type based on the runtimeType of
/// [body].
void sendEvent(EventBody body) {
final event = Event(
seq: _sequence++,
event: eventTypes[body.runtimeType]!,
body: body,
);
_channel.sendEvent(event);
}
/// Sends a request to the client, looking up the request type based on the
/// runtimeType of [arguments].
void sendRequest(RequestArguments arguments) {
final request = Request(
seq: _sequence++,
command: commandTypes[arguments.runtimeType]!,
arguments: arguments,
);
_channel.sendRequest(request);
}
FutureOr<void> terminateRequest(
Request request,
TerminateArguments? args,
void Function(void) sendResponse,
);
/// Wraps a fromJson handler for requests that allow null arguments.
_NullableFromJsonHandler<T> _allowNullArg<T extends RequestArguments>(
_FromJsonHandler<T> fromJson,
) {
return (data) => data == null ? null : fromJson(data);
}
/// Handles incoming messages from the client editor.
void _handleIncomingMessage(ProtocolMessage message) {
if (message is Request) {
_handleIncomingRequest(message);
} else if (message is Response) {
_handleIncomingResponse(message);
} else {
throw Exception('Unknown Protocol message ${message.type}');
}
}
/// Handles an incoming request, calling the appropriate method to handle it.
void _handleIncomingRequest(Request request) {
if (request.command == 'initialize') {
handle(request, initializeRequest, InitializeRequestArguments.fromJson);
} else if (request.command == 'launch') {
handle(request, launchRequest, parseLaunchArgs);
} else if (request.command == 'attach') {
handle(request, attachRequest, parseLaunchArgs);
} else if (request.command == 'terminate') {
handle(
request,
terminateRequest,
_allowNullArg(TerminateArguments.fromJson),
);
} else if (request.command == 'disconnect') {
handle(
request,
disconnectRequest,
_allowNullArg(DisconnectArguments.fromJson),
);
} else if (request.command == 'configurationDone') {
handle(
request,
configurationDoneRequest,
_allowNullArg(ConfigurationDoneArguments.fromJson),
);
} else {
final response = Response(
success: false,
requestSeq: request.seq,
seq: _sequence++,
command: request.command,
message: 'Unknown command: ${request.command}',
);
_channel.sendResponse(response);
}
}
void _handleIncomingResponse(Response response) {
// TODO(dantup): Implement this when the server sends requests to the client
// (for example runInTerminalRequest).
}
}