Skip to content

Commit 605f827

Browse files
committed
Tests, docs
1 parent 074b766 commit 605f827

File tree

8 files changed

+237
-76
lines changed

8 files changed

+237
-76
lines changed

README.md

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
A .NET source generator for creating
77
* Simple value objects wrapping other type(s), without the hassle of manual `Equals`/`GetHashCode`
88
* Value objects wrapping math primitives
9-
* I.e. `[WrapperValueObject(typeof(int))] partial readonly struct MeterLength` - the type is implicitly castable to `int`, and you can create your own math operations
9+
* I.e. `[WrapperValueObject(typeof(int))] readonly partial struct MeterLength { }` - the type is implicitly castable to `int`, and you can create your own math operations
1010
* Strongly typed ID's
11-
* Similar to F# `type ProductId = ProductId of Guid`, here it becomes `[WrapperValueObject] partial readonly struct ProductId`
11+
* Similar to F# `type ProductId = ProductId of Guid`, here it becomes `[WrapperValueObject] readonly partial struct ProductId { }`
1212

1313
Note that record type feature for structs is planned for C# 10, in which cases some of the
1414
use cases this library supports will be easier to achieve without this libray.
@@ -35,85 +35,94 @@ dotnet add package WrapperValueObject.Generator --version 0.0.1-alpha04
3535
1. Use the attribute to specify the underlying type.
3636
2. Declare the struct or class with the `partial` keyword. (and does not support nested types)
3737

38-
### Simple example
38+
### Strongly typed ID
3939

40+
41+
```csharp
42+
[WrapperValueObject] readonly partial struct ProductId { }
43+
44+
var id = ProductId.New(); // Strongly typed Guid wrapper, i.e. {1658db8c-89a4-46ea-b97e-8cf966cfb3f1}
45+
46+
Assert.NotEqual(ProductId.New(), id);
47+
Assert.False(ProductId.New() == id);
48+
```
49+
50+
### Money type
51+
52+
```csharp
53+
[WrapperValueObject(typeof(decimal))] readonly partial struct Money { }
54+
55+
Money money = 2m;
56+
57+
var result = money + 2m; // 4.0
58+
var result2 = money + new Money(2m);
59+
60+
Assert.True(result == result2);
61+
Assert.Equal(4m, (decimal)result);
62+
```
63+
64+
65+
### Metric types
4066
```csharp
4167
[WrapperValueObject(typeof(int))]
4268
public readonly partial struct MeterLength
4369
{
44-
public static implicit operator CentimeterLength(MeterLength meter) => meter.Value * 100;
70+
public static implicit operator CentimeterLength(MeterLength meter) => meter.Value * 100; // .Value is the inner type, in this case int
4571
}
4672

4773
[WrapperValueObject(typeof(int))]
4874
public readonly partial struct CentimeterLength
4975
{
50-
public static implicit operator MeterLength(CentimeterLength centiMeter) => centiMeter / 100;
76+
public static implicit operator MeterLength(CentimeterLength centiMeter) => centiMeter.Value / 100;
5177
}
5278

5379
MeterLength meters = 2;
5480

55-
CentimeterLength centiMeters = meters;
81+
CentimeterLength centiMeters = meters; // 200
5682
5783
Assert.Equal(200, (int)centiMeters);
5884
```
5985

60-
### Full example
86+
### Complex types
6187

