Skip to content

Commit c30079d

Browse files
authored
Merge pull request #68 from fossa-app/add-problemdetails-errors
Add validation error details to ProblemDetailsModel
2 parents 1615319 + e31f180 commit c30079d

5 files changed

Lines changed: 72 additions & 7 deletions

File tree

src/Bridge/Models/ApiModels/SharedModels.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
namespace Fossa.Bridge.Models.ApiModels
22

33
open System
4+
open System.Collections.Generic
45

56
[<CLIMutable>]
67
type ProblemDetailsModel =
78
{ Type: string | null
89
Title: string | null
910
Status: int
1011
Detail: string | null
11-
Instance: string | null }
12+
Instance: string | null
13+
Errors: Dictionary<string, string array>
14+
TraceId: string | null }
1215

1316
[<CLIMutable>]
1417
type AddressModel =

tests/Bridge.Tests/ClientResultTests.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module ClientResultTests
22

3+
open System.Collections.Generic
34
open Expecto
45
open Fossa.Bridge.Models.ApiModels
56
open Fossa.Bridge.Models.ApiModels.Helpers
@@ -9,7 +10,9 @@ let private problem =
910
Title = "Conflict"
1011
Status = 409
1112
Detail = "The requested change conflicts with current state."
12-
Instance = "/companies/42" }
13+
Instance = "/companies/42"
14+
Errors = Unchecked.defaultof<Dictionary<string, string array>>
15+
TraceId = null }
1316

1417
[<Tests>]
1518
let tests =

tests/Bridge.Tests/ClientUnitResultTests.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module ClientUnitResultTests
22

3+
open System.Collections.Generic
34
open Expecto
45
open Fossa.Bridge.Models.ApiModels
56
open Fossa.Bridge.Models.ApiModels.Helpers
@@ -9,7 +10,9 @@ let private problem =
910
Title = "Conflict"
1011
Status = 409
1112
Detail = "The requested change conflicts with current state."
12-
Instance = "/companies/42" }
13+
Instance = "/companies/42"
14+
Errors = Unchecked.defaultof<Dictionary<string, string array>>
15+
TraceId = null }
1316

1417
[<Tests>]
1518
let tests =

tests/Bridge.Tests/JsonSerializerTests.fs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module JsonSerializerTests
22

33
open System
4+
open System.Collections.Generic
45
open Expecto
56
open Fossa.Bridge.Models.ApiModels
67
open Fossa.Bridge.Services
@@ -85,7 +86,9 @@ let tests =
8586
Title = "Conflict"
8687
Status = 409
8788
Detail = "The requested change conflicts with current state."
88-
Instance = "/companies/42" }
89+
Instance = "/companies/42"
90+
Errors = Unchecked.defaultof<Dictionary<string, string array>>
91+
TraceId = null }
8992

9093
let json = serializer.Serialize(model)
9194

@@ -94,6 +97,8 @@ let tests =
9497
Expect.isTrue (json.Contains("\"status\"")) "JSON should contain lowercase status"
9598
Expect.isTrue (json.Contains("\"detail\"")) "JSON should contain lowercase detail"
9699
Expect.isTrue (json.Contains("\"instance\"")) "JSON should contain lowercase instance"
100+
Expect.isTrue (json.Contains("\"errors\"")) "JSON should contain lowercase errors"
101+
Expect.isTrue (json.Contains("\"traceId\"")) "JSON should contain camelCase traceId"
97102
Expect.isFalse (json.Contains("\"Type\"")) "JSON should not contain PascalCase Type"
98103

99104
testCase "ProblemDetailsModel serializes null core fields"
@@ -105,15 +110,40 @@ let tests =
105110
Title = null
106111
Status = 404
107112
Detail = null
108-
Instance = null }
113+
Instance = null
114+
Errors = Unchecked.defaultof<Dictionary<string, string array>>
115+
TraceId = null }
109116

