Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo/Primify.Demo/Primify.Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.21" />
<PackageReference Include="primify" Version="1.4.20" />
<PackageReference Include="primify" Version="1.4.22" />
</ItemGroup>

</Project>
160 changes: 129 additions & 31 deletions src/Primify.Generators/PrimifyGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,36 +323,134 @@ private static string GenerateExplicitCasting(string name, string argument) =>
public static explicit operator {argument}({name} value) => value.Value;
""";

private static string GenerateImplicitCasting(string name, string argument) =>
$"""
// Casting for BSON
public static implicit operator LiteDB.BsonValue({name} value) =>
new LiteDB.BsonValue(value.Value);
public static implicit operator {name}(LiteDB.BsonValue value) =>
From(({argument})System.Convert.ChangeType(value.RawValue, typeof({argument})));
""";
private static string GenerateImplicitCasting(string name, string argument)
{
string toBson;
// We will generate the "from BsonValue" part differently based on complexity.
string fromBsonImplementation;

switch (argument)
{
case "System.DateOnly":
toBson = "new LiteDB.BsonValue(value.Value.ToDateTime(System.TimeOnly.MinValue))";
fromBsonImplementation = $"=> {name}.From(System.DateOnly.FromDateTime(value.AsDateTime));";
break;

case "System.TimeOnly":
toBson = "new LiteDB.BsonValue(value.Value.Ticks)";
fromBsonImplementation = $"=> {name}.From(new System.TimeOnly(value.AsInt64));";
break;

case "System.DateTimeOffset":
// Create a BsonDocument for serialization
toBson = """
new LiteDB.BsonDocument
{
["DateTime"] = value.Value.UtcDateTime,
["Offset"] = value.Value.Offset.Ticks
}
""";

// Generate a full method body for deserialization
fromBsonImplementation = $$"""
{
var doc = value.AsDocument;
var utcDateTime = doc["DateTime"].AsDateTime;
var offset = new System.TimeSpan(doc["Offset"].AsInt64);

// Create a UTC DateTimeOffset first, then convert to the original offset
var utcTime = new System.DateTimeOffset(utcDateTime);
var originalTime = utcTime.ToOffset(offset);

return {{name}}.From(originalTime);
}
""";
break;

default:
toBson = "new LiteDB.BsonValue(value.Value)";
fromBsonImplementation =
$"=> {name}.From(({argument})System.Convert.ChangeType(value.RawValue, typeof({argument})));";
break;
}

return $"""
// Casting for BSON
public static implicit operator LiteDB.BsonValue({name} value) =>
{toBson};