6288
```csharp
63-
using System;
64-
using System.Diagnostics;
89+
[WrapperValueObject] // Is Guid ID by default
90+
readonly partial struct MatchId { }
6591

66-
namespace WrapperValueObject.TestConsole
67-
{
68-
[WrapperValueObject] // Is Guid by default
69-
public readonly partial struct MatchId
70-
{
71-
public static MatchId New() => Guid.NewGuid();
72-
}
92+
[WrapperValueObject("HomeGoals", typeof(byte), "AwayGoals", typeof(byte))]
93+
readonly partial struct MatchResult { }
7394

74-
[WrapperValueObject("HomeGoals", typeof(byte), "AwayGoals", typeof(byte))]
75-
public readonly partial struct MatchResult
76-
{
77-
}
78-
79-
public partial struct Match
80-
{
81-
public readonly MatchId MatchId { get; }
95+
partial struct Match
96+
{
97+
public readonly MatchId MatchId { get; }
8298

83-
public MatchResult Result { get; private set; }
99+
public MatchResult Result { get; private set; }
84100

85-
public void SetResult(MatchResult result) => Result = result;
101+
public void SetResult(MatchResult result) => Result = result;
86102

87-
public Match(in MatchId matchId)
88-
{
89-
MatchId = matchId;
90-
Result = default;
91-
}
103+
public Match(in MatchId matchId)
104+
{
105+
MatchId = matchId;
106+
Result = default;
92107
}
108+
}
93109

94-
public static class Program
95-
{
96-
static void Main()
97-
{
98-
var match = new Match(MatchId.New());
110+
var match = new Match(MatchId.New());
99111

100-
match.SetResult((1, 2));
101-
match.SetResult(new MatchResult(1, 2));
112+
match.SetResult((1, 2)); // Complex types use value tuples underneath, so can be implicitly converted
113+
match.SetResult(new MatchResult(1, 2)); // Or the full constructor
102114
103-
var otherResult = new MatchResult(2, 1);
115+
var otherResult = new MatchResult(2, 1);
104116

105-
Debug.Assert(otherResult != match.Result);
117+
Debug.Assert(otherResult != match.Result);
106118

107-
match.SetResult((2, 1));
108-
Debug.Assert(otherResult == match.Result);
119+
match.SetResult((2, 1));
120+
Debug.Assert(otherResult == match.Result);
109121

110-
Debug.Assert(match.MatchId != default);
111-
Debug.Assert(match.Result != default);
112-
Debug.Assert(match.Result.HomeGoals == 2);
113-
Debug.Assert(match.Result.AwayGoals == 1);
114-
}
115-
}
116-
}
122+
Debug.Assert(match.MatchId != default);
123+
Debug.Assert(match.Result != default);
124+
Debug.Assert(match.Result.HomeGoals == 2);
125+
Debug.Assert(match.Result.AwayGoals == 1);
117126
```
118127

119128
## TODO/under consideration