110117
let json = serializer.Serialize(model)
111118

112119
Expect.equal
113120
json
114-
"{\"type\":null,\"title\":null,\"status\":404,\"detail\":null,\"instance\":null}"
121+
"{\"type\":null,\"title\":null,\"status\":404,\"detail\":null,\"instance\":null,\"errors\":null,\"traceId\":null}"
115122
"Null core fields should be serialized"
116123

124+
testCase "ProblemDetailsModel serializes validation problem details"
125+
<| fun _ ->
126+
let serializer = JsonSerializer() :> IJsonSerializer
127+
let errors = Dictionary<string, string array>()
128+
errors.Add("Name", [| "The Name field is required." |])
129+
130+
let model =
131+
{ Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
132+
Title = "One or more validation errors occurred."
133+
Status = 400
134+
Detail = null
135+
Instance = null
136+
Errors = errors
137+
TraceId = "00-abc-123-00" }
138+
139+
let json = serializer.Serialize(model)
140+
141+
Expect.isTrue
142+
(json.Contains("\"errors\":{\"Name\":[\"The Name field is required.\"]}"))
143+
"Errors should serialize"
144+
145+
Expect.isTrue (json.Contains("\"traceId\":\"00-abc-123-00\"")) "TraceId should serialize"
146+
117147
testCase "ProblemDetailsModel deserializes known fields"
118148
<| fun _ ->
119149
let serializer = JsonSerializer() :> IJsonSerializer
@@ -129,6 +159,29 @@ let tests =
129159
Expect.equal model.Detail "Missing." "Detail should deserialize"
130160
Expect.equal model.Instance "/companies/99" "Instance should deserialize"
131161

162+
testCase "ProblemDetailsModel deserializes validation problem details"
163+
<| fun _ ->
164+
let serializer = JsonSerializer() :> IJsonSerializer
165+
166+
let json =
167+
"{\"type\":\"https://tools.ietf.org/html/rfc7231#section-6.5.1\",\"title\":\"One or more validation errors occurred.\",\"status\":400,\"errors\":{\"Name\":[\"The Name field is required.\"]},\"traceId\":\"00-abc-123-00\"}"
168+
169+
let model = serializer.Deserialize<ProblemDetailsModel>(json)
170+
171+
Expect.equal model.Type "https://tools.ietf.org/html/rfc7231#section-6.5.1" "Type should deserialize"
172+
Expect.equal model.Title "One or more validation errors occurred." "Title should deserialize"
173+
Expect.equal model.Status 400 "Status should deserialize"
174+
Expect.isNull model.Detail "Missing detail should deserialize to null"
175+
Expect.isNull model.Instance "Missing instance should deserialize to null"
176+
Expect.isTrue (model.Errors.ContainsKey "Name") "Errors should contain Name"
177+
178+
Expect.sequenceEqual
179+
model.Errors["Name"]
180+
[| "The Name field is required." |]
181+
"Name errors should deserialize"
182+
183+
Expect.equal model.TraceId "00-abc-123-00" "TraceId should deserialize"
184+
132185
testCase "ProblemDetailsModel deserializes missing status as default int"
133186
<| fun _ ->
134187
let serializer = JsonSerializer() :> IJsonSerializer

tests/Bridge.Tests/StatusCodeHelpersTests.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module StatusCodeHelpersTests
22

3+
open System.Collections.Generic
34
open Expecto
45
open Fossa.Bridge.Models.ApiModels
56
open Fossa.Bridge.Services.StatusCodeHelpers
@@ -9,7 +10,9 @@ let private problemWithStatus status =
910
Title = null
1011
Status = status
1112
Detail = null
12-
Instance = null }
13+
Instance = null
14+
Errors = Unchecked.defaultof<Dictionary<string, string array>>
15+
TraceId = null }
1316

1417
[<Tests>]
1518
let tests =

0 commit comments

Comments
 (0)