//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Diagnostics;
using Microsoft.SqlTools.Hosting.Contracts;
using Microsoft.SqlTools.Hosting.Contracts.Internal;
using Microsoft.SqlTools.Hosting.v2;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace Microsoft.SqlTools.Hosting.Protocol
{
///
/// Defines all possible message types.
///
public enum MessageType
{
Request,
Response,
ResponseError,
Event
}
///
/// Representation for a JSON RPC message. Provides logic for converting back and forth from
/// string
///
[DebuggerDisplay("MessageType = {MessageType.ToString()}, Method = {Method}, Id = {Id}")]
public class Message
{
#region Constants
private const string JsonRpcVersion = "2.0";
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
private static readonly JsonSerializer ContentSerializer = JsonSerializer.Create(JsonSerializerSettings);
#endregion
#region Construction
private Message(MessageType messageType, JToken contents)
{
MessageType = messageType;
Contents = contents;
}
///
/// Creates a message with a Request type
///
/// Configuration for the request
/// ID of the message
/// Contents of the message
/// Type of the contents of the message
/// Type of the contents of the results to this request
/// Message with a Request type
public static Message CreateRequest(
RequestType requestType,
string id,
TParams contents)
{
JToken contentsToken = contents == null ? null : JToken.FromObject(contents, ContentSerializer);
return new Message(MessageType.Request, contentsToken)
{
Id = id,
Method = requestType.MethodName
};
}
///
/// Creates a message with a Response type.
///
/// The sequence ID of the original request.
/// The contents of the response.
/// A message with a Response type.
public static Message CreateResponse(
string id,
TParams contents)
{
JToken contentsToken = contents == null ? null : JToken.FromObject(contents, ContentSerializer);
return new Message(MessageType.Response, contentsToken)
{
Id = id
};
}
///
/// Creates a message with a Response type and error details.
///
/// The sequence ID of the original request.
/// The error details of the response.
/// A message with a Response type and error details.
public static Message CreateResponseError(
string id,
Error error)
{
JToken errorToken = error == null ? null : JToken.FromObject(error, ContentSerializer);
return new Message(MessageType.ResponseError, errorToken)
{
Id = id
};
}
///
/// Creates a message with an Event type.
///
/// Configuration for the event message
/// The contents of the event.
///
/// A message with an Event type
public static Message CreateEvent(
EventType eventType,
TParams contents)
{
JToken contentsToken = contents == null ? null : JToken.FromObject(contents, ContentSerializer);
return new Message(MessageType.Event, contentsToken)
{
Method = eventType.MethodName
};
}
#endregion
#region Properties
///
/// Gets or sets the message type.
///
public MessageType MessageType { get; }
///
/// Gets or sets the message's sequence ID.
///
public string Id { get; private set; }
///
/// Gets or sets the message's method/command name.
///
public string Method { get; private set; }
///
/// Gets or sets a JToken containing the contents of the message.
///
public JToken Contents { get; }
#endregion
#region Serialization/Deserialization
public static Message Deserialize(string jsonString)
{
// Deserialize the object from the JSON into an intermediate object
JObject messageObject = JObject.Parse(jsonString);
JToken token;
// Ensure there's a JSON RPC version or else it's invalid
if (!messageObject.TryGetValue("jsonrpc", out token) || token.Value() != JsonRpcVersion)
{
throw new MessageParseException(null, SR.HostingJsonRpcVersionMissing);
}
if (messageObject.TryGetValue("id", out token))
{
// Message with ID is a Request or Response
string messageId = token.ToString();
if (messageObject.TryGetValue("result", out token))
{
return new Message(MessageType.Response, token) {Id = messageId};
}
if (messageObject.TryGetValue("error", out token))
{
return new Message(MessageType.ResponseError, token) {Id = messageId};
}
// Message without result/error is a Request
JToken messageParams;
messageObject.TryGetValue("params", out messageParams);
if (!messageObject.TryGetValue("method", out token))
{
throw new MessageParseException(null, SR.HostingMessageMissingMethod);
}
return new Message(MessageType.Request, messageParams) {Id = messageId, Method = token.ToString()};
}
else
{
// Messages without an id are events
JToken messageParams;
messageObject.TryGetValue("params", out messageParams);
if (!messageObject.TryGetValue("method", out token))
{
throw new MessageParseException(null, SR.HostingMessageMissingMethod);
}
return new Message(MessageType.Event, messageParams) {Method = token.ToString()};
}
}
public string Serialize()
{
JObject messageObject = new JObject
{
{"jsonrpc", JToken.FromObject(JsonRpcVersion)}
};
switch (MessageType)
{
case MessageType.Request:
messageObject.Add("id", JToken.FromObject(Id));
messageObject.Add("method", Method);
messageObject.Add("params", Contents);
break;
case MessageType.Event:
messageObject.Add("method", Method);
messageObject.Add("params", Contents);
break;
case MessageType.Response:
messageObject.Add("id", JToken.FromObject(Id));
messageObject.Add("result", Contents);
break;
case MessageType.ResponseError:
messageObject.Add("id", JToken.FromObject(Id));
messageObject.Add("error", Contents);
break;
}
return JsonConvert.SerializeObject(messageObject);
}
public TContents GetTypedContents()
{
TContents typedContents = default(TContents);
if (Contents != null)
{
typedContents = Contents.ToObject();
}
return typedContents;
}
#endregion
}
}