// 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; /// A response to a service protocol extension RPC. /// /// If the RPC was successful, use [ServiceExtensionResponse.result], otherwise /// use [ServiceExtensionResponse.error]. class ServiceExtensionResponse { final String _result; final int _errorCode; final String _errorDetail; /// Creates a successful to a service protocol extension RPC. /// /// Requires [result] to be a JSON object encoded as a string. When forming /// the JSON-RPC message [result] will be inlined directly. ServiceExtensionResponse.result(String result) : _result = result, _errorCode = null, _errorDetail = null { if (_result is! String) { throw new ArgumentError.value(_result, "result", "Must be a String"); } } /// Creates an error response to a service protocol extension RPC. /// /// Requires [errorCode] to be [invalidParams] or between [extensionErrorMin] /// and [extensionErrorMax]. Requires [errorDetail] to be a JSON object /// encoded as a string. When forming the JSON-RPC message [errorDetail] will /// be inlined directly. ServiceExtensionResponse.error(int errorCode, String errorDetail) : _result = null, _errorCode = errorCode, _errorDetail = errorDetail { _validateErrorCode(_errorCode); if (_errorDetail is! String) { throw new ArgumentError.value(_errorDetail, "errorDetail", "Must be a String"); } } /// Invalid method parameter(s) error code. @deprecated static const kInvalidParams = invalidParams; /// Generic extension error code. @deprecated static const kExtensionError = extensionError; /// Maximum extension provided error code. @deprecated static const kExtensionErrorMax = extensionErrorMax; /// Minimum extension provided error code. @deprecated static const kExtensionErrorMin = extensionErrorMin; /// Invalid method parameter(s) error code. static const invalidParams = -32602; /// Generic extension error code. static const extensionError = -32000; /// Maximum extension provided error code. static const extensionErrorMax = -32000; /// Minimum extension provided error code. static const extensionErrorMin = -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 == invalidParams) { return; } if ((errorCode >= extensionErrorMin) && (errorCode <= extensionErrorMax)) { 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 of the service protocol request. /// [parameters] - A map holding the parameters to the service protocol request. /// /// *NOTE*: All parameter names and values are **encoded as strings**. typedef Future ServiceExtensionHandler(String method, Map parameters); /// Register a [ServiceExtensionHandler] that will be invoked in this isolate /// for [method]. *NOTE*: Service protocol extensions must be registered /// in each isolate. /// /// *NOTE*: [method] must begin with 'ext.' and you should use the following /// structure to avoid conflicts with other packages: 'ext.package.command'. /// That is, immediately following the 'ext.' prefix, should be the registering /// package name followed by another period ('.') and then the command name. /// For example: 'ext.dart.io.getOpenFiles'. /// /// Because service extensions are isolate specific, clients using extensions /// must always include an 'isolateId' parameter with each RPC. void registerExtension(String method, ServiceExtensionHandler handler) { if (method is! String) { throw new ArgumentError.value(method, 'method', 'Must be a String'); } if (!method.startsWith('ext.')) { throw new ArgumentError.value(method, 'method', 'Must begin with ext.'); } 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); } /// Post an event of [eventKind] with payload of [eventData] to the `Extension` /// event stream. void postEvent(String eventKind, Map eventData) { if (eventKind is! String) { throw new ArgumentError.value(eventKind, 'eventKind', 'Must be a String'); } if (eventData is! Map) { throw new ArgumentError.value(eventData, 'eventData', 'Must be a Map'); } String eventDataAsString = JSON.encode(eventData); _postEvent(eventKind, eventDataAsString); } external _postEvent(String eventKind, String eventData); // 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);