Skip to content

Commit

Permalink
WIP Either
Browse files Browse the repository at this point in the history
  • Loading branch information
georgii-borovinskikh-sonarsource committed Jan 24, 2024
1 parent 6f2443a commit 8a20f45
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 0 deletions.
65 changes: 65 additions & 0 deletions src/SLCore.UnitTests/Protocol/EitherJsonConverterTests.cs
Original file line number Diff line number Diff line change
@@ -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<V1Dto, V2Dto>.CreateLeft(new V1Dto()) };
var respRight = new SomeResponse { dto = Either<V1Dto, V2Dto>.CreateRight(new V2Dto()) };

JsonConvert.DeserializeObject<SomeResponse>(JsonConvert.SerializeObject(respLeft)).Should().BeEquivalentTo(respLeft);
JsonConvert.DeserializeObject<SomeResponse>(JsonConvert.SerializeObject(respRight)).Should().BeEquivalentTo(respRight);
}

// todo add more tests

public class SomeResponse
{
public string str = "aaaaa";
[JsonConverter(typeof(EitherJsonConverter<V1Dto, V2Dto>))]
public Either<V1Dto, V2Dto> 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 };
}
}
36 changes: 36 additions & 0 deletions src/SLCore/Protocol/Either.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents an option class where only one of the properties <see cref="Left"/> or <see cref="Right"/> is not null
/// </summary>
public class Either<TLeft, TRight>
where TLeft : class
where TRight : class
{
public TLeft Left { get; private set; }
public TRight Right { get; private set; }

public static Either<TLeft, TRight> CreateLeft(TLeft left) => new Either<TLeft, TRight>() { Left = left };
public static Either<TLeft, TRight> CreateRight(TRight right) => new Either<TLeft, TRight>() { Right = right };
}
}
106 changes: 106 additions & 0 deletions src/SLCore/Protocol/EitherJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// <see cref="JsonConverter" /> for <see cref="Either{TLeft,TRight}" /> type.
/// Left and Right are serialized to and deserialize from the same json property.
/// </summary>
/// <typeparam name="TLeft">A class that converts to json object. Primitive types (int, string, etc.) are not supported</typeparam>
/// <typeparam name="TRight">A class that converts to json object. Primitive types (int, string, etc.) are not supported</typeparam>
public class EitherJsonConverter<TLeft, TRight> : JsonConverter
where TLeft : class
where TRight : class
{
private readonly HashSet<string> leftProperties;
private readonly HashSet<string> 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<string> 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<TLeft, TRight>;

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<TLeft, TRight>);
}

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<TLeft, TRight>.CreateLeft(jToken.ToObject<TLeft>());
}

if (rightProperties.Contains(jsonProperty))
{
return Either<TLeft, TRight>.CreateRight(jToken.ToObject<TRight>());
}
}

throw new InvalidOperationException();
}
}
}

0 comments on commit 8a20f45

Please sign in to comment.