Skip to content

Commit

Permalink
Merge pull request #61 from jinaga/visualize-proxy
Browse files Browse the repository at this point in the history
Tolerate model changes that add and remove predecessors
  • Loading branch information
michaellperry authored Jan 24, 2024
2 parents f1bb6b0 + 205604e commit 00e9088
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 41 deletions.
4 changes: 4 additions & 0 deletions Jinaga.Graphviz/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ private static IEnumerable<object> GetFacts(object projection, int depth)
{
yield return projection;
}
else if (typeof(IFactProxy).IsAssignableFrom(type))
{
yield return projection;
}
else
{
if (type.IsAssignableTo(typeof(IEnumerable)) && type != typeof(string))
Expand Down
164 changes: 164 additions & 0 deletions Jinaga.Test/Facts/ComparisonTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System.Threading.Tasks;
using FluentAssertions;
using Jinaga.UnitTest;
using Xunit;

namespace Jinaga.Test.Facts;

public class ComparisonTest
{
[Fact]
public void CanCompareSimilarUserFacts()
{
var a = new User("--- FAKE PUBLIC KEY ---");
var b = new User("--- FAKE PUBLIC KEY ---");

a.Should().Be(b);
a.Should().BeEquivalentTo(b);
a.Should().NotBeSameAs(b);
(a == b).Should().BeTrue();
(a != b).Should().BeFalse();
a.GetHashCode().Should().Be(b.GetHashCode());
}

[Fact]
public void CanCompareDifferentUserFacts()
{
var a = new User("--- FAKE PUBLIC KEY ---");
var b = new User("--- DIFFERENT FAKE PUBLIC KEY ---");

a.Should().NotBe(b);
a.Should().NotBeEquivalentTo(b);
a.Should().NotBeSameAs(b);
(a == b).Should().BeFalse();
(a != b).Should().BeTrue();
a.GetHashCode().Should().NotBe(b.GetHashCode());
}

[Fact]
public async Task CanCompareUserFactToProxy()
{
var jinagaClient = JinagaTest.Create();
var user = new User("--- FAKE PUBLIC KEY ---");
var proxy = await jinagaClient.Fact(user);

user.Should().Be(proxy);
user.Should().BeEquivalentTo(proxy);
user.Should().NotBeSameAs(proxy);
(user == proxy).Should().BeTrue();
(user != proxy).Should().BeFalse();
user.GetHashCode().Should().Be(proxy.GetHashCode());
}

[Fact]
public async Task CanCompareProxyToUserFact()
{
var jinagaClient = JinagaTest.Create();
var user = new User("--- FAKE PUBLIC KEY ---");
var proxy = await jinagaClient.Fact(user);

proxy.Should().Be(user);
proxy.Should().BeEquivalentTo(user);
proxy.Should().NotBeSameAs(user);
(proxy == user).Should().BeTrue();
(proxy != user).Should().BeFalse();
proxy.GetHashCode().Should().Be(user.GetHashCode());
}

[Fact]
public async Task CanCompareProxyToProxy()
{
var jinagaClient = JinagaTest.Create();
var user = new User("--- FAKE PUBLIC KEY ---");
var proxy = await jinagaClient.Fact(user);
var proxy2 = await jinagaClient.Fact(user);

proxy.Should().Be(proxy2);
proxy.Should().BeEquivalentTo(proxy2);
proxy.Should().NotBeSameAs(proxy2);
(proxy == proxy2).Should().BeTrue();
(proxy != proxy2).Should().BeFalse();
proxy.GetHashCode().Should().Be(proxy2.GetHashCode());
}

[Fact]
public async Task CanCompareProxyToDifferentUserFact()
{
var jinagaClient = JinagaTest.Create();
var user = new User("--- FAKE PUBLIC KEY ---");
var proxy = await jinagaClient.Fact(user);
var user2 = new User("--- DIFFERENT FAKE PUBLIC KEY ---");

proxy.Should().NotBe(user2);
proxy.Should().NotBeEquivalentTo(user2);
proxy.Should().NotBeSameAs(user2);
(proxy == user2).Should().BeFalse();
(proxy != user2).Should().BeTrue();
proxy.GetHashCode().Should().NotBe(user2.GetHashCode());
}

[Fact]
public async Task CanCompareDifferentUserFactToProxy()
{
var jinagaClient = JinagaTest.Create();
var user = new User("--- FAKE PUBLIC KEY ---");
var proxy = await jinagaClient.Fact(user);
var user2 = new User("--- DIFFERENT FAKE PUBLIC KEY ---");

user2.Should().NotBe(proxy);
user2.Should().NotBeEquivalentTo(proxy);
user2.Should().NotBeSameAs(proxy);
(user2 == proxy).Should().BeFalse();
(user2 != proxy).Should().BeTrue();
user2.GetHashCode().Should().NotBe(proxy.GetHashCode());
}

[Fact]
public async Task CanCompareRecordFactToProxy()
{
var jinagaClient = JinagaTest.Create();
var blog = new Blog("example.com");
var proxy = await jinagaClient.Fact(new Blog("example.com"));

blog.Should().Be(proxy);
blog.Should().BeEquivalentTo(proxy);
blog.Should().NotBeSameAs(proxy);
(blog == proxy).Should().BeTrue();
(blog != proxy).Should().BeFalse();
blog.GetHashCode().Should().Be(proxy.GetHashCode());
}

[Fact]
public async Task CanCompareProxyToRecordFact()
{
var jinagaClient = JinagaTest.Create();
var blog = new Blog("example.com");
var proxy = await jinagaClient.Fact(new Blog("example.com"));

proxy.Should().Be(blog);
proxy.Should().BeEquivalentTo(blog);
proxy.Should().NotBeSameAs(blog);
(proxy == blog).Should().BeTrue();
(proxy != blog).Should().BeFalse();
proxy.GetHashCode().Should().Be(blog.GetHashCode());
}

[Fact]
public async Task CanCompareProxyToProxyRecordFact()
{
var jinagaClient = JinagaTest.Create();
var blog = new Blog("example.com");
var proxy = await jinagaClient.Fact(new Blog("example.com"));
var proxy2 = await jinagaClient.Fact(new Blog("example.com"));

proxy.Should().Be(proxy2);
proxy.Should().BeEquivalentTo(proxy2);
proxy.Should().NotBeSameAs(proxy2);
(proxy == proxy2).Should().BeTrue();
(proxy != proxy2).Should().BeFalse();
proxy.GetHashCode().Should().Be(proxy2.GetHashCode());
}
}

[FactType("Blog")]
public record Blog(string domain) {}
40 changes: 39 additions & 1 deletion Jinaga.Test/Facts/VersioningTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,38 @@ public void CanDowngrade()
deserialized.domain.Should().Be("michaelperry.net");
}

