diff --git a/c-sharp-tests/DummyPapiClient.cs b/c-sharp-tests/DummyPapiClient.cs index 20f05b11e6..18d65acf83 100644 --- a/c-sharp-tests/DummyPapiClient.cs +++ b/c-sharp-tests/DummyPapiClient.cs @@ -1,16 +1,16 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using Paranext.DataProvider.MessageHandlers; using Paranext.DataProvider.Messages; using Paranext.DataProvider.MessageTransports; -using PtxUtils; namespace TestParanextDataProvider { [ExcludeFromCodeCoverage] internal class DummyPapiClient : PapiClient { - private readonly Dictionary, Func> _eventHandlers = new(); + private readonly Dictionary> _eventHandlers = new(); public Stack EventMessages { get; } = new(); @@ -42,28 +42,34 @@ public override Task DisconnectAsync() return Task.CompletedTask; } - public override Task RegisterRequestHandler(Enum requestType, - Func requestHandler, int responseTimeoutInMs = 1000) + public override Task RegisterRequestHandler( + string requestType, + Func requestHandler, + int responseTimeoutInMs = 5000 + ) { var responder = (MessageHandlerRequestByRequestType) - _messageHandlersByMessageType[MessageType.Request]; + _messageHandlersByMessageType[MessageType.REQUEST]; responder.SetHandlerForRequestType(requestType, requestHandler); return Task.FromResult(true); } - public override void RegisterEventHandler(Enum eventType, Func eventHandler) + public override void RegisterEventHandler( + string eventType, + Func eventHandler + ) { - _eventHandlers.Add(eventType, eventHandler); + _eventHandlers.Add(eventType, eventHandler); } public override void SendEvent(MessageEvent message) { - if (!_eventHandlers.TryGetValue(message.EventType, out var handler)) - return; + if (!_eventHandlers.TryGetValue(message.EventType, out var handler)) + return; - Message? result = handler(message.Event); - EventMessages.Push(result); + Message? result = handler(message); + EventMessages.Push(result); } #endregion } diff --git a/c-sharp-tests/JsonUtils/MessageConverterTests.cs b/c-sharp-tests/JsonUtils/MessageConverterTests.cs index 771e9305b6..379478d56e 100644 --- a/c-sharp-tests/JsonUtils/MessageConverterTests.cs +++ b/c-sharp-tests/JsonUtils/MessageConverterTests.cs @@ -25,8 +25,8 @@ public void Deserialize_ClientConnect_StronglyTypedContentsAreCorrect() {"type":"event","eventType":"network:onDidClientConnect","senderId":0,"event":{"clientId":3,"didReconnect":false}} """; var msg = DeserializeMessageEvent(messageToDecode); - Assert.That(msg.EventContents!.ClientId, Is.EqualTo(3)); - Assert.That(msg.EventContents!.DidReconnect, Is.False); + Assert.That(msg.Event.ClientId, Is.EqualTo(3)); + Assert.That(msg.Event.DidReconnect, Is.False); } [Test] @@ -36,7 +36,7 @@ public void Deserialize_ClientDisconnect_StronglyTypedContentsAreCorrect() {"type":"event","eventType":"network:onDidClientDisconnect","senderId":0,"event":{"clientId":123}} """; var msg = DeserializeMessageEvent(messageToDecode); - Assert.That(msg.EventContents!.ClientId, Is.EqualTo(123)); + Assert.That(msg.Event.ClientId, Is.EqualTo(123)); } [Test] @@ -46,7 +46,7 @@ public void Deserialize_ObjectDispose_StronglyTypedContentsAreCorrect() {"type":"event","eventType":"object:onDidDisposeNetworkObject","senderId":0,"event":"test-main"} """; var msg = DeserializeMessageEvent(messageToDecode); - Assert.That(msg.EventContents!, Is.EqualTo("test-main")); + Assert.That(msg.Event, Is.EqualTo("test-main")); } [Test] diff --git a/c-sharp-tests/JsonUtils/TupleTreeTests.cs b/c-sharp-tests/JsonUtils/TupleTreeTests.cs new file mode 100644 index 0000000000..b606799cb6 --- /dev/null +++ b/c-sharp-tests/JsonUtils/TupleTreeTests.cs @@ -0,0 +1,42 @@ +using Paranext.DataProvider.JsonUtils; + +namespace TestParanextDataProvider.JsonUtils +{ + internal class TupleTreeTests + { + private static List<(string key, string val)> ConvertStringsToTuples(params string[] pairs) + { + var retVal = new List<(string key, string val)>(); + if (pairs.Length % 2 != 0) + throw new ArgumentException("keys and values must be provided in pairs"); + for (int i = 0; i < pairs.Length; i += 2) + { + retVal.Add((pairs[i], pairs[i + 1])); + } + return retVal; + } + + [TestCase(new[] { "a", "b" }, new[] { "2" })] + [TestCase(new[] { "a", "b", "c", "d" }, new[] { "2" })] + [TestCase(new[] { "a", "b", "c", "d", "e", "f" }, new[] { "3" })] + [TestCase(new[] { "g", "h" }, new[] { "1" })] + [TestCase(new[] { "y", "z" }, new[] { "4" })] + [TestCase(new[] { "a", "b", "y", "z" }, new[] { "2", "4" })] + [TestCase(new[] { "a", "b", "c", "d", "y", "z" }, new[] { "2", "4" })] + [TestCase(new[] { "a", "b", "c", "d", "e", "f", "y", "z" }, new[] { "3", "4" })] + [TestCase(new string[0], new[] { "1" })] + public void TupleTree_FindAllResults_IsAccurate(string[] nameValuePairs, string[] expected) + { + var tree = new TupleTree("1"); + var values = new List<(string val1, string val2)> { ("a", "b") }; + tree.Add(values, "2"); + values = new List<(string val1, string val2)> { ("a", "b"), ("c", "d"), ("e", "f") }; + tree.Add(values, "3"); + values = new List<(string val1, string val2)> { ("y", "z") }; + tree.Add(values, "4"); + var results = tree.FindAllResults(ConvertStringsToTuples(nameValuePairs)); + var resultsList = results.ToArray(); + Assert.That(resultsList, Is.EquivalentTo(expected)); + } + } +} diff --git a/c-sharp-tests/MessageHandlers/MessageHandlerEventTests.cs b/c-sharp-tests/MessageHandlers/MessageHandlerEventTests.cs index 0724d84240..29914059b8 100644 --- a/c-sharp-tests/MessageHandlers/MessageHandlerEventTests.cs +++ b/c-sharp-tests/MessageHandlers/MessageHandlerEventTests.cs @@ -1,6 +1,5 @@ using Paranext.DataProvider.MessageHandlers; using Paranext.DataProvider.Messages; -using PtxUtils; using System.Diagnostics.CodeAnalysis; namespace TestParanextDataProvider.MessageHandlers; @@ -53,15 +52,16 @@ public void HandleMessage_SeveralHandlers_RegistrationWorks() private static void VerifyResults(IEnumerable messages, int expectedCount) { - Assert.That(messages.Count(), Is.EqualTo(expectedCount)); + var messageList = new List(messages); + Assert.That(messageList, Has.Count.EqualTo(expectedCount)); if (expectedCount == 0) return; List messageContents = new(); - foreach (var msg in messages) + foreach (var msg in messageList) { - messageContents.Add(((MessageEvent)msg).Event); + messageContents.Add(((MessageEvent)msg).Event!.ToString()!); } messageContents.Sort(); for (int i = 0; i < expectedCount; i++) @@ -70,21 +70,21 @@ private static void VerifyResults(IEnumerable messages, int expectedCou } } - private static Enum TestEventType => EventType.ObjectDispose; + private static string TestEventType => EventType.OBJECT_DISPOSE; private static MessageEvent TestMessage => new MessageEventObjectDisposed("test"); - private Message? ProcessEvent1(MessageEvent messageEvent) + private static Message ProcessEvent1(MessageEvent messageEvent) { return new MessageEventObjectDisposed("1"); } - private Message? ProcessEvent2(MessageEvent messageEvent) + private static Message ProcessEvent2(MessageEvent messageEvent) { return new MessageEventObjectDisposed("2"); } - private Message? ProcessEvent3(MessageEvent messageEvent) + private static Message ProcessEvent3(MessageEvent messageEvent) { return new MessageEventObjectDisposed("3"); } diff --git a/c-sharp-tests/PapiTestBase.cs b/c-sharp-tests/PapiTestBase.cs index 015e7c0b68..ff48de39b1 100644 --- a/c-sharp-tests/PapiTestBase.cs +++ b/c-sharp-tests/PapiTestBase.cs @@ -4,7 +4,6 @@ using System.Text.Json.Nodes; using System.Xml.Linq; using Paratext.Data; -using PtxUtils; using Paranext.DataProvider.Messages; using System.Web; using System.Diagnostics.CodeAnalysis; @@ -34,8 +33,9 @@ public virtual void TestSetup() [TearDown] public virtual void TestTearDown() { - List projects = - ScrTextCollection.ScrTexts(IncludeProjects.Everything).ToList(); + List projects = ScrTextCollection + .ScrTexts(IncludeProjects.Everything) + .ToList(); foreach (ScrText project in projects) ScrTextCollection.Remove(project, false); @@ -48,7 +48,9 @@ protected DummyPapiClient Client get { if (_client == null) - throw new InvalidOperationException("Can not access Client before test setup is run"); + throw new InvalidOperationException( + "Can not access Client before test setup is run" + ); return _client; } } @@ -58,7 +60,9 @@ protected DummyLocalParatextProjects ParatextProjects get { if (_projects == null) - throw new InvalidOperationException("Can not access Projects before test setup is run"); + throw new InvalidOperationException( + "Can not access Projects before test setup is run" + ); return _projects; } } @@ -87,7 +91,11 @@ protected static ProjectDetails CreateProjectDetails(ScrText scrText) /// Creates fake project details to fake the existence of a project /// /// - protected static ProjectDetails CreateProjectDetails(string id, string name, string projectType = "") + protected static ProjectDetails CreateProjectDetails( + string id, + string name, + string projectType = "" + ) { ProjectMetadata metadata = new(id, name, "ParatextFolders", projectType); return new ProjectDetails(metadata, "testDirectoryThatDoesNotExist"); @@ -98,8 +106,10 @@ protected static ProjectDetails CreateProjectDetails(string id, string name, str /// protected static JsonNode CreateVerseRefNode(int bookNum, int chapterNum, int verseNum) { - return JsonNode.Parse("{ \"versification\":\"English\", " + - $"\"_bookNum\":{bookNum}, \"_chapterNum\":{chapterNum}, \"_verseNum\":{verseNum} }}")!; + return JsonNode.Parse( + "{ \"versification\":\"English\", " + + $"\"_bookNum\":{bookNum}, \"_chapterNum\":{chapterNum}, \"_verseNum\":{verseNum} }}" + )!; } /// @@ -107,7 +117,9 @@ protected static JsonNode CreateVerseRefNode(int bookNum, int chapterNum, int ve /// protected static JsonNode CreateJsonString(string data) { - JsonNode node = JsonNode.Parse($"{{ \"data\":\"{HttpUtility.JavaScriptStringEncode(data)}\" }}")!; + JsonNode node = JsonNode.Parse( + $"{{ \"data\":\"{HttpUtility.JavaScriptStringEncode(data)}\" }}" + )!; return node.Root["data"]!; } @@ -115,7 +127,10 @@ protected static JsonNode CreateJsonString(string data) /// Replicates the creation of the JsonElement that is given to requests when /// coming from the server. /// - protected static JsonElement CreateRequestMessage(string function, params object[] parameters) + protected static JsonElement CreateRequestMessage( + string function, + params object[] parameters + ) { StringBuilder jsonBldr = new StringBuilder(); jsonBldr.Append("{ \"value\":["); @@ -132,7 +147,10 @@ protected static JsonElement CreateRequestMessage(string function, params object switch (param) { case string str: - jsonBldr.Append("\"").Append(HttpUtility.JavaScriptStringEncode(str)).Append("\""); + jsonBldr + .Append("\"") + .Append(HttpUtility.JavaScriptStringEncode(str)) + .Append("\""); break; case JsonNode node: jsonBldr.Append(node.ToJsonString()); @@ -153,13 +171,19 @@ protected static JsonElement CreateRequestMessage(string function, params object /// Creates a Data Scope node that is given to requests when /// coming from the server. /// - protected static JsonNode CreateDataScope(string extensionName, string dataQualifier, string? dataType = null) + protected static JsonNode CreateDataScope( + string extensionName, + string dataQualifier, + string? dataType = null + ) { // NOTE: projectId and projectName are usually automatically supplied - return JsonNode.Parse($"{{ \"extensionName\":\"{extensionName}\", " + - $"\"dataQualifier\":\"{dataQualifier}\" }}" + - (dataType != null ? $"\"dataType\":\"{dataType}\"" : ""))!; + return JsonNode.Parse( + $"{{ \"extensionName\":\"{extensionName}\", " + + $"\"dataQualifier\":\"{dataQualifier}\" }}" + + (dataType != null ? $"\"dataType\":\"{dataType}\"" : "") + )!; } #endregion @@ -168,7 +192,12 @@ protected static JsonNode CreateDataScope(string extensionName, string dataQuali /// Asserts that the two snippets of USFM are the same. This function normalizes both snippets /// to ensure maximum compatibility between them. /// - protected static void VerifyUsfmSame(string usfm1, string usfm2, ScrText scrText, int bookNum) + protected static void VerifyUsfmSame( + string usfm1, + string usfm2, + ScrText scrText, + int bookNum + ) { usfm1 = UsfmToken.NormalizeUsfm(scrText, bookNum, usfm1); usfm2 = UsfmToken.NormalizeUsfm(scrText, bookNum, usfm2); @@ -190,20 +219,31 @@ protected static void VerifyUsxSame(string usx1, string usx2) using (TextReader reader2 = new StringReader(usx2)) doc2 = XDocument.Load(reader2); - Assert.That(doc1.Root!.ToString().Replace("\r", "").Replace("\n", ""), - Is.EqualTo(doc2.Root!.ToString().Replace("\r", "").Replace("\n", ""))); + Assert.That( + doc1.Root!.ToString().Replace("\r", "").Replace("\n", ""), + Is.EqualTo(doc2.Root!.ToString().Replace("\r", "").Replace("\n", "")) + ); } /// /// Verifies the contents of a server response message /// - protected static void VerifyResponse(Message message, string? expectedErrorMessage, - Enum expectedResponseType, - int expectedRequestId, object? expectedContents) + protected static void VerifyResponse( + Message message, + string? expectedErrorMessage, + string expectedResponseType, + int expectedRequestId, + object? expectedContents + ) { Assert.Multiple(() => { - VerifyResponseExceptContents(message, expectedErrorMessage, expectedResponseType, expectedRequestId); + VerifyResponseExceptContents( + message, + expectedErrorMessage, + expectedResponseType, + expectedRequestId + ); Assert.That(((MessageResponse)message).Contents, Is.EqualTo(expectedContents)); }); } @@ -211,17 +251,23 @@ protected static void VerifyResponse(Message message, string? expectedErrorMessa /// /// Verifies the contents of a server response message ignoring the contents /// - protected static void VerifyResponseExceptContents(Message message, string? expectedErrorMessage, - Enum expectedResponseType, - int expectedRequestId) + protected static void VerifyResponseExceptContents( + Message message, + string? expectedErrorMessage, + string expectedResponseType, + int expectedRequestId + ) { Assert.Multiple(() => { - Assert.That(message.Type, Is.EqualTo(MessageType.Response)); + Assert.That(message.Type, Is.EqualTo(MessageType.RESPONSE)); MessageResponse response = (MessageResponse)message; Assert.That(response.ErrorMessage ?? "", Does.Contain(expectedErrorMessage ?? "")); - Assert.That(response.Success, Is.EqualTo(string.IsNullOrEmpty(expectedErrorMessage))); + Assert.That( + response.Success, + Is.EqualTo(string.IsNullOrEmpty(expectedErrorMessage)) + ); Assert.That(response.RequestType, Is.EqualTo(expectedResponseType)); Assert.That(response.RequestId, Is.EqualTo(expectedRequestId)); }); diff --git a/c-sharp-tests/Projects/ParatextDataProviderTests.cs b/c-sharp-tests/Projects/ParatextDataProviderTests.cs index c3a9ad1cbb..1957c23328 100644 --- a/c-sharp-tests/Projects/ParatextDataProviderTests.cs +++ b/c-sharp-tests/Projects/ParatextDataProviderTests.cs @@ -43,7 +43,7 @@ public async Task GetFunctions_MissingParameter(string function) JsonElement serverMessage = CreateRequestMessage(function); - Enum requestType = new(PdpDataRequest); + string requestType = PdpDataRequest; Message result = Client .FakeMessageFromServer(new MessageRequest(requestType, requesterId, serverMessage)) .First(); @@ -99,7 +99,7 @@ string expectedError CreateVerseRefNode(bookNum, chapterNum, verseNum) ); - Enum requestType = new(PdpDataRequest); + string requestType = PdpDataRequest; Message result = Client .FakeMessageFromServer(new MessageRequest(requestType, requesterId, serverMessage)) .First(); @@ -159,13 +159,14 @@ string expectedResult CreateVerseRefNode(bookNum, chapterNum, verseNum) ); - Enum requestType = new(PdpDataRequest); + string requestType = PdpDataRequest; Message result = Client .FakeMessageFromServer(new MessageRequest(requestType, requesterId, serverMessage)) .First(); VerifyResponseExceptContents(result, null, requestType, requesterId); - VerifyUsfmSame(((MessageResponse)result).Contents, expectedResult, _scrText, 1); + string? stringContents = (string?)((MessageResponse)result).Contents; + VerifyUsfmSame(stringContents!, expectedResult, _scrText, 1); } [TestCase( @@ -207,8 +208,8 @@ string expectedResult await provider.RegisterDataProvider(); // Set up an event listener to listen for the update - List updateEvents = new(); - Enum dataUpdateEvent = new(PdpDataUpdateEvent); + List updateEvents = new(); + string dataUpdateEvent = PdpDataUpdateEvent; Client.RegisterEventHandler( dataUpdateEvent, (e) => @@ -224,7 +225,7 @@ string expectedResult newValue ); - Enum requestType = new(PdpDataRequest); + string requestType = PdpDataRequest; Message result = Client .FakeMessageFromServer(new MessageRequest(requestType, requesterId, serverMessage)) .First(); @@ -240,7 +241,7 @@ string expectedResult // Verify an update event was sent out properly Assert.That(updateEvents.Count, Is.EqualTo(1)); Assert.That( - updateEvents[0], + updateEvents[0].Event, Is.EqualTo(ParatextProjectStorageInterpreter.AllScriptureDataTypes) ); @@ -259,7 +260,8 @@ string expectedResult .FakeMessageFromServer(new MessageRequest(requestType, requesterId, serverMessage2)) .First(); VerifyResponseExceptContents(result2, null, requestType, requesterId); - VerifyUsxSame(((MessageResponse)result2).Contents, newValue); + string? stringContents = (string?)((MessageResponse)result2).Contents; + VerifyUsxSame(stringContents!, newValue); } [TestCase( @@ -299,8 +301,8 @@ string expectedResult await provider.RegisterDataProvider(); // Set up an event listener to listen for the update - List updateEvents = new(); - Enum dataUpdateEvent = new(PdpDataUpdateEvent); + List updateEvents = new(); + string dataUpdateEvent = PdpDataUpdateEvent; Client.RegisterEventHandler( dataUpdateEvent, (e) => @@ -316,7 +318,7 @@ string expectedResult newValue ); - Enum requestType = new(PdpDataRequest); + string requestType = PdpDataRequest; Message result = Client .FakeMessageFromServer(new MessageRequest(requestType, requesterId, serverMessage)) .First(); @@ -332,7 +334,7 @@ string expectedResult // Verify an update event was sent out properly Assert.That(updateEvents.Count, Is.EqualTo(1)); Assert.That( - updateEvents[0], + updateEvents[0].Event, Is.EqualTo(ParatextProjectStorageInterpreter.AllScriptureDataTypes) ); @@ -351,7 +353,8 @@ string expectedResult .FakeMessageFromServer(new MessageRequest(requestType, requesterId, serverMessage2)) .First(); VerifyResponseExceptContents(result2, null, requestType, requesterId); - VerifyUsfmSame(((MessageResponse)result2).Contents, newValue, _scrText, 1); + string? stringContents = (string?)((MessageResponse)result2).Contents; + VerifyUsfmSame(stringContents!, newValue, _scrText, 1); } [Test] @@ -366,7 +369,7 @@ public async Task GetExtensionData_NoData_ReturnsError() JsonNode scope = CreateDataScope("myExtension", "myFile.txt"); JsonElement serverMessage = CreateRequestMessage("getExtensionData", scope); - Enum requestType = new(PdpDataRequest); + string requestType = new(PdpDataRequest); Message result = Client .FakeMessageFromServer(new MessageRequest(requestType, requesterId, serverMessage)) .First(); @@ -390,7 +393,7 @@ public async Task SetAndGetExtensionData_SavesAndGetsData() CreateJsonString("Random file contents") ); - Enum requestType = new(PdpDataRequest); + string requestType = PdpDataRequest; Message result = Client .FakeMessageFromServer(new MessageRequest(requestType, requesterId, serverMessage)) .First(); diff --git a/c-sharp-tests/Projects/ParatextProjectDataProviderFactoryTests.cs b/c-sharp-tests/Projects/ParatextProjectDataProviderFactoryTests.cs index 25e85e2c73..e38c180fc1 100644 --- a/c-sharp-tests/Projects/ParatextProjectDataProviderFactoryTests.cs +++ b/c-sharp-tests/Projects/ParatextProjectDataProviderFactoryTests.cs @@ -2,14 +2,13 @@ using System.Text.Json; using Paranext.DataProvider.Messages; using Paranext.DataProvider.Projects; -using PtxUtils; namespace TestParanextDataProvider.Projects { [ExcludeFromCodeCoverage] internal class ParatextProjectDataProviderFactoryTests : PsiTestBase { - private const string PdbFactoryGetRequest = + private const string PDB_FACTORY_GET_REQUEST = "object:platform.pdpFactory-ParatextStandard.function"; [Test] @@ -28,15 +27,11 @@ public async Task InvalidProjectId_ReturnsError() Message result = Client .FakeMessageFromServer( - new MessageRequest( - new Enum(PdbFactoryGetRequest), - requesterId, - serverMessage - ) + new MessageRequest(PDB_FACTORY_GET_REQUEST, requesterId, serverMessage) ) .First(); - Assert.That(result.Type, Is.EqualTo(MessageType.Response)); + Assert.That(result.Type, Is.EqualTo(MessageType.RESPONSE)); MessageResponse response = (MessageResponse)result; Assert.That(response.ErrorMessage, Is.EqualTo("Unknown project ID: unknownProj")); Assert.That(response.RequestId, Is.EqualTo(requesterId)); @@ -58,15 +53,11 @@ public async Task WrongNumberOfParameters_ReturnsError() Message result = Client .FakeMessageFromServer( - new MessageRequest( - new Enum(PdbFactoryGetRequest), - requesterId, - serverMessage - ) + new MessageRequest(PDB_FACTORY_GET_REQUEST, requesterId, serverMessage) ) .First(); - Assert.That(result.Type, Is.EqualTo(MessageType.Response)); + Assert.That(result.Type, Is.EqualTo(MessageType.RESPONSE)); MessageResponse response = (MessageResponse)result; Assert.That( response.ErrorMessage, @@ -97,15 +88,11 @@ public async Task InvalidType_ReturnsError() Message result = Client .FakeMessageFromServer( - new MessageRequest( - new Enum(PdbFactoryGetRequest), - requesterId, - serverMessage - ) + new MessageRequest(PDB_FACTORY_GET_REQUEST, requesterId, serverMessage) ) .First(); - Assert.That(result.Type, Is.EqualTo(MessageType.Response)); + Assert.That(result.Type, Is.EqualTo(MessageType.RESPONSE)); MessageResponse response = (MessageResponse)result; Assert.That( response.ErrorMessage, @@ -134,15 +121,11 @@ public async Task InvalidFunction_ReturnsError() Message result = Client .FakeMessageFromServer( - new MessageRequest( - new Enum(PdbFactoryGetRequest), - requesterId, - serverMessage - ) + new MessageRequest(PDB_FACTORY_GET_REQUEST, requesterId, serverMessage) ) .First(); - Assert.That(result.Type, Is.EqualTo(MessageType.Response)); + Assert.That(result.Type, Is.EqualTo(MessageType.RESPONSE)); MessageResponse response = (MessageResponse)result; Assert.That( response.ErrorMessage, @@ -172,15 +155,11 @@ public async Task GetProjectDataProviderID_ReturnsIdForProvider() Message result1 = Client .FakeMessageFromServer( - new MessageRequest( - new Enum(PdbFactoryGetRequest), - requesterId1, - serverMessage - ) + new MessageRequest(PDB_FACTORY_GET_REQUEST, requesterId1, serverMessage) ) .First(); - Assert.That(result1.Type, Is.EqualTo(MessageType.Response)); + Assert.That(result1.Type, Is.EqualTo(MessageType.RESPONSE)); MessageResponse response1 = (MessageResponse)result1; Assert.That(response1.ErrorMessage, Is.Null); Assert.That(response1.RequestId, Is.EqualTo(requesterId1)); @@ -191,15 +170,11 @@ public async Task GetProjectDataProviderID_ReturnsIdForProvider() // Make sure another request for the same provider gets the same ID Message result2 = Client .FakeMessageFromServer( - new MessageRequest( - new Enum(PdbFactoryGetRequest), - requesterId2, - serverMessage - ) + new MessageRequest(PDB_FACTORY_GET_REQUEST, requesterId2, serverMessage) ) .First(); - Assert.That(result2.Type, Is.EqualTo(MessageType.Response)); + Assert.That(result2.Type, Is.EqualTo(MessageType.RESPONSE)); MessageResponse response2 = (MessageResponse)result2; Assert.That(response2.ErrorMessage, Is.Null); Assert.That(response2.RequestId, Is.EqualTo(requesterId2)); diff --git a/c-sharp/JsonUtils/JsonMessageDeserializationAttribute.cs b/c-sharp/JsonUtils/JsonMessageDeserializationAttribute.cs new file mode 100644 index 0000000000..b3160b428e --- /dev/null +++ b/c-sharp/JsonUtils/JsonMessageDeserializationAttribute.cs @@ -0,0 +1,25 @@ +namespace Paranext.DataProvider.JsonUtils +{ + [AttributeUsage(AttributeTargets.Class)] + public class JsonMessageDeserializationAttribute : Attribute + { + /// + /// Keys and values from a flat array converted into string tuples + /// + public List<(string key, string value)> KeyValuePairs { get; } + + public JsonMessageDeserializationAttribute(params string[] pairs) + { + KeyValuePairs = new List<(string key, string value)>(); + if (pairs.Length % 2 != 0) + throw new ArgumentException( + "JSON deserialization keys and values must be provided in pairs" + ); + + for (int i = 0; i < pairs.Length; i += 2) + { + KeyValuePairs.Add((pairs[i], pairs[i + 1])); + } + } + } +} diff --git a/c-sharp/JsonUtils/MessageConverter.cs b/c-sharp/JsonUtils/MessageConverter.cs index fdc718685d..8924cceaaf 100644 --- a/c-sharp/JsonUtils/MessageConverter.cs +++ b/c-sharp/JsonUtils/MessageConverter.cs @@ -2,7 +2,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using Paranext.DataProvider.Messages; -using PtxUtils; namespace Paranext.DataProvider.JsonUtils; @@ -14,78 +13,18 @@ internal sealed class MessageConverter : JsonConverter private static readonly JsonSerializerOptions s_jsonOptions = SerializationOptions.CreateSerializationOptions(); - // Our type hierarchy for messages follows this pattern: - // Base (abstract) <- Messages <- Base subtype (abstract) <- Message Subtypes - // ^ - // |-- Not all messages have subtypes - // For example: - // Message <- MessageRequest - // Message <- MessageEvent <- MessageEventGeneric <- MessageEventClientConnect - // - // All non-abstract types have Enum values that can be seen in raw message JSON. - // s_messageTypeMap holds a mapping from Enum values to the .NET types. - // It does not contain message subtypes, only individual message types. - // For example, it maps "event" to MessageEvent and nothing to MessageEventClientConnect. - private static readonly Dictionary, Type> s_messageTypeMap = new(); - - // For event messages, all but MessageEventGeneric (since it is abstract) hold an - // Enum value that can be discerned from raw message JSON. - // s_eventTypeMap holds a mapping from Enum values to .NET types. - // It doesn't contain all message types, only event subtypes (subtypes of MessageEventGeneric). - // For example, it maps "network:onDidClientConnect" to "MessageEventClientConnect". - private static readonly Dictionary, Type> s_eventTypeMap = new(); + // Rules are in the form of (key, value) pairs => Type to deserialize + // The default type if nothing else matches is "Message" + private static readonly TupleTree s_deserializationRules = + new(typeof(Message)); static MessageConverter() { - foreach (var msg in GetObjectsOfClosestSubtypes()) + foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) { - s_messageTypeMap.Add(msg.Type, msg.GetType()); - } - - foreach (var evt in GetObjectsOfClosestSubtypes()) - { - if (evt.EventType != Enum.Null) - s_eventTypeMap.Add(evt.EventType, evt.GetType()); - } - } - - /// - /// "Closest" means there isn't a subclass in the hierarchy that can be created. For example: - /// C is a subclass of B which is a subclass of A. - /// When calling this for A, if B is abstract, then an object of type C will be returned. - /// When calling this for A, if B is not abstract, then an object of type B will be returned. - /// - private static IEnumerable GetObjectsOfClosestSubtypes() - where BaseType : class - { - var possibilities = Assembly - .GetExecutingAssembly() - .GetTypes() - // Note that "IsSubclassOf" goes arbitrarily deep in the hierarchy - .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(BaseType))) - .Select(type => Activator.CreateInstance(type, true) as BaseType) - .Where(obj => obj is not null); - - foreach (var obj in possibilities) - { - var baseType = obj!.GetType().BaseType; - while (baseType != null) - { - if (baseType == typeof(BaseType)) - { - yield return obj; - break; - } - - if (baseType.IsAbstract) - { - baseType = baseType.BaseType; - continue; - } - - // At this point, a closer, creatable (i.e., non-abstract) subclass was found - break; - } + var attribute = type.GetCustomAttribute(false); + if (attribute != null) + s_deserializationRules.Add(attribute.KeyValuePairs, type); } } @@ -98,29 +37,22 @@ public override Message Read( JsonSerializerOptions options ) { - if (reader.TokenType != JsonTokenType.StartObject) - throw new JsonException("Not at start of JSON object"); - - // Find the right message type to deserialize - Enum messageType = ReadValue(reader, "type"); - if (!s_messageTypeMap.TryGetValue(messageType, out Type? messageDataType)) - throw new ArgumentException("Unexpected message type: " + messageType); - - // Provide a more specific type for event messages if we know about it - if (messageDataType == typeof(MessageEvent)) + // Copy the current state of the reader because we will need it again + var readerClone = reader; + var propertyNamesAndValues = new HashSet<(string key, string value)>(); + using (var jsonDocument = JsonDocument.ParseValue(ref readerClone)) { - Enum eventType = ReadValue(reader, "eventType"); - if (s_eventTypeMap.TryGetValue(eventType, out Type? eventMessageDataType)) - messageDataType = eventMessageDataType; + foreach (var jsonProperty in jsonDocument.RootElement.EnumerateObject()) + { + // For now we only need string properties from messages. This could be expanded. + if (jsonProperty.Value.ValueKind == JsonValueKind.String) + propertyNamesAndValues.Add((jsonProperty.Name, jsonProperty.Value.ToString())); + } } - - // Copy the current state of the reader because we might need it again - Utf8JsonReader readerClone = reader; - - var msg = (Message)JsonSerializer.Deserialize(ref reader, messageDataType!, s_jsonOptions)!; - if (msg is MessageEvent msgEvent) - msgEvent.Event = GetEventData(readerClone, msgEvent.EventContentsType); - return msg; + // If we want to be picky, we could look for more matching types and throw/warn if any are seen. + // That would mean the protocol itself is busted, though, which is really a design problem. + var messageType = s_deserializationRules.FindAllResults(propertyNamesAndValues).First(); + return (Message)JsonSerializer.Deserialize(ref reader, messageType, s_jsonOptions)!; } public override void Write( @@ -131,58 +63,4 @@ JsonSerializerOptions options { JsonSerializer.Serialize(writer, message, message.GetType(), s_jsonOptions); } - - /// - /// Reads the property from the message given the specified reader - /// - private static Enum ReadValue(Utf8JsonReader reader, string property) - where T : class, EnumType - { - do - { - bool success = reader.Read(); - if (!success) - return Enum.Null; - - if (reader.TokenType != JsonTokenType.PropertyName) - continue; - - string? propertyName = reader.GetString(); - if (propertyName != property) - continue; - - success = reader.Read(); - if (!success) - return Enum.Null; - - if (reader.TokenType != JsonTokenType.String) - throw new JsonException($"Unexpected token {reader.TokenType} (expected String)"); - - return new Enum(reader.GetString()); - } while (true); - } - - /// - /// Deserializes the specific type for the "event" property from the given reader - /// - private static dynamic? GetEventData(Utf8JsonReader reader, Type type) - { - do - { - if (!reader.Read()) - break; - - if (reader.TokenType != JsonTokenType.PropertyName) - continue; - - string? propertyName = reader.GetString(); - if (propertyName != "event") - continue; - - reader.Read(); - return JsonSerializer.Deserialize(ref reader, type, s_jsonOptions); - } while (true); - - throw new JsonException("Could not find event data within event message"); - } } diff --git a/c-sharp/JsonUtils/PrivateConstructorResolver.cs b/c-sharp/JsonUtils/PrivateConstructorResolver.cs index 5a25b7382b..8e190c7dde 100644 --- a/c-sharp/JsonUtils/PrivateConstructorResolver.cs +++ b/c-sharp/JsonUtils/PrivateConstructorResolver.cs @@ -13,7 +13,7 @@ public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions option { JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options); - if (jsonTypeInfo.Kind == JsonTypeInfoKind.Object && jsonTypeInfo.CreateObject is null) + if (jsonTypeInfo is { Kind: JsonTypeInfoKind.Object, CreateObject: null }) { if ( jsonTypeInfo.Type diff --git a/c-sharp/JsonUtils/SerializationOptions.cs b/c-sharp/JsonUtils/SerializationOptions.cs index bf5b67533e..61bda0ab53 100644 --- a/c-sharp/JsonUtils/SerializationOptions.cs +++ b/c-sharp/JsonUtils/SerializationOptions.cs @@ -1,7 +1,6 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Unicode; -using Paranext.DataProvider.Messages; namespace Paranext.DataProvider.JsonUtils; @@ -21,10 +20,6 @@ public static JsonSerializerOptions CreateSerializationOptions() WriteIndented = false, // No need to waste bytes with nice formatting IgnoreReadOnlyProperties = false, // Need types to be serialized }; - - options.Converters.Add(new EnumConverter()); - options.Converters.Add(new EnumConverter()); - options.Converters.Add(new EnumConverter()); return options; } } diff --git a/c-sharp/JsonUtils/TupleTree.cs b/c-sharp/JsonUtils/TupleTree.cs new file mode 100644 index 0000000000..2d65091893 --- /dev/null +++ b/c-sharp/JsonUtils/TupleTree.cs @@ -0,0 +1,98 @@ +namespace Paranext.DataProvider.JsonUtils; + +/// +/// This class represents a tree data structure whose children are identified by a unique Tuple<T1, T2>. +/// Each node may contain a value of type TResult. The root node and leaf nodes must contain TResult values. +/// +internal class TupleTree + where T1 : IEquatable + where T2 : IEquatable + where TResult : class +{ + private class ChildNode + { + public TResult? Result { get; set; } + public TupleTree? SubTree { get; set; } + } + + private readonly TResult? _defaultValue; + private readonly Dictionary<(T1 val1, T2 val2), ChildNode> _children = new(); + + private TupleTree() { } + + public TupleTree(TResult defaultValue) + { + _defaultValue = defaultValue; + } + + /// + /// Add an ordered set of tuples as a new path in the tree with a given result + /// + /// Ordered list of tuples that will each represent a node in the tree + /// Result value to store for the final item in the list + /// An exception will be thrown if the given path already has a value + public void Add(IList<(T1 val1, T2 val2)> tuples, TResult result) + { + var firstTuple = tuples.First(); + if (!_children.TryGetValue(firstTuple, out var childNode)) + { + childNode = new ChildNode(); + _children[firstTuple] = childNode; + } + + if (tuples.Count == 1) + { + if (childNode.Result != null) + throw new Exception( + $"Value \"{childNode.Result}\" already exists for {firstTuple}, cannot change it to \"{result}\"" + ); + childNode.Result = result; + } + else + { + childNode.SubTree ??= new TupleTree(); + childNode.SubTree.Add(tuples.Skip(1).ToList(), result); + } + } + + /// + /// Find all values in the tree that match the given set of tuples. + /// For paths that don't fully match, values from the closest interior node of the tree are used. + /// For example, take a path A* -> B* -> C -> D* that exists on a tree, where A, B, and D have values assigned. + /// If the given tuples match C but not D, then the value of B will be returned. + /// + public IEnumerable FindAllResults(IEnumerable<(T1 val1, T2 val2)> tuples) + { + return FindAllResultsInternal(new HashSet<(T1 val1, T2 val2)>(tuples)); + } + + private IEnumerable FindAllResultsInternal(IReadOnlySet<(T1 val1, T2 val2)> tuples) + { + bool foundReturnValue = false; + foreach (var child in _children.Where(child => tuples.Contains(child.Key))) + { + if (child.Value.SubTree != null) + { + foreach (var returnValue in child.Value.SubTree.FindAllResultsInternal(tuples)) + { + foundReturnValue = true; + yield return returnValue; + } + } + else if (child.Value.Result != null) + { + foundReturnValue = true; + yield return child.Value.Result; + } + + if (!foundReturnValue && (child.Value.Result != null)) + { + foundReturnValue = true; + yield return child.Value.Result; + } + } + + if (!foundReturnValue && _defaultValue != null) + yield return _defaultValue; + } +} diff --git a/c-sharp/MessageHandlers/MessageHandlerEvent.cs b/c-sharp/MessageHandlers/MessageHandlerEvent.cs index ff354bc831..9115a6682c 100644 --- a/c-sharp/MessageHandlers/MessageHandlerEvent.cs +++ b/c-sharp/MessageHandlers/MessageHandlerEvent.cs @@ -10,10 +10,10 @@ namespace Paranext.DataProvider.MessageHandlers; /// internal class MessageHandlerEvent : IMessageHandler { - private readonly Dictionary, PapiEventHandler> _handlers = new(); + private readonly Dictionary _handlers = new(); private readonly object _handlersLock = new(); - public void RegisterEventHandler(Enum eventType, PapiEventHandler handler) + public void RegisterEventHandler(string eventType, PapiEventHandler handler) { lock (_handlersLock) { @@ -34,7 +34,7 @@ public void RegisterEventHandler(Enum eventType, PapiEventHandler han } } - public void UnregisterEventHandler(Enum eventType, PapiEventHandler handler) + public void UnregisterEventHandler(string eventType, PapiEventHandler handler) { lock (_handlersLock) { @@ -54,13 +54,12 @@ public IEnumerable HandleMessage(Message message) if (message == null) throw new ArgumentNullException(nameof(message)); - if (message.Type != MessageType.Event) + if (message is not MessageEvent evt) throw new ArgumentException("Incorrect message type", nameof(message)); - Console.WriteLine($"Event received: {StringUtils.LimitLength(message.ToString(), 180)}"); + Console.WriteLine($"Event received: {StringUtils.LimitLength(evt.ToString(), 180)}"); - MessageEvent evt = (MessageEvent)message; - Delegate[]? handlersToRun = null; + Delegate[]? handlersToRun; lock (_handlersLock) { if (!_handlers.TryGetValue(evt.EventType, out PapiEventHandler? handlersForEventType)) diff --git a/c-sharp/MessageHandlers/MessageHandlerInitClient.cs b/c-sharp/MessageHandlers/MessageHandlerInitClient.cs index 3f193963f6..d07272d26a 100644 --- a/c-sharp/MessageHandlers/MessageHandlerInitClient.cs +++ b/c-sharp/MessageHandlers/MessageHandlerInitClient.cs @@ -7,7 +7,7 @@ namespace Paranext.DataProvider.MessageHandlers; /// internal class MessageHandlerInitClient : IMessageHandler { - Action _clientIdCallback; + private readonly Action _clientIdCallback; public MessageHandlerInitClient(Action clientIdCallback) { @@ -19,10 +19,9 @@ public IEnumerable HandleMessage(Message message) if (message == null) throw new ArgumentNullException(nameof(message)); - if (message.Type != MessageType.InitClient) + if (message is not MessageInitClient initClientMsg) throw new ArgumentException("Incorrect message type", nameof(message)); - var initClientMsg = (MessageInitClient)message; int clientId = initClientMsg.ConnectorInfo.ClientId; _clientIdCallback(clientId); yield return new MessageClientConnect(clientId); diff --git a/c-sharp/MessageHandlers/MessageHandlerRequestByRequestType.cs b/c-sharp/MessageHandlers/MessageHandlerRequestByRequestType.cs index 7a34f44e37..b3a8ab338d 100644 --- a/c-sharp/MessageHandlers/MessageHandlerRequestByRequestType.cs +++ b/c-sharp/MessageHandlers/MessageHandlerRequestByRequestType.cs @@ -1,22 +1,19 @@ using Paranext.DataProvider.Messages; -using PtxUtils; using System.Collections.Concurrent; +using System.Text.Json; namespace Paranext.DataProvider.MessageHandlers; -using RequestHandler = Func; +using RequestHandler = Func; /// /// Handler for "Request" messages that assumes there should be 1 way to respond to each RequestType /// internal class MessageHandlerRequestByRequestType : IMessageHandler { - private readonly ConcurrentDictionary< - Enum, - RequestHandler - > _handlersByRequestType = new(); + private readonly ConcurrentDictionary _handlersByRequestType = new(); - public void SetHandlerForRequestType(Enum requestType, RequestHandler handler) + public void SetHandlerForRequestType(string requestType, RequestHandler handler) { _handlersByRequestType[requestType] = handler; } @@ -26,10 +23,9 @@ public IEnumerable HandleMessage(Message message) if (message == null) throw new ArgumentNullException(nameof(message)); - if (message.Type != MessageType.Request) + if (message is not MessageRequest request) throw new ArgumentException("Incorrect message type", nameof(message)); - var request = (MessageRequest)message; if (!_handlersByRequestType.TryGetValue(request.RequestType, out RequestHandler? handler)) { Console.Error.WriteLine($"Unable to process request type: {request.RequestType}"); @@ -52,7 +48,7 @@ public IEnumerable HandleMessage(Message message) request.RequestType, request.RequestId, request.SenderId, - response.ErrorMessage + response.ErrorMessage ?? "Unspecified error" ); } } diff --git a/c-sharp/MessageHandlers/MessageHandlerResponse.cs b/c-sharp/MessageHandlers/MessageHandlerResponse.cs index 85ec08ef33..9b49d613d6 100644 --- a/c-sharp/MessageHandlers/MessageHandlerResponse.cs +++ b/c-sharp/MessageHandlers/MessageHandlerResponse.cs @@ -8,9 +8,9 @@ namespace Paranext.DataProvider.MessageHandlers; internal class MessageHandlerResponse : IMessageHandler { private readonly MessageRequest _originalRequest; - private readonly Action _messageProcessingCallback; + private readonly Action _messageProcessingCallback; - public MessageHandlerResponse(MessageRequest originalRequest, Action callback) + public MessageHandlerResponse(MessageRequest originalRequest, Action callback) { _originalRequest = originalRequest; _messageProcessingCallback = callback; @@ -19,22 +19,19 @@ public MessageHandlerResponse(MessageRequest originalRequest, Action HandleMessage(Message message) { if (message == null) throw new ArgumentNullException(nameof(message)); - if (message.Type != MessageType.Response) + if (message is not MessageResponse response) throw new ArgumentException("Incorrect message type", nameof(message)); - var response = (MessageResponse)message; if (!response.Success) Console.Error.WriteLine( - "Request failed: \"{0}\" with error message \"{1}\"", - _originalRequest, - response.ErrorMessage ?? "" + $"Request failed: \"{_originalRequest}\" with error message \"{response.ErrorMessage ?? ""}\"" ); _messageProcessingCallback(response.Success, response.Contents); diff --git a/c-sharp/MessageHandlers/ResponseToRequest.cs b/c-sharp/MessageHandlers/ResponseToRequest.cs index 38d1a3843c..28711365a4 100644 --- a/c-sharp/MessageHandlers/ResponseToRequest.cs +++ b/c-sharp/MessageHandlers/ResponseToRequest.cs @@ -5,21 +5,19 @@ namespace Paranext.DataProvider.MessageHandlers; /// public sealed record ResponseToRequest { - private ResponseToRequest(bool success, dynamic? details) + private ResponseToRequest(bool success, object? contents, string? errorMessage) { Success = success; - if (success) - Contents = details; - else - ErrorMessage = details; + Contents = contents; + ErrorMessage = errorMessage; } /// /// Response when successful /// - public static ResponseToRequest Succeeded(dynamic? contents = null) + public static ResponseToRequest Succeeded(object? contents = null) { - return new ResponseToRequest(true, contents); + return new ResponseToRequest(true, contents, null); } /// @@ -27,12 +25,12 @@ public static ResponseToRequest Succeeded(dynamic? contents = null) /// public static ResponseToRequest Failed(string errorMessage) { - return new ResponseToRequest(false, errorMessage); + return new ResponseToRequest(false, null, errorMessage); } public bool Success { get; } public string? ErrorMessage { get; } - public dynamic? Contents { get; } + public object? Contents { get; } } diff --git a/c-sharp/MessageTransports/PapiClient.cs b/c-sharp/MessageTransports/PapiClient.cs index b60d5fa59d..fd30453252 100644 --- a/c-sharp/MessageTransports/PapiClient.cs +++ b/c-sharp/MessageTransports/PapiClient.cs @@ -5,7 +5,6 @@ using Paranext.DataProvider.JsonUtils; using Paranext.DataProvider.MessageHandlers; using Paranext.DataProvider.Messages; -using PtxUtils; namespace Paranext.DataProvider.MessageTransports; @@ -22,10 +21,7 @@ internal class PapiClient : IDisposable private static readonly Uri s_connectionUri = new("ws://localhost:8876"); private static readonly JsonSerializerOptions s_serializationOptions; - protected readonly Dictionary< - Enum, - IMessageHandler - > _messageHandlersByMessageType = new(); + protected readonly Dictionary _messageHandlersByMessageType = new(); protected readonly ConcurrentDictionary _messageHandlersForMyRequests = new(); private readonly ClientWebSocket _webSocket = new(); @@ -58,8 +54,8 @@ public PapiClient() _incomingMessageTask = new Task(HandleIncomingMessages, _cancellationToken); _outgoingMessageTask = new Task(HandleOutgoingMessages, _cancellationToken); - _messageHandlersByMessageType[MessageType.Event] = new MessageHandlerEvent(); - _messageHandlersByMessageType[MessageType.InitClient] = new MessageHandlerInitClient( + _messageHandlersByMessageType[MessageType.EVENT] = new MessageHandlerEvent(); + _messageHandlersByMessageType[MessageType.INIT_CLIENT] = new MessageHandlerInitClient( (int clientId) => { if (_clientInitializationComplete.Task.IsCompletedSuccessfully) @@ -69,7 +65,7 @@ public PapiClient() _clientInitializationComplete.TrySetResult(true); } ); - _messageHandlersByMessageType[MessageType.Request] = + _messageHandlersByMessageType[MessageType.REQUEST] = new MessageHandlerRequestByRequestType(); } @@ -197,8 +193,8 @@ await _webSocket.CloseAsync( /// Number of milliseconds to wait for the registration response to be received /// that will resolve to true if registration was successful, false otherwise public virtual async Task RegisterRequestHandler( - Enum requestType, - Func requestHandler, + string requestType, + Func requestHandler, int responseTimeoutInMs = 5000 ) { @@ -210,14 +206,14 @@ public virtual async Task RegisterRequestHandler( using Task registrationTask = registrationSource.Task; var requestMessage = new MessageRequest( - RequestType.RegisterRequest, + "server:registerRequest", Interlocked.Increment(ref _nextRequestId), - new dynamic[] { requestType.ToString(), _clientId } + new object[] { requestType, _clientId } ); _messageHandlersForMyRequests[requestMessage.RequestId] = new MessageHandlerResponse( requestMessage, - (bool success, dynamic? _) => + (bool success, object? _) => { if (!success) { @@ -228,7 +224,7 @@ public virtual async Task RegisterRequestHandler( else { var responder = (MessageHandlerRequestByRequestType) - _messageHandlersByMessageType[MessageType.Request]; + _messageHandlersByMessageType[MessageType.REQUEST]; responder.SetHandlerForRequestType(requestType, requestHandler); Console.WriteLine( $"Request type \"{requestType}\" successfully registered with the server" @@ -257,15 +253,15 @@ public virtual async Task RegisterRequestHandler( /// Configure PapiClient to call whenever an event of type is received. /// /// Event type to monitor - /// Function that optionally returns messages to send when an event is received + /// Function that accepts an event message and optionally returns messages to send public virtual void RegisterEventHandler( - Enum eventType, - Func eventHandler + string eventType, + Func eventHandler ) { ObjectDisposedException.ThrowIf(_isDisposed, this); - var msgHandler = (MessageHandlerEvent)_messageHandlersByMessageType[MessageType.Event]; + var msgHandler = (MessageHandlerEvent)_messageHandlersByMessageType[MessageType.EVENT]; msgHandler.RegisterEventHandler(eventType, eventHandler); Console.WriteLine($"Handler for event type \"{eventType}\" successfully registered"); } @@ -276,13 +272,13 @@ public virtual void RegisterEventHandler( /// Event type to monitor /// Same function reference previously passed to RegisterEventHandler public virtual void UnregisterEventHandler( - Enum eventType, - Func eventHandler + string eventType, + Func eventHandler ) { ObjectDisposedException.ThrowIf(_isDisposed, this); - var msgHandler = (MessageHandlerEvent)_messageHandlersByMessageType[MessageType.Event]; + var msgHandler = (MessageHandlerEvent)_messageHandlersByMessageType[MessageType.EVENT]; msgHandler.UnregisterEventHandler(eventType, eventHandler); Console.WriteLine($"Handler for event type \"{eventType}\" successfully unregistered"); } diff --git a/c-sharp/Messages/EventType.cs b/c-sharp/Messages/EventType.cs index cccf42ec35..6198079646 100644 --- a/c-sharp/Messages/EventType.cs +++ b/c-sharp/Messages/EventType.cs @@ -1,13 +1,13 @@ -using PtxUtils; - namespace Paranext.DataProvider.Messages; -public sealed class EventType : EnumType +/// +/// These are well known event types, but there isn't a fixed set of all possible event types +/// +public static class EventType { - public static readonly Enum ClientConnect = new("network:onDidClientConnect"); - public static readonly Enum ClientDisconnect = new("network:onDidClientDisconnect"); - public static readonly Enum ObjectDispose = new("object:onDidDisposeNetworkObject"); - public static readonly Enum ObjectCreate = new("object:onDidCreateNetworkObject"); - - private EventType() { } // Can't create an instance + public const string UNKNOWN = "UNKNOWN"; + public const string CLIENT_CONNECT = "network:onDidClientConnect"; + public const string CLIENT_DISCONNECT = "network:onDidClientDisconnect"; + public const string OBJECT_DISPOSE = "object:onDidDisposeNetworkObject"; + public const string OBJECT_CREATE = "object:onDidCreateNetworkObject"; } diff --git a/c-sharp/Messages/Message.cs b/c-sharp/Messages/Message.cs index 69d9c01ffc..bf301e6dbe 100644 --- a/c-sharp/Messages/Message.cs +++ b/c-sharp/Messages/Message.cs @@ -1,20 +1,26 @@ -using PtxUtils; - namespace Paranext.DataProvider.Messages; /// /// Base class for messages sent over the websocket connection /// -public abstract class Message +public class Message { - protected Message(int senderId) + private Message() { - SenderId = senderId; + SenderId = UNKNOWN_SENDER_ID; + Type = MessageType.UNKNOWN; } - protected Message() + protected Message(string messageType) { SenderId = UNKNOWN_SENDER_ID; + Type = messageType; + } + + protected Message(string messageType, int senderId) + { + SenderId = senderId; + Type = messageType; } public const int UNKNOWN_SENDER_ID = -1; @@ -22,7 +28,7 @@ protected Message() /// /// Message type /// - public abstract Enum Type { get; } + public string Type { get; protected set; } /// /// ID (as assigned by the server) of the original sender of this message @@ -31,6 +37,6 @@ protected Message() public override string ToString() { - return $"Type: {Type} from {SenderId}"; + return $"MessageType: {Type} from {SenderId}"; } } diff --git a/c-sharp/Messages/MessageClientConnect.cs b/c-sharp/Messages/MessageClientConnect.cs index 702e6cd9ba..a4a3a46730 100644 --- a/c-sharp/Messages/MessageClientConnect.cs +++ b/c-sharp/Messages/MessageClientConnect.cs @@ -1,19 +1,19 @@ -using PtxUtils; +using Paranext.DataProvider.JsonUtils; namespace Paranext.DataProvider.Messages; /// /// Message responding to the server to let it know this connection is ready to receive messages /// +[JsonMessageDeserialization(MessageField.MESSAGE_TYPE, MessageType.CLIENT_CONNECT)] public sealed class MessageClientConnect : Message { /// /// ONLY FOR DESERIALIZATION /// - private MessageClientConnect() { } + private MessageClientConnect() + : base(MessageType.CLIENT_CONNECT) { } public MessageClientConnect(int senderId) - : base(senderId) { } - - public override Enum Type => MessageType.ClientConnect; + : base(MessageType.CLIENT_CONNECT, senderId) { } } diff --git a/c-sharp/Messages/MessageEvent.cs b/c-sharp/Messages/MessageEvent.cs index ec9ff3586b..834f2fc071 100644 --- a/c-sharp/Messages/MessageEvent.cs +++ b/c-sharp/Messages/MessageEvent.cs @@ -1,38 +1,39 @@ -using PtxUtils; +using Paranext.DataProvider.JsonUtils; namespace Paranext.DataProvider.Messages; /// /// Message events to/from the server. /// +[JsonMessageDeserialization(MessageField.MESSAGE_TYPE, MessageType.EVENT)] public class MessageEvent : Message { /// /// ONLY FOR DESERIALIZATION /// - protected MessageEvent() + private MessageEvent() + : base(MessageType.EVENT) { - // Default for new events that don't have a custom class - EventContentsType = typeof(System.Text.Json.JsonElement); + EventType = Messages.EventType.UNKNOWN; + Event = null; } - public override Enum Type => MessageType.Event; - - public virtual Enum EventType { get; set; } + public MessageEvent(string eventType, object? eventContents) + : base(MessageType.EVENT) + { + EventType = eventType; + Event = eventContents; + } - /// - /// Weakly typed contents of the event message. See also - /// - public dynamic? Event { get; set; } + public string EventType { get; set; } /// - /// The intended type of the data stored in . This is used during deserialization. + /// Weakly typed event contents - subclasses hide this with a strongly typed property /// - [System.Text.Json.Serialization.JsonIgnore] - public Type EventContentsType { get; protected set; } + public object? Event { get; set; } public override string ToString() { - return $"Event: {EventType} from {SenderId} is \"{Event}\""; + return base.ToString() + $", EventType = {EventType}"; } } diff --git a/c-sharp/Messages/MessageEventClientConnect.cs b/c-sharp/Messages/MessageEventClientConnect.cs index 8b148fb55a..e4c6b477ba 100644 --- a/c-sharp/Messages/MessageEventClientConnect.cs +++ b/c-sharp/Messages/MessageEventClientConnect.cs @@ -1,5 +1,13 @@ +using Paranext.DataProvider.JsonUtils; + namespace Paranext.DataProvider.Messages; +[JsonMessageDeserialization( + MessageField.MESSAGE_TYPE, + MessageType.EVENT, + MessageField.EVENT_TYPE, + Messages.EventType.CLIENT_CONNECT +)] public sealed class MessageEventClientConnect : MessageEventGeneric { @@ -7,10 +15,17 @@ public sealed class MessageEventClientConnect /// ONLY FOR DESERIALIZATION /// private MessageEventClientConnect() - : base(Messages.EventType.ClientConnect) { } + : base(Messages.EventType.CLIENT_CONNECT, null!) { } - public MessageEventClientConnect(MessageEventClientConnectContents eventContents) - : base(Messages.EventType.ClientConnect, eventContents) { } + public MessageEventClientConnect(int clientId, bool didReconnect) + : base( + Messages.EventType.CLIENT_CONNECT, + new MessageEventClientConnectContents + { + ClientId = clientId, + DidReconnect = didReconnect + } + ) { } } public sealed record MessageEventClientConnectContents diff --git a/c-sharp/Messages/MessageEventClientDisconnect.cs b/c-sharp/Messages/MessageEventClientDisconnect.cs index fc2759efe7..18bb437942 100644 --- a/c-sharp/Messages/MessageEventClientDisconnect.cs +++ b/c-sharp/Messages/MessageEventClientDisconnect.cs @@ -1,5 +1,13 @@ +using Paranext.DataProvider.JsonUtils; + namespace Paranext.DataProvider.Messages; +[JsonMessageDeserialization( + MessageField.MESSAGE_TYPE, + MessageType.EVENT, + MessageField.EVENT_TYPE, + Messages.EventType.CLIENT_DISCONNECT +)] public sealed class MessageEventClientDisconnect : MessageEventGeneric { @@ -7,10 +15,13 @@ public sealed class MessageEventClientDisconnect /// ONLY FOR DESERIALIZATION /// private MessageEventClientDisconnect() - : base(Messages.EventType.ClientDisconnect) { } + : base(Messages.EventType.CLIENT_DISCONNECT, null!) { } - public MessageEventClientDisconnect(MessageEventClientDisconnectContents eventContents) - : base(Messages.EventType.ClientDisconnect, eventContents) { } + public MessageEventClientDisconnect(int clientId) + : base( + Messages.EventType.CLIENT_DISCONNECT, + new MessageEventClientDisconnectContents { ClientId = clientId } + ) { } } public sealed record MessageEventClientDisconnectContents diff --git a/c-sharp/Messages/MessageEventGeneric.cs b/c-sharp/Messages/MessageEventGeneric.cs index 0d9a3fa6fa..a0b44ec4bc 100644 --- a/c-sharp/Messages/MessageEventGeneric.cs +++ b/c-sharp/Messages/MessageEventGeneric.cs @@ -1,37 +1,41 @@ -using PtxUtils; - namespace Paranext.DataProvider.Messages; /// -/// This is what all event messages (other than "MessageEvent" itself) should use as a base class +/// Message events to/from the server. /// -public abstract class MessageEventGeneric : MessageEvent -// Unfortunately using the EventType as a generic type is not supported, as in "MessageEventGeneric". -// You can't pass any old object as a type to generics. They must be System.Type values, and PtxUtils.Enum values are not System.Type values. -// Even if PtxUtils.Enum values were actually enums it wouldn't help. https://stackoverflow.com/a/1331811/7303994 +public class MessageEventGeneric : MessageEvent { - private readonly Enum _eventType; + private TContents _contents; - protected MessageEventGeneric(Enum eventType) + /// + /// ONLY FOR DESERIALIZATION + /// + private MessageEventGeneric() + : base(Messages.EventType.UNKNOWN, default) { - _eventType = eventType; - EventContentsType = typeof(ContentsType); + _contents = default!; } - protected MessageEventGeneric(Enum eventType, ContentsType eventContents) - : this(eventType) + public MessageEventGeneric(string eventType, TContents eventContents) + : base(eventType, eventContents) { - Event = eventContents; + _contents = eventContents; } - public sealed override Enum EventType => _eventType; + public new TContents Event + { + get => _contents; + // When we update the strongly typed event variable, + // also update the weakly typed one owned by MessageEvent + set + { + _contents = value; + base.Event = _contents!; + } + } - /// - /// Strongly typed contents of the event message. See also - /// - [System.Text.Json.Serialization.JsonIgnore] - public ContentsType? EventContents + public override string ToString() { - get { return Event; } + return base.ToString() + $", Event = {Event}"; } } diff --git a/c-sharp/Messages/MessageEventObjectCreated.cs b/c-sharp/Messages/MessageEventObjectCreated.cs index cc559ad27b..fad36f1473 100644 --- a/c-sharp/Messages/MessageEventObjectCreated.cs +++ b/c-sharp/Messages/MessageEventObjectCreated.cs @@ -1,5 +1,13 @@ +using Paranext.DataProvider.JsonUtils; + namespace Paranext.DataProvider.Messages; +[JsonMessageDeserialization( + MessageField.MESSAGE_TYPE, + MessageType.EVENT, + MessageField.EVENT_TYPE, + Messages.EventType.OBJECT_CREATE +)] public sealed class MessageEventObjectCreated : MessageEventGeneric { @@ -7,10 +15,13 @@ public sealed class MessageEventObjectCreated /// ONLY FOR DESERIALIZATION /// private MessageEventObjectCreated() - : base(Messages.EventType.ObjectCreate) { } + : base(Messages.EventType.OBJECT_CREATE, null!) { } - public MessageEventObjectCreated(MessageEventObjectCreatedContents eventContents) - : base(Messages.EventType.ObjectCreate, eventContents) { } + public MessageEventObjectCreated(string id, string[] functions) + : base( + Messages.EventType.OBJECT_CREATE, + new MessageEventObjectCreatedContents { Id = id, Functions = functions } + ) { } } public sealed record MessageEventObjectCreatedContents diff --git a/c-sharp/Messages/MessageEventObjectDisposed.cs b/c-sharp/Messages/MessageEventObjectDisposed.cs index da449bde7b..c608ee711c 100644 --- a/c-sharp/Messages/MessageEventObjectDisposed.cs +++ b/c-sharp/Messages/MessageEventObjectDisposed.cs @@ -1,13 +1,21 @@ +using Paranext.DataProvider.JsonUtils; + namespace Paranext.DataProvider.Messages; +[JsonMessageDeserialization( + MessageField.MESSAGE_TYPE, + MessageType.EVENT, + MessageField.EVENT_TYPE, + Messages.EventType.OBJECT_DISPOSE +)] public sealed class MessageEventObjectDisposed : MessageEventGeneric { /// /// ONLY FOR DESERIALIZATION /// private MessageEventObjectDisposed() - : base(Messages.EventType.ObjectDispose) { } + : base(Messages.EventType.OBJECT_DISPOSE, null!) { } - public MessageEventObjectDisposed(string eventContents) - : base(Messages.EventType.ObjectDispose, eventContents) { } + public MessageEventObjectDisposed(string objectName) + : base(Messages.EventType.OBJECT_DISPOSE, objectName) { } } diff --git a/c-sharp/Messages/MessageField.cs b/c-sharp/Messages/MessageField.cs new file mode 100644 index 0000000000..e098f29df3 --- /dev/null +++ b/c-sharp/Messages/MessageField.cs @@ -0,0 +1,7 @@ +namespace Paranext.DataProvider.Messages; + +internal static class MessageField +{ + public const string MESSAGE_TYPE = "type"; + public const string EVENT_TYPE = "eventType"; +} diff --git a/c-sharp/Messages/MessageInitClient.cs b/c-sharp/Messages/MessageInitClient.cs index e9acd2ef4c..229b5162d6 100644 --- a/c-sharp/Messages/MessageInitClient.cs +++ b/c-sharp/Messages/MessageInitClient.cs @@ -1,28 +1,40 @@ -using PtxUtils; +using Paranext.DataProvider.JsonUtils; namespace Paranext.DataProvider.Messages; /// /// Message sent to the client to give it ConnectorInfo /// +[JsonMessageDeserialization(MessageField.MESSAGE_TYPE, MessageType.INIT_CLIENT)] public sealed class MessageInitClient : Message { /// /// ONLY FOR DESERIALIZATION /// private MessageInitClient() + : base(MessageType.INIT_CLIENT) { - ConnectorInfo = new(MessageInitClientConnectorInfo.CLIENT_ID_UNSET); + ConnectorInfo = new MessageInitClientConnectorInfo( + MessageInitClientConnectorInfo.CLIENT_ID_UNSET + ); + ClientGuid = null!; } public MessageInitClient(MessageInitClientConnectorInfo connectorInfo) + : base(MessageType.INIT_CLIENT) { ConnectorInfo = connectorInfo; + ClientGuid = string.Empty; } - public override Enum Type => MessageType.InitClient; - public MessageInitClientConnectorInfo ConnectorInfo { get; set; } + + public string ClientGuid { get; set; } + + public override string ToString() + { + return base.ToString() + $", ConnectorInfo={ConnectorInfo}, ClientGuid={ClientGuid}"; + } } public sealed class MessageInitClientConnectorInfo @@ -43,4 +55,9 @@ public MessageInitClientConnectorInfo(int clientId) } public int ClientId { get; set; } + + public override string ToString() + { + return $"ClientId={ClientId}"; + } } diff --git a/c-sharp/Messages/MessageRequest.cs b/c-sharp/Messages/MessageRequest.cs index cdd46fca99..0f990ea95d 100644 --- a/c-sharp/Messages/MessageRequest.cs +++ b/c-sharp/Messages/MessageRequest.cs @@ -1,31 +1,37 @@ -using PtxUtils; +using System.Text.Json; +using Paranext.DataProvider.JsonUtils; namespace Paranext.DataProvider.Messages; /// /// Message requests to/from the server. /// +[JsonMessageDeserialization(MessageField.MESSAGE_TYPE, MessageType.REQUEST)] public sealed class MessageRequest : Message { /// /// ONLY FOR DESERIALIZATION /// - private MessageRequest() { } + private MessageRequest() + : base(MessageType.REQUEST) + { + RequestType = null!; + Contents = new JsonElement(); + } - public MessageRequest(Enum requestType, int requestId, dynamic? contents) + public MessageRequest(string requestType, int requestId, object contents) + : base(MessageType.REQUEST) { RequestType = requestType; RequestId = requestId; - Contents = contents; + Contents = JsonSerializer.SerializeToElement(contents); } - public override Enum Type => MessageType.Request; - - public Enum RequestType { get; set; } + public string RequestType { get; set; } public int RequestId { get; set; } - public dynamic? Contents { get; set; } + public JsonElement Contents { get; set; } public override string ToString() { diff --git a/c-sharp/Messages/MessageResponse.cs b/c-sharp/Messages/MessageResponse.cs index c813c68c0f..094b6b510d 100644 --- a/c-sharp/Messages/MessageResponse.cs +++ b/c-sharp/Messages/MessageResponse.cs @@ -1,22 +1,27 @@ -using PtxUtils; +using Paranext.DataProvider.JsonUtils; namespace Paranext.DataProvider.Messages; /// /// Message responses from/to the server - It is the result of sending/getting a request /// +[JsonMessageDeserialization(MessageField.MESSAGE_TYPE, MessageType.RESPONSE)] public sealed class MessageResponse : Message { /// /// ONLY FOR DESERIALIZATION /// - private MessageResponse() { } + private MessageResponse() + : base(MessageType.RESPONSE) + { + RequestType = string.Empty; + } /// /// Response when there was an error - no contents /// public static MessageResponse Failed( - Enum requestType, + string requestType, int requestId, int requesterId, string errorMessage @@ -36,10 +41,10 @@ string errorMessage /// Response when successful /// public static MessageResponse Succeeded( - Enum requestType, + string requestType, int requestId, int requesterId, - dynamic? contents + object? contents ) { return new MessageResponse @@ -52,9 +57,7 @@ public static MessageResponse Succeeded( }; } - public sealed override Enum Type => MessageType.Response; - - public Enum RequestType { get; set; } + public string RequestType { get; set; } public bool Success { get; set; } @@ -64,7 +67,7 @@ public static MessageResponse Succeeded( public int RequesterId { get; set; } - public dynamic? Contents { get; set; } + public object? Contents { get; set; } public override string ToString() { diff --git a/c-sharp/Messages/MessageType.cs b/c-sharp/Messages/MessageType.cs index 3c4b4cdc52..39b42d741e 100644 --- a/c-sharp/Messages/MessageType.cs +++ b/c-sharp/Messages/MessageType.cs @@ -1,14 +1,14 @@ -using PtxUtils; - namespace Paranext.DataProvider.Messages; -public sealed class MessageType : EnumType +/// +/// This should represent all possible message types +/// +public static class MessageType { - public static readonly Enum InitClient = new("init-client"); - public static readonly Enum ClientConnect = new("client-connect"); - public static readonly Enum Request = new("request"); - public static readonly Enum Response = new("response"); - public static readonly Enum Event = new("event"); - - private MessageType() { } // Can't create an instance + public const string UNKNOWN = "UNKNOWN"; + public const string INIT_CLIENT = "init-client"; + public const string CLIENT_CONNECT = "client-connect"; + public const string REQUEST = "request"; + public const string RESPONSE = "response"; + public const string EVENT = "event"; } diff --git a/c-sharp/Messages/RequestType.cs b/c-sharp/Messages/RequestType.cs deleted file mode 100644 index 484e046e3b..0000000000 --- a/c-sharp/Messages/RequestType.cs +++ /dev/null @@ -1,11 +0,0 @@ -using PtxUtils; - -namespace Paranext.DataProvider.Messages; - -public sealed partial class RequestType : EnumType -{ - public static readonly Enum RegisterRequest = new("server:registerRequest"); - public static readonly Enum AddOne = new("command:test.addOne"); - - private RequestType() { } // Can't create an instance -} diff --git a/c-sharp/NetworkObjects/DataProvider.cs b/c-sharp/NetworkObjects/DataProvider.cs index b9c7401e2e..8ae7676d15 100644 --- a/c-sharp/NetworkObjects/DataProvider.cs +++ b/c-sharp/NetworkObjects/DataProvider.cs @@ -1,7 +1,6 @@ using Paranext.DataProvider.MessageHandlers; using Paranext.DataProvider.Messages; using Paranext.DataProvider.MessageTransports; -using PtxUtils; using System.Collections.Concurrent; using System.Text.Json; using System.Text.Json.Nodes; @@ -16,13 +15,13 @@ private class MessageEventDataUpdated : MessageEventGeneric // A parameterless constructor is required for serialization to work // ReSharper disable once UnusedMember.Local public MessageEventDataUpdated() - : base(Enum.Null) { } + : base(Messages.EventType.UNKNOWN, default!) { } - public MessageEventDataUpdated(Enum eventType, object dataScope) + public MessageEventDataUpdated(string eventType, object dataScope) : base(eventType, dataScope) { } } - private readonly Enum _eventType; + private readonly string _eventType; private readonly ConcurrentDictionary _updateEventsByScope = new(); @@ -33,7 +32,7 @@ protected DataProvider(string name, PapiClient papiClient) DataProviderName = name + "-data"; // "onDidUpdate" is the event name used by PAPI for data providers to notify consumers of updates - _eventType = new Enum($"{DataProviderName}:onDidUpdate"); + _eventType = $"{DataProviderName}:onDidUpdate"; } public string DataProviderName { get; } @@ -51,13 +50,13 @@ public async Task RegisterDataProvider() // The first item in the array is the name of the function to call. // All remaining items are arguments to pass to the function. // Data providers must provide "get" and "set" functions. - private ResponseToRequest FunctionHandler(dynamic? request) + private ResponseToRequest FunctionHandler(JsonElement request) { string functionName; JsonArray jsonArray; try { - jsonArray = ((JsonElement)request!).Deserialize()!.AsArray(); + jsonArray = request.Deserialize()!.AsArray(); if (jsonArray.Count == 0) return ResponseToRequest.Failed( $"No function name provided when calling data provider {DataProviderName}" @@ -83,8 +82,11 @@ private ResponseToRequest FunctionHandler(dynamic? request) /// Indicator of what data changed in the provider. Can be '*' for all /// updates, a `string` to update one data type, or a `List<string>` of data types to update. /// If dataScope is null, nothing happens. - protected void SendDataUpdateEvent(dynamic? dataScope) + protected void SendDataUpdateEvent(object? dataScope) { + if (dataScope == null) + return; + // The final computed data scope to send out in the update event. Based on dataScope object dataScopeResult; @@ -107,10 +109,9 @@ protected void SendDataUpdateEvent(dynamic? dataScope) } else { - if (dataScope != null) - Console.WriteLine( - "Did not send data update event. dataScope is not a string or list of strings" - ); + Console.WriteLine( + "Did not send data update event. dataScope is not a string or list of strings" + ); return; } diff --git a/c-sharp/NetworkObjects/NetworkObject.cs b/c-sharp/NetworkObjects/NetworkObject.cs index 14a46fee46..a4ed9e3464 100644 --- a/c-sharp/NetworkObjects/NetworkObject.cs +++ b/c-sharp/NetworkObjects/NetworkObject.cs @@ -1,7 +1,7 @@ +using System.Text.Json; using Paranext.DataProvider.MessageHandlers; using Paranext.DataProvider.Messages; using Paranext.DataProvider.MessageTransports; -using PtxUtils; namespace Paranext.DataProvider.NetworkObjects; @@ -27,7 +27,7 @@ protected NetworkObject(PapiClient papiClient) protected async Task RegisterNetworkObject( string networkObjectName, List functionNames, - Func requestHandler + Func requestHandler ) { if (_networkObjectFunctionNames.Length > 0) @@ -36,8 +36,8 @@ Func requestHandler throw new ArgumentException($"Must provide function names for {networkObjectName}"); // PAPI requires network objects to expose "get" and "function" requests - var getReqType = new Enum($"object:{networkObjectName}.get"); - var functionReqType = new Enum($"object:{networkObjectName}.function"); + var getReqType = $"object:{networkObjectName}.get"; + var functionReqType = $"object:{networkObjectName}.function"; if (!await PapiClient.RegisterRequestHandler(getReqType, HandleGet)) throw new Exception($"Could not register GET for {networkObjectName}"); @@ -48,15 +48,12 @@ Func requestHandler // Notify the network that we registered this network object functionNames.Sort(); _networkObjectFunctionNames = functionNames.ToArray(); - var newObjectCreationDetails = new MessageEventObjectCreatedContents() - { - Id = networkObjectName, - Functions = _networkObjectFunctionNames - }; - PapiClient.SendEvent(new MessageEventObjectCreated(newObjectCreationDetails)); + PapiClient.SendEvent( + new MessageEventObjectCreated(networkObjectName, _networkObjectFunctionNames) + ); } - private ResponseToRequest HandleGet(dynamic getRequest) + private ResponseToRequest HandleGet(JsonElement getRequest) { // Respond that this network object exists along with its function list return ResponseToRequest.Succeeded(new List(_networkObjectFunctionNames)); diff --git a/c-sharp/Program.cs b/c-sharp/Program.cs index 5023081f94..7ec988a9c3 100644 --- a/c-sharp/Program.cs +++ b/c-sharp/Program.cs @@ -1,6 +1,5 @@ using System.Text.Json; using Paranext.DataProvider.MessageHandlers; -using Paranext.DataProvider.Messages; using Paranext.DataProvider.MessageTransports; using Paranext.DataProvider.NetworkObjects; using Paranext.DataProvider.Projects; @@ -25,7 +24,7 @@ public static async Task Main() } //TODO: Delete this once we stop including test objects in the builds - if (!await papi.RegisterRequestHandler(RequestType.AddOne, RequestAddOne)) + if (!await papi.RegisterRequestHandler("command:test.addOne", RequestAddOne)) { Console.WriteLine("Paranext data provider could not register request handler"); return; @@ -60,10 +59,10 @@ public static async Task Main() #region Request handlers - private static ResponseToRequest RequestAddOne(dynamic val) + private static ResponseToRequest RequestAddOne(JsonElement element) { - if (val is not JsonElement element || element.GetArrayLength() != 1) - return ResponseToRequest.Failed("Unexpected data in request: " + val); + if (element.GetArrayLength() != 1) + return ResponseToRequest.Failed("Unexpected data in request: " + element); int intVal = ErrorUtils.IgnoreErrors( "Trying to parse data from server", diff --git a/c-sharp/Projects/ProjectDataProviderFactory.cs b/c-sharp/Projects/ProjectDataProviderFactory.cs index c22c5a405c..59cf14ad94 100644 --- a/c-sharp/Projects/ProjectDataProviderFactory.cs +++ b/c-sharp/Projects/ProjectDataProviderFactory.cs @@ -32,7 +32,7 @@ await RegisterNetworkObject( // The first item in the array is the name of the function to call. // All remaining items are arguments to pass to the function. // Data providers must provide "get" and "set" functions. - private ResponseToRequest FunctionHandler(dynamic? request) + private ResponseToRequest FunctionHandler(JsonElement request) { string functionName; JsonArray jsonArray;