Better error reporting in ResponseMessage. Fix #317

This commit is contained in:
Fangrui Song 2018-01-19 14:31:49 -08:00
parent 8eff5e2e4f
commit 90c2a54bbc
3 changed files with 36 additions and 32 deletions

View File

@ -238,33 +238,30 @@ void LaunchStdinLoop(Config* config,
WorkThread::StartThread("stdin", [request_times]() { WorkThread::StartThread("stdin", [request_times]() {
auto* queue = QueueManager::instance(); auto* queue = QueueManager::instance();
while (true) { while (true) {
std::variant<std::string, std::unique_ptr<BaseIpcMessage>> err_or_message = std::unique_ptr<BaseIpcMessage> message;
optional<std::string> err =
MessageRegistry::instance()->ReadMessageFromStdin( MessageRegistry::instance()->ReadMessageFromStdin(
g_log_stdin_stdout_to_stderr); g_log_stdin_stdout_to_stderr, &message);
// Message parsing can fail if we don't recognize the method. // Message parsing can fail if we don't recognize the method.
auto* err = std::get_if<std::string>(&err_or_message);
if (err) { if (err) {
// FIXME LanguageClient-neovim will error without the check, probably // The message may be partially deserialized.
// because we do not support didChangeConfiguration and do not fill in // Emit an error ResponseMessage if |id| is available.
// the |id| field. if (message) {
if (err->find("workspace/didChangeConfiguration") ==
std::string::npos) {
Out_Error out; Out_Error out;
// TODO We cannot fill in out.id because RequestMessage.id is not a base out.id = message->GetRequestId();
// field. if (!std::holds_alternative<std::monostate>(out.id)) {
out.error.code = lsErrorCodes::InvalidParams; out.error.code = lsErrorCodes::InvalidParams;
out.error.message = std::move(*err); out.error.message = std::move(*err);
queue->WriteStdout(IpcId::Unknown, out); queue->WriteStdout(IpcId::Unknown, out);
}
} }
continue; continue;
} }
// Cache |method_id| so we can access it after moving |message|. // Cache |method_id| so we can access it after moving |message|.
auto& message = std::get<std::unique_ptr<BaseIpcMessage>>(err_or_message);
IpcId method_id = message->method_id; IpcId method_id = message->method_id;
(*request_times)[method_id] = Timer();
(*request_times)[message->method_id] = Timer();
switch (method_id) { switch (method_id) {
case IpcId::Initialized: { case IpcId::Initialized: {

View File

@ -113,8 +113,9 @@ optional<char> ReadCharFromStdinBlocking() {
return nullopt; return nullopt;
} }
std::variant<std::string, std::unique_ptr<BaseIpcMessage>> optional<std::string> MessageRegistry::ReadMessageFromStdin(
MessageRegistry::ReadMessageFromStdin(bool log_stdin_to_stderr) { bool log_stdin_to_stderr,
std::unique_ptr<BaseIpcMessage>* message) {
optional<std::string> content = optional<std::string> content =
ReadJsonRpcContentFrom(&ReadCharFromStdinBlocking); ReadJsonRpcContentFrom(&ReadCharFromStdinBlocking);
if (!content) { if (!content) {
@ -135,11 +136,12 @@ MessageRegistry::ReadMessageFromStdin(bool log_stdin_to_stderr) {
assert(!document.HasParseError()); assert(!document.HasParseError());
JsonReader json_reader{&document}; JsonReader json_reader{&document};
return Parse(json_reader); return Parse(json_reader, message);
} }
std::variant<std::string, std::unique_ptr<BaseIpcMessage>> optional<std::string> MessageRegistry::Parse(
MessageRegistry::Parse(Reader& visitor) { Reader& visitor,
std::unique_ptr<BaseIpcMessage>* message) {
if (!visitor.HasMember("jsonrpc") || if (!visitor.HasMember("jsonrpc") ||
std::string(visitor["jsonrpc"]->GetString()) != "2.0") { std::string(visitor["jsonrpc"]->GetString()) != "2.0") {
LOG_S(FATAL) << "Bad or missing jsonrpc version"; LOG_S(FATAL) << "Bad or missing jsonrpc version";
@ -155,8 +157,11 @@ std::variant<std::string, std::unique_ptr<BaseIpcMessage>>
Allocator& allocator = allocators[method]; Allocator& allocator = allocators[method];
try { try {
return allocator(visitor); allocator(visitor, message);
return nullopt;
} catch (std::invalid_argument& e) { } catch (std::invalid_argument& e) {
// *message is partially deserialized but some field (e.g. |id|) are likely
// available.
return std::string("Unable to deserialize request '") + method + "' " + return std::string("Unable to deserialize request '") + method + "' " +
static_cast<JsonReader&>(visitor).GetPath() + " " + e.what(); static_cast<JsonReader&>(visitor).GetPath() + " " + e.what();
} }

View File

@ -40,24 +40,26 @@ struct MessageRegistry {
static MessageRegistry* instance(); static MessageRegistry* instance();
using Allocator = using Allocator =
std::function<std::unique_ptr<BaseIpcMessage>(Reader& visitor)>; std::function<void(Reader& visitor, std::unique_ptr<BaseIpcMessage>*)>;
std::unordered_map<std::string, Allocator> allocators; std::unordered_map<std::string, Allocator> allocators;
std::variant<std::string, std::unique_ptr<BaseIpcMessage>> optional<std::string> ReadMessageFromStdin(
ReadMessageFromStdin(bool log_stdin_to_stderr); bool log_stdin_to_stderr,
std::variant<std::string, std::unique_ptr<BaseIpcMessage>> Parse( std::unique_ptr<BaseIpcMessage>* message);
Reader& visitor); optional<std::string> Parse(Reader& visitor,
std::unique_ptr<BaseIpcMessage>* message);
}; };
template <typename T> template <typename T>
struct MessageRegistryRegister { struct MessageRegistryRegister {
MessageRegistryRegister() { MessageRegistryRegister() {
std::string method_name = IpcIdToString(T::kIpcId); std::string method_name = IpcIdToString(T::kIpcId);
MessageRegistry::instance()->allocators[method_name] = [](Reader& visitor) { MessageRegistry::instance()->allocators[method_name] =
auto result = MakeUnique<T>(); [](Reader& visitor, std::unique_ptr<BaseIpcMessage>* message) {
Reflect(visitor, *result); *message = MakeUnique<T>();
return result; // Reflect may throw and *message will be partially deserialized.
}; Reflect(visitor, static_cast<T&>(**message));
};
} }
}; };