diff --git a/Directory.Packages.props b/Directory.Packages.props
index 462cbe578..b16855566 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -22,11 +22,12 @@
+
-
+
-
+
diff --git a/nuget.config b/nuget.config
index 0a73357e8..ee3500d0c 100644
--- a/nuget.config
+++ b/nuget.config
@@ -5,10 +5,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/StreamJsonRpc/FormatterBase.cs b/src/StreamJsonRpc/FormatterBase.cs
index 7dd4c6479..42b283359 100644
--- a/src/StreamJsonRpc/FormatterBase.cs
+++ b/src/StreamJsonRpc/FormatterBase.cs
@@ -6,7 +6,9 @@
using System.IO.Pipelines;
using System.Reflection;
using System.Runtime.Serialization;
+using Nerdbank.MessagePack;
using Nerdbank.Streams;
+using PolyType;
using StreamJsonRpc.Protocol;
using StreamJsonRpc.Reflection;
@@ -320,7 +322,7 @@ public void Dispose()
///
/// A base class for top-level property bags that should be declared in the derived formatter class.
///
- protected abstract class TopLevelPropertyBagBase
+ protected internal abstract class TopLevelPropertyBagBase
{
private readonly bool isOutbound;
private Dictionary? outboundProperties;
@@ -436,6 +438,7 @@ protected abstract class JsonRpcErrorBase : JsonRpcError, IJsonRpcMessageBufferM
///
[Newtonsoft.Json.JsonIgnore]
[IgnoreDataMember]
+ [PropertyShape(Ignore = true)]
public TopLevelPropertyBagBase? TopLevelPropertyBag { get; set; }
void IJsonRpcMessageBufferManager.DeserializationComplete(JsonRpcMessage message)
@@ -480,6 +483,7 @@ protected abstract class JsonRpcResultBase : JsonRpcResult, IJsonRpcMessageBuffe
///
[Newtonsoft.Json.JsonIgnore]
[IgnoreDataMember]
+ [PropertyShape(Ignore = true)]
public TopLevelPropertyBagBase? TopLevelPropertyBag { get; set; }
void IJsonRpcMessageBufferManager.DeserializationComplete(JsonRpcMessage message)
diff --git a/src/StreamJsonRpc/JsonRpc.cs b/src/StreamJsonRpc/JsonRpc.cs
index e8ab2712a..e5f1e596a 100644
--- a/src/StreamJsonRpc/JsonRpc.cs
+++ b/src/StreamJsonRpc/JsonRpc.cs
@@ -1697,7 +1697,7 @@ protected virtual async ValueTask DispatchRequestAsync(JsonRpcRe
}
///
- /// Sends the JSON-RPC message to intance to be transmitted.
+ /// Sends the JSON-RPC message to instance to be transmitted.
///
/// The message to send.
/// A token to cancel the send request.
diff --git a/src/StreamJsonRpc/NerdbankMessagePackFormatter.AsyncEnumerableConverters.cs b/src/StreamJsonRpc/NerdbankMessagePackFormatter.AsyncEnumerableConverters.cs
new file mode 100644
index 000000000..1938eb38f
--- /dev/null
+++ b/src/StreamJsonRpc/NerdbankMessagePackFormatter.AsyncEnumerableConverters.cs
@@ -0,0 +1,154 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Buffers;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Nodes;
+using Nerdbank.MessagePack;
+using Nerdbank.Streams;
+using PolyType.Abstractions;
+using StreamJsonRpc.Reflection;
+
+namespace StreamJsonRpc;
+
+///
+/// Serializes JSON-RPC messages using MessagePack (a fast, compact binary format).
+///
+public partial class NerdbankMessagePackFormatter
+{
+ private static class AsyncEnumerableConverters
+ {
+ ///
+ /// Converts an enumeration token to an
+ /// or an into an enumeration token.
+ ///
+#pragma warning disable CA1812
+ internal class PreciseTypeConverter : MessagePackConverter>
+#pragma warning restore CA1812
+ {
+ ///
+ /// The constant "token", in its various forms.
+ ///
+ private static readonly MessagePackString TokenPropertyName = new(MessageFormatterEnumerableTracker.TokenPropertyName);
+
+ ///
+ /// The constant "values", in its various forms.
+ ///
+ private static readonly MessagePackString ValuesPropertyName = new(MessageFormatterEnumerableTracker.ValuesPropertyName);
+
+ public override IAsyncEnumerable? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ if (reader.TryReadNil())
+ {
+ return default;
+ }
+
+ NerdbankMessagePackFormatter mainFormatter = context.GetFormatter();
+
+ context.DepthStep();
+
+ RawMessagePack? token = default;
+ IReadOnlyList? initialElements = null;
+ int propertyCount = reader.ReadMapHeader();
+ for (int i = 0; i < propertyCount; i++)
+ {
+ if (TokenPropertyName.TryRead(ref reader))
+ {
+ // The value needs to outlive the reader, so we clone it.
+ token = new RawMessagePack(reader.ReadRaw(context)).ToOwned();
+ }
+ else if (ValuesPropertyName.TryRead(ref reader))
+ {
+ initialElements = context.GetConverter>(context.TypeShapeProvider).Read(ref reader, context);
+ }
+ else
+ {
+ reader.Skip(context);
+ }
+ }
+
+ return mainFormatter.EnumerableTracker.CreateEnumerableProxy(token.HasValue ? token.Value : null, initialElements);
+ }
+
+ [SuppressMessage("Usage", "NBMsgPack031:Converters should read or write exactly one msgpack structure", Justification = "Writer is passed to helper method")]
+ public override void Write(ref MessagePackWriter writer, in IAsyncEnumerable? value, SerializationContext context)
+ {
+ context.DepthStep();
+
+ NerdbankMessagePackFormatter mainFormatter = context.GetFormatter();
+ Serialize_Shared(mainFormatter, ref writer, value, context);
+ }
+
+ public override JsonObject? GetJsonSchema(JsonSchemaContext context, ITypeShape typeShape)
+ {
+ return CreateUndocumentedSchema(typeof(PreciseTypeConverter));
+ }
+
+ internal static void Serialize_Shared(NerdbankMessagePackFormatter mainFormatter, ref MessagePackWriter writer, IAsyncEnumerable? value, SerializationContext context)
+ {
+ if (value is null)
+ {
+ writer.WriteNil();
+ }
+ else
+ {
+ (IReadOnlyList elements, bool finished) = value.TearOffPrefetchedElements();
+ long token = mainFormatter.EnumerableTracker.GetToken(value);
+
+ int propertyCount = 0;
+ if (elements.Count > 0)
+ {
+ propertyCount++;
+ }
+
+ if (!finished)
+ {
+ propertyCount++;
+ }
+
+ writer.WriteMapHeader(propertyCount);
+
+ if (!finished)
+ {
+ writer.Write(TokenPropertyName);
+ writer.Write(token);
+ }
+
+ if (elements.Count > 0)
+ {
+ writer.Write(ValuesPropertyName);
+ context.GetConverter>(context.TypeShapeProvider).Write(ref writer, elements, context);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Converts an instance of to an enumeration token.
+ ///
+#pragma warning disable CA1812
+ internal class GeneratorConverter : MessagePackConverter
+ where TClass : IAsyncEnumerable
+#pragma warning restore CA1812
+ {
+ public override TClass Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ throw new NotSupportedException();
+ }
+
+ [SuppressMessage("Usage", "NBMsgPack031:Converters should read or write exactly one msgpack structure", Justification = "Writer is passed to helper method")]
+ public override void Write(ref MessagePackWriter writer, in TClass? value, SerializationContext context)
+ {
+ NerdbankMessagePackFormatter mainFormatter = context.GetFormatter();
+
+ context.DepthStep();
+ PreciseTypeConverter.Serialize_Shared(mainFormatter, ref writer, value, context);
+ }
+
+ public override JsonObject? GetJsonSchema(JsonSchemaContext context, ITypeShape typeShape)
+ {
+ return CreateUndocumentedSchema(typeof(GeneratorConverter));
+ }
+ }
+ }
+}
diff --git a/src/StreamJsonRpc/NerdbankMessagePackFormatter.Constants.cs b/src/StreamJsonRpc/NerdbankMessagePackFormatter.Constants.cs
new file mode 100644
index 000000000..8990d0a14
--- /dev/null
+++ b/src/StreamJsonRpc/NerdbankMessagePackFormatter.Constants.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Nerdbank.MessagePack;
+using StreamJsonRpc.Protocol;
+
+namespace StreamJsonRpc;
+
+///
+/// Serializes JSON-RPC messages using MessagePack (a fast, compact binary format).
+///
+public partial class NerdbankMessagePackFormatter
+{
+ ///
+ /// The constant "jsonrpc", in its various forms.
+ ///
+ private static readonly MessagePackString VersionPropertyName = new(Constants.jsonrpc);
+
+ ///
+ /// The constant "id", in its various forms.
+ ///
+ private static readonly MessagePackString IdPropertyName = new(Constants.id);
+
+ ///
+ /// The constant "method", in its various forms.
+ ///
+ private static readonly MessagePackString MethodPropertyName = new(Constants.Request.method);
+
+ ///
+ /// The constant "result", in its various forms.
+ ///
+ private static readonly MessagePackString ResultPropertyName = new(Constants.Result.result);
+
+ ///
+ /// The constant "error", in its various forms.
+ ///
+ private static readonly MessagePackString ErrorPropertyName = new(Constants.Error.error);
+
+ ///
+ /// The constant "params", in its various forms.
+ ///
+ private static readonly MessagePackString ParamsPropertyName = new(Constants.Request.@params);
+
+ ///
+ /// The constant "traceparent", in its various forms.
+ ///
+ private static readonly MessagePackString TraceParentPropertyName = new(Constants.Request.traceparent);
+
+ ///
+ /// The constant "tracestate", in its various forms.
+ ///
+ private static readonly MessagePackString TraceStatePropertyName = new(Constants.Request.tracestate);
+
+ ///
+ /// The constant "2.0", in its various forms.
+ ///
+ private static readonly MessagePackString Version2 = new("2.0");
+}
diff --git a/src/StreamJsonRpc/NerdbankMessagePackFormatter.EnumeratorResultsConverter.cs b/src/StreamJsonRpc/NerdbankMessagePackFormatter.EnumeratorResultsConverter.cs
new file mode 100644
index 000000000..379623815
--- /dev/null
+++ b/src/StreamJsonRpc/NerdbankMessagePackFormatter.EnumeratorResultsConverter.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Nodes;
+using Nerdbank.MessagePack;
+using PolyType.Abstractions;
+using StreamJsonRpc.Reflection;
+
+namespace StreamJsonRpc;
+
+///
+/// Serializes JSON-RPC messages using MessagePack (a fast, compact binary format).
+///
+public partial class NerdbankMessagePackFormatter
+{
+ private class EnumeratorResultsConverter : MessagePackConverter>
+ {
+ [SuppressMessage("Usage", "NBMsgPack031:Converters should read or write exactly one msgpack structure", Justification = "Reader is passed to user data context")]
+ public override MessageFormatterEnumerableTracker.EnumeratorResults? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ if (reader.TryReadNil())
+ {
+ return default;
+ }
+
+ NerdbankMessagePackFormatter formatter = context.GetFormatter();
+ context.DepthStep();
+
+ Verify.Operation(reader.ReadArrayHeader() == 2, "Expected array of length 2.");
+ return new MessageFormatterEnumerableTracker.EnumeratorResults()
+ {
+ Values = formatter.userDataProfile.Deserialize>(ref reader, context.CancellationToken),
+ Finished = formatter.userDataProfile.Deserialize(ref reader, context.CancellationToken),
+ };
+ }
+
+ [SuppressMessage("Usage", "NBMsgPack031:Converters should read or write exactly one msgpack structure", Justification = "Writer is passed to user data context")]
+ public override void Write(ref MessagePackWriter writer, in MessageFormatterEnumerableTracker.EnumeratorResults? value, SerializationContext context)
+ {
+ if (value is null)
+ {
+ writer.WriteNil();
+ }
+ else
+ {
+ NerdbankMessagePackFormatter formatter = context.GetFormatter();
+ context.DepthStep();
+
+ writer.WriteArrayHeader(2);
+ formatter.userDataProfile.Serialize(ref writer, value.Values, context.CancellationToken);
+ formatter.userDataProfile.Serialize(ref writer, value.Finished, context.CancellationToken);
+ }
+ }
+
+ public override JsonObject? GetJsonSchema(JsonSchemaContext context, ITypeShape typeShape)
+ {
+ return CreateUndocumentedSchema(typeof(EnumeratorResultsConverter));
+ }
+ }
+}
diff --git a/src/StreamJsonRpc/NerdbankMessagePackFormatter.EventArgsConverter.cs b/src/StreamJsonRpc/NerdbankMessagePackFormatter.EventArgsConverter.cs
new file mode 100644
index 000000000..24e0bf76d
--- /dev/null
+++ b/src/StreamJsonRpc/NerdbankMessagePackFormatter.EventArgsConverter.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Text.Json.Nodes;
+using Nerdbank.MessagePack;
+using PolyType.Abstractions;
+
+namespace StreamJsonRpc;
+
+///
+/// Serializes JSON-RPC messages using MessagePack (a fast, compact binary format).
+///
+public partial class NerdbankMessagePackFormatter
+{
+ ///
+ /// Enables formatting the default/empty class.
+ ///
+ private class EventArgsConverter : MessagePackConverter
+ {
+ internal static readonly EventArgsConverter Instance = new();
+
+ private EventArgsConverter()
+ {
+ }
+
+ ///
+ public override void Write(ref MessagePackWriter writer, in EventArgs? value, SerializationContext context)
+ {
+ Requires.NotNull(value!, nameof(value));
+ context.DepthStep();
+ writer.WriteMapHeader(0);
+ }
+
+ ///
+ public override EventArgs Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ context.DepthStep();
+ reader.Skip(context);
+ return EventArgs.Empty;
+ }
+
+ public override JsonObject? GetJsonSchema(JsonSchemaContext context, ITypeShape typeShape)
+ {
+ return CreateUndocumentedSchema(typeof(EventArgsConverter));
+ }
+ }
+}
diff --git a/src/StreamJsonRpc/NerdbankMessagePackFormatter.ExceptionConverter.cs b/src/StreamJsonRpc/NerdbankMessagePackFormatter.ExceptionConverter.cs
new file mode 100644
index 000000000..6831f6245
--- /dev/null
+++ b/src/StreamJsonRpc/NerdbankMessagePackFormatter.ExceptionConverter.cs
@@ -0,0 +1,127 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.Serialization;
+using System.Text.Json.Nodes;
+using Nerdbank.MessagePack;
+using PolyType.Abstractions;
+using StreamJsonRpc.Reflection;
+
+namespace StreamJsonRpc;
+
+///
+/// Serializes JSON-RPC messages using MessagePack (a fast, compact binary format).
+///
+public partial class NerdbankMessagePackFormatter
+{
+ ///
+ /// Manages serialization of any -derived type that follows standard rules.
+ ///
+ ///
+ /// A serializable class will:
+ /// 1. Derive from
+ /// 2. Be attributed with
+ /// 3. Declare a constructor with a signature of (, ).
+ ///
+ private class ExceptionConverter : MessagePackConverter
+ where T : Exception
+ {
+ public static readonly ExceptionConverter Instance = new();
+
+ public override T? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ NerdbankMessagePackFormatter formatter = context.GetFormatter();
+ Assumes.NotNull(formatter.JsonRpc);
+
+ context.DepthStep();
+
+ if (reader.TryReadNil())
+ {
+ return null;
+ }
+
+ // We have to guard our own recursion because the serializer has no visibility into inner exceptions.
+ // Each exception in the russian doll is a new serialization job from its perspective.
+ formatter.exceptionRecursionCounter.Value++;
+ try
+ {
+ if (formatter.exceptionRecursionCounter.Value > formatter.JsonRpc.ExceptionOptions.RecursionLimit)
+ {
+ // Exception recursion has gone too deep. Skip this value and return null as if there were no inner exception.
+ // Note that in skipping, the parser may use recursion internally and may still throw if its own limits are exceeded.
+ reader.Skip(context);
+ return null;
+ }
+
+ // TODO: Is this the right context?
+ var info = new SerializationInfo(typeof(T), new MessagePackFormatterConverter(formatter.userDataProfile));
+ int memberCount = reader.ReadMapHeader();
+ for (int i = 0; i < memberCount; i++)
+ {
+ string? name = context.GetConverter(context.TypeShapeProvider).Read(ref reader, context)
+ ?? throw new MessagePackSerializationException(Resources.UnexpectedNullValueInMap);
+
+ // SerializationInfo.GetValue(string, typeof(object)) does not call our formatter,
+ // so the caller will get a boxed RawMessagePack struct in that case.
+ // Although we can't do much about *that* in general, we can at least ensure that null values
+ // are represented as null instead of this boxed struct.
+ var value = reader.TryReadNil() ? null : (object)reader.ReadRaw(context);
+
+ info.AddSafeValue(name, value);
+ }
+
+ return ExceptionSerializationHelpers.Deserialize(formatter.JsonRpc, info, formatter.JsonRpc.TraceSource);
+ }
+ finally
+ {
+ formatter.exceptionRecursionCounter.Value--;
+ }
+ }
+
+ public override void Write(ref MessagePackWriter writer, in T? value, SerializationContext context)
+ {
+ NerdbankMessagePackFormatter formatter = context.GetFormatter();
+
+ context.DepthStep();
+ if (value is null)
+ {
+ writer.WriteNil();
+ return;
+ }
+
+ formatter.exceptionRecursionCounter.Value++;
+ try
+ {
+ if (formatter.exceptionRecursionCounter.Value > formatter.JsonRpc?.ExceptionOptions.RecursionLimit)
+ {
+ // Exception recursion has gone too deep. Skip this value and write null as if there were no inner exception.
+ writer.WriteNil();
+ return;
+ }
+
+ // TODO: Is this the right profile?
+ var info = new SerializationInfo(typeof(T), new MessagePackFormatterConverter(formatter.userDataProfile));
+ ExceptionSerializationHelpers.Serialize(value, info);
+ writer.WriteMapHeader(info.GetSafeMemberCount());
+ foreach (SerializationEntry element in info.GetSafeMembers())
+ {
+ writer.Write(element.Name);
+ formatter.rpcProfile.SerializeObject(
+ ref writer,
+ element.Value,
+ element.ObjectType,
+ context.CancellationToken);
+ }
+ }
+ finally
+ {
+ formatter.exceptionRecursionCounter.Value--;
+ }
+ }
+
+ public override JsonObject? GetJsonSchema(JsonSchemaContext context, ITypeShape typeShape)
+ {
+ return CreateUndocumentedSchema(typeof(ExceptionConverter));
+ }
+ }
+}
diff --git a/src/StreamJsonRpc/NerdbankMessagePackFormatter.IJsonRpcMessagePackRetention.cs b/src/StreamJsonRpc/NerdbankMessagePackFormatter.IJsonRpcMessagePackRetention.cs
new file mode 100644
index 000000000..6ceb33db0
--- /dev/null
+++ b/src/StreamJsonRpc/NerdbankMessagePackFormatter.IJsonRpcMessagePackRetention.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Buffers;
+
+namespace StreamJsonRpc;
+
+///
+/// Serializes JSON-RPC messages using MessagePack (a fast, compact binary format).
+///
+public partial class NerdbankMessagePackFormatter
+{
+ private interface IJsonRpcMessagePackRetention
+ {
+ ///
+ /// Gets the original msgpack sequence that was deserialized into this message.
+ ///
+ ///
+ /// The buffer is only retained for a short time. If it has already been cleared, the result of this property is an empty sequence.
+ ///
+ ReadOnlySequence OriginalMessagePack { get; }
+ }
+}
diff --git a/src/StreamJsonRpc/NerdbankMessagePackFormatter.MessagePackFormatterConverter.cs b/src/StreamJsonRpc/NerdbankMessagePackFormatter.MessagePackFormatterConverter.cs
new file mode 100644
index 000000000..784455db9
--- /dev/null
+++ b/src/StreamJsonRpc/NerdbankMessagePackFormatter.MessagePackFormatterConverter.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Buffers;
+using System.Runtime.Serialization;
+using Nerdbank.MessagePack;
+using StreamJsonRpc.Reflection;
+
+namespace StreamJsonRpc;
+
+///
+/// Serializes JSON-RPC messages using MessagePack (a fast, compact binary format).
+///
+public partial class NerdbankMessagePackFormatter
+{
+ private class MessagePackFormatterConverter : IFormatterConverter
+ {
+ private readonly Profile formatterContext;
+
+ internal MessagePackFormatterConverter(Profile formatterContext)
+ {
+ this.formatterContext = formatterContext;
+ }
+
+#pragma warning disable CS8766 // This method may in fact return null, and no one cares.
+ public object? Convert(object value, Type type)
+#pragma warning restore CS8766
+ {
+ return this.formatterContext.DeserializeObject((ReadOnlySequence)value, type);
+ }
+
+ public object Convert(object value, TypeCode typeCode)
+ {
+ return typeCode switch
+ {
+ TypeCode.Object => this.formatterContext.Deserialize