// // 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 } }