[Fact]
public void CanAddPredecessor()
{
var original = new CommentV1(new BlogV5(DateTime.UtcNow), "Hello, world!");
var factGraph = Serialize(original);
var deserialized = Deserialize<CommentV2>(factGraph, factGraph.Last);

deserialized.message.Should().Be("Hello, world!");
deserialized.author.Should().BeNull();
}

[Fact]
public void CanRemovePredecessor()
{
var original = new CommentV2(new BlogV5(DateTime.UtcNow), "Hello, world!", new User("Michael"));
var factGraph = Serialize(original);
var deserialized = Deserialize<CommentV1>(factGraph, factGraph.Last);

deserialized.message.Should().Be("Hello, world!");
}

[Fact]
public void CanSerializeNullPredecessor()
{
var original = new CommentV2(new BlogV5(DateTime.UtcNow), "Hello, world!", null);
var factGraph = Serialize(original);
var deserialized = Deserialize<CommentV2>(factGraph, factGraph.Last);

deserialized.message.Should().Be("Hello, world!");
deserialized.author.Should().BeNull();
}

private static FactGraph Serialize(object fact)
{
var serializerCache = SerializerCache.Empty;
Expand Down Expand Up @@ -133,4 +165,10 @@ public record BlogV3(string domain, string title, int stars) {}
public record BlogV4(DateTime? createdAt) {}

[FactType("Blog")]
public record BlogV5(DateTime createdAt) {}
public record BlogV5(DateTime createdAt) {}

[FactType("Comment")]
public record CommentV1(BlogV5 blog, string message) {}

[FactType("Comment")]
public record CommentV2(BlogV5 blog, string message, User author) {}
39 changes: 17 additions & 22 deletions Jinaga/Facts/Fact.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public class Fact
{
public static Fact Create(string type, ImmutableList<Field> fields, ImmutableList<Predecessor> predecessors)
{
predecessors = predecessors
.Where(predecessor => predecessor != null)
.ToImmutableList();
var reference = new FactReference(type, ComputeHash(fields, predecessors));
return new Fact(reference, fields, predecessors);
}
Expand Down Expand Up @@ -47,7 +50,7 @@ public FieldValue GetFieldValue(string name)
}
}

