Skip to content

Commit 0e9a4d2

Browse files
Naming convention translation for object mapping (#840)
* initial working commit * Ensure MappingTestWithGlobalState classes reset mapping config after running * Small changes * Remove unused StandardNamingConventions enum * Made changes after unification meeting * Changed test and added one * Change test naming
1 parent e4e0030 commit 0e9a4d2

19 files changed

+1021
-29
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) "Neo4j"
2+
// Neo4j Sweden AB [https://neo4j.com]
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License").
5+
// You may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
using FluentAssertions;
16+
using Neo4j.Driver.Mapping.ConventionTranslation;
17+
using Xunit;
18+
19+
namespace Neo4j.Driver.Tests.Mapping.ConventionTranslation;
20+
21+
public class FormatterTests
22+
{
23+
[Fact]
24+
public void SnakeCaseFormatter_ShouldFormat()
25+
{
26+
var formatter = new StandardCaseFormatter(FieldCaseConvention.SnakeCase);
27+
var result = formatter.Format(new[] { "aPple", "BANANA", "Cherry" });
28+
result.Should().Be("apple_banana_cherry");
29+
}
30+
31+
[Fact]
32+
public void SnakeCaseFormatter_ShouldCombineSingleToken()
33+
{
34+
var formatter = new StandardCaseFormatter(FieldCaseConvention.SnakeCase);
35+
var result = formatter.Format(new[] { "aPple" });
36+
result.Should().Be("apple");
37+
}
38+
39+
[Fact]
40+
public void CamelCaseFormatter_ShouldFormat()
41+
{
42+
var formatter = new StandardCaseFormatter(FieldCaseConvention.CamelCase);
43+
var result = formatter.Format(new[] { "aPple", "BANANA", "Cherry" });
44+
result.Should().Be("appleBananaCherry");
45+
}
46+
47+
[Fact]
48+
public void CamelCaseFormatter_ShouldCombineSingleToken()
49+
{
50+
var formatter = new StandardCaseFormatter(FieldCaseConvention.CamelCase);
51+
var result = formatter.Format(new[] { "aPple" });
52+
result.Should().Be("apple");
53+
}
54+
55+
[Fact]
56+
public void PascalCaseFormatter_ShouldFormat()
57+
{
58+
var formatter = new StandardCaseFormatter(FieldCaseConvention.PascalCase);
59+
var result = formatter.Format(new[] { "aPple", "BANANA", "Cherry" });
60+
result.Should().Be("AppleBananaCherry");
61+
}
62+
63+
[Fact]
64+
public void PascalCaseFormatter_ShouldCombineSingleToken()
65+
{
66+
var formatter = new StandardCaseFormatter(FieldCaseConvention.PascalCase);
67+
var result = formatter.Format(new[] { "aPple" });
68+
result.Should().Be("Apple");
69+
}
70+
71+
[Fact]
72+
public void ScreamingSnakeCaseFormatter_ShouldFormat()
73+
{
74+
var formatter = new StandardCaseFormatter(FieldCaseConvention.ScreamingSnakeCase);
75+
var result = formatter.Format(new[] { "aPple", "BANANA", "Cherry" });
76+
result.Should().Be("APPLE_BANANA_CHERRY");
77+
}
78+
79+
[Fact]
80+
public void ScreamingSnakeCaseFormatter_ShouldCombineSingleToken()
81+
{
82+
var formatter = new StandardCaseFormatter(FieldCaseConvention.ScreamingSnakeCase);
83+
var result = formatter.Format(new[] { "aPple" });
84+
result.Should().Be("APPLE");
85+
}
86+
87+
[Fact]
88+
public void KebabCaseFormatter_ShouldFormat()
89+
{
90+
var formatter = new StandardCaseFormatter(FieldCaseConvention.KebabCase);
91+
var result = formatter.Format(new[] { "aPple", "BANANA", "Cherry" });
92+
result.Should().Be("apple-banana-cherry");
93+
}
94+
95+
[Fact]
96+
public void KebabCaseFormatter_ShouldCombineSingleToken()
97+
{
98+
var formatter = new StandardCaseFormatter(FieldCaseConvention.KebabCase);
99+
var result = formatter.Format(new[] { "aPple" });
100+
result.Should().Be("apple");
101+
}
102+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright (c) "Neo4j"
2+
// Neo4j Sweden AB [https://neo4j.com]
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License").
5+
// You may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
using System;
17+
using FluentAssertions;
18+
using Neo4j.Driver.Mapping.ConventionTranslation;
19+
using Xunit;
20+
21+
namespace Neo4j.Driver.Tests.Mapping.ConventionTranslation;
22+
23+
public class ParserTests
24+
{
25+
[Fact]
26+
public void ShouldParseSnakeCaseTokens()
27+
{
28+
var parser = new StandardCaseParser(IdentifierCaseConvention.SnakeCase);
29+
var tokens = parser.ParseIdentifier("apple_banana_cherry");
30+
tokens.Should().BeEquivalentTo("apple", "banana", "cherry");
31+
}
32+
33+
[Fact]
34+
public void ShouldParseCamelCaseTokens()
35+
{
36+
var parser = new StandardCaseParser(IdentifierCaseConvention.CamelCase);
37+
var tokens = parser.ParseIdentifier("appleBananaCherry");
38+
tokens.Should().BeEquivalentTo("apple", "Banana", "Cherry");
39+
}
40+
41+
[Fact]
42+
public void ShouldParsePascalCaseTokens()
43+
{
44+
var parser = new StandardCaseParser(IdentifierCaseConvention.PascalCase);
45+
var tokens = parser.ParseIdentifier("AppleBananaCherry");
46+
tokens.Should().BeEquivalentTo("Apple", "Banana", "Cherry");
47+
}
48+
49+
[Fact]
50+
public void ShouldParseScreamingSnakeCaseTokens()
51+
{
52+
var parser = new StandardCaseParser(IdentifierCaseConvention.ScreamingSnakeCase);
53+
var tokens = parser.ParseIdentifier("APPLE_BANANA_CHERRY");
54+
tokens.Should().BeEquivalentTo("APPLE", "BANANA", "CHERRY");
55+
}
56+
57+
[Fact]
58+
public void ShouldParseKebabCaseTokens()
59+
{
60+
var parser = new StandardCaseParser(IdentifierCaseConvention.KebabCase);
61+
var tokens = parser.ParseIdentifier("apple-banana-cherry");
62+
tokens.Should().BeEquivalentTo("apple", "banana", "cherry");
63+
}
64+
65+
[Fact]
66+
public void ShouldParseCSharpIdentifierTokens_LowerCaseStart()
67+
{
68+
var parser = new StandardCaseParser(IdentifierCaseConvention.CSharpIdentifier);
69+
var tokens = parser.ParseIdentifier("appleBananaCherry");
70+
tokens.Should().BeEquivalentTo("apple", "Banana", "Cherry");
71+
}
72+
73+
[Fact]
74+
public void ShouldParseCSharpIdentifierTokens_UpperCaseStart()
75+
{
76+
var parser = new StandardCaseParser(IdentifierCaseConvention.CSharpIdentifier);
77+
var tokens = parser.ParseIdentifier("AppleBananaCherry");
78+
tokens.Should().BeEquivalentTo("Apple", "Banana", "Cherry");
79+
}
80+
81+
[Fact]
82+
public void ShouldThrowExceptionForInvalidCamelCase()
83+
{
84+
var parser = new StandardCaseParser(IdentifierCaseConvention.CamelCase);
85+
Action act = () => parser.ParseIdentifier("AppleBananaCherry");
86+
act.Should().Throw<ArgumentException>();
87+
}
88+
89+
[Fact]
90+
public void ShouldThrowExceptionForInvalidPascalCase()
91+
{
92+
var parser = new StandardCaseParser(IdentifierCaseConvention.PascalCase);
93+
Action act = () => parser.ParseIdentifier("appleBananaCherry");
94+
act.Should().Throw<ArgumentException>();
95+
}
96+
97+
[Fact]
98+
public void ShouldThrowExceptionForInvalidSnakeCase()
99+
{
100+
var parser = new StandardCaseParser(IdentifierCaseConvention.SnakeCase);
101+
Action act = () => parser.ParseIdentifier("apple-Banana-Cherry");
102+
act.Should().Throw<ArgumentException>();
103+
}
104+
105+
[Fact]
106+
public void ShouldThrowExceptionForInvalidScreamingSnakeCase()
107+
{
108+
var parser = new StandardCaseParser(IdentifierCaseConvention.ScreamingSnakeCase);
109+
Action act = () => parser.ParseIdentifier("APPLE-banana-CHERRY");
110+
act.Should().Throw<ArgumentException>();
111+
}
112+
113+
[Fact]
114+
public void ShouldThrowExceptionForInvalidKebabCase()
115+
{
116+
var parser = new StandardCaseParser(IdentifierCaseConvention.KebabCase);
117+
Action act = () => parser.ParseIdentifier("apple_Banana_Cherry");
118+
act.Should().Throw<ArgumentException>();
119+
}
120+
121+
[Fact]
122+
public void ShouldThrowExceptionForInvalidCSharpIdentifier()
123+
{
124+
var parser = new StandardCaseParser(IdentifierCaseConvention.CSharpIdentifier);
125+
Action act = () => parser.ParseIdentifier("apple_bananaCherry");
126+
act.Should().Throw<ArgumentException>();
127+
}
128+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) "Neo4j"
2+
// Neo4j Sweden AB [https://neo4j.com]
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License").
5+
// You may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
using System.Collections.Generic;
17+
using FluentAssertions;
18+
using Neo4j.Driver.Internal.Types;
19+
using Neo4j.Driver.Mapping;
20+
using Neo4j.Driver.Mapping.ConventionTranslation;
21+
using Neo4j.Driver.Tests.TestUtil;
22+
using Xunit;
23+
24+
namespace Neo4j.Driver.Tests.Mapping.ConventionTranslation;
25+
26+
public class TranslationEndToEndTests : MappingTestWithGlobalState
27+
{
28+
[Fact]
29+
public void ShouldDoDefaultTranslation()
30+
{
31+
var record = TestRecord.Create(["personName", "yearBorn"], ["Bob", 1977]);
32+
RecordObjectMapping.TranslateIdentifiers();
33+
var person = record.AsObjectFromBlueprint(new { PersonName = "", YearBorn = 0 });
34+
person.PersonName.Should().Be("Bob");
35+
person.YearBorn.Should().Be(1977);
36+
}
37+
38+
[Fact]
39+
public void ShouldMapKebabCaseFieldsToSnakeCaseProperties()
40+
{
41+
var record = TestRecord.Create(["person-name", "year-born"], ["Bob", 1977]);
42+
RecordObjectMapping.TranslateIdentifiers(IdentifierCaseConvention.SnakeCase, FieldCaseConvention.KebabCase);
43+
var person = record.AsObjectFromBlueprint(new { person_name = "", year_born = 0 });
44+
person.person_name.Should().Be("Bob");
45+
person.year_born.Should().Be(1977);
46+
}
47+
48+
[Fact]
49+
public void ShouldMapSnakeCaseFieldsToKebabCaseProperties()
50+
{
51+
var record = TestRecord.Create(["person_name", "year_born"], ["Bob", 1977]);
52+
RecordObjectMapping.TranslateIdentifiers(
53+
IdentifierCaseConvention.ScreamingSnakeCase,
54+
FieldCaseConvention.SnakeCase);
55+
56+
var person = record.AsObjectFromBlueprint(new { PERSON_NAME = "", YEAR_BORN = 0 });
57+
person.PERSON_NAME.Should().Be("Bob");
58+
person.YEAR_BORN.Should().Be(1977);
59+
}
60+
61+
private class ExplicitNamePerson
62+
{
63+
[MappingSource("name-of-person")]
64+
public string Name { get; set; }
65+
66+
public int YearBorn { get; set; }
67+
}
68+
69+
[Fact]
70+
public void ShouldNotTranslateWhenPropertyIsMarkedWithMappingSourceAttribute()
71+
{
72+
var record = TestRecord.Create(["name-of-person", "year_born"], ["Bob", 1977]);
73+
RecordObjectMapping.TranslateIdentifiers(FieldCaseConvention.SnakeCase);
74+
var person = record.AsObject<ExplicitNamePerson>();
75+
person.Name.Should().Be("Bob");
76+
person.YearBorn.Should().Be(1977);
77+
}
78+
79+
public record Person(int NumberOfMiddleNames, string FavouriteColor);
80+
81+
public class FlightCrew(Person pilot, Person coPilot)
82+
{
83+
public Person Pilot { get; set; }
84+
public Person CoPilot { get; set; }
85+
}
86+
87+
[Fact]
88+
public void ShouldTranslateThroughNesting()
89+
{
90+
var pilotNode = new Node(
91+
0,
92+
[],
93+
new Dictionary<string, object> { ["number_of_middle_names"] = 1, ["favourite_color"] = "red" });
94+
95+
var coPilotNode = new Node(
96+
1,
97+
[],
98+
new Dictionary<string, object> { ["number_of_middle_names"] = 2, ["favourite_color"] = "blue" });
99+
100+
var record = TestRecord.Create(("pilot", pilotNode), ("co_pilot", coPilotNode));
101+
RecordObjectMapping.TranslateIdentifiers(FieldCaseConvention.SnakeCase);
102+
103+
var flightCrew = record.AsObject<FlightCrew>();
104+
105+
flightCrew.Pilot.NumberOfMiddleNames.Should().Be(1);
106+
flightCrew.Pilot.FavouriteColor.Should().Be("red");
107+
flightCrew.CoPilot.NumberOfMiddleNames.Should().Be(2);
108+
flightCrew.CoPilot.FavouriteColor.Should().Be("blue");
109+
}
110+
}

0 commit comments

Comments
 (0)