src/WrapperValueObject.Generator/Generator.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,16 @@ private bool GenerateWrapper(in GenerationContext context)
210210
var innerType = string.Empty;
211211
var isMathType = false;
212212
var isSingleType = context.InnerTypes.Count() == 1;
213+
var isDefaultIdCase = false;
213214
if (isSingleType)
214215
{
215216
// If we have 1 type, we might be able to safely generate math operations
216217
var singleType = context.InnerTypes.Single();
217218
innerType = $"{singleType.Type!.ContainingNamespace}.{singleType.Type.Name}";
218219
isMathType = MathTypes!.Contains(innerType);
220+
isDefaultIdCase = innerType == "System.Guid";
221+
222+
//System.Diagnostics.Debugger.Launch();
219223
}
220224
else
221225
{
@@ -268,6 +272,13 @@ namespace {context.Type.ContainingNamespace}
268272
269273
");
270274

275+
if (isDefaultIdCase)
276+
{
277+
context.SourceBuilder.AppendLine(@$"
278+
public static {context.Type.Name} New() => Guid.NewGuid();
279+
");
280+
}
281+
271282
if (isSingleType)
272283
{
273284
context.SourceBuilder.AppendLine(@$"

test/WrapperValueObject.TestConsole/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ namespace WrapperValueObject.TestConsole
66
[WrapperValueObject]
77
public readonly partial struct MatchId
88
{
9-
public static MatchId New() => Guid.NewGuid();
109
}
1110

1211
[WrapperValueObject("HomeGoals", typeof(byte), "AwayGoals", typeof(byte))]

test/WrapperValueObject.TestConsole/WrapperValueObject.TestConsole.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
<IsPackable>false</IsPackable>
99
</PropertyGroup>
1010

11-
<!--<ItemGroup>
11+
<ItemGroup>
1212
<ProjectReference Include="..\..\src\WrapperValueObject.Generator\WrapperValueObject.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
13-
</ItemGroup>-->
13+
</ItemGroup>
1414

15-
<ItemGroup>
15+
<!--<ItemGroup>
1616
<PackageReference Include="WrapperValueObject.Generator" Version="0.0.1-alpha04">
1717
<PrivateAssets>all</PrivateAssets>
1818
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
1919
</PackageReference>
20-
</ItemGroup>
20+
</ItemGroup>-->
2121

2222
</Project>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using Xunit;
2+
3+
namespace WrapperValueObject.Tests
4+
{
5+
[WrapperValueObject(typeof(int))]
6+
public readonly partial struct MeterLength
7+
{
8+
public static implicit operator CentimeterLength(MeterLength meter) => meter.Value * 100;
9+
}
10+
11+
[WrapperValueObject(typeof(int))]
12+
public readonly partial struct CentimeterLength
13+
{
14+
public static implicit operator MeterLength(CentimeterLength centiMeter) => centiMeter.Value / 100;
15+
}
16+
17+
public class MetricTypesTests
18+
{
19+
[Fact]
20+
public void Test_Conversion()
21+
{
22+
MeterLength meters = 2;
23+
24+
CentimeterLength centiMeters = meters;
25+
26+
Assert.Equal(200, (int)centiMeters);
27+
}
28+
29+
[Fact]
30+
public void Test_Add()
31+
{
32+
MeterLength meters = 2;
33+
34+
var result = meters + 2;
35+
36+
Assert.Equal(((int)meters) + 2, (int)result);
37+
Assert.True(meters != result);
38+
Assert.True(meters == 2);
39+
}
40+
41+
[Fact]
42+
public void Test_Subtract()
43+
{
44+
MeterLength meters = 5;
45+
46+
var result = meters - 2;
47+
48+
Assert.Equal(((int)meters) - 2, (int)result);
49+
Assert.True(meters != result);
50+
Assert.True(meters == 5);
51+
}
52+
53+
[Fact]
54+
public void Test_Multiply()
55+
{
56+
MeterLength meters = 5;
57+
58+
var result = meters * 2;
59+
60+
Assert.Equal(((int)meters) * 2, (int)result);
61+
Assert.True(meters != result);
62+
Assert.True(meters == 5);
63+
}
64+
65+
[Fact]
66+
public void Test_Divide()
67+
{
68+
MeterLength meters = 2;
69+
70+
var result = meters / 2;
71+
72+
Assert.Equal(((int)meters) / 2, (int)result);
73+
Assert.True(meters != result);
74+
Assert.True(meters == 2);
75+
}
76+
}
77+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Xunit;
2+
3+
namespace WrapperValueObject.Tests
4+
{
5+
[WrapperValueObject(typeof(decimal))]
6+
public readonly partial struct Money
7+
{
8+
}
9+
10+
public class MoneyTypeTests
11+
{
12+
[Fact]
13+
public void Test_Add()
14+
{
15+
Money money = 2m;
16+
17+
var result = money + 2m;
18+
var result2 = money + new Money(2m);
19+
20+
Assert.True(result == result2);
21+
Assert.Equal(((decimal)money) + 2m, (decimal)result);
22+
Assert.True(money != result);
23+
Assert.True(money == 2m);
24+
}
25+
26+
[Fact]
27+
public void Test_Subtract()
28+
{
29+
Money money = 5m;
30+
31+
var result = money - 2m;
32+
33+
Assert.Equal(((decimal)money) - 2m, (decimal)result);
34+
Assert.True(money != result);
35+
Assert.True(money == 5m);
36+
}
37+
38+
[Fact]
39+
public void Test_Multiply()
40+
{
41+
Money money = 5m;
42+
43+
var result = money * 2m;
44+
45+
Assert.Equal(((decimal)money) * 2m, (decimal)result);
46+
Assert.True(money != result);
47+
Assert.True(money == 5m);
48+
}
49+
50+
[Fact]
51+
public void Test_Divide()
52+
{
53+
Money money = 2m;
54+
55+
var result = money / 2m;
56+
57+
Assert.Equal(((decimal)money) / 2m, (decimal)result);
58+
Assert.True(money != result);
59+
Assert.True(money == 2m);
60+
}
61+
}
62+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using Xunit;
3+
4+
namespace WrapperValueObject.Tests
5+
{
6+
[WrapperValueObject] readonly partial struct ProductId { }
7+
8+
public class ProductIdTypeTests
9+
{
10+
[Fact]
11+
public void Test_New()
12+
{
13+
var id = ProductId.New();
14+
15+
Assert.NotEqual(Guid.Empty, (Guid)id);
16+
17+
var id2 = id;
18+
19+
Assert.Equal(id2, id);
20+
Assert.True(id2 == id);
21+
Assert.NotEqual(ProductId.New(), id);
22+
Assert.True(ProductId.New() != id);
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)