git.iko.soy/rowtype-yoga/purescript-graphql-fundeps
on main help sign in
Adds better error handler
018f8c7Mike Solomon
package-lock.json+10
@@ -11,6 +11,7 @@
11 "license": "ISC",
11 "license": "ISC",
12 "devDependencies": {
12 "devDependencies": {
13 "purescript": "^0.14.1",
13 "purescript": "^0.14.1",
14+ "purs-tidy": "^0.10.0",
14 "spago": "^0.20.3",
15 "spago": "^0.20.3",
15 "xhr2": "^0.2.1"
16 "xhr2": "^0.2.1"
16 }
17 }
@@ -1119,6 +1120,15 @@
1119 "node": ">=8.3.0"
1120 "node": ">=8.3.0"
1120 }
1121 }
1121 },
1122 },
1123+ "node_modules/purs-tidy": {
1124+ "version": "0.10.0",
1125+ "resolved": "https://registry.npmjs.org/purs-tidy/-/purs-tidy-0.10.0.tgz",
1126+ "integrity": "sha512-ULbJfBHRngczYwcOzugytRiNy+Guy2VXoAG1jMtGaSFEaUZk1lZkKU11t8jIZTrDdOrkgqlWCZLi3mfcHvdimA==",
1127+ "dev": true,
1128+ "bin": {
1129+ "purs-tidy": "bin/index.js"
1130+ }
1131+ },
1122 "node_modules/qs": {
1132 "node_modules/qs": {
1123 "version": "6.5.2",
1133 "version": "6.5.2",
1124 "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
1134 "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
package.json+1
@@ -10,6 +10,7 @@
10 "license": "ISC",
10 "license": "ISC",
11 "devDependencies": {
11 "devDependencies": {
12 "purescript": "^0.14.1",
12 "purescript": "^0.14.1",
13+ "purs-tidy": "^0.10.0",
13 "spago": "^0.20.3",
14 "spago": "^0.20.3",
14 "xhr2": "^0.2.1"
15 "xhr2": "^0.2.1"
15 }
16 }
src/GraphQL.purs+104−17
@@ -6,9 +6,10 @@ import Affjax as AX
6import Affjax.RequestBody as RequestBody
6import Affjax.RequestBody as RequestBody
7import Affjax.RequestHeader (RequestHeader(..))
7import Affjax.RequestHeader (RequestHeader(..))
8import Affjax.ResponseFormat as ResponseFormat
8import Affjax.ResponseFormat as ResponseFormat
9−import Control.Monad.Error.Class (class MonadError, class MonadThrow)
9+import Control.Monad.Error.Class (class MonadThrow)
10import Data.Either (Either(..))
10import Data.Either (Either(..))
11import Data.HTTP.Method (Method(..))
11import Data.HTTP.Method (Method(..))
12+import Data.List.Types (NonEmptyList)
12import Data.Maybe (Maybe(..))
13import Data.Maybe (Maybe(..))
13import Data.MediaType (MediaType(..))
14import Data.MediaType (MediaType(..))
14import Data.String as String
15import Data.String as String
@@ -17,35 +18,113 @@ import Effect.Aff (Aff, error, throwError)
17import Effect.Aff.Class (class MonadAff, liftAff)
18import Effect.Aff.Class (class MonadAff, liftAff)
18import Effect.Class (liftEffect)
19import Effect.Class (liftEffect)
19import Effect.Class.Console as Log
20import Effect.Class.Console as Log
21+import Foreign (ForeignError)
20import Simple.JSON as JSON
22import Simple.JSON as JSON
21import Type.Proxy (Proxy(..))
23import Type.Proxy (Proxy(..))
22
24
23data GraphQL
25data GraphQL
24
26
25−data Gql (operation :: GraphQL)
27+data Gql (operation :: GraphQL) = Gql
26− = Gql
27
28
28−class GraphQLReqRes (operation :: GraphQL) (gql :: Symbol) (i :: Row Type) (o :: Row Type) | operation -> gql i o
29+class
30+ GraphQLReqRes
31+ (operation :: GraphQL)
32+ (gql :: Symbol)
33+ (i :: Row Type)
34+ (o :: Row Type)
35+ | operation -> gql i o
29
36
30type Endpoint = String
37type Endpoint = String
38+type NetworkErrorInfo =
39+ { endpoint :: Endpoint
40+ , requestHeaders :: Array RequestHeader
41+ , requestBody :: String
42+ , error :: AX.Error
43+ }
31
44
32−type GraphQLClient' (operation :: GraphQL) (gql :: Symbol) (i :: Row Type) (o :: Row Type) (m :: Type -> Type) (e :: Type) = GraphQLReqRes operation gql i o => IsSymbol gql => JSON.WriteForeign { | i } => JSON.ReadForeign { | o } => MonadAff m => MonadThrow e m => MonadError e m => (AX.Error -> e) -> (AX.Response String -> e) ->Gql operation -> Record i -> m { | o }
45+type ParsingErrorInfo =
46+ { endpoint :: Endpoint
47+ , requestHeaders :: Array RequestHeader
48+ , requestBody :: String
49+ , response :: AX.Response String
50+ , jsonParsingError :: NonEmptyList ForeignError
51+ }
33
52
34−type GraphQLClient = forall (operation :: GraphQL) (gql :: Symbol) (i :: Row Type) (o :: Row Type) (m :: Type -> Type) (e :: Type). GraphQLClient' operation gql i o m e
53+type GraphQLClient'
54+ (operation :: GraphQL)
55+ (gql :: Symbol)
56+ (i :: Row Type)
57+ (o :: Row Type)
58+ (m :: Type -> Type)
59+ (e :: Type) =
60+ GraphQLReqRes operation gql i o
61+ => IsSymbol gql
62+ => JSON.WriteForeign { | i }
63+ => JSON.ReadForeign { | o }
64+ => MonadAff m
65+ => MonadThrow e m
66+ => (NetworkErrorInfo -> e)
67+ -> (ParsingErrorInfo -> e)
68+ -> Gql operation
69+ -> Record i
70+ -> m { | o }
35
71
72+type GraphQLClient =
73+ forall (operation :: GraphQL)
74+ (gql :: Symbol) (i :: Row Type) (o :: Row Type) (m :: Type -> Type) (e :: Type)
75+ . GraphQLClient' operation gql i o m e
76+
77+graphQL
78+ :: Endpoint
79+ -> Array RequestHeader
36−graphQL :: Endpoint -> Array RequestHeader -> (forall (operation :: GraphQL) (gql :: Symbol) (i :: Row Type) (o :: Row Type). GraphQLReqRes operation gql i o => IsSymbol gql => JSON.WriteForeign { | i } => JSON.ReadForeign { | o } => Gql operation -> Record i -> Aff { | o })
80+ -> ( forall (operation :: GraphQL) (gql :: Symbol) (i :: Row Type) (o :: Row Type)
81+ . GraphQLReqRes operation gql i o
82+ => IsSymbol gql
83+ => JSON.WriteForeign { | i }
84+ => JSON.ReadForeign { | o }
85+ => Gql operation
86+ -> Record i
87+ -> Aff { | o }
88+ )
37−graphQL endpoint headers = graphQL' endpoint headers (error <<< AX.printError) (error <<< _.body)
89+graphQL endpoint headers = graphQL'
90+ endpoint
91+ headers
92+ (error <<< AX.printError <<< _.error)
93+ (error <<< _.body <<< _.response)
38
94
39graphQL' :: Endpoint -> Array RequestHeader -> GraphQLClient
95graphQL' :: Endpoint -> Array RequestHeader -> GraphQLClient
40−graphQL' endpoint headers = go
96+graphQL' endpoint headers' = go
41 where
97 where
42− go :: forall (operation :: GraphQL) (gql :: Symbol) (i :: Row Type) (o :: Row Type) (m :: Type -> Type) (e :: Type). GraphQLReqRes operation gql i o => IsSymbol gql => JSON.WriteForeign { | i } => JSON.ReadForeign { | o } => MonadAff m => MonadThrow e m => MonadError e m => (AX.Error -> e) -> (AX.Response String -> e) ->Gql operation -> Record i -> m { | o }
98+ requestHeaders = headers' <> [ ContentType $ MediaType "application/json" ]
99+
100+ go
101+ :: forall (operation :: GraphQL) (gql :: Symbol)
102+ (i :: Row Type) (o :: Row Type) (m :: Type -> Type) (e :: Type)
103+ . GraphQLReqRes operation gql i o
104+ => IsSymbol gql
105+ => JSON.WriteForeign
106+ { | i }
107+ => JSON.ReadForeign { | o }
108+ => MonadAff m
109+ => MonadThrow e m
110+ => (NetworkErrorInfo -> e)
111+ -> (ParsingErrorInfo -> e)
112+ -> Gql operation
113+ -> Record i
114+ -> m { | o }
43 go networkErrorF readErrorF _ variables = do
115 go networkErrorF readErrorF _ variables = do
44 let
116 let
45 input =
117 input =
46 { variables
118 { variables
47− , query: String.replaceAll (String.Pattern "\n") (String.Replacement " ") (String.replaceAll (String.Pattern "\r\n") (String.Replacement " ") (reflectSymbol (Proxy :: Proxy gql)))
119+ , query: String.replaceAll (String.Pattern "\n")
120+ (String.Replacement " ")
121+ ( String.replaceAll
122+ (String.Pattern "\r\n")
123+ (String.Replacement " ")
124+ (reflectSymbol (Proxy :: Proxy gql))
125+ )
48 }
126 }
127+ requestBody = JSON.writeJSON input
49 res <-
128 res <-
50 liftAff $ AX.request
129 liftAff $ AX.request
51 ( AX.defaultRequest
130 ( AX.defaultRequest
@@ -53,15 +132,23 @@ graphQL' endpoint headers = go
53 , method = Left POST
132 , method = Left POST
54 , responseFormat = ResponseFormat.string
133 , responseFormat = ResponseFormat.string
55 , content =
134 , content =
56− Just
135+ Just
57− (RequestBody.string (JSON.writeJSON input))
136+ (RequestBody.string requestBody)
58− , headers = headers <> [ContentType $ MediaType "application/json"]
137+ , headers = requestHeaders
59 }
138 }
60 )
139 )
61 case res of
140 case res of
62− Left err -> do
141+ Left error -> do
63 liftEffect $ Log.info "Request did not go through"
142 liftEffect $ Log.info "Request did not go through"
64− throwError (networkErrorF err)
143+ throwError (networkErrorF { endpoint, requestHeaders, error, requestBody })
65 Right response -> case (JSON.readJSON response.body) of
144 Right response -> case (JSON.readJSON response.body) of
145+ Left jsonParsingError -> throwError
66− Left _ -> throwError (readErrorF response)
146+ ( readErrorF
147+ { endpoint
148+ , requestHeaders
149+ , jsonParsingError
150+ , requestBody
151+ , response
152+ }
153+ )
67 Right ({ data: d } :: { data :: { | o } }) -> pure d
154 Right ({ data: d } :: { data :: { | o } }) -> pure d