Skip to content

Commit 6c39c39

Browse files
authored
Merge pull request #61 from fossa-app/add-union-type-for-client
Refactor client result models to records
2 parents 09e0c77 + 61d252f commit 6c39c39

12 files changed

Lines changed: 108 additions & 163 deletions

src/Bridge/Bridge.fsproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
<ItemGroup>
1010
<Compile Include="Fable.fs" />
1111
<Compile Include="Models\ApiModels\SharedModels.fs" />
12+
<Compile Include="Models\ApiModels\ClientResults.fs" />
13+
<Compile Include="Models\ApiModels\ClientUnitResults.fs" />
14+
<Compile Include="Models\ApiModels\Helpers\ClientResultHelpers.fs" />
15+
<Compile Include="Models\ApiModels\Helpers\ClientUnitResultHelpers.fs" />
1216
<Compile Include="Models\ApiModels\EnvelopeModels.fs" />
1317
<Compile Include="Models\ApiModels\PayloadModels.fs" />
1418
<Compile Include="Services\Endpoints.fs" />
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Fossa.Bridge.Models.ApiModels
2+
3+
type ClientResult<'T when 'T: not struct and 'T: not null> =
4+
{ Succeeded: bool
5+
Value: 'T | null
6+
Problem: ProblemDetailsModel | null }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace Fossa.Bridge.Models.ApiModels
2+
3+
type ClientUnitResult =
4+
{ Succeeded: bool
5+
Problem: ProblemDetailsModel | null }
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Fossa.Bridge.Models.ApiModels.Helpers
2+
3+
open Fossa.Bridge.Models.ApiModels
4+
5+
module ClientResultHelpers =
6+
let success<'T when 'T: not struct and 'T: not null> (value: 'T) : ClientResult<'T> =
7+
{ Succeeded = true
8+
Value = value
9+
Problem = null }
10+
11+
let problem<'T when 'T: not struct and 'T: not null> (problem: ProblemDetailsModel) : ClientResult<'T> =
12+
{ Succeeded = false
13+
Value = Unchecked.defaultof<'T>
14+
Problem = problem }
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Fossa.Bridge.Models.ApiModels.Helpers
2+
3+
open Fossa.Bridge.Models.ApiModels
4+
5+
module ClientUnitResultHelpers =
6+
let success: ClientUnitResult = { Succeeded = true; Problem = null }
7+
8+
let problem (problem: ProblemDetailsModel) : ClientUnitResult =
9+
{ Succeeded = false; Problem = problem }

src/Bridge/Models/ApiModels/SharedModels.fs

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10,79 +10,6 @@ type ProblemDetailsModel =
1010
Detail: string | null
1111
Instance: string | null }
1212

