Skip to content

Commit d2e713d

Browse files
authored
Remove [<AllowNullLiteral>] attribute from Json.ISerializer and Xml.ISerializer (#685)
* Remove [<AllowNullLiteral>] attribute from Json.ISerializer * Remove [<AllowNullLiteral>] attribute from Xml.ISerializer * Tests: add more Json and Xml tests * Add json route to the EndpointRoutingApp sample project, and update it to .NET 9 * Update the NewtonsoftJson sample to .NET 9, enable the checknulls verification and fix the code to deal with it
1 parent 4367647 commit d2e713d

9 files changed

Lines changed: 761 additions & 19 deletions

File tree

samples/EndpointRoutingApp/EndpointRoutingApp.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net8.0</TargetFramework>
5+
<TargetFramework>net9.0</TargetFramework>
66
</PropertyGroup>
77

88
<ItemGroup>

samples/EndpointRoutingApp/Program.fs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ open Microsoft.Extensions.Hosting
88
open Giraffe
99
open Giraffe.EndpointRouting
1010

11+
type Car =
12+
{
13+
Brand: string
14+
Color: string
15+
ReleaseYear: int
16+
}
17+
1118
let handler1: HttpHandler =
1219
fun (_: HttpFunc) (ctx: HttpContext) -> ctx.WriteTextAsync "Hello World"
1320

@@ -21,6 +28,23 @@ let handler3 (a: string, b: string, c: string, d: int) : HttpHandler =
2128
let handlerNamed (petId: int) : HttpHandler =
2229
fun (_: HttpFunc) (ctx: HttpContext) -> sprintf "PetId: %i" petId |> ctx.WriteTextAsync
2330

31+
/// Example request:
32+
///
33+
/// ```bash
34+
/// curl -v localhost:5000/json -X Post -d '{"brand":"Ford", "color":"Black", "releaseYear":2015}'
35+
/// ```
36+
let jsonHandler: HttpHandler =
37+
fun (next: HttpFunc) (ctx: HttpContext) ->
38+
task {
39+
match! ctx.BindJsonAsync<Car>() with
40+
| {
41+
Brand = _brand
42+
Color = _color
43+
ReleaseYear = releaseYear
44+
} when releaseYear >= 1990 -> return! json {| Message = "Valid car" |} next ctx
45+
| _ -> return! (setStatusCode 400 >=> json {| Message = "Invalid car year" |}) next ctx
46+
}
47+
2448
let endpoints =
2549
[
2650
subRoute "/foo" [ GET [ route "/bar" (text "Aloha!") ] ]
@@ -38,6 +62,7 @@ let endpoints =
3862
]
3963
// Not specifying a http verb means it will listen to all verbs
4064
subRoute "/sub" [ route "/test" handler1 ]
65+
POST [ route "/json" jsonHandler ]
4166
]
4267
4368
let notFoundHandler = "Not Found" |> text |> RequestErrors.notFound

samples/NewtonsoftJson/NewtonsoftJson.fsproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net8.0</TargetFramework>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<Nullable>enable</Nullable>
67
</PropertyGroup>
78

89
<ItemGroup>

samples/NewtonsoftJson/Program.fs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,30 @@ module NewtonsoftJson =
4040
}
4141
:> Task
4242

43-
member __.Deserialize<'T>(json: string) =
44-
JsonConvert.DeserializeObject<'T>(json, settings)
43+
member __.Deserialize<'T>(json: string) : 'T =
44+
match JsonConvert.DeserializeObject<'T>(json, settings) with
45+
| null -> failwith "Deserialized object is null"
46+
| (notNull: 'T) -> notNull
4547

46-
member __.Deserialize<'T>(bytes: byte array) =
48+
member __.Deserialize<'T>(bytes: byte array) : 'T =
4749
let json = Encoding.UTF8.GetString bytes
48-
JsonConvert.DeserializeObject<'T>(json, settings)
4950

50-
member __.DeserializeAsync<'T>(stream: Stream) =
51+
match JsonConvert.DeserializeObject<'T>(json, settings) with
52+
| null -> failwith "Deserialized object is null"
53+
| (notNull: 'T) -> notNull
54+
55+
member __.DeserializeAsync<'T>(stream: Stream) : Task<'T> =
5156
task {
5257
use memoryStream = rmsManager.GetStream("giraffe-json-deserialize")
5358
do! stream.CopyToAsync(memoryStream)
5459
memoryStream.Seek(0L, SeekOrigin.Begin) |> ignore
5560
use streamReader = new StreamReader(memoryStream)
5661
use jsonTextReader = new JsonTextReader(streamReader)
57-
return serializer.Deserialize<'T>(jsonTextReader)
62+
63+
return
64+
match serializer.Deserialize<'T>(jsonTextReader) with
65+
| null -> failwith "Deserialized object is null"
66+
| (notNull: 'T) -> notNull
5867
}
5968

6069
type JsonResponse = { Foo: string; Bar: string; Age: int }
@@ -67,23 +76,22 @@ let notFoundHandler = "Not Found" |> text |> RequestErrors.notFound
6776
let configureServices (services: IServiceCollection) =
6877
services
6978
.AddSingleton<Json.ISerializer>(fun serviceProvider ->
70-
NewtonsoftJson.Serializer(
71-
JsonSerializerSettings(),
79+
let rmsManager =
7280
serviceProvider.GetService<Microsoft.IO.RecyclableMemoryStreamManager>()
73-
)
74-
:> Json.ISerializer
81+
|> function
82+
| null -> Microsoft.IO.RecyclableMemoryStreamManager()
83+
| notNull -> notNull
84+
85+
NewtonsoftJson.Serializer(JsonSerializerSettings(), rmsManager) :> Json.ISerializer
7586
)
7687
.AddRouting()
7788
.AddResponseCaching()
7889
.AddGiraffe()
7990
|> ignore
8091

8192
let configureApp (appBuilder: IApplicationBuilder) =
82-
appBuilder
83-
.UseRouting()
84-
.UseResponseCaching()
85-
.UseEndpoints(fun e -> e.MapGiraffeEndpoints(endpoints))
86-
.UseGiraffe(notFoundHandler)
93+
appBuilder.UseRouting().UseResponseCaching().UseEndpoints(_.MapGiraffeEndpoints(endpoints)).UseGiraffe
94+
notFoundHandler
8795

8896
[<EntryPoint>]
8997
let main (args: string array) : int =

src/Giraffe/Json.fs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ module Json =
1212
/// Interface defining JSON serialization methods.
1313
/// Use this interface to customize JSON serialization in Giraffe.
1414
/// </summary>
15-
[<AllowNullLiteral>]
1615
type ISerializer =
1716
abstract member SerializeToString<'T> : 'T -> string
1817
abstract member SerializeToBytes<'T> : 'T -> byte array

src/Giraffe/Xml.fs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ module Xml =
66
/// Interface defining XML serialization methods.
77
/// Use this interface to customize XML serialization in Giraffe.
88
/// </summary>
9-
[<AllowNullLiteral>]
109
type ISerializer =
1110
abstract member Serialize: obj -> byte array
1211
abstract member Deserialize<'T> : string -> 'T

tests/Giraffe.Tests/Giraffe.Tests.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
<Compile Include="HttpContextExtensionsTests.fs" />
2121
<Compile Include="StreamingTests.fs" />
2222
<Compile Include="PreconditionalTests.fs" />
23+
<Compile Include="JsonTests.fs" />
24+
<Compile Include="XmlTests.fs" />
2325
</ItemGroup>
2426

2527
<ItemGroup>

0 commit comments

Comments
 (0)