Skip to content

Commit

Permalink
Tests, docs
Browse files Browse the repository at this point in the history
  • Loading branch information
martinothamar committed Oct 22, 2020
1 parent 074b766 commit 605f827
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 76 deletions.
107 changes: 58 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
A .NET source generator for creating
* Simple value objects wrapping other type(s), without the hassle of manual `Equals`/`GetHashCode`
* Value objects wrapping math primitives
* I.e. `[WrapperValueObject(typeof(int))] partial readonly struct MeterLength` - the type is implicitly castable to `int`, and you can create your own math operations
* I.e. `[WrapperValueObject(typeof(int))] readonly partial struct MeterLength { }` - the type is implicitly castable to `int`, and you can create your own math operations
* Strongly typed ID's
* Similar to F# `type ProductId = ProductId of Guid`, here it becomes `[WrapperValueObject] partial readonly struct ProductId`
* Similar to F# `type ProductId = ProductId of Guid`, here it becomes `[WrapperValueObject] readonly partial struct ProductId { }`

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

### Simple example
### Strongly typed ID


```csharp
[WrapperValueObject] readonly partial struct ProductId { }

var id = ProductId.New(); // Strongly typed Guid wrapper, i.e. {1658db8c-89a4-46ea-b97e-8cf966cfb3f1}
Assert.NotEqual(ProductId.New(), id);
Assert.False(ProductId.New() == id);
```

### Money type

```csharp
[WrapperValueObject(typeof(decimal))] readonly partial struct Money { }

Money money = 2m;

var result = money + 2m; // 4.0
var result2 = money + new Money(2m);

Assert.True(result == result2);
Assert.Equal(4m, (decimal)result);
```


### Metric types
```csharp
[WrapperValueObject(typeof(int))]
public readonly partial struct MeterLength
{
public static implicit operator CentimeterLength(MeterLength meter) => meter.Value * 100;
public static implicit operator CentimeterLength(MeterLength meter) => meter.Value * 100; // .Value is the inner type, in this case int
}

[WrapperValueObject(typeof(int))]
public readonly partial struct CentimeterLength
{
public static implicit operator MeterLength(CentimeterLength centiMeter) => centiMeter / 100;
public static implicit operator MeterLength(CentimeterLength centiMeter) => centiMeter.Value / 100;
}

MeterLength meters = 2;

CentimeterLength centiMeters = meters;
CentimeterLength centiMeters = meters; // 200
Assert.Equal(200, (int)centiMeters);
```

### Full example
### Complex types

```csharp
using System;
using System.Diagnostics;
[WrapperValueObject] // Is Guid ID by default
readonly partial struct MatchId { }

namespace WrapperValueObject.TestConsole
{
[WrapperValueObject] // Is Guid by default
public readonly partial struct MatchId
{
public static MatchId New() => Guid.NewGuid();
}
[WrapperValueObject("HomeGoals", typeof(byte), "AwayGoals", typeof(byte))]
readonly partial struct MatchResult { }

[WrapperValueObject("HomeGoals", typeof(byte), "AwayGoals", typeof(byte))]
public readonly partial struct MatchResult
{
}

public partial struct Match
{
public readonly MatchId MatchId { get; }
partial struct Match
{
public readonly MatchId MatchId { get; }

public MatchResult Result { get; private set; }
public MatchResult Result { get; private set; }

public void SetResult(MatchResult result) => Result = result;
public void SetResult(MatchResult result) => Result = result;

public Match(in MatchId matchId)
{
MatchId = matchId;
Result = default;
}
public Match(in MatchId matchId)
{
MatchId = matchId;
Result = default;
}
}

public static class Program
{
static void Main()
{
var match = new Match(MatchId.New());
var match = new Match(MatchId.New());

match.SetResult((1, 2));
match.SetResult(new MatchResult(1, 2));
match.SetResult((1, 2)); // Complex types use value tuples underneath, so can be implicitly converted
match.SetResult(new MatchResult(1, 2)); // Or the full constructor
var otherResult = new MatchResult(2, 1);
var otherResult = new MatchResult(2, 1);

Debug.Assert(otherResult != match.Result);
Debug.Assert(otherResult != match.Result);

match.SetResult((2, 1));
Debug.Assert(otherResult == match.Result);
match.SetResult((2, 1));
Debug.Assert(otherResult == match.Result);

Debug.Assert(match.MatchId != default);
Debug.Assert(match.Result != default);
Debug.Assert(match.Result.HomeGoals == 2);
Debug.Assert(match.Result.AwayGoals == 1);
}
}
}
Debug.Assert(match.MatchId != default);
Debug.Assert(match.Result != default);
Debug.Assert(match.Result.HomeGoals == 2);
Debug.Assert(match.Result.AwayGoals == 1);
```