public FactReference GetPredecessorSingle(string role)
public FactReference? GetPredecessorSingle(string role)
{
var references = Predecessors
.Where(p => p.Role == role)
Expand All @@ -56,11 +59,11 @@ public FactReference GetPredecessorSingle(string role)
.ToImmutableList();
if (references.Count == 0)
{
throw new ArgumentException($"The fact {Reference.Type} did not contain any predecessors in role {role}");
return null;
}
else if (references.Count > 1)
{
throw new ArgumentException($"The fact {Reference.Type} contained {references.Count} predecessors in role {role}; there should only be 1");
return null;
}
else
{
Expand All @@ -73,20 +76,9 @@ public ImmutableList<FactReference> GetPredecessorMultiple(string role)
var references = Predecessors
.Where(p => p.Role == role)
.OfType<PredecessorMultiple>()
.Select(p => p.References)
.SelectMany(p => p.References)
.ToImmutableList();
if (references.Count == 0)
{
throw new ArgumentException($"The fact {Reference.Type} did not contain any predecessors in role {role}");
}
else if (references.Count > 1)
{
throw new ArgumentException($"The fact {Reference.Type} contained {references.Count} predecessors in role {role}; there should only be 1");
}
else
{
return references.Single();
}
return references;
}

public ImmutableList<FactReference> GetPredecessors(string role)
Expand All @@ -112,12 +104,14 @@ public ImmutableList<FactReference> GetPredecessors(string role)

public IEnumerable<FactReference> GetAllPredecessorReferences()
{
return Predecessors.SelectMany(p => p switch
{
PredecessorSingle single => ImmutableList<FactReference>.Empty.Add(single.Reference),
PredecessorMultiple multiple => multiple.References,
_ => throw new InvalidOperationException("Unknown predecessor type")
});
return Predecessors
.Where(p => p != null)
.SelectMany(p => p switch
{
PredecessorSingle single => ImmutableList<FactReference>.Empty.Add(single.Reference),
PredecessorMultiple multiple => multiple.References,
_ => throw new InvalidOperationException("Unknown predecessor type")
});
}

private static string ComputeHash(ImmutableList<Field> fields, ImmutableList<Predecessor> predecessors)
Expand Down Expand Up @@ -166,6 +160,7 @@ private static string SerializeFieldValue(FieldValue value)
private static string CanonicalizePredecessors(ImmutableList<Predecessor> predecessors)
{
var serializedPredecessors = predecessors
.Where(predecessor => predecessor != null)
.OrderBy(predecessor => predecessor.Role, StringComparer.Ordinal)
.Select(predecessor => $"\"{predecessor.Role}\":{SerializePredecessor(predecessor)}")
.ToArray();
Expand Down
3 changes: 2 additions & 1 deletion Jinaga/Serialization/Collector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ public FactReference Serialize(object runtimeFact)
reference = fact.Reference;

Graph = Graph.Add(fact);
referenceByObject = referenceByObject.Add(runtimeFact, reference);
}
visiting = visiting.Remove(runtimeFact);
referenceByObject = referenceByObject.Add(runtimeFact, reference);
}
return reference;
}
Expand Down
25 changes: 21 additions & 4 deletions Jinaga/Serialization/DeserializerCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,21 +173,38 @@ private static Expression GetFieldValue(string name, Type parameterType, Paramet

private static Expression GetPredecessor(string role, Type parameterType, ParameterExpression factParameter, ParameterExpression emitterParameter)
{
/*
var predecessorSingle = fact.GetPredecessorSingle(role);
return predecessorSingle == null
? (T)null
: emitter.Deserialize<T>(predecessorSingle);
*/
var predecessorSingle = Expression.Call(
factParameter,
typeof(Fact).GetMethod(nameof(Fact.GetPredecessorSingle)),
Expression.Constant(role)
);
var deserialize = Expression.Call(
emitterParameter,
typeof(Emitter).GetMethod(nameof(Emitter.Deserialize)).MakeGenericMethod(parameterType),
predecessorSingle
var deserialize = Expression.Condition(
Expression.Equal(
predecessorSingle,
Expression.Constant(null)
),
Expression.Constant(null, parameterType),
Expression.Call(
emitterParameter,
typeof(Emitter).GetMethod(nameof(Emitter.Deserialize)).MakeGenericMethod(parameterType),
predecessorSingle
)
);
return deserialize;
}

private static Expression GetPredecessorArray(string role, Type elementType, ParameterExpression factParameter, ParameterExpression emitterParameter)
{
/*
var predecessorMultiple = fact.GetPredecessorMultiple(role);
return emitter.Deserialize<T[]>(predecessorMultiple);
*/
var predecessorMultiple = Expression.Call(
factParameter,
typeof(Fact).GetMethod(nameof(Fact.GetPredecessorMultiple)),
Expand Down
Loading

0 comments on commit 00e9088

Please sign in to comment.