Skip to content

Commit

Permalink
Invariant capable records (#780)
Browse files Browse the repository at this point in the history
* Refactor records to allow case-insensitive lookups

* minor formatting, added test

* remove List<> from every record; test coverage

* removed unnecessary wrapper

* undo accidental rename

* undo wrong refactor

* small reformats

* Review notes, fix testkit test

* Make IRecord implement IReadOnlyDictionary<string, object>

* Add Get<T>, TryGet<T> and case insensitive versions to Record types

* Add Get<T> and TryGet<T> to IEntity

* Fix MatchesRecord tesst helper function

* Review notes; removed duplicate method

* Minor Record tidyups

* Review notes and new AsRecord method
  • Loading branch information
RichardIrons-neo4j authored Feb 21, 2024
1 parent 45173b2 commit bc664bb
Show file tree
Hide file tree
Showing 29 changed files with 835 additions and 488 deletions.
5 changes: 2 additions & 3 deletions Neo4j.Driver/Neo4j.Driver.Tests/Mapping/BuiltMapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
using System;
using FluentAssertions;
using Neo4j.Driver.Preview.Mapping;
using Neo4j.Driver.Tests.TestUtil;
using Xunit;

using Record = Neo4j.Driver.Internal.Result.Record;

namespace Neo4j.Driver.Tests.Mapping;

public class BuiltMapperTests
Expand Down Expand Up @@ -49,7 +48,7 @@ public void ShouldUseConstructorWhenInstructed()

var constructor = typeof(NoParameterlessConstructor).GetConstructors()[0];
mapper.AddConstructorMapping(constructor);
var result = mapper.Map(new Record(new[] { "value" }, new object[] { 48 }));
var result = mapper.Map(TestRecord.Create(new[] { "value" }, new object[] { 48 }));
result.Value.Should().Be(48);
}
}
26 changes: 13 additions & 13 deletions Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DefaultMapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
using FluentAssertions;
using Neo4j.Driver.Internal.Types;
using Neo4j.Driver.Preview.Mapping;
using Neo4j.Driver.Tests.TestUtil;
using Xunit;
using Record = Neo4j.Driver.Internal.Result.Record;

namespace Neo4j.Driver.Tests.Mapping;