13-
[<RequireQualifiedAccess>]
14-
type ClientResult<'T> =
15-
| Success of 'T
16-
| Problem of ProblemDetailsModel
17-
18-
[<RequireQualifiedAccess>]
19-
type ClientUnitResult =
20-
| Success
21-
| Problem of ProblemDetailsModel
22-
23-
[<RequireQualifiedAccess>]
24-
module ClientResult =
25-
let isSuccess (result: ClientResult<'T>) : bool =
26-
match result with
27-
| ClientResult.Success _ -> true
28-
| ClientResult.Problem _ -> false
29-
30-
let isProblem (result: ClientResult<'T>) : bool = not (isSuccess result)
31-
32-
let map (mapper: 'T -> 'U) (result: ClientResult<'T>) : ClientResult<'U> =
33-
match result with
34-
| ClientResult.Success value -> ClientResult.Success(mapper value)
35-
| ClientResult.Problem problem -> ClientResult.Problem problem
36-
37-
let bind (binder: 'T -> ClientResult<'U>) (result: ClientResult<'T>) : ClientResult<'U> =
38-
match result with
39-
| ClientResult.Success value -> binder value
40-
| ClientResult.Problem problem -> ClientResult.Problem problem
41-
42-
let defaultValue (fallback: 'T) (result: ClientResult<'T>) : 'T =
43-
match result with
44-
| ClientResult.Success value -> value
45-
| ClientResult.Problem _ -> fallback
46-
47-
let valueOrDefault (result: ClientResult<'T>) : 'T =
48-
match result with
49-
| ClientResult.Success value -> value
50-
| ClientResult.Problem _ -> Unchecked.defaultof<'T>
51-
52-
let problemOrNone (result: ClientResult<'T>) : ProblemDetailsModel option =
53-
match result with
54-
| ClientResult.Success _ -> None
55-
| ClientResult.Problem problem -> Some problem
56-
57-
let valueOrNone (result: ClientResult<'T>) : 'T option =
58-
match result with
59-
| ClientResult.Success value -> Some value
60-
| ClientResult.Problem _ -> None
61-
62-
[<RequireQualifiedAccess>]
63-
module ClientUnitResult =
64-
let isSuccess (result: ClientUnitResult) : bool =
65-
match result with
66-
| ClientUnitResult.Success -> true
67-
| ClientUnitResult.Problem _ -> false
68-
69-
let isProblem (result: ClientUnitResult) : bool = not (isSuccess result)
70-
71-
let problemOrNone (result: ClientUnitResult) : ProblemDetailsModel option =
72-
match result with
73-
| ClientUnitResult.Success -> None
74-
| ClientUnitResult.Problem problem -> Some problem
75-
76-
let toGeneric (result: ClientUnitResult) : ClientResult<unit> =
77-
match result with
78-
| ClientUnitResult.Success -> ClientResult.Success()
79-
| ClientUnitResult.Problem problem -> ClientResult.Problem problem
80-
81-
let ofGeneric (result: ClientResult<unit>) : ClientUnitResult =
82-
match result with
83-
| ClientResult.Success() -> ClientUnitResult.Success
84-
| ClientResult.Problem problem -> ClientUnitResult.Problem problem
85-
8613
[<CLIMutable>]
8714
type AddressModel =
8815
{ Line1: string | null

src/Bridge/Services/HttpTransport.fs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Fossa.Bridge.Services
44
open System.Threading
55
open System.Threading.Tasks
66
open Fossa.Bridge.Models.ApiModels
7+
open Fossa.Bridge.Models.ApiModels.Helpers
78
open Fossa.Bridge.Services.StatusCodeHelpers
89

910

@@ -25,14 +26,14 @@ type HttpTransport(sender: IHttpRequestSender, serializer: IJsonSerializer, toke
2526

2627
let toClientUnitResult (response: HttpResponseMessage) =
2728
if isStatusCodeSuccess response.StatusCode then
28-
ClientUnitResult.Success
29+
ClientUnitResultHelpers.success
2930
else
3031
response.Content
3132
|> serializer.Deserialize<ProblemDetailsModel>
32-
|> ClientUnitResult.Problem
33+
|> ClientUnitResultHelpers.problem
3334

3435
interface IHttpTransport with
35-
member _.GetAsync<'TResponse when 'TResponse: not null>
36+
member _.GetAsync<'TResponse when 'TResponse: not struct and 'TResponse: not null>
3637
(endpointUrl: string, endpointSecurity: EndpointSecurity, cancellationToken: CancellationToken)
3738
=
3839
let computation =
@@ -48,12 +49,15 @@ type HttpTransport(sender: IHttpRequestSender, serializer: IJsonSerializer, toke
4849
let! response = sender.SendAsync(req, cancellationToken) |> AsyncHelpers.awaitTask
4950

5051
if isStatusCodeSuccess response.StatusCode then
51-
return response.Content |> serializer.Deserialize<'TResponse> |> ClientResult.Success
52+
return
53+
response.Content
54+
|> serializer.Deserialize<'TResponse>
55+
|> ClientResultHelpers.success
5256
else
5357
return
5458
response.Content
5559
|> serializer.Deserialize<ProblemDetailsModel>
56-
|> ClientResult.Problem
60+
|> ClientResultHelpers.problem
5761
}
5862

5963
AsyncHelpers.startAsTaskGeneric (computation, cancellationToken)

src/Bridge/Services/IHttpTransport.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ open System.Threading.Tasks
66
open Fossa.Bridge.Models.ApiModels
77

88
type IHttpTransport =
9-
abstract GetAsync<'TResponse when 'TResponse: not null> :
9+
abstract GetAsync<'TResponse when 'TResponse: not struct and 'TResponse: not null> :
1010
endpointUrl: string * endpointSecurity: EndpointSecurity * cancellationToken: CancellationToken ->
1111
Task<ClientResult<'TResponse>>
1212

tests/Bridge.Tests/Bridge.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<ItemGroup>
88
<Compile Include="JsonSerializerTests.fs" />
99
<Compile Include="ClientResultTests.fs" />
10+
<Compile Include="ClientUnitResultTests.fs" />
1011
<Compile Include="UrlHelpersTests.fs" />
1112
<Compile Include="StatusCodeHelpersTests.fs" />
1213
<Compile Include="HttpTransportTests.fs" />

tests/Bridge.Tests/ClientResultTests.fs

Lines changed: 13 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module ClientResultTests
22

33
open Expecto
44
open Fossa.Bridge.Models.ApiModels
5+
open Fossa.Bridge.Models.ApiModels.Helpers
56

67
let private problem =
78
{ Type = "https://example.com/problems/conflict"
@@ -14,74 +15,18 @@ let private problem =
1415
let tests =
1516
testList
1617
"ClientResultTests"
17-
[ testList
18-
"ClientResult helpers"
19-
[ testCase "success helpers expose the value"
20-
<| fun _ ->
21-
let result = ClientResult.Success 21
18+
[ testCase "success helper creates success result"
19+
<| fun _ ->
20+
let result = ClientResultHelpers.success "bridge"
2221

23-
Expect.isTrue (ClientResult.isSuccess result) "Success should be success"
24-
Expect.isFalse (ClientResult.isProblem result) "Success should not be problem"
22+
Expect.isTrue result.Succeeded "Success should be success"
23+
Expect.equal result.Value "bridge" "Value should be set"
24+
Expect.isNull result.Problem "Problem should be absent"
2525

26-
Expect.equal
27-
(ClientResult.map ((*) 2) result)
28-
(ClientResult.Success 42)
29-
"Map should transform value"
26+
testCase "problem helper creates problem result"
27+
<| fun _ ->
28+
let result: ClientResult<string> = ClientResultHelpers.problem problem
3029

31-
Expect.equal (ClientResult.defaultValue 0 result) 21 "Default value should not be used"
32-
Expect.equal (ClientResult.valueOrDefault result) 21 "Value should be returned"
33-
Expect.equal (ClientResult.valueOrNone result) (Some 21) "Value option should be present"
34-
Expect.equal (ClientResult.problemOrNone result) None "Problem option should be absent"
35-
36-
testCase "problem helpers expose the problem"
37-
<| fun _ ->
38-
let result: ClientResult<int> = ClientResult.Problem problem
39-
40-
Expect.isFalse (ClientResult.isSuccess result) "Problem should not be success"
41-
Expect.isTrue (ClientResult.isProblem result) "Problem should be problem"
42-
43-
Expect.equal
44-
(ClientResult.map ((*) 2) result)
45-
(ClientResult.Problem problem)
46-
"Map should preserve problem"
47-
48-
Expect.equal
49-
(ClientResult.bind (fun value -> ClientResult.Success(value * 2)) result)
50-
(ClientResult.Problem problem)
51-
"Bind should preserve problem"
52-
53-
Expect.equal (ClientResult.defaultValue 42 result) 42 "Default value should be used"
54-
Expect.equal (ClientResult.valueOrDefault result) 0 "Default int should be returned"
55-
Expect.equal (ClientResult.valueOrNone result) None "Value option should be absent"
56-
Expect.equal (ClientResult.problemOrNone result) (Some problem) "Problem option should be present" ]
57-
58-
testList
59-
"ClientUnitResult helpers"
60-
[ testCase "success converts to generic unit success"
61-
<| fun _ ->
62-
let result = ClientUnitResult.Success
63-
64-
Expect.isTrue (ClientUnitResult.isSuccess result) "Success should be success"
65-
Expect.isFalse (ClientUnitResult.isProblem result) "Success should not be problem"
66-
Expect.equal (ClientUnitResult.problemOrNone result) None "Problem option should be absent"
67-
68-
Expect.equal
69-
(ClientUnitResult.toGeneric result)
70-
(ClientResult.Success())
71-
"Generic result should be success"
72-
73-
testCase "problem converts through generic unit result"
74-
<| fun _ ->
75-
let result = ClientUnitResult.Problem problem
76-
let generic = ClientUnitResult.toGeneric result
77-
78-
Expect.isFalse (ClientUnitResult.isSuccess result) "Problem should not be success"
79-
Expect.isTrue (ClientUnitResult.isProblem result) "Problem should be problem"
80-
81-
Expect.equal
82-
(ClientUnitResult.problemOrNone result)
83-
(Some problem)
84-
"Problem option should be present"
85-
86-
Expect.equal generic (ClientResult.Problem problem) "Generic result should preserve problem"
87-
Expect.equal (ClientUnitResult.ofGeneric generic) result "Unit result should round-trip" ] ]
30+
Expect.isFalse result.Succeeded "Problem should not be success"
31+
Expect.isNull (box result.Value) "Value should be absent"
32+
Expect.equal result.Problem problem "Problem should be set" ]

0 commit comments

Comments
 (0)