private static string GenerateLiteDbInitializer(string name, string argument) =>
$$"""
/// <summary>
/// Automatically registers the LiteDB BSON mapper for the {{name}} type.
/// </summary>
file static class {{name}}LiteDbInitializer
{
[ModuleInitializer]
internal static void Register()
{
BsonMapper.Global.RegisterType<{{name}}>(
serialize: wrapper => new LiteDB.BsonValue(wrapper.Value),
deserialize: bson =>
{
var rawValue = bson.RawValue;
var typedValue = ({{argument}})System.Convert.ChangeType(rawValue, typeof({{argument}}));

return {{name}}.From(typedValue);
}
);
}
}
""";
public static implicit operator {name}(LiteDB.BsonValue value)
{fromBsonImplementation}
""";
}

private static string GenerateLiteDbInitializer(string name, string argument)
{
string serializeCode;
string deserializeCode;

switch (argument)
{
case "System.DateOnly":
serializeCode = "wrapper => new LiteDB.BsonValue(wrapper.Value.ToDateTime(System.TimeOnly.MinValue))";
deserializeCode = $"bson => {name}.From(System.DateOnly.FromDateTime(bson.AsDateTime))";
break;

case "System.TimeOnly":
serializeCode = "wrapper => new LiteDB.BsonValue(wrapper.Value.Ticks)";
deserializeCode = $"bson => {name}.From(new System.TimeOnly(bson.AsInt64))";
break;

case "System.DateTimeOffset":
serializeCode = """
wrapper =>
new LiteDB.BsonDocument
{
["DateTime"] = wrapper.Value.UtcDateTime,
["Offset"] = wrapper.Value.Offset.Ticks
}
""";

deserializeCode = $$"""
bson =>
{
var doc = bson.AsDocument;
var utcDateTime = doc["DateTime"].AsDateTime;
var offset = new System.TimeSpan(doc["Offset"].AsInt64);

// 1. Create a DateTimeOffset from the UTC time (its offset will be zero).
var utcTime = new System.DateTimeOffset(utcDateTime);

// 2. Convert it to the original offset.
var originalTime = utcTime.ToOffset(offset);

return {{name}}.From(originalTime);
}
""";
break;

default:
serializeCode = "wrapper => new LiteDB.BsonValue(wrapper.Value)";
deserializeCode =
$"bson => {name}.From(({argument})System.Convert.ChangeType(bson.RawValue, typeof({argument})))";
break;
}

return $$"""
/// <summary>
/// Automatically registers the LiteDB BSON mapper for the {{name}} type.
/// </summary>
file static class {{name}}LiteDbInitializer
{
[System.Runtime.CompilerServices.ModuleInitializer]
internal static void Register()
{
LiteDB.BsonMapper.Global.RegisterType<{{name}}>(
serialize: {{serializeCode}},
deserialize: {{deserializeCode}}
);
}
}
""";
}
}
2 changes: 1 addition & 1 deletion src/Primify/Primify.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<PackageId>Primify</PackageId>
<Version>1.4.22</Version>
<Version>1.4.23</Version>
<IsPackable>true</IsPackable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
Expand Down
26 changes: 26 additions & 0 deletions tests/Primify.Tests/DateOnlyWrapperClassTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,34 @@ public partial class DateOnlyPrimowrapClassWithPredefinedProperty
public static DateOnlyPrimowrapClassWithPredefinedProperty Empty => new(DateOnly.MinValue);
}

[Primify<DateOnly>]
public partial record struct DateOnlyId;

public class Foo
{
public DateOnlyId Id { get; set; }
}

public class DateOnlyWrapperClassTests(ITestOutputHelper testOutputHelper)
{
[Fact]
public void DateOnly_ReadWrite_ForLiteDb()
{
using var db = new LiteDatabase(":memory:");
var collection = db.GetCollection<Foo>();

var foo = new Foo()
{
Id = DateOnlyId.From(DateOnly.MaxValue)
};

var insert = collection.Insert(foo);

var result = collection.FindById(foo.Id);

Assert.Equal(foo.Id, result.Id);
}

[Fact]
public void DateOnlyWrapperClass_CreatesType_WhenFromIsCalled()
{
Expand Down
26 changes: 26 additions & 0 deletions tests/Primify.Tests/DateTimeOffsetWrapperClassTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,34 @@ public partial class DateTimeOffsetPrimowrapClassWithPredefinedProperty
public static DateTimeOffsetPrimowrapClassWithPredefinedProperty Empty => new(DateTimeOffset.MinValue);
}

[Primify<DateTimeOffset>]
public partial record struct DateTimeOffsetId;

public class DateTimeOffsetFoo
{
public DateTimeOffsetId Id { get; set; }
}

public class DateTimeOffsetWrapperClassTests(ITestOutputHelper testOutputHelper)
{
[Fact]
public void DateTimeOffset_ReadWrite_ForLiteDb()
{
using var db = new LiteDatabase(":memory:");
var collection = db.GetCollection<DateTimeOffsetFoo>();

var foo = new DateTimeOffsetFoo()
{
Id = DateTimeOffsetId.From(DateTimeOffset.UtcNow)
};

var insert = collection.Insert(foo);

var result = collection.FindById(foo.Id);

Assert.Equal(foo.Id.ToString(), result.Id.ToString());
}

[Fact]
public void DateTimeOffsetWrapperClass_CreatesType_WhenFromIsCalled()
{
Expand Down
Loading
Loading