From 22582f2597918b8993e248cb1919d13b8965c740 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Sun, 29 Dec 2024 14:03:21 +0100 Subject: [PATCH 01/16] backup - not working --- .../FSharp.MongoDB.Bson.fsproj | 6 +- .../NullableReferenceTypeConvention.fs | 34 ++++++++++ .../Serialization/FSharpValueSerializer.fs | 3 + .../Serializers/FSharpMapSerializer.fs | 2 +- .../FSharp.MongoDB.Bson.Tests.fsproj | 1 + .../FSharpNRTSerializationTests.fs | 62 +++++++++++++++++++ 6 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs create mode 100644 tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs diff --git a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj index b8c9467..a90a299 100644 --- a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj +++ b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj @@ -1,7 +1,7 @@  - netstandard2.1 + net9.0 true enable true @@ -12,6 +12,7 @@ + @@ -26,7 +27,8 @@ - + + diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs new file mode 100644 index 0000000..8f7fab7 --- /dev/null +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs @@ -0,0 +1,34 @@ +namespace MongoDB.Bson.Serialization.Conventions + +open MongoDB.Bson.Serialization.Conventions +open System.Reflection + +/// +/// Convention for F# option/voption types that writes the value in the Some case and omits the field +/// in the None case. +/// +type NullableReferenceTypeConvention() = + inherit ConventionBase() + + static let nrtContext = NullabilityInfoContext() + + interface IMemberMapConvention with + member _.Apply memberMap = + printfn $"Considering nullability for {memberMap.ElementName} for {memberMap.MemberInfo.DeclaringType}" + match memberMap.MemberInfo with + | :? PropertyInfo as propInfo -> + let nrtInfo = nrtContext.Create(propInfo) + printfn $"Info = {nrtInfo.WriteState}" + if nrtInfo.WriteState = NullabilityState.Unknown then + printfn $"Enabling null" + memberMap.SetDefaultValue(null) |> ignore + else + printfn $"Not null" + | :? FieldInfo as fieldInfo -> + let nrtInfo = nrtContext.Create(fieldInfo) + if nrtInfo.WriteState = NullabilityState.Nullable then + printfn $"Enabling null" + memberMap.SetDefaultValue(null) |> ignore + else + printfn $"Not null" + | _ -> () diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs index 3aae692..846c225 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs @@ -71,5 +71,8 @@ module FSharp = UnionCaseConvention() |> addConvention "__fsharp_union_type__" (|IsUnion|_|) + NullableReferenceTypeConvention() + |> addConvention "__fsharp_nullable_reference_type__" (Some) + FSharpValueSerializationProvider() |> BsonSerializer.RegisterSerializationProvider diff --git a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpMapSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpMapSerializer.fs index 58d4009..43ab319 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpMapSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpMapSerializer.fs @@ -21,7 +21,7 @@ open MongoDB.Bson.Serialization.Serializers /// /// Serializer for F# maps. /// -type FSharpMapSerializer<'KeyType, 'ValueType when 'KeyType : comparison>() = +type FSharpMapSerializer<'KeyType, 'ValueType when 'KeyType : comparison and 'KeyType: not null>() = inherit SerializerBase>() let serializer = DictionaryInterfaceImplementerSerializer>() diff --git a/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj b/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj index 6e07e2c..f8fa929 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj +++ b/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj @@ -13,6 +13,7 @@ + diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs new file mode 100644 index 0000000..301d953 --- /dev/null +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs @@ -0,0 +1,62 @@ +(* Copyright (c) 2015 MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +namespace FSharp.MongoDB.Bson.Tests.Serialization + +open MongoDB.Bson +open FsUnit +open NUnit.Framework + +module FSharpNRTSerialization = + + type Primitive = + { String : string | null } + + [] + let ``test serialize nullable reference (null) in a record type``() = + let value = { String = null } + + let result = serialize value + let expected = BsonDocument([ BsonElement("String", BsonNull.Value) ]) + + result |> should equal expected + + [] + let ``test deserialize nullable reference (null) in a record type)``() = + // let doc = BsonDocument([ BsonElement("String", BsonNull.Value) ]) + let doc = BsonDocument() + + let result = deserialize doc + let expected = { String = null } + + result |> should equal expected + + [] + let ``test serialize nullable reference (some) in a record type``() = + let value = { String = "A String" } + + let result = serialize value + let expected = BsonDocument([ BsonElement("String", BsonString "A String") ]) + + result |> should equal expected + + [] + let ``test deserialize nullable reference (some) in a record type``() = + let doc = BsonDocument([ BsonElement("String", BsonString "A String") ]) + + let result = deserialize doc + let expected = { String = "A String" } + + result |> should equal expected From 79b0c8479b73c817f50a50451a537f5000a6b509 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Sun, 29 Dec 2024 17:48:50 +0100 Subject: [PATCH 02/16] stash --- .../FSharp.MongoDB.Bson.fsproj | 3 +-- .../Conventions/FSharpRecordConvention.fs | 16 +++++++++++++++- .../NullableReferenceTypeConvention.fs | 2 +- .../Serialization/FSharpValueSerializer.fs | 2 +- .../Serialization/FSharpNRTSerializationTests.fs | 3 +-- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj index 04cfb1d..6c989ca 100644 --- a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj +++ b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj @@ -1,8 +1,7 @@  - net9.0 - net9.0;netstandard2.1 + net9.0 true enable true diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs index 60f228c..8876621 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs @@ -18,6 +18,7 @@ namespace MongoDB.Bson.Serialization.Conventions open Microsoft.FSharp.Reflection open MongoDB.Bson.Serialization.Conventions open MongoDB.Bson.Serialization.Helpers +open System.Reflection /// /// Convention for F# record types that initializes a BsonClassMap by mapping the record @@ -26,6 +27,10 @@ open MongoDB.Bson.Serialization.Helpers type FSharpRecordConvention() = inherit ConventionBase() +#if NET8_0_OR_GREATER + // let nrtContext = NullabilityInfoContext() +#endif + interface IClassMapConvention with member _.Apply classMap = match classMap.ClassType with @@ -38,5 +43,14 @@ type FSharpRecordConvention() = classMap.MapConstructor(ctor, names) |> ignore // Map each field of the record type. - fields |> Array.iter (classMap.MapMember >> ignore) + fields |> Array.iter (fun pi -> + let memberMap = classMap.MapMember(pi) + // memberMap.SetDefaultValue(fun () -> null) |> ignore +#if NET8_0_OR_GREATER + // let nrtInfo = nrtContext.Create(pi) + // if nrtInfo.WriteState = NullabilityState.Nullable then + // memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore +#endif + () + ) | _ -> () diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs index 8f7fab7..2ca6e7f 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs @@ -19,7 +19,7 @@ type NullableReferenceTypeConvention() = | :? PropertyInfo as propInfo -> let nrtInfo = nrtContext.Create(propInfo) printfn $"Info = {nrtInfo.WriteState}" - if nrtInfo.WriteState = NullabilityState.Unknown then + if nrtInfo.WriteState = NullabilityState.Nullable then printfn $"Enabling null" memberMap.SetDefaultValue(null) |> ignore else diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs index 846c225..744b652 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs @@ -72,7 +72,7 @@ module FSharp = |> addConvention "__fsharp_union_type__" (|IsUnion|_|) NullableReferenceTypeConvention() - |> addConvention "__fsharp_nullable_reference_type__" (Some) + |> addConvention "__fsharp_nrt__" (Some) FSharpValueSerializationProvider() |> BsonSerializer.RegisterSerializationProvider diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs index 4720c17..1b40440 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs @@ -36,8 +36,7 @@ module FSharpNRTSerialization = let ``test deserialize nullable reference (null) in a record type)``() = // FIXME: this shall support deserializing missing null value for NRT // as of now this means NRT can't be a missing value while deserializing - // let doc = BsonDocument() - let doc = BsonDocument([ BsonElement("String", BsonNull.Value) ]) + let doc = BsonDocument() let result = deserialize doc let expected = { String = null } From 22aecc9f684cc7c585f0fb687d2302a360cc6a6e Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 22:02:44 +0100 Subject: [PATCH 03/16] cleanup --- .../FSharp.MongoDB.Bson.fsproj | 3 +- .../Conventions/FSharpRecordConvention.fs | 9 ++--- .../NullableReferenceTypeConvention.fs | 34 ------------------ .../Serialization/FSharpValueSerializer.fs | 3 -- .../FSharpNRTSerializationTests.fs | 36 ++++++++++++------- 5 files changed, 27 insertions(+), 58 deletions(-) delete mode 100644 src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs diff --git a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj index 6c989ca..8345709 100644 --- a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj +++ b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj @@ -1,7 +1,7 @@  - net9.0 + net9.0;net8.0 true enable true @@ -12,7 +12,6 @@ - diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs index 8876621..5ddd1fa 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs @@ -27,9 +27,7 @@ open System.Reflection type FSharpRecordConvention() = inherit ConventionBase() -#if NET8_0_OR_GREATER - // let nrtContext = NullabilityInfoContext() -#endif + static let nrtContext = NullabilityInfoContext() interface IClassMapConvention with member _.Apply classMap = @@ -45,12 +43,9 @@ type FSharpRecordConvention() = // Map each field of the record type. fields |> Array.iter (fun pi -> let memberMap = classMap.MapMember(pi) - // memberMap.SetDefaultValue(fun () -> null) |> ignore -#if NET8_0_OR_GREATER + memberMap.SetDefaultValue(fun () -> null) |> ignore // let nrtInfo = nrtContext.Create(pi) // if nrtInfo.WriteState = NullabilityState.Nullable then // memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore -#endif - () ) | _ -> () diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs deleted file mode 100644 index 2ca6e7f..0000000 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/NullableReferenceTypeConvention.fs +++ /dev/null @@ -1,34 +0,0 @@ -namespace MongoDB.Bson.Serialization.Conventions - -open MongoDB.Bson.Serialization.Conventions -open System.Reflection - -/// -/// Convention for F# option/voption types that writes the value in the Some case and omits the field -/// in the None case. -/// -type NullableReferenceTypeConvention() = - inherit ConventionBase() - - static let nrtContext = NullabilityInfoContext() - - interface IMemberMapConvention with - member _.Apply memberMap = - printfn $"Considering nullability for {memberMap.ElementName} for {memberMap.MemberInfo.DeclaringType}" - match memberMap.MemberInfo with - | :? PropertyInfo as propInfo -> - let nrtInfo = nrtContext.Create(propInfo) - printfn $"Info = {nrtInfo.WriteState}" - if nrtInfo.WriteState = NullabilityState.Nullable then - printfn $"Enabling null" - memberMap.SetDefaultValue(null) |> ignore - else - printfn $"Not null" - | :? FieldInfo as fieldInfo -> - let nrtInfo = nrtContext.Create(fieldInfo) - if nrtInfo.WriteState = NullabilityState.Nullable then - printfn $"Enabling null" - memberMap.SetDefaultValue(null) |> ignore - else - printfn $"Not null" - | _ -> () diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs index 744b652..3aae692 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs @@ -71,8 +71,5 @@ module FSharp = UnionCaseConvention() |> addConvention "__fsharp_union_type__" (|IsUnion|_|) - NullableReferenceTypeConvention() - |> addConvention "__fsharp_nrt__" (Some) - FSharpValueSerializationProvider() |> BsonSerializer.RegisterSerializationProvider diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs index 1b40440..e770a21 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs @@ -21,42 +21,54 @@ open NUnit.Framework module FSharpNRTSerialization = type Primitive = - { String : string | null } + { String : string | null + Int: int } + + [] + type Primitive2 = + { String : string + Int: int } + [] let ``test serialize nullable reference (null) in a record type``() = - let value = { String = null } + let value = { String = null + Int = 42 } let result = serialize value - let expected = BsonDocument([ BsonElement("String", BsonNull.Value) ]) + let expected = BsonDocument([ BsonElement("String", BsonNull.Value) + BsonElement("Int", BsonInt32 42) ]) result |> should equal expected [] let ``test deserialize nullable reference (null) in a record type)``() = - // FIXME: this shall support deserializing missing null value for NRT - // as of now this means NRT can't be a missing value while deserializing - let doc = BsonDocument() + let doc = BsonDocument([ BsonElement("Int", BsonInt32 42) ]) - let result = deserialize doc - let expected = { String = null } + let result = deserialize doc + let expected = { String = null + Int = 42 } result |> should equal expected [] let ``test serialize nullable reference (some) in a record type``() = - let value = { String = "A String" } + let value = { String = "A String" + Int = 42 } let result = serialize value - let expected = BsonDocument([ BsonElement("String", BsonString "A String") ]) + let expected = BsonDocument([ BsonElement("String", BsonString "A String") + BsonElement("Int", BsonInt32 42) ]) result |> should equal expected [] let ``test deserialize nullable reference (some) in a record type``() = - let doc = BsonDocument([ BsonElement("String", BsonString "A String") ]) + let doc = BsonDocument([ BsonElement("String", BsonString "A String") + BsonElement("Int", BsonInt32 42) ]) let result = deserialize doc - let expected = { String = "A String" } + let expected = { String = "A String" + Int = 42 } result |> should equal expected From 8d673d8975229ba3e7e7336073b50c71a6112f22 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 22:12:00 +0100 Subject: [PATCH 04/16] add missing for non-null reference --- .../Conventions/FSharpRecordConvention.fs | 7 +++---- .../FSharpNRTSerializationTests.fs | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs index 5ddd1fa..5472385 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs @@ -43,9 +43,8 @@ type FSharpRecordConvention() = // Map each field of the record type. fields |> Array.iter (fun pi -> let memberMap = classMap.MapMember(pi) - memberMap.SetDefaultValue(fun () -> null) |> ignore - // let nrtInfo = nrtContext.Create(pi) - // if nrtInfo.WriteState = NullabilityState.Nullable then - // memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore + let nrtInfo = nrtContext.Create(pi) + if nrtInfo.WriteState = NullabilityState.Nullable then + memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore ) | _ -> () diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs index e770a21..a4f118b 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs @@ -19,14 +19,12 @@ open FsUnit open NUnit.Framework module FSharpNRTSerialization = - type Primitive = { String : string | null Int: int } - [] - type Primitive2 = - { String : string + type PrimitiveNoNull = + { StringNotNull : string Int: int } @@ -43,9 +41,11 @@ module FSharpNRTSerialization = [] let ``test deserialize nullable reference (null) in a record type)``() = - let doc = BsonDocument([ BsonElement("Int", BsonInt32 42) ]) + // FIXME: once .net 9.0.200 is released, String can be omitted + let doc = BsonDocument([ BsonElement("String", BsonNull.Value) + BsonElement("Int", BsonInt32 42) ]) - let result = deserialize doc + let result = deserialize doc let expected = { String = null Int = 42 } @@ -72,3 +72,10 @@ module FSharpNRTSerialization = Int = 42 } result |> should equal expected + + [] + let ``test deserialize with missing non-null reference shall fail``() = + let doc = BsonDocument([ BsonElement("Int", BsonInt32 42) ]) + + (fun () -> deserialize doc |> ignore) + |> should throw typeof From 5d45b504d99db38eb0c175eb3ae1cf82ecf11079 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 22:14:09 +0100 Subject: [PATCH 05/16] upgrade .net sdk --- .github/workflows/on-push-branch.yml | 2 +- .github/workflows/on-push-tag.yml | 2 +- .github/workflows/on-release-published.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/on-push-branch.yml b/.github/workflows/on-push-branch.yml index 0283d2e..324d3c7 100644 --- a/.github/workflows/on-push-branch.yml +++ b/.github/workflows/on-push-branch.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.100 + dotnet-version: 9.0.101 - name: Build & Test run: make test config=Release diff --git a/.github/workflows/on-push-tag.yml b/.github/workflows/on-push-tag.yml index ad59115..d53a6ab 100644 --- a/.github/workflows/on-push-tag.yml +++ b/.github/workflows/on-push-tag.yml @@ -24,7 +24,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.100 + dotnet-version: 9.0.101 - name: Extract Version Suffix run: | diff --git a/.github/workflows/on-release-published.yml b/.github/workflows/on-release-published.yml index c58be85..471454b 100644 --- a/.github/workflows/on-release-published.yml +++ b/.github/workflows/on-release-published.yml @@ -13,7 +13,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.100 + dotnet-version: 9.0.101 - name: Download Release artifacts uses: robinraju/release-downloader@v1.11 From c7b84a48f67b888ee295d57d38df0a42f20b8fd6 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 22:15:50 +0100 Subject: [PATCH 06/16] fix mongodb driver reference --- src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj index 8345709..e617507 100644 --- a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj +++ b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj @@ -26,8 +26,8 @@ - - + + From df62bbc74b04a36a22fd3ee1e2b2361b19a7b92c Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 22:33:55 +0100 Subject: [PATCH 07/16] add NRT on union types --- .../Conventions/FSharpRecordConvention.fs | 3 +- .../Conventions/UnionCaseConvention.fs | 9 +++- .../FSharpNRTSerializationTests.fs | 44 +++++++++++++++++-- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs index 5472385..3d4b007 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs @@ -45,6 +45,5 @@ type FSharpRecordConvention() = let memberMap = classMap.MapMember(pi) let nrtInfo = nrtContext.Create(pi) if nrtInfo.WriteState = NullabilityState.Nullable then - memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore - ) + memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore) | _ -> () diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs index fbd1112..f7d79b0 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs @@ -30,6 +30,8 @@ open MongoDB.Bson.Serialization.Helpers type UnionCaseConvention() = inherit ConventionBase() + static let nrtContext = NullabilityInfoContext() + let tryGetUnionCase (typ:System.Type) = // 8.5.4. Compiled Form of Union Types for Use from Other CLI Languages // A compiled union type U has [o]ne CLI nested type U.C for each non-null union case C. @@ -76,7 +78,12 @@ type UnionCaseConvention() = classMap.MapCreator(del, names) |> ignore // Map each field of the union case. - fields |> Array.iter (classMap.MapMember >> ignore) + // Map each field of the record type. + fields |> Array.iter (fun pi -> + let memberMap = classMap.MapMember(pi) + let nrtInfo = nrtContext.Create(pi) + if nrtInfo.WriteState = NullabilityState.Nullable then + memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore) interface IClassMapConvention with member _.Apply classMap = diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs index a4f118b..142a44b 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs @@ -19,22 +19,39 @@ open FsUnit open NUnit.Framework module FSharpNRTSerialization = + + type UnionCase = + | Tuple of Int:int * String:(string | null) + + type UnionCaseNotNull = + | TupleNotNull of Int:int * String:string + type Primitive = - { String : string | null + { String : string | null + UnionCase: UnionCase Int: int } - type PrimitiveNoNull = + type RecordNoNull = { StringNotNull : string Int: int } + type UnionCaseNoNull = + { UnionCaseNotNull : UnionCaseNotNull + Int: int } + [] let ``test serialize nullable reference (null) in a record type``() = let value = { String = null + UnionCase = Tuple(Int = 42, String = null) Int = 42 } let result = serialize value + printfn $"{result}" let expected = BsonDocument([ BsonElement("String", BsonNull.Value) + BsonElement("UnionCase", BsonDocument([ BsonElement("_t", BsonString "Tuple") + BsonElement("Int", BsonInt32 42) + BsonElement("String", BsonNull.Value) ])) BsonElement("Int", BsonInt32 42) ]) result |> should equal expected @@ -43,10 +60,14 @@ module FSharpNRTSerialization = let ``test deserialize nullable reference (null) in a record type)``() = // FIXME: once .net 9.0.200 is released, String can be omitted let doc = BsonDocument([ BsonElement("String", BsonNull.Value) + BsonElement("UnionCase", BsonDocument([ BsonElement("_t", BsonString "Tuple") + BsonElement("Int", BsonInt32 42) + BsonElement("String", BsonNull.Value) ])) BsonElement("Int", BsonInt32 42) ]) let result = deserialize doc let expected = { String = null + UnionCase = Tuple(Int = 42, String = null) Int = 42 } result |> should equal expected @@ -54,10 +75,14 @@ module FSharpNRTSerialization = [] let ``test serialize nullable reference (some) in a record type``() = let value = { String = "A String" + UnionCase = Tuple(Int = 42, String = "Another String") Int = 42 } let result = serialize value let expected = BsonDocument([ BsonElement("String", BsonString "A String") + BsonElement("UnionCase", BsonDocument([ BsonElement("_t", BsonString "Tuple") + BsonElement("Int", BsonInt32 42) + BsonElement("String", BsonString "Another String") ])) BsonElement("Int", BsonInt32 42) ]) result |> should equal expected @@ -65,17 +90,28 @@ module FSharpNRTSerialization = [] let ``test deserialize nullable reference (some) in a record type``() = let doc = BsonDocument([ BsonElement("String", BsonString "A String") + BsonElement("UnionCase", BsonDocument([ BsonElement("_t", BsonString "Tuple") + BsonElement("Int", BsonInt32 42) + BsonElement("String", BsonString "Another String") ])) BsonElement("Int", BsonInt32 42) ]) let result = deserialize doc let expected = { String = "A String" + UnionCase = Tuple(Int = 42, String = "Another String") Int = 42 } result |> should equal expected [] - let ``test deserialize with missing non-null reference shall fail``() = + let ``test deserialize with missing non-null reference in record shall fail``() = + let doc = BsonDocument([ BsonElement("Int", BsonInt32 42) ]) + + (fun () -> deserialize doc |> ignore) + |> should throw typeof + + [] + let ``test deserialize with missing non-null reference in union case shall fail``() = let doc = BsonDocument([ BsonElement("Int", BsonInt32 42) ]) - (fun () -> deserialize doc |> ignore) + (fun () -> deserialize doc |> ignore) |> should throw typeof From 70f2be5b480e7eede977957157aa7e1f1c91a0c3 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 22:44:35 +0100 Subject: [PATCH 08/16] code refactoring --- .../Serialization/Conventions/FSharpRecordConvention.fs | 8 +------- .../Serialization/Conventions/UnionCaseConvention.fs | 9 +-------- .../Serialization/FSharpTypeHelpers.fs | 8 ++++++++ 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs index 3d4b007..12cff76 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs @@ -27,8 +27,6 @@ open System.Reflection type FSharpRecordConvention() = inherit ConventionBase() - static let nrtContext = NullabilityInfoContext() - interface IClassMapConvention with member _.Apply classMap = match classMap.ClassType with @@ -41,9 +39,5 @@ type FSharpRecordConvention() = classMap.MapConstructor(ctor, names) |> ignore // Map each field of the record type. - fields |> Array.iter (fun pi -> - let memberMap = classMap.MapMember(pi) - let nrtInfo = nrtContext.Create(pi) - if nrtInfo.WriteState = NullabilityState.Nullable then - memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore) + fields |> Array.iter (mkMemberNullable classMap) | _ -> () diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs index f7d79b0..4ab83a8 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs @@ -30,8 +30,6 @@ open MongoDB.Bson.Serialization.Helpers type UnionCaseConvention() = inherit ConventionBase() - static let nrtContext = NullabilityInfoContext() - let tryGetUnionCase (typ:System.Type) = // 8.5.4. Compiled Form of Union Types for Use from Other CLI Languages // A compiled union type U has [o]ne CLI nested type U.C for each non-null union case C. @@ -78,12 +76,7 @@ type UnionCaseConvention() = classMap.MapCreator(del, names) |> ignore // Map each field of the union case. - // Map each field of the record type. - fields |> Array.iter (fun pi -> - let memberMap = classMap.MapMember(pi) - let nrtInfo = nrtContext.Create(pi) - if nrtInfo.WriteState = NullabilityState.Nullable then - memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore) + fields |> Array.iter (mkMemberNullable classMap) interface IClassMapConvention with member _.Apply classMap = diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs index 09104f1..515a241 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs @@ -23,6 +23,8 @@ module private Helpers = let bindingFlags = BindingFlags.Public ||| BindingFlags.NonPublic + let nrtContext = NullabilityInfoContext() + /// /// Returns Some typ when pred typ returns true, and None when /// pred typ returns false. @@ -90,3 +92,9 @@ module private Helpers = /// Creates a generic type 'T using the generic arguments of typ. /// let mkGenericUsingDef<'T> (typ:System.Type) = typ.GetGenericArguments() |> mkGeneric<'T> + + let mkMemberNullable (memberMap: BsonClassMap) (propertyInfo: PropertyInfo) = + let memberMap = memberMap.MapMember(propertyInfo) + let nrtInfo = nrtContext.Create(propertyInfo) + if nrtInfo.WriteState = NullabilityState.Nullable then + memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore From eda3992bbdd3166e8fb6b4fef9564e6e04ab392e Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 22:58:44 +0100 Subject: [PATCH 09/16] add support for netstandard2.1 / net8 / net9 --- README.md | 8 ++++++-- src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj | 2 +- .../Serialization/FSharpTypeHelpers.fs | 6 ++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8b6463d..0e37cea 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ Repository origins are: ## Supported platforms -This project targets `netstandard2.1` ([compatible runtimes](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-1#select-net-standard-version)). +This project targets `.net 8`, `.net 9` and `netstandard2.1` ([compatible runtimes](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-1#select-net-standard-version)) + +:warning: NRT support starts with `.net sdk 9.0.200`. F# compiler in .net sdk 9.0.10x does not set correctly nullable attributes on F# types. NRT are not supported on `netstandard2.1`. ## Contributing * If you have a question about the library, then create an [issue][issues] with the `question` label. @@ -89,7 +91,9 @@ NRT are serialized as: * `null` if `Null` * `object` if `NonNull` object -:warning: As of now, NRT can't be considered `null` when deserializing if value is missing. +On deserialization, missing value is mapped to `null`. + +:warning: NRT support starts with .net sdk 9.0.200. F# compiler in .net sdk 9.0.10x does not set correctly nullable attributes on F# types. # License The contents of this library are made available under the [Apache License, Version 2.0][license]. diff --git a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj index e617507..820c4f8 100644 --- a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj +++ b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj @@ -1,7 +1,7 @@  - net9.0;net8.0 + net9.0;net8.0;netstandard2.1 true enable true diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs index 515a241..92a1d99 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs @@ -23,7 +23,9 @@ module private Helpers = let bindingFlags = BindingFlags.Public ||| BindingFlags.NonPublic +#if !NETSTANDARD2_1 let nrtContext = NullabilityInfoContext() +#endif /// /// Returns Some typ when pred typ returns true, and None when @@ -95,6 +97,10 @@ module private Helpers = let mkMemberNullable (memberMap: BsonClassMap) (propertyInfo: PropertyInfo) = let memberMap = memberMap.MapMember(propertyInfo) +#if !NETSTANDARD2_1 let nrtInfo = nrtContext.Create(propertyInfo) if nrtInfo.WriteState = NullabilityState.Nullable then memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore +#else + () +#endif From eef6c989d1fa588fcfcce124774bffb53f49f72b Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 23:01:41 +0100 Subject: [PATCH 10/16] remove useless SetIsRequired (default is false) --- src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs index 92a1d99..c6b7b6a 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs @@ -100,7 +100,7 @@ module private Helpers = #if !NETSTANDARD2_1 let nrtInfo = nrtContext.Create(propertyInfo) if nrtInfo.WriteState = NullabilityState.Nullable then - memberMap.SetDefaultValue(null).SetIsRequired(false) |> ignore + memberMap.SetDefaultValue(null) |> ignore #else () #endif From 99ab49ea096536556513d24f4c6b8ae8161c1fc3 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 23:10:09 +0100 Subject: [PATCH 11/16] remove debug log --- .../Serialization/FSharpNRTSerializationTests.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs index 142a44b..c682849 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpNRTSerializationTests.fs @@ -47,7 +47,6 @@ module FSharpNRTSerialization = Int = 42 } let result = serialize value - printfn $"{result}" let expected = BsonDocument([ BsonElement("String", BsonNull.Value) BsonElement("UnionCase", BsonDocument([ BsonElement("_t", BsonString "Tuple") BsonElement("Int", BsonInt32 42) From 8741ae1e8a4ece56758bc3128f895c0b4762333f Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 1 Jan 2025 23:15:56 +0100 Subject: [PATCH 12/16] code cleanup --- .../Serialization/Conventions/FSharpRecordConvention.fs | 5 ++--- .../Serialization/Conventions/UnionCaseConvention.fs | 4 ++-- src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs | 5 ++++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs index 12cff76..dc1f900 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/FSharpRecordConvention.fs @@ -18,7 +18,6 @@ namespace MongoDB.Bson.Serialization.Conventions open Microsoft.FSharp.Reflection open MongoDB.Bson.Serialization.Conventions open MongoDB.Bson.Serialization.Helpers -open System.Reflection /// /// Convention for F# record types that initializes a BsonClassMap by mapping the record @@ -32,12 +31,12 @@ type FSharpRecordConvention() = match classMap.ClassType with | IsRecord typ -> let fields = FSharpType.GetRecordFields(typ, bindingFlags) - let names = fields |> Array.map (fun x -> x.Name) + let names = fields |> Array.map _.Name // Map the constructor of the record type. let ctor = FSharpValue.PreComputeRecordConstructorInfo(typ, bindingFlags) classMap.MapConstructor(ctor, names) |> ignore // Map each field of the record type. - fields |> Array.iter (mkMemberNullable classMap) + fields |> Array.iter (mapMemberNullable classMap) | _ -> () diff --git a/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs b/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs index 4ab83a8..fbc458d 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Conventions/UnionCaseConvention.fs @@ -65,7 +65,7 @@ type UnionCaseConvention() = let mapUnionCase (classMap:BsonClassMap) (unionCase:UnionCaseInfo) = let fields = unionCase.GetFields() - let names = fields |> Array.map (fun x -> x.Name) + let names = fields |> Array.map _.Name classMap.SetDiscriminator unionCase.Name classMap.SetDiscriminatorIsRequired true @@ -76,7 +76,7 @@ type UnionCaseConvention() = classMap.MapCreator(del, names) |> ignore // Map each field of the union case. - fields |> Array.iter (mkMemberNullable classMap) + fields |> Array.iter (mapMemberNullable classMap) interface IClassMapConvention with member _.Apply classMap = diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs index c6b7b6a..494f833 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs @@ -95,7 +95,10 @@ module private Helpers = /// let mkGenericUsingDef<'T> (typ:System.Type) = typ.GetGenericArguments() |> mkGeneric<'T> - let mkMemberNullable (memberMap: BsonClassMap) (propertyInfo: PropertyInfo) = + /// + /// Maps a member of a BsonClassMap to a nullable value if possible. + /// + let mapMemberNullable (memberMap: BsonClassMap) (propertyInfo: PropertyInfo) = let memberMap = memberMap.MapMember(propertyInfo) #if !NETSTANDARD2_1 let nrtInfo = nrtContext.Create(propertyInfo) From 92268eec94a6aaf587a13ad483372fdc4818e8d1 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Wed, 12 Feb 2025 23:01:59 +0100 Subject: [PATCH 13/16] adapt for .net 9.0.200 --- .github/workflows/on-push-branch.yml | 2 +- .github/workflows/on-push-tag.yml | 2 +- .github/workflows/on-release-published.yml | 2 +- .../Serialization/FSharpTypeHelpers.fs | 9 +-------- .../Serialization/FSharpValueSerializer.fs | 2 +- .../Serialization/Serializers/FSharpUnionSerializer.fs | 2 +- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/on-push-branch.yml b/.github/workflows/on-push-branch.yml index 324d3c7..3f81970 100644 --- a/.github/workflows/on-push-branch.yml +++ b/.github/workflows/on-push-branch.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.101 + dotnet-version: 9.0.200 - name: Build & Test run: make test config=Release diff --git a/.github/workflows/on-push-tag.yml b/.github/workflows/on-push-tag.yml index d53a6ab..8991174 100644 --- a/.github/workflows/on-push-tag.yml +++ b/.github/workflows/on-push-tag.yml @@ -24,7 +24,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.101 + dotnet-version: 9.0.200 - name: Extract Version Suffix run: | diff --git a/.github/workflows/on-release-published.yml b/.github/workflows/on-release-published.yml index 471454b..4055927 100644 --- a/.github/workflows/on-release-published.yml +++ b/.github/workflows/on-release-published.yml @@ -13,7 +13,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.101 + dotnet-version: 9.0.200 - name: Download Release artifacts uses: robinraju/release-downloader@v1.11 diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs index 494f833..d64b845 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs @@ -99,11 +99,4 @@ module private Helpers = /// Maps a member of a BsonClassMap to a nullable value if possible. /// let mapMemberNullable (memberMap: BsonClassMap) (propertyInfo: PropertyInfo) = - let memberMap = memberMap.MapMember(propertyInfo) -#if !NETSTANDARD2_1 - let nrtInfo = nrtContext.Create(propertyInfo) - if nrtInfo.WriteState = NullabilityState.Nullable then - memberMap.SetDefaultValue(null) |> ignore -#else - () -#endif + memberMap.MapMember(propertyInfo) |> ignore diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs index 3aae692..6fed8af 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpValueSerializer.fs @@ -33,7 +33,7 @@ module FSharp = member _.GetSerializer typ = let mkSerializer typ = typ - |> Option.map (fun typ -> System.Activator.CreateInstance typ :?> IBsonSerializer) + |> Option.map (fun typ -> System.Activator.CreateInstance typ |> nonNull :?> IBsonSerializer) |> Option.toObj match typ with diff --git a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpUnionSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpUnionSerializer.fs index 41ba739..ab0c60e 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpUnionSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpUnionSerializer.fs @@ -38,7 +38,7 @@ type FSharpUnionSerializer<'T>() = let mkClassMapSerializer (caseType:System.Type) = let classMap = BsonClassMap.LookupClassMap caseType let serializerType = typedefof>.MakeGenericType [| caseType |] - System.Activator.CreateInstance(serializerType, classMap) :?> IBsonSerializer + System.Activator.CreateInstance(serializerType, classMap) |> nonNull :?> IBsonSerializer // 8.5.4. Compiled Form of Union Types for Use from Other CLI Languages // A compiled union type U has [o]ne CLI nested type U.C for each non-null union case C. From 8cf1d8ec4d0170cf0c6bb62a020c35f127ecf883 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Sun, 25 May 2025 20:31:07 +0200 Subject: [PATCH 14/16] upgrade to .net 9.0.203 --- .github/workflows/on-push-branch.yml | 2 +- .github/workflows/on-push-tag.yml | 2 +- .github/workflows/on-release-published.yml | 2 +- Makefile | 3 + global.json | 5 + .../FSharp.MongoDB.Bson.fsproj | 5 +- .../Serialization/FSharpTypeHelpers.fs | 11 +- .../Serializers/FSharpOptionSerializer.fs | 2 +- .../CSharpDataModels/CSharpDataModels.csproj | 4 +- tests/CSharpDataModels/DataModels.cs | 38 ++--- .../FSharp.MongoDB.Bson.Tests.fsproj | 15 +- .../Isomorphic/IsomorphicTests.fs | 155 ++++++++++-------- 12 files changed, 135 insertions(+), 109 deletions(-) create mode 100644 global.json diff --git a/.github/workflows/on-push-branch.yml b/.github/workflows/on-push-branch.yml index 3f81970..ff664b3 100644 --- a/.github/workflows/on-push-branch.yml +++ b/.github/workflows/on-push-branch.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.200 + dotnet-version: 9.0.203 - name: Build & Test run: make test config=Release diff --git a/.github/workflows/on-push-tag.yml b/.github/workflows/on-push-tag.yml index 8991174..06113c0 100644 --- a/.github/workflows/on-push-tag.yml +++ b/.github/workflows/on-push-tag.yml @@ -24,7 +24,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.200 + dotnet-version: 9.0.203 - name: Extract Version Suffix run: | diff --git a/.github/workflows/on-release-published.yml b/.github/workflows/on-release-published.yml index 4055927..d44a377 100644 --- a/.github/workflows/on-release-published.yml +++ b/.github/workflows/on-release-published.yml @@ -13,7 +13,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.2.0 with: - dotnet-version: 9.0.200 + dotnet-version: 9.0.203 - name: Download Release artifacts uses: robinraju/release-downloader@v1.11 diff --git a/Makefile b/Makefile index 159f988..ef22016 100644 --- a/Makefile +++ b/Makefile @@ -10,3 +10,6 @@ test: nuget: dotnet pack -c $(config) -p:Version=$(version) -o .out + +upgrade: + dotnet restore --force-evaluate diff --git a/global.json b/global.json new file mode 100644 index 0000000..79790f9 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "9.0.203" + } + } \ No newline at end of file diff --git a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj index 820c4f8..13e2a40 100644 --- a/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj +++ b/src/FSharp.MongoDB.Bson/FSharp.MongoDB.Bson.fsproj @@ -1,4 +1,4 @@ - + net9.0;net8.0;netstandard2.1 @@ -26,8 +26,7 @@ - - + diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs index d64b845..034e5cb 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs @@ -98,5 +98,12 @@ module private Helpers = /// /// Maps a member of a BsonClassMap to a nullable value if possible. /// - let mapMemberNullable (memberMap: BsonClassMap) (propertyInfo: PropertyInfo) = - memberMap.MapMember(propertyInfo) |> ignore + let mapMemberNullable (classMap: BsonClassMap) (propertyInfo: PropertyInfo) = + let memberMap = classMap.MapMember(propertyInfo) +#if !NETSTANDARD2_1 + let nrtInfo = nrtContext.Create(propertyInfo) + if nrtInfo.WriteState = NullabilityState.Nullable then + memberMap.SetDefaultValue(objnull) |> ignore +#else + () +#endif diff --git a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpOptionSerializer.fs b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpOptionSerializer.fs index 4a8752e..ad1a836 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpOptionSerializer.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/Serializers/FSharpOptionSerializer.fs @@ -40,4 +40,4 @@ type FSharpOptionSerializer<'T when 'T: not null>() = match reader.GetCurrentBsonType() with | BsonType.Null -> reader.ReadNull(); None - | _ -> Some (serializer.Value.Deserialize(context, args)) + | t -> Some (serializer.Value.Deserialize(context, args)) diff --git a/tests/CSharpDataModels/CSharpDataModels.csproj b/tests/CSharpDataModels/CSharpDataModels.csproj index 4bd36d5..737ad2b 100644 --- a/tests/CSharpDataModels/CSharpDataModels.csproj +++ b/tests/CSharpDataModels/CSharpDataModels.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -8,7 +8,7 @@ - + diff --git a/tests/CSharpDataModels/DataModels.cs b/tests/CSharpDataModels/DataModels.cs index fe7f642..5cf93dd 100644 --- a/tests/CSharpDataModels/DataModels.cs +++ b/tests/CSharpDataModels/DataModels.cs @@ -18,25 +18,25 @@ public record PairValue(Pair Value) : Value; public record RecordDataModel { - public ObjectId Id { get; init; } - - public required int Int { get; init; } - public int? IntOpt { get; init; } - - public required string String { get; init; } - public string? StringOpt { get; init; } - - public required int[] Array { get; init; } - public int[]? ArrayOpt { get; init; } - - public required Value Value { get; init; } - public Value? ValueOpt { get; init; } - - public required Value[] ValueArray { get; init; } - public Value[]? ValueArrayOpt { get; init; } - - public required Pair Record { get; init; } + // public ObjectId Id { get; init; } + // + // public required int Int { get; init; } + // public int? IntOpt { get; init; } + // + // public required string String { get; init; } + // public string? StringOpt { get; init; } + // + // public required int[] Array { get; init; } + // public int[]? ArrayOpt { get; init; } + + // public required Value Value { get; init; } + // public Value? ValueOpt { get; init; } + + // public required Value[] ValueArray { get; init; } + // public Value[]? ValueArrayOpt { get; init; } + + // public required Pair Record { get; init; } public Pair? RecordOpt { get; init; } - public required Dictionary Map { get; init; } + // public required Dictionary Map { get; init; } } diff --git a/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj b/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj index 3795dcc..5c86b75 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj +++ b/tests/FSharp.MongoDB.Bson.Tests/FSharp.MongoDB.Bson.Tests.fsproj @@ -1,4 +1,4 @@ - + net9.0 @@ -20,20 +20,17 @@ - - - - - - - - + + + + + \ No newline at end of file diff --git a/tests/FSharp.MongoDB.Bson.Tests/Isomorphic/IsomorphicTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Isomorphic/IsomorphicTests.fs index bcc4d35..3fd0d0e 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Isomorphic/IsomorphicTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Isomorphic/IsomorphicTests.fs @@ -19,27 +19,29 @@ module IsomorphicSerialization = [] type RecordDataModel = - { Id: ObjectId + { + // Id: ObjectId - Int: int - IntOpt: int option + // Int: int + // IntOpt: int option + // + // String: string + // StringOpt: string option + // + // Array: int array + // ArrayOpt: int array option + // + // Value: Value + // ValueOpt: Value option - String: string - StringOpt: string option + // ValueArray: Value array + // ValueArrayOpt: Value array option - Array: int array - ArrayOpt: int array option - - Value: Value - ValueOpt: Value option - - ValueArray: Value array - ValueArrayOpt: Value array option - - Record: Pair + // Record: Pair RecordOpt: Pair option - Map: Map } + // Map: Map + } let ModelSome() = let csModel = @@ -50,38 +52,41 @@ module IsomorphicSerialization = map RecordDataModel( - Id = ObjectId.GenerateNewId(), - Int = 42, - IntOpt = 666, - String = "String", - StringOpt = "StringOpt", - Array = [| 1; 2; 3 |], - ArrayOpt = [| 5; 6; 7; 8 |], - Value = CSharpDataModels.Value.IntValue(42), - ValueOpt = CSharpDataModels.Value.StringValue("ValueStringOpt"), - ValueArray = [| CSharpDataModels.Value.IntValue(42) - CSharpDataModels.Value.StringValue("String") - CSharpDataModels.Value.PairValue(CSharpDataModels.Pair(First = 99, Second = "SecondPair")) |], - ValueArrayOpt = [| CSharpDataModels.Value.IntValue(101) |], - Record = CSharpDataModels.Pair(First = 1, Second = "Second"), - RecordOpt = CSharpDataModels.Pair(First = -1, Second = "SecondOpt"), - Map = map) + // Id = ObjectId.GenerateNewId(), + // Int = 42, + // IntOpt = 666, + // String = "String", + // StringOpt = "StringOpt", + // Array = [| 1; 2; 3 |], + // ArrayOpt = [| 5; 6; 7; 8 |], + // Value = CSharpDataModels.Value.IntValue(42), + // ValueOpt = CSharpDataModels.Value.StringValue("ValueStringOpt"), + // ValueArray = [| CSharpDataModels.Value.IntValue(42) + // CSharpDataModels.Value.StringValue("String") + // CSharpDataModels.Value.PairValue(CSharpDataModels.Pair(First = 99, Second = "SecondPair")) |], + // ValueArrayOpt = [| CSharpDataModels.Value.IntValue(101) |], + // Record = CSharpDataModels.Pair(First = 1, Second = "Second"), + RecordOpt = CSharpDataModels.Pair(First = -1, Second = "SecondOpt") + // Map = map + ) let fsModel = - { Id = csModel.Id - Int = 42 - IntOpt = Some 666 - String = "String" - StringOpt = Some "StringOpt" - Array = [| 1; 2; 3 |] - ArrayOpt = Some [| 5; 6; 7; 8 |] - Value = Value.IntValue 42 - ValueOpt = Some <| Value.StringValue "ValueStringOpt" - ValueArray = [| Value.IntValue 42; Value.StringValue "String"; Value.PairValue { First = 99; Second = Some "SecondPair" } |] - ValueArrayOpt = Some [| Value.IntValue 101 |] - Record = { First = 1; Second = Some "Second" } + { + // Id = csModel.Id + // Int = 42 + // IntOpt = Some 666 + // String = "String" + // StringOpt = Some "StringOpt" + // Array = [| 1; 2; 3 |] + // ArrayOpt = Some [| 5; 6; 7; 8 |] + // Value = Value.IntValue 42 + // ValueOpt = Some <| Value.StringValue "ValueStringOpt" + // ValueArray = [| Value.IntValue 42; Value.StringValue "String"; Value.PairValue { First = 99; Second = Some "SecondPair" } |] + // ValueArrayOpt = Some [| Value.IntValue 101 |] + // Record = { First = 1; Second = Some "Second" } RecordOpt = Some { First = -1; Second = Some "SecondOpt" } - Map = Map [ "1", 1; "2", 2 ] } + // Map = Map [ "1", 1; "2", 2 ] + } csModel, fsModel @@ -94,32 +99,35 @@ module IsomorphicSerialization = map RecordDataModel( - Id = ObjectId.GenerateNewId(), - Int = 42, - String = "String", - Array = [| 1; 2; 3 |], - Value = CSharpDataModels.Value.IntValue(42), - ValueArray = [| CSharpDataModels.Value.IntValue(42) - CSharpDataModels.Value.StringValue("String") - CSharpDataModels.Value.PairValue(CSharpDataModels.Pair(First = 99, Second = "SecondPair")) |], - Record = CSharpDataModels.Pair(First = 1, Second = "Second"), - Map = map) + // Id = ObjectId.GenerateNewId(), + // Int = 42, + // String = "String", + // Array = [| 1; 2; 3 |], + // Value = CSharpDataModels.Value.IntValue(42), + // ValueArray = [| CSharpDataModels.Value.IntValue(42) + // CSharpDataModels.Value.StringValue("String") + // CSharpDataModels.Value.PairValue(CSharpDataModels.Pair(First = 99, Second = "SecondPair")) |], + // Record = CSharpDataModels.Pair(First = 1, Second = "Second"), + // Map = map + ) let fsModel = - { Id = csModel.Id - Int = 42 - IntOpt = None - String = "String" - StringOpt = None - Array = [| 1; 2; 3 |] - ArrayOpt = None - Value = Value.IntValue 42 - ValueOpt = None - ValueArray = [| Value.IntValue 42; Value.StringValue "String"; Value.PairValue { First = 99; Second = Some "SecondPair" } |] - ValueArrayOpt = None - Record = { First = 1; Second = Some "Second" } + { + // Id = csModel.Id + // Int = 42 + // IntOpt = None + // String = "String" + // StringOpt = None + // Array = [| 1; 2; 3 |] + // ArrayOpt = None + // Value = Value.IntValue 42 + // ValueOpt = None + // ValueArray = [| Value.IntValue 42; Value.StringValue "String"; Value.PairValue { First = 99; Second = Some "SecondPair" } |] + // ValueArrayOpt = None + // Record = { First = 1; Second = Some "Second" } RecordOpt = None - Map = Map [ "1", 1; "2", 2 ] } + // Map = Map [ "1", 1; "2", 2 ] + } csModel, fsModel @@ -127,16 +135,23 @@ module IsomorphicSerialization = let ``Isomorphic Bson Some cs / fs``() = let csModel, fsModel = ModelSome() + // serialization shall be same let csDoc = serialize csModel let fsDoc = serialize fsModel - csDoc |> should equal fsDoc + // cross-deserialization shall work + let fsModel2 = deserialize csDoc + fsModel2 |> should equal fsModel + [] let ``Isomorphic Bson None cs / fs``() = let csModel, fsModel = ModelNone() let csDoc = serialize csModel let fsDoc = serialize fsModel - csDoc |> should equal fsDoc + + // cross-deserialization shall work + let fsModel2 = deserialize csDoc + fsModel2 |> should equal fsModel From 7ee1590e88f04fce1f5a9e49e5d9783c8c9a3d93 Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Sun, 25 May 2025 21:57:47 +0200 Subject: [PATCH 15/16] add option for record --- .../FSharpOptionSerializationTests.fs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpOptionSerializationTests.fs b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpOptionSerializationTests.fs index 0e76e8c..f6097f0 100644 --- a/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpOptionSerializationTests.fs +++ b/tests/FSharp.MongoDB.Bson.Tests/Serialization/FSharpOptionSerializationTests.fs @@ -21,24 +21,30 @@ open NUnit.Framework module FSharpOptionSerialization = + type Record = + { Value: string } + type Primitive = { Bool : bool option Int : int option String : string option - Float : float option } + Float : float option + Record: Record option } [] let ``test serialize optional primitives (none) in a record type``() = let value = { Bool = None Int = None String = None - Float = None } + Float = None + Record = None } let result = serialize value let expected = BsonDocument([ BsonElement("Bool", BsonNull.Value) BsonElement("Int", BsonNull.Value) BsonElement("String", BsonNull.Value) - BsonElement("Float", BsonNull.Value) ]) + BsonElement("Float", BsonNull.Value) + BsonElement("Record", BsonNull.Value) ]) result |> should equal expected @@ -50,7 +56,8 @@ module FSharpOptionSerialization = let expected = { Bool = None Int = None String = None - Float = None } + Float = None + Record = None } result |> should equal expected @@ -59,13 +66,15 @@ module FSharpOptionSerialization = let value = { Bool = Some false Int = Some 0 String = Some "0.0" - Float = Some 0.0 } + Float = Some 0.0 + Record = Some { Value = "value" } } let result = serialize value let expected = BsonDocument([ BsonElement("Bool", BsonBoolean false) BsonElement("Int", BsonInt32 0) BsonElement("String", BsonString "0.0") - BsonElement("Float", BsonDouble 0.0) ]) + BsonElement("Float", BsonDouble 0.0) + BsonElement("Record", BsonDocument([ BsonElement("Value", "value") ])) ]) result |> should equal expected @@ -74,12 +83,14 @@ module FSharpOptionSerialization = let doc = BsonDocument([ BsonElement("Bool", BsonBoolean true) BsonElement("Int", BsonInt32 1) BsonElement("String", BsonString "1.0") - BsonElement("Float", BsonDouble 1.0) ]) + BsonElement("Float", BsonDouble 1.0) + BsonElement("Record", BsonDocument([ BsonElement("Value", "value") ])) ]) let result = deserialize doc let expected = { Bool = Some true Int = Some 1 String = Some "1.0" - Float = Some 1.0 } + Float = Some 1.0 + Record = Some { Value = "value" } } result |> should equal expected From 32eebf9e677ae6f733110b091e7418eec7edcc6e Mon Sep 17 00:00:00 2001 From: Pierre Chalamet Date: Sun, 25 May 2025 23:45:03 +0200 Subject: [PATCH 16/16] fix nullability check --- src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs index 034e5cb..2aa8c57 100644 --- a/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs +++ b/src/FSharp.MongoDB.Bson/Serialization/FSharpTypeHelpers.fs @@ -102,8 +102,8 @@ module private Helpers = let memberMap = classMap.MapMember(propertyInfo) #if !NETSTANDARD2_1 let nrtInfo = nrtContext.Create(propertyInfo) - if nrtInfo.WriteState = NullabilityState.Nullable then + if nrtInfo.ReadState = NullabilityState.Nullable then memberMap.SetDefaultValue(objnull) |> ignore #else - () + memberMap.SetDefaultValue(objnull) |> ignore #endif