blob: 05eeae7d855e48e422e47c8a9a5e212d44eed30c [file] [log] [blame]
// Copyright (c) 2015, 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.
part of dart.developer;
class ServiceExtensionResponse {
final String _result;
final int _errorCode;
final String _errorDetail;
ServiceExtensionResponse.result(this._result)
: _errorCode = null,
_errorDetail = null {
if (_result is! String) {
throw new ArgumentError.value(_result, "result", "Must be a String");
}
}
ServiceExtensionResponse.error(this._errorCode, this._errorDetail)
: _result = null {
_validateErrorCode(_errorCode);
if (_errorDetail is! String) {
throw new ArgumentError.value(_errorDetail,
"errorDetail",
"Must be a String");
}
}
/// Invalid method parameter(s) error code.
static const kInvalidParams = -32602;
/// Generic extension error code.
static const kExtensionError = -32000;
/// Maximum extension provided error code.
static const kExtensionErrorMax = -32000;
/// Minimum extension provided error code.
static const kExtensionErrorMin = -32016;
static String _errorCodeMessage(int errorCode) {
_validateErrorCode(errorCode);
if (errorCode == kInvalidParams) {
return "Invalid params";
}
return "Server error";
}
static _validateErrorCode(int errorCode) {
if (errorCode is! int) {
throw new ArgumentError.value(errorCode, "errorCode", "Must be an int");
}
if (errorCode == kInvalidParams) {
return;
}
if ((errorCode >= kExtensionErrorMin) &&
(errorCode <= kExtensionErrorMax)) {
return;
}
throw new ArgumentError.value(errorCode, "errorCode", "Out of range");
}
bool _isError() => (_errorCode != null) && (_errorDetail != null);
String _toString() {
if (_result != null) {
return _result;
} else {
assert(_errorCode != null);
assert(_errorDetail != null);
return JSON.encode({
'code': _errorCode,
'message': _errorCodeMessage(_errorCode),
'data': {
'details': _errorDetail
}
});
}
}
}
/// A service protocol extension handler. Registered with [registerExtension].
///
/// Must complete to a [ServiceExtensionResponse].
///
/// [method] - the method name.
/// [parameters] - the parameters.
typedef Future<ServiceExtensionResponse>
ServiceExtensionHandler(String method, Map parameters);
/// Register a [ServiceExtensionHandler] that will be invoked in this isolate
/// for [method].
void registerExtension(String method, ServiceExtensionHandler handler) {
if (method is! String) {
throw new ArgumentError.value(method,
'method',
'Must be a String');
}
if (_lookupExtension(method) != null) {
throw new ArgumentError('Extension already registered: $method');
}
if (handler is! ServiceExtensionHandler) {
throw new ArgumentError.value(handler,
'handler',
'Must be a ServiceExtensionHandler');
}
_registerExtension(method, handler);
}
// Both of these functions are written inside C++ to avoid updating the data
// structures in Dart, getting an OOB, and observing stale state. Do not move
// these into Dart code unless you can ensure that the operations will can be
// done atomically. Native code lives in vm/isolate.cc-
// LookupServiceExtensionHandler and RegisterServiceExtensionHandler.
external ServiceExtensionHandler _lookupExtension(String method);
external _registerExtension(String method, ServiceExtensionHandler handler);
// This code is only invoked when there is no other Dart code on the stack.
_runExtension(ServiceExtensionHandler handler,
String method,
List<String> parameterKeys,
List<String> parameterValues,
SendPort replyPort,
Object id) {
var parameters = {};
for (var i = 0; i < parameterKeys.length; i++) {
parameters[parameterKeys[i]] = parameterValues[i];
}
var response;
try {
response = handler(method, parameters);
} catch (e, st) {
var errorDetails = (st == null) ? '$e' : '$e\n$st';
response = new ServiceExtensionResponse.error(
ServiceExtensionResponse.kExtensionError,
errorDetails);
_postResponse(replyPort, id, response);
return;
}
if (response is! Future) {
response = new ServiceExtensionResponse.error(
ServiceExtensionResponse.kExtensionError,
"Extension handler must return a Future");
_postResponse(replyPort, id, response);
return;
}
response.catchError((e, st) {
// Catch any errors eagerly and wrap them in a ServiceExtensionResponse.
var errorDetails = (st == null) ? '$e' : '$e\n$st';
return new ServiceExtensionResponse.error(
ServiceExtensionResponse.kExtensionError,
errorDetails);
}).then((response) {
// Post the valid response or the wrapped error after verifying that
// the response is a ServiceExtensionResponse.
if (response is! ServiceExtensionResponse) {
response = new ServiceExtensionResponse.error(
ServiceExtensionResponse.kExtensionError,
"Extension handler must complete to a ServiceExtensionResponse");
}
_postResponse(replyPort, id, response);
}).catchError((e, st) {
// We do not expect any errors to occur in the .then or .catchError blocks
// but, suppress them just in case.
});
}
// This code is only invoked by _runExtension.
_postResponse(SendPort replyPort,
Object id,
ServiceExtensionResponse response) {
assert(replyPort != null);
if (id == null) {
// No id -> no response.
replyPort.send(null);
return;
}
assert(id != null);
StringBuffer sb = new StringBuffer();
sb.write('{"jsonrpc":"2.0",');
if (response._isError()) {
sb.write('"error":');
} else {
sb.write('"result":');
}
sb.write('${response._toString()},');
if (id is String) {
sb.write('"id":"$id"}');
} else {
sb.write('"id":$id}');
}
replyPort.send(sb.toString());
}