Skip to content

Commit e0c9ac2

Browse files
committed
feat: #26 check for nullness
1 parent dadd054 commit e0c9ac2

File tree

4 files changed

+92
-5
lines changed

4 files changed

+92
-5
lines changed

src/Diffract/DiffPrinter.fs

+16-4
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,27 @@ let toStreamImpl (w: TextWriter) param (d: Diff) =
1111
let indentLike str = String.replicate (String.length str) " "
1212
let displayPath path = if path = "" then param.neutralName else path
1313

14-
let printValue indent path x1 x2 =
14+
let printValue indent path (x1: obj) (x2: obj) =
1515
let dpath = if path = "" then "" else path + " "
16-
w.WriteLine($"%s{indent}%s{dpath}%s{param.x1Name} = %A{x1}")
17-
w.WriteLine($"%s{indent}%s{indentLike dpath}%s{param.x2Name} = %A{x2}")
18-
16+
if isNull x1 then
17+
w.WriteLine($"%s{indent}%s{dpath}%s{param.x1Name} is null")
18+
w.WriteLine($"%s{indent}%s{indentLike dpath}%s{param.x2Name} = %A{x2}")
19+
elif isNull x2 then
20+
w.WriteLine($"%s{indent}%s{dpath}%s{param.x1Name} = %A{x1}")
21+
w.WriteLine($"%s{indent}%s{indentLike dpath}%s{param.x2Name} is null")
22+
else
23+
w.WriteLine($"%s{indent}%s{dpath}%s{param.x1Name} = %A{x1}")
24+
w.WriteLine($"%s{indent}%s{indentLike dpath}%s{param.x2Name} = %A{x2}")
25+
1926
let rec loop (indent: string) (path: string) (d: Diff) =
2027
match d with
2128
| Diff.Value (x1, x2) ->
2229
printValue indent path x1 x2
30+
| Diff.Nullness (x1, x2) ->
31+
let dpath = if path = "" then "" else path + " "
32+
let dindent = indentLike dpath
33+
w.WriteLine($"""%s{indent}%s{dpath}%s{param.x1Name} is%s{if isNull x1 then "" else " not"} null""")
34+
w.WriteLine($"""%s{indent}%s{dindent}%s{param.x2Name} is%s{if isNull x2 then "" else " not"} null""")
2335
| Diff.Record fields when fields.Count = 1 ->
2436
loop indent (addPathField path fields.[0].Name) fields.[0].Diff
2537
| Diff.Record fields ->

src/Diffract/Differ.fs

+12-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ module DifferImpl =
1313
type private Cache = Dictionary<Type, IDifferFactory>
1414
type private CachedDiffer<'T> = Cache -> IDiffer<'T>
1515

16+
let private checkNull<'T> (differ: IDiffer<'T>) =
17+
if typeof<'T>.IsValueType then
18+
differ
19+
else
20+
{ new IDiffer<'T> with
21+
member _.Diff(x1, x2) =
22+
match isNull (box x1), isNull (box x2) with
23+
| false, false -> differ.Diff(x1, x2)
24+
| true, true -> None
25+
| _ -> Some (Nullness(x1, x2)) }
26+
1627
/// Add to the cache a differ for a type that may be recursive (ie have nested fields of its own type).
1728
let private addRecursiveToCache (differ: CachedDiffer<'T>) : CachedDiffer<'T> =
1829
let ty = typeof<'T>
@@ -25,7 +36,7 @@ module DifferImpl =
2536
{ new IDiffer<'U> with
2637
member _.Diff(x1, x2) =
2738
(unbox<IDiffer<'U>> !r).Diff(x1, x2) } })
28-
let differ = differ cache
39+
let differ = differ cache |> checkNull
2940
r := differ
3041
differ
3142
| true, differFactory ->

src/Diffract/Types.fs

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ open TypeShape.Core
99
type Diff =
1010
/// The objects are leaf values and are different.
1111
| Value of x1: obj * x2: obj
12+
/// One of the objects is null and the other isn't.
13+
| Nullness of x1: obj * x2: obj
1214
/// The objects are records or plain objects and some of their fields differ.
1315
| Record of fields: IReadOnlyList<FieldDiff>
1416
/// The objects are F# unions with different cases.

tests/Diffract.Tests/Tests.fs

+62
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ type U =
1313
| U2 of x: int * y: int
1414
type Bar = { aaa: Foo; bbb: U }
1515

16+
type Baz = { xx: int; yy: string }
17+
18+
[<AllowNullLiteral>]
19+
type Class(x: int) =
20+
member _.X = x
21+
1622
let assertStr (expected: string, actual: string) =
1723
Assert.Equal(expected.Replace("\r\n", "\n"), actual)
1824

@@ -27,6 +33,62 @@ let ``Exception for non-equal values`` (x: Bar) (y: Bar) =
2733
Differ.Assert(x, y))
2834
|> ignore
2935

36+
[<Fact>]
37+
let ``Both have null leaf`` () =
38+
let actual = Differ.Diff({ xx = 1; yy = null }, { xx = 1; yy = null })
39+
Assert.Equal(None, actual)
40+
41+
[<Fact>]
42+
let ``Expected has null leaf`` () =
43+
let actual = Differ.Diff({ xx = 1; yy = null }, { xx = 1; yy = "a" })
44+
Assert.Equal(Some(Diff.Record [ { Name = "yy"; Diff = Diff.Value(null, "a") } ]), actual)
45+
46+
let actual = Differ.ToString({ xx = 1; yy = null }, { xx = 1; yy = "a" })
47+
assertStr("\
48+
yy Expect is null
49+
Actual = \"a\"
50+
", actual)
51+
52+
[<Fact>]
53+
let ``Actual has null leaf`` () =
54+
let actual = Differ.Diff({ xx = 1; yy = "a" }, { xx = 1; yy = null })
55+
Assert.Equal(Some(Diff.Record [ { Name = "yy"; Diff = Diff.Value("a", null) } ]), actual)
56+
57+
let actual = Differ.ToString({ xx = 1; yy = "a" }, { xx = 1; yy = null })
58+
assertStr("\
59+
yy Expect = \"a\"
60+
Actual is null
61+
", actual)
62+
63+
[<Fact>]
64+
let ``Both are null`` () =
65+
let actual = Differ.Diff((null: Class), null)
66+
Assert.Equal(None, actual)
67+
68+
[<Fact>]
69+
let ``Expected is null`` () =
70+
let actualValue = Class(3)
71+
let actual = Differ.Diff(null, actualValue)
72+
Assert.Equal(Some(Diff.Nullness(null, actualValue)), actual)
73+
74+
let actual = Differ.ToString(null, actualValue)
75+
assertStr("\
76+
Expect is null
77+
Actual is not null
78+
", actual)
79+
80+
[<Fact>]
81+
let ``Actual is null`` () =
82+
let actualValue = Class(3)
83+
let actual = Differ.Diff(actualValue, null)
84+
Assert.Equal(Some(Diff.Nullness(actualValue, null)), actual)
85+
86+
let actual = Differ.ToString(actualValue, null)
87+
assertStr("\
88+
Expect is not null
89+
Actual is null
90+
", actual)
91+
3092
[<Property>]
3193
let ``List diff`` (l1: int list) (l2: int list) =
3294
let d = Differ.Diff(l1, l2)

0 commit comments

Comments
 (0)