From 8a20f452d7bfe5de8a501e1d88ad5338924dc924 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Fri, 29 Dec 2023 14:31:43 +0100 Subject: [PATCH] WIP Either --- .../Protocol/EitherJsonConverterTests.cs | 65 +++++++++++ src/SLCore/Protocol/Either.cs | 36 ++++++ src/SLCore/Protocol/EitherJsonConverter.cs | 106 ++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 src/SLCore.UnitTests/Protocol/EitherJsonConverterTests.cs create mode 100644 src/SLCore/Protocol/Either.cs create mode 100644 src/SLCore/Protocol/EitherJsonConverter.cs diff --git a/src/SLCore.UnitTests/Protocol/EitherJsonConverterTests.cs b/src/SLCore.UnitTests/Protocol/EitherJsonConverterTests.cs new file mode 100644 index 0000000000..2051f51292 --- /dev/null +++ b/src/SLCore.UnitTests/Protocol/EitherJsonConverterTests.cs @@ -0,0 +1,65 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using Newtonsoft.Json; +using SonarLint.VisualStudio.SLCore.Protocol; + +namespace SonarLint.VisualStudio.SLCore.UnitTests.Protocol; + +[TestClass] +public class EitherJsonConverterTests +{ + [TestMethod] + public void SimpleTest() + { + var respLeft = new SomeResponse { dto = Either.CreateLeft(new V1Dto()) }; + var respRight = new SomeResponse { dto = Either.CreateRight(new V2Dto()) }; + + JsonConvert.DeserializeObject(JsonConvert.SerializeObject(respLeft)).Should().BeEquivalentTo(respLeft); + JsonConvert.DeserializeObject(JsonConvert.SerializeObject(respRight)).Should().BeEquivalentTo(respRight); + } + + // todo add more tests + + public class SomeResponse + { + public string str = "aaaaa"; + [JsonConverter(typeof(EitherJsonConverter))] + public Either dto { get; set; } + } + + public class V1Dto + { + public object ACommon { get; set; } = new { lala = 10 }; + public object ACommon2 = new { lala = 10 }; + public object V1Obj { get; set; } = new { dada = 20 }; + public object V1Str = "strstrstr"; + public object XCommon { get; set; } = new { lala = 10 }; + } + + public class V2Dto + { + public object ACommon { get; set; } = new { lala = 20 }; + public object ACommon2 = new { lala = 20 }; + public object V2Obj { get; set; } = new { dada = 40 }; + public object V2Str = "strstrstr2"; + public object XCommon { get; set; } = new { lala = 20 }; + } +} diff --git a/src/SLCore/Protocol/Either.cs b/src/SLCore/Protocol/Either.cs new file mode 100644 index 0000000000..62713b1585 --- /dev/null +++ b/src/SLCore/Protocol/Either.cs @@ -0,0 +1,36 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarLint.VisualStudio.SLCore.Protocol +{ + /// + /// Represents an option class where only one of the properties or is not null + /// + public class Either + where TLeft : class + where TRight : class + { + public TLeft Left { get; private set; } + public TRight Right { get; private set; } + + public static Either CreateLeft(TLeft left) => new Either() { Left = left }; + public static Either CreateRight(TRight right) => new Either() { Right = right }; + } +} diff --git a/src/SLCore/Protocol/EitherJsonConverter.cs b/src/SLCore/Protocol/EitherJsonConverter.cs new file mode 100644 index 0000000000..0eb12fcc85 --- /dev/null +++ b/src/SLCore/Protocol/EitherJsonConverter.cs @@ -0,0 +1,106 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace SonarLint.VisualStudio.SLCore.Protocol +{ + /// + /// for type. + /// Left and Right are serialized to and deserialize from the same json property. + /// + /// A class that converts to json object. Primitive types (int, string, etc.) are not supported + /// A class that converts to json object. Primitive types (int, string, etc.) are not supported + public class EitherJsonConverter : JsonConverter + where TLeft : class + where TRight : class + { + private readonly HashSet leftProperties; + private readonly HashSet rightProperties; + + public EitherJsonConverter() + { + // todo check primitive types? + leftProperties = GetAllPropertyAndFieldNames(typeof(TLeft)); + rightProperties = GetAllPropertyAndFieldNames(typeof(TRight)); + var intersection = leftProperties.Intersect(rightProperties).ToArray(); + leftProperties.ExceptWith(intersection); + rightProperties.ExceptWith(intersection); + } + + private static HashSet GetAllPropertyAndFieldNames(Type type) + { + const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public; + return type.GetProperties(bindingFlags).Select(x => x.Name) + .Concat(type.GetFields(bindingFlags).Select(x => x.Name)) + .ToHashSet(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var either = value as Either; + + if (either.Left != null) + { + serializer.Serialize(writer, either.Left); + return; + } + + serializer.Serialize(writer, either.Right); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Either); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + var jToken = JToken.ReadFrom(reader); + + if (jToken.Type != JTokenType.Object) + { + // ?? + throw new InvalidOperationException(); + } + + foreach (var jsonProperty in jToken.Children().Select(x => x.Path)) + { + if (leftProperties.Contains(jsonProperty)) + { + return Either.CreateLeft(jToken.ToObject()); + } + + if (rightProperties.Contains(jsonProperty)) + { + return Either.CreateRight(jToken.ToObject()); + } + } + + throw new InvalidOperationException(); + } + } +}