Skip to content

Commit

Permalink
add NRT on union types
Browse files Browse the repository at this point in the history
  • Loading branch information
pchalamet committed Jan 1, 2025
1 parent c7b84a4 commit df62bbc
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
| _ -> ()
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }


[<Test>]
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
Expand All @@ -43,39 +60,58 @@ 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<Primitive> doc
let expected = { String = null
UnionCase = Tuple(Int = 42, String = null)
Int = 42 }

result |> should equal expected

[<Test>]
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

[<Test>]
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<Primitive> doc
let expected = { String = "A String"
UnionCase = Tuple(Int = 42, String = "Another String")
Int = 42 }

result |> should equal expected

[<Test>]
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<RecordNoNull> doc |> ignore)
|> should throw typeof<BsonSerializationException>

[<Test>]
let ``test deserialize with missing non-null reference in union case shall fail``() =
let doc = BsonDocument([ BsonElement("Int", BsonInt32 42) ])

(fun () -> deserialize<PrimitiveNoNull> doc |> ignore)
(fun () -> deserialize<UnionCaseNoNull> doc |> ignore)
|> should throw typeof<BsonSerializationException>

0 comments on commit df62bbc

Please sign in to comment.