## TODO/under consideration
Expand Down
11 changes: 11 additions & 0 deletions src/WrapperValueObject.Generator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,16 @@ private bool GenerateWrapper(in GenerationContext context)
var innerType = string.Empty;
var isMathType = false;
var isSingleType = context.InnerTypes.Count() == 1;
var isDefaultIdCase = false;
if (isSingleType)
{
// If we have 1 type, we might be able to safely generate math operations
var singleType = context.InnerTypes.Single();
innerType = $"{singleType.Type!.ContainingNamespace}.{singleType.Type.Name}";
isMathType = MathTypes!.Contains(innerType);
isDefaultIdCase = innerType == "System.Guid";

//System.Diagnostics.Debugger.Launch();
}
else
{
Expand Down Expand Up @@ -268,6 +272,13 @@ namespace {context.Type.ContainingNamespace}
");

if (isDefaultIdCase)
{
context.SourceBuilder.AppendLine(@$"
public static {context.Type.Name} New() => Guid.NewGuid();
");
}

if (isSingleType)
{
context.SourceBuilder.AppendLine(@$"
Expand Down
1 change: 0 additions & 1 deletion test/WrapperValueObject.TestConsole/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ namespace WrapperValueObject.TestConsole
[WrapperValueObject]
public readonly partial struct MatchId
{
public static MatchId New() => Guid.NewGuid();
}

[WrapperValueObject("HomeGoals", typeof(byte), "AwayGoals", typeof(byte))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
<IsPackable>false</IsPackable>
</PropertyGroup>

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

<ItemGroup>
<!--<ItemGroup>
<PackageReference Include="WrapperValueObject.Generator" Version="0.0.1-alpha04">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</ItemGroup>-->

</Project>
77 changes: 77 additions & 0 deletions test/WrapperValueObject.Tests/MetricTypesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Xunit;

namespace WrapperValueObject.Tests
{
[WrapperValueObject(typeof(int))]
public readonly partial struct MeterLength
{
public static implicit operator CentimeterLength(MeterLength meter) => meter.Value * 100;
}

[WrapperValueObject(typeof(int))]
public readonly partial struct CentimeterLength
{
public static implicit operator MeterLength(CentimeterLength centiMeter) => centiMeter.Value / 100;
}

public class MetricTypesTests
{
[Fact]
public void Test_Conversion()
{
MeterLength meters = 2;

CentimeterLength centiMeters = meters;

Assert.Equal(200, (int)centiMeters);
}

[Fact]
public void Test_Add()
{
MeterLength meters = 2;

var result = meters + 2;

Assert.Equal(((int)meters) + 2, (int)result);
Assert.True(meters != result);
Assert.True(meters == 2);
}

[Fact]
public void Test_Subtract()
{
MeterLength meters = 5;

var result = meters - 2;

Assert.Equal(((int)meters) - 2, (int)result);
Assert.True(meters != result);
Assert.True(meters == 5);
}

[Fact]
public void Test_Multiply()
{
MeterLength meters = 5;

var result = meters * 2;

Assert.Equal(((int)meters) * 2, (int)result);
Assert.True(meters != result);
Assert.True(meters == 5);
}

[Fact]
public void Test_Divide()
{
MeterLength meters = 2;

var result = meters / 2;

Assert.Equal(((int)meters) / 2, (int)result);
Assert.True(meters != result);
Assert.True(meters == 2);
}
}
}
62 changes: 62 additions & 0 deletions test/WrapperValueObject.Tests/MoneyTypeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Xunit;

namespace WrapperValueObject.Tests
{
[WrapperValueObject(typeof(decimal))]
public readonly partial struct Money
{
}

public class MoneyTypeTests
{
[Fact]
public void Test_Add()
{
Money money = 2m;

var result = money + 2m;
var result2 = money + new Money(2m);

Assert.True(result == result2);
Assert.Equal(((decimal)money) + 2m, (decimal)result);
Assert.True(money != result);
Assert.True(money == 2m);
}

[Fact]
public void Test_Subtract()
{
Money money = 5m;

var result = money - 2m;

Assert.Equal(((decimal)money) - 2m, (decimal)result);
Assert.True(money != result);
Assert.True(money == 5m);
}

[Fact]
public void Test_Multiply()
{
Money money = 5m;

var result = money * 2m;

Assert.Equal(((decimal)money) * 2m, (decimal)result);
Assert.True(money != result);
Assert.True(money == 5m);
}

[Fact]
public void Test_Divide()
{
Money money = 2m;

var result = money / 2m;

Assert.Equal(((decimal)money) / 2m, (decimal)result);
Assert.True(money != result);
Assert.True(money == 2m);
}
}
}
25 changes: 25 additions & 0 deletions test/WrapperValueObject.Tests/ProductIdTypeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using Xunit;

namespace WrapperValueObject.Tests
{
[WrapperValueObject] readonly partial struct ProductId { }

public class ProductIdTypeTests
{
[Fact]
public void Test_New()
{
var id = ProductId.New();

Assert.NotEqual(Guid.Empty, (Guid)id);

var id2 = id;

Assert.Equal(id2, id);
Assert.True(id2 == id);
Assert.NotEqual(ProductId.New(), id);
Assert.True(ProductId.New() != id);
}
}
}
Loading

0 comments on commit 605f827

Please sign in to comment.