Expand All @@ -36,7 +36,7 @@ public void ShouldMapSimpleClass()
{
var mapper = DefaultMapper.Get<SimpleClass>();

var record = new Record(new[] { "id", "name" }, new object[] { 1, "Foo" });
var record = TestRecord.Create(new[] { "id", "name" }, new object[] { 1, "Foo" });
var result = mapper.Map(record);

result.Id.Should().Be(1);
Expand All @@ -60,7 +60,7 @@ public void ShouldMapConstructorClass()
{
var mapper = DefaultMapper.Get<ConstructorClass>();

var record = new Record(new[] { "id", "name" }, new object[] { 1, "Foo" });
var record = TestRecord.Create(new[] { "id", "name" }, new object[] { 1, "Foo" });
var result = mapper.Map(record);

result.Id.Should().Be(1);
Expand Down Expand Up @@ -91,7 +91,7 @@ public void ShouldMapNonDefaultConstructorClass()
{
var mapper = DefaultMapper.Get<NonDefaultConstructorClass>();

var record = new Record(new[] { "id", "name" }, new object[] { 1, "Foo" });
var record = TestRecord.Create(new[] { "id", "name" }, new object[] { 1, "Foo" });
var result = mapper.Map(record);

result.Id.Should().Be(1);
Expand All @@ -116,7 +116,7 @@ public Person(
public void ShouldMapFromInsideDictionaries()
{
var dict = new Dictionary<string, object> { { "name", "Dani" }, { "born", 1977 } };
var record = new Record(new[] { "Person" }, new object[] { dict });
var record = TestRecord.Create(new[] { "Person" }, new object[] { dict });
var mapper = DefaultMapper.Get<Person>();
var person = mapper.Map(record);
person.Name.Should().Be("Dani");
Expand All @@ -126,7 +126,7 @@ public void ShouldMapFromInsideDictionaries()
[Fact]
public void ShouldThrowWhenConstructorParametersUnavailable()
{
var record = new Record(new[] { "something" }, new object[] { 69 });
var record = TestRecord.Create(new[] { "something" }, new object[] { 69 });
var mapper = DefaultMapper.Get<Person>();
var act = () => mapper.Map(record);
act.Should().Throw<InvalidOperationException>();
Expand All @@ -135,7 +135,7 @@ public void ShouldThrowWhenConstructorParametersUnavailable()
[Fact]
public void ShouldMapFromNodesInRecords()
{
var record = new Record(
var record = TestRecord.Create(
new[] { "person" },
new object[]
{
Expand Down Expand Up @@ -166,7 +166,7 @@ public NaturalPhenomenon(string name, List<string> components)
[Fact]
public void ShouldMapListsThroughConstructor()
{
var record = new Record(
var record = TestRecord.Create(
new[] { "name", "components" },
new object[] { "Hurricane", new List<string> { "wind", "rain" } });

Expand All @@ -191,7 +191,7 @@ public NaturalPhenomenonCommaSeparated(string name, string components)
[Fact]
public void ShouldMapCommaSeparatedListsThroughConstructor()
{
var record = new Record(
var record = TestRecord.Create(
new[] { "name", "components" },
new object[] { "Hurricane", new List<string> { "wind", "rain" } });

Expand All @@ -216,7 +216,7 @@ public HistoricalPhenomenon(NaturalPhenomenon phenomenon, int year)
[Fact]
public void ShouldMapNestedObjectsThroughConstructor()
{
var record = new Record(
var record = TestRecord.Create(
new[] { "phenomenon", "year" },
new object[]
{
Expand Down Expand Up @@ -267,7 +267,7 @@ public void ShouldMapListsOfNodesThroughConstructor()

var phenomena = new List<Node> { firstPhenomenon, secondPhenomenon, thirdPhenomenon };

var record = new Record(
var record = TestRecord.Create(
new[] { "year", "phenomena" },
new object[] { 2021, phenomena });

Expand Down Expand Up @@ -299,7 +299,7 @@ public ClassWithProperties(int year, string occurrence)
[Fact]
public void ShouldSetPropertiesNotSetInConstructor()
{
var record = new Record(
var record = TestRecord.Create(
new[] { "year", "occurrence", "description", "something" },
new object[] { 2020, "PANDEMIC", "Covid-19", "something" });

Expand Down Expand Up @@ -331,7 +331,7 @@ public ClassWithPropertiesWithMappingHints(
[Fact]
public void ShouldSetPropertiesNotSetInConstructorWithMappingHints()
{
var record = new Record(
var record = TestRecord.Create(
new[] { "year", "occurrence", "description", "something" },
new object[] { 2021, "PANDEMIC", "Covid-19", "something" });

Expand Down
10 changes: 5 additions & 5 deletions Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DictAsRecordTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
using FluentAssertions;
using Neo4j.Driver.Internal.Types;
using Neo4j.Driver.Preview.Mapping;
using Neo4j.Driver.Tests.TestUtil;
using Xunit;
using Record = Neo4j.Driver.Internal.Result.Record;

namespace Neo4j.Driver.Tests.Mapping;

Expand All @@ -28,7 +28,7 @@ public class DictAsRecordTests
[Fact]
public void ShouldUsePropertiesOfDict()
{
var originalRecord = new Record(new[] { "name", "age" }, new object[] { "Bob", 42 });
var originalRecord = TestRecord.Create(new[] { "name", "age" }, new object[] { "Bob", 42 });
var dict = new Dictionary<string, object>
{
{ "key1", "value1" },
Expand All @@ -43,13 +43,13 @@ public void ShouldUsePropertiesOfDict()
subject[0].Should().Be("value1");
subject[1].Should().Be("value2");
subject["key1"].Should().Be("value1");
subject["key2"].Should().Be("value2");
subject["KEY2"].Should().Be("value2");
}

[Fact]
public void ShouldUsePropertiesOfEntity()
{
var originalRecord = new Record(new[] { "name", "age" }, new object[] { "Bob", 42 });
var originalRecord = TestRecord.Create(new[] { "name", "age" }, new object[] { "Bob", 42 });
var entity = new Node(
1,
new[] { "Person" },
Expand All @@ -63,7 +63,7 @@ public void ShouldUsePropertiesOfEntity()
subject[0].Should().Be("value1");
subject[1].Should().Be("value2");
subject["key1"].Should().Be("value1");
subject["key2"].Should().Be("value2");
subject["KEY2"].Should().Be("value2");
}

[Fact]
Expand Down
14 changes: 7 additions & 7 deletions Neo4j.Driver/Neo4j.Driver.Tests/Mapping/LabelCaptureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
using FluentAssertions;
using Neo4j.Driver.Internal.Types;
using Neo4j.Driver.Preview.Mapping;
using Neo4j.Driver.Tests.TestUtil;
using Xunit;
using Record = Neo4j.Driver.Internal.Result.Record;

namespace Neo4j.Driver.Tests.Mapping;

Expand All @@ -46,7 +46,7 @@ public LabelCaptureTests()
public void ShouldCaptureSingleNodeLabel()
{
var node = new Node(1, new[] { "Test" }, new Dictionary<string, object>());
var record = new Record(new[] { "Person" }, new object[] { node });
var record = TestRecord.Create(new[] { "Person" }, new object[] { node });

var mapped = record.AsObject<TestMappedClass>();

Expand All @@ -57,7 +57,7 @@ public void ShouldCaptureSingleNodeLabel()
public void ShouldCaptureMultipleNodeLabelsIntoString()
{
var node = new Node(1, new[] { "Alpha", "Bravo", "Charlie" }, new Dictionary<string, object>());
var record = new Record(new[] { "Person" }, new object[] { node });
var record = TestRecord.Create(new[] { "Person" }, new object[] { node });

var mapped = record.AsObject<TestMappedClass>();

Expand All @@ -68,7 +68,7 @@ public void ShouldCaptureMultipleNodeLabelsIntoString()
public void ShouldCaptureRelationshipType()
{
var node = new Relationship(1, 2, 3, "ACTED_IN", new Dictionary<string, object>());
var record = new Record(new[] { "Relationship" }, new object[] { node });
var record = TestRecord.Create(new[] { "Relationship" }, new object[] { node });

var mapped = record.AsObject<TestMappedClass>();

Expand Down Expand Up @@ -105,7 +105,7 @@ public void ShouldCaptureAndConvertLabels()
{
RecordObjectMapping.RegisterProvider<CustomMapper>();
var node = new Node(1, new[] { "Alpha", "Bravo", "Charlie" }, new Dictionary<string, object>());
var record = new Record(new[] { "Person" }, new object[] { node });
var record = TestRecord.Create(new[] { "Person" }, new object[] { node });

var mapped = record.AsObject<TestMappedClass>();

Expand All @@ -118,10 +118,10 @@ public void ShouldCaptureAndConvertRelationshipType()
{
RecordObjectMapping.RegisterProvider<CustomMapper>();
var node = new Relationship(1, 2, 3, "ACTED_IN", new Dictionary<string, object>());
var record = new Record(new[] { "Relationship" }, new object[] { node });
var record = TestRecord.Create(new[] { "Relationship" }, new object[] { node });

var mapped = record.AsObject<TestMappedClass>();

mapped.RelationshipType.Should().Be("acted_in");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ public void ShouldCreateListOfMappedObjectsFromDictionaries()
[Fact]
public void ShouldCreateListOfMappedObjectsFromNodes()
{
var list = new List<IEntity> { Mock.Of<IEntity>(), Mock.Of<IEntity>(), Mock.Of<IEntity>() };
var mockEntity = new Mock<IEntity>();
mockEntity.Setup(x => x.Properties).Returns(new Dictionary<string, object>());
var list = new List<IEntity> { mockEntity.Object, mockEntity.Object, mockEntity.Object };
var record = Mock.Of<IRecord>();
var people = new List<Person> { new("Alan", 99), new("Basil", 999), new("David", 9999) };

Expand Down
18 changes: 9 additions & 9 deletions Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappingProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@

using FluentAssertions;
using Neo4j.Driver.Preview.Mapping;
using Neo4j.Driver.Tests.TestUtil;
using Xunit;
using Record = Neo4j.Driver.Internal.Result.Record;

namespace Neo4j.Driver.Tests.Mapping;

Expand Down Expand Up @@ -60,14 +60,14 @@ public void CreateMappers(IMappingRegistry registry)
.MapWholeObject(
r => new SecondTestObject
{
Number = r.GetValue<int>("intValue") + 1,
Text = r.GetValue<string>("stringValue").ToLower()
Number = r.Get<int>("intValue") + 1,
Text = r.Get<string>("stringValue").ToLower()
}))
.RegisterMapping<ThirdTestObject>(_ => {})
.RegisterMapping<PersonWithAge>(
b => b
.UseDefaultMapping()
.Map(x => x.Age, r => r.GetValue<int>("active") - r.GetValue<int>("born")));
.Map(x => x.Age, r => r.Get<int>("active") - r.Get<int>("born")));
}
}

Expand All @@ -79,7 +79,7 @@ public MappingProviderTests()
[Fact]
public void ShouldOverrideDefaultMapping()
{
var record = new Record(new[] { "stringValue", "intValue" }, new object[] { "test", 69 });
var record = TestRecord.Create(new[] { "stringValue", "intValue" }, new object[] { "test", 69 });
RecordObjectMapping.RegisterProvider<TestMappingProvider>();

var obj = record.AsObject<TestObject>();
Expand All @@ -91,7 +91,7 @@ public void ShouldOverrideDefaultMapping()
[Fact]
public void ShouldUseWholeObjectMapping()
{
var record = new Record(new[] { "stringValue", "intValue" }, new object[] { "TEST", 100 });
var record = TestRecord.Create(new[] { "stringValue", "intValue" }, new object[] { "TEST", 100 });
RecordObjectMapping.RegisterProvider<TestMappingProvider>();

var obj = record.AsObject<SecondTestObject>();
Expand All @@ -103,7 +103,7 @@ public void ShouldUseWholeObjectMapping()
[Fact]
public void ShouldNotUseDefaultMapperIfEmptyMappingConfigInProvider()
{
var record = new Record(new[] { "stringValue", "intValue" }, new object[] { "TEST", 100 });
var record = TestRecord.Create(new[] { "stringValue", "intValue" }, new object[] { "TEST", 100 });
RecordObjectMapping.RegisterProvider<TestMappingProvider>();

var obj = record.AsObject<ThirdTestObject>();
Expand All @@ -115,12 +115,12 @@ public void ShouldNotUseDefaultMapperIfEmptyMappingConfigInProvider()
[Fact]
public void ShouldMapPropertiesFromRecordIfRequired()
{
var record = new Record(new[] { "name", "born", "active" }, new object[] { "Bob", 1977, 2000 });
var record = TestRecord.Create(new[] { "name", "born", "active" }, new object[] { "Bob", 1977, 2000 });
RecordObjectMapping.RegisterProvider<TestMappingProvider>();

var obj = record.AsObject<PersonWithAge>();

obj.Name.Should().Be("Bob");
obj.Age.Should().Be(23);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
using FluentAssertions;
using Neo4j.Driver.Internal.Types;
using Neo4j.Driver.Preview.Mapping;
using Neo4j.Driver.Tests.TestUtil;
using Xunit;
using Record = Neo4j.Driver.Internal.Result.Record;

namespace Neo4j.Driver.Tests.Mapping;

Expand All @@ -27,7 +27,7 @@ public class MappingSourceDelegateBuilderTests
[Fact]
public void ShouldGetSimplePaths()
{
var record = new Record(new[] { "a" }, new object[] { "b" });
var record = TestRecord.Create(new[] { "a" }, new object[] { "b" });
var getter = new MappingSourceDelegateBuilder();
var mappingSource = new EntityMappingInfo("a", EntityMappingSource.Property);

Expand All @@ -41,7 +41,7 @@ public void ShouldGetSimplePaths()
[Fact]
public void ShouldReturnFalseWhenPathNotFound()
{
var record = new Record(new[] { "a" }, new object[] { "b" });
var record = TestRecord.Create(new[] { "a" }, new object[] { "b" });
var getter = new MappingSourceDelegateBuilder();
var mappingSource = new EntityMappingInfo("c", EntityMappingSource.Property);

Expand All @@ -55,9 +55,10 @@ public void ShouldReturnFalseWhenPathNotFound()
public void ShouldGetNodeLabels()
{
var node = new Node(1, new[] { "Actor", "Director" }, new Dictionary<string, object>());
var record = new Record(new[] { "a" }, new object[] { node });
var record = TestRecord.Create(new[] { "a" }, new object[] { node });
var getter = new MappingSourceDelegateBuilder();
var mappingSource = new EntityMappingInfo("a", EntityMappingSource.NodeLabel);
var mappingSource = new EntityMappingInfo("zzz", EntityMappingSource.NodeLabel);
mappingSource = mappingSource with { Path = "a" };

var mappingDelegate = getter.GetMappingDelegate(mappingSource);
var found = mappingDelegate(record, out var value);
Expand All @@ -70,7 +71,7 @@ public void ShouldGetNodeLabels()
public void ShouldGetRelationshipType()
{
var rel = new Relationship(1, 2, 3, "ACTED_IN", new Dictionary<string, object>());
var record = new Record(new[] { "a" }, new object[] { rel });
var record = TestRecord.Create(new[] { "a" }, new object[] { rel });
var getter = new MappingSourceDelegateBuilder();
var mappingSource = new EntityMappingInfo("a", EntityMappingSource.RelationshipType);

Expand All @@ -80,4 +81,4 @@ public void ShouldGetRelationshipType()
found.Should().BeTrue();
value.Should().Be("ACTED_IN");
}
}
}
Loading

0 comments on commit bc664bb

Please sign in to comment.