Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
haskell-gargantext
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
160
Issues
160
List
Board
Labels
Milestones
Merge Requests
14
Merge Requests
14
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
gargantext
haskell-gargantext
Commits
60e1953f
Commit
60e1953f
authored
Oct 31, 2023
by
Alfredo Di Napoli
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Deriving generally the error code for a BackendErrorCode
parent
821311ae
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
127 additions
and
54 deletions
+127
-54
gargantext.cabal
gargantext.cabal
+1
-0
Errors.hs
src/Gargantext/API/Errors.hs
+11
-13
TH.hs
src/Gargantext/API/Errors/TH.hs
+74
-0
Types.hs
src/Gargantext/API/Errors/Types.hs
+39
-39
JSON.hs
test/Test/Offline/JSON.hs
+2
-2
No files found.
gargantext.cabal
View file @
60e1953f
...
...
@@ -53,6 +53,7 @@ library
Gargantext.API.Dev
Gargantext.API.Errors
Gargantext.API.Errors.Class
Gargantext.API.Errors.TH
Gargantext.API.Errors.Types
Gargantext.API.HashedResponse
Gargantext.API.Ngrams
...
...
src/Gargantext/API/Errors.hs
View file @
60e1953f
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module
Gargantext.API.Errors
(
...
...
@@ -17,17 +18,14 @@ import Prelude
import
Gargantext.API.Errors.Class
as
Class
import
Gargantext.API.Errors.Types
as
Types
import
Gargantext.API.Errors.TH
(
deriveHttpStatusCode
)
import
Gargantext.Database.Query.Table.Node.Error
hiding
(
nodeError
)
import
Servant.Server
import
qualified
Data.Aeson
as
JSON
import
qualified
Network.HTTP.Types.Status
as
HTTP
import
qualified
Data.Text
as
T
backendErrorTypeToErrStatus
::
BackendErrorType
->
HTTP
.
Status
backendErrorTypeToErrStatus
=
\
case
BE_node_error_root_not_found
->
HTTP
.
status404
BE_node_error_corpus_not_found
->
HTTP
.
status404
BE_tree_error_root_not_found
->
HTTP
.
status404
$
(
deriveHttpStatusCode
''
B
ackendErrorCode
)
-- | Transforms a backend internal error into something that the frontend
-- can consume. This is the only representation we offer to the outside world,
...
...
@@ -90,15 +88,15 @@ frontendErrorToServerError :: FrontendError -> ServerError
frontendErrorToServerError
fe
@
(
FrontendError
diag
ty
_
)
=
ServerError
{
errHTTPCode
=
HTTP
.
statusCode
$
backendErrorTypeToErrStatus
ty
,
errReasonPhrase
=
T
.
unpack
diag
,
errBody
=
JSON
.
encode
fe
,
errHeaders
=
mempty
,
errBody
=
JSON
.
encode
fe
,
errHeaders
=
mempty
}
showAsServantJSONErr
::
BackendInternalError
->
ServerError
showAsServantJSONErr
(
InternalNodeError
err
@
(
NoListFound
{}))
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalNodeError
err
@
NoRootFound
{})
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalNodeError
err
@
NoCorpusFound
)
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalNodeError
err
@
NoUserFound
{})
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalNodeError
err
@
(
NoListFound
{}))
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalNodeError
err
@
NoRootFound
{})
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalNodeError
err
@
NoCorpusFound
)
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalNodeError
err
@
NoUserFound
{})
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalNodeError
err
@
(
DoesNotExist
{}))
=
err404
{
errBody
=
JSON
.
encode
err
}
showAsServantJSONErr
(
InternalServerError
err
)
=
err
showAsServantJSONErr
a
=
err500
{
errBody
=
JSON
.
encode
a
}
showAsServantJSONErr
(
InternalServerError
err
)
=
err
showAsServantJSONErr
a
=
err500
{
errBody
=
JSON
.
encode
a
}
src/Gargantext/API/Errors/TH.hs
0 → 100644
View file @
60e1953f
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module
Gargantext.API.Errors.TH
(
deriveHttpStatusCode
)
where
import
Prelude
import
Network.HTTP.Types
import
qualified
Data.Map.Strict
as
Map
import
qualified
Data.Text
as
T
import
qualified
Language.Haskell.TH
as
TH
import
Gargantext.API.Errors.Types
import
qualified
Network.HTTP.Types
as
HTTP
-- | A static map of the HTTP status code we support.
supported_http_status_map
::
Map
.
Map
T
.
Text
(
TH
.
Q
TH
.
Exp
)
supported_http_status_map
=
Map
.
fromList
[
(
"200"
,
TH
.
varE
's
t
atus200
)
,
(
"400"
,
TH
.
varE
's
t
atus400
)
,
(
"403"
,
TH
.
varE
's
t
atus403
)
,
(
"404"
,
TH
.
varE
's
t
atus404
)
,
(
"500"
,
TH
.
varE
's
t
atus500
)
]
deriveHttpStatusCode
::
TH
.
Name
->
TH
.
Q
[
TH
.
Dec
]
deriveHttpStatusCode
appliedType
=
do
info
<-
TH
.
reify
appliedType
case
info
of
TH
.
TyConI
(
TH
.
DataD
_
_
_
_
ctors
_
)
->
case
extract_names
ctors
of
Left
ctor
->
error
$
"Only enum-like constructors supported: "
++
show
ctor
Right
names
->
case
parse_error_codes
names
of
Left
n
->
error
$
"Couldn't extract error code from : "
++
TH
.
nameBase
n
++
". Make sure it's in the form XX_<validHttpStatusCode>__<textual_diagnostic>"
Right
codes
->
do
let
static_matches
=
flip
map
codes
$
\
(
n
,
stE
,
_txt
)
->
TH
.
match
(
TH
.
conP
n
[]
)
(
TH
.
normalB
[
|
$
(
stE
)
|
])
[]
[
d
|
backendErrorTypeToErrStatus :: BackendErrorCode -> HTTP.Status
backendErrorTypeToErrStatus = $(TH.lamCaseE static_matches)
|]
err
->
error
$
"Cannot call deriveHttpStatusCode on: "
++
show
err
extract_names
::
[
TH
.
Con
]
->
Either
TH
.
Con
[
TH
.
Name
]
extract_names
=
mapM
go
where
go
::
TH
.
Con
->
Either
TH
.
Con
TH
.
Name
go
=
\
case
(
TH
.
NormalC
n
[]
)
->
Right
n
e
->
Left
e
parse_error_codes
::
[
TH
.
Name
]
->
Either
TH
.
Name
[(
TH
.
Name
,
TH
.
Q
TH
.
Exp
,
T
.
Text
)]
parse_error_codes
=
mapM
go
where
do_parse
=
\
n_txt
->
let
sts_tl
=
T
.
drop
3
n_txt
code
=
T
.
take
3
sts_tl
msg
=
T
.
drop
5
sts_tl
in
(
code
,
msg
)
go
::
TH
.
Name
->
Either
TH
.
Name
(
TH
.
Name
,
TH
.
Q
TH
.
Exp
,
T
.
Text
)
go
n
=
case
Map
.
lookup
code
supported_http_status_map
of
Nothing
->
Left
n
Just
st
->
Right
(
n
,
st
,
msg
)
where
(
code
,
msg
)
=
do_parse
$
(
T
.
pack
$
TH
.
nameBase
n
)
src/Gargantext/API/Errors/Types.hs
View file @
60e1953f
...
...
@@ -17,7 +17,7 @@ module Gargantext.API.Errors.Types (
FrontendError
(
..
)
-- * The internal backend type and an enumeration of all possible backend error types
,
BackendError
Typ
e
(
..
)
,
BackendError
Cod
e
(
..
)
,
BackendInternalError
(
..
)
,
ToFrontendErrorData
(
..
)
...
...
@@ -117,23 +117,23 @@ instance HasJoseError BackendInternalError where
_JoseError
=
_InternalJoseError
-- | A (hopefully and eventually) exhaustive list of backend errors.
data
BackendError
Typ
e
data
BackendError
Cod
e
=
-- node errors
BE
_node_error_root_not_found
|
BE
_node_error_corpus_not_found
EC_404_
_node_error_root_not_found
|
EC_404_
_node_error_corpus_not_found
-- tree errors
|
BE
_tree_error_root_not_found
|
EC_404_
_tree_error_root_not_found
deriving
(
Show
,
Read
,
Eq
,
Enum
,
Bounded
)
$
(
genSingletons
[
''
B
ackendError
Typ
e
])
$
(
genSingletons
[
''
B
ackendError
Cod
e
])
-- | An error that can be returned to the frontend. It carries a human-friendly
-- diagnostic, the 'type' of the error as well as some context-specific data.
data
FrontendError
where
FrontendError
::
forall
b
.
IsFrontendErrorData
b
=>
{
fe_diagnostic
::
!
T
.
Text
,
fe_type
::
!
BackendError
Typ
e
,
fe_type
::
!
BackendError
Cod
e
,
fe_data
::
ToFrontendErrorData
b
}
->
FrontendError
...
...
@@ -156,30 +156,30 @@ class ( SingI payload
)
=>
IsFrontendErrorData
payload
where
isFrontendErrorData
::
Proxy
payload
->
Dict
IsFrontendErrorData
payload
instance
IsFrontendErrorData
'
B
E
_node_error_root_not_found
where
instance
IsFrontendErrorData
'
E
C
_404_
_node_error_root_not_found
where
isFrontendErrorData
_
=
Dict
instance
IsFrontendErrorData
'
B
E
_node_error_corpus_not_found
where
instance
IsFrontendErrorData
'
E
C
_404_
_node_error_corpus_not_found
where
isFrontendErrorData
_
=
Dict
instance
IsFrontendErrorData
'
B
E
_tree_error_root_not_found
where
instance
IsFrontendErrorData
'
E
C
_404_
_tree_error_root_not_found
where
isFrontendErrorData
_
=
Dict
----------------------------------------------------------------------------
-- This data family maps a 'BackendError
Typ
e' into a concrete payload.
-- This data family maps a 'BackendError
Cod
e' into a concrete payload.
----------------------------------------------------------------------------
data
NoFrontendErrorData
=
NoFrontendErrorData
data
family
ToFrontendErrorData
(
payload
::
BackendError
Typ
e
)
::
Type
data
family
ToFrontendErrorData
(
payload
::
BackendError
Cod
e
)
::
Type
data
instance
ToFrontendErrorData
'
B
E
_node_error_root_not_found
=
data
instance
ToFrontendErrorData
'
E
C
_404_
_node_error_root_not_found
=
FE_node_error_root_not_found
deriving
(
Show
,
Eq
,
Generic
)
data
instance
ToFrontendErrorData
'
B
E
_node_error_corpus_not_found
=
data
instance
ToFrontendErrorData
'
E
C
_404_
_node_error_corpus_not_found
=
FE_node_error_corpus_not_found
deriving
(
Show
,
Eq
,
Generic
)
data
instance
ToFrontendErrorData
'
B
E
_tree_error_root_not_found
=
data
instance
ToFrontendErrorData
'
E
C
_404_
_tree_error_root_not_found
=
RootNotFound
{
_rnf_rootId
::
RootId
}
deriving
(
Show
,
Eq
,
Generic
)
...
...
@@ -187,22 +187,22 @@ data instance ToFrontendErrorData 'BE_tree_error_root_not_found =
-- JSON instances. It's important to have nice and human readable instances.
----------------------------------------------------------------------------
instance
ToJSON
(
ToFrontendErrorData
'
B
E
_node_error_root_not_found
)
where
instance
ToJSON
(
ToFrontendErrorData
'
E
C
_404_
_node_error_root_not_found
)
where
toJSON
_
=
JSON
.
Null
instance
FromJSON
(
ToFrontendErrorData
'
B
E
_node_error_root_not_found
)
where
instance
FromJSON
(
ToFrontendErrorData
'
E
C
_404_
_node_error_root_not_found
)
where
parseJSON
_
=
pure
FE_node_error_root_not_found
instance
ToJSON
(
ToFrontendErrorData
'
B
E
_node_error_corpus_not_found
)
where
instance
ToJSON
(
ToFrontendErrorData
'
E
C
_404_
_node_error_corpus_not_found
)
where
toJSON
_
=
JSON
.
Null
instance
FromJSON
(
ToFrontendErrorData
'
B
E
_node_error_corpus_not_found
)
where
instance
FromJSON
(
ToFrontendErrorData
'
E
C
_404_
_node_error_corpus_not_found
)
where
parseJSON
_
=
pure
FE_node_error_corpus_not_found
instance
ToJSON
(
ToFrontendErrorData
'
B
E
_tree_error_root_not_found
)
where
instance
ToJSON
(
ToFrontendErrorData
'
E
C
_404_
_tree_error_root_not_found
)
where
toJSON
RootNotFound
{
..
}
=
object
[
"root_id"
.=
toJSON
_rnf_rootId
]
instance
FromJSON
(
ToFrontendErrorData
'
B
E
_tree_error_root_not_found
)
where
instance
FromJSON
(
ToFrontendErrorData
'
E
C
_404_
_tree_error_root_not_found
)
where
parseJSON
=
withObject
"RootNotFound"
$
\
o
->
do
_rnf_rootId
<-
o
.:
"root_id"
pure
RootNotFound
{
..
}
...
...
@@ -214,11 +214,11 @@ mkFrontendErr et = mkFrontendErr' mempty et
mkFrontendErr'
::
forall
payload
.
IsFrontendErrorData
payload
=>
T
.
Text
->
ToFrontendErrorData
(
payload
::
BackendError
Typ
e
)
->
ToFrontendErrorData
(
payload
::
BackendError
Cod
e
)
->
FrontendError
mkFrontendErr'
diag
pl
=
FrontendError
diag
(
fromSing
$
sing
@
payload
)
pl
instance
Arbitrary
BackendError
Typ
e
where
instance
Arbitrary
BackendError
Cod
e
where
arbitrary
=
arbitraryBoundedEnum
instance
Arbitrary
FrontendError
where
...
...
@@ -227,24 +227,24 @@ instance Arbitrary FrontendError where
txt
<-
arbitrary
genFrontendErr
txt
et
genFrontendErr
::
T
.
Text
->
BackendError
Typ
e
->
Gen
FrontendError
genFrontendErr
::
T
.
Text
->
BackendError
Cod
e
->
Gen
FrontendError
genFrontendErr
txt
be
=
case
be
of
BE
_node_error_root_not_found
EC_404_
_node_error_root_not_found
->
pure
$
mkFrontendErr'
txt
FE_node_error_root_not_found
BE
_node_error_corpus_not_found
EC_404_
_node_error_corpus_not_found
->
pure
$
mkFrontendErr'
txt
FE_node_error_corpus_not_found
BE
_tree_error_root_not_found
EC_404_
_tree_error_root_not_found
->
do
rootId
<-
arbitrary
pure
$
mkFrontendErr'
txt
(
RootNotFound
rootId
)
instance
ToJSON
BackendError
Typ
e
where
instance
ToJSON
BackendError
Cod
e
where
toJSON
=
JSON
.
String
.
T
.
pack
.
drop
3
.
show
instance
FromJSON
BackendError
Typ
e
where
parseJSON
(
String
s
)
=
case
readMaybe
(
T
.
unpack
$
"
BE
_"
<>
s
)
of
instance
FromJSON
BackendError
Cod
e
where
parseJSON
(
String
s
)
=
case
readMaybe
(
T
.
unpack
$
"
EC
_"
<>
s
)
of
Just
v
->
pure
v
Nothing
->
fail
$
"FromJSON BackendError
Typ
e unexpected value: "
<>
T
.
unpack
s
parseJSON
ty
=
typeMismatch
"BackendError
Typ
e"
ty
Nothing
->
fail
$
"FromJSON BackendError
Cod
e unexpected value: "
<>
T
.
unpack
s
parseJSON
ty
=
typeMismatch
"BackendError
Cod
e"
ty
instance
ToJSON
FrontendError
where
toJSON
(
FrontendError
diag
ty
dt
)
=
...
...
@@ -256,14 +256,14 @@ instance ToJSON FrontendError where
instance
FromJSON
FrontendError
where
parseJSON
=
withObject
"FrontendError"
$
\
o
->
do
(
fe_diagnostic
::
T
.
Text
)
<-
o
.:
"diagnostic"
(
fe_type
::
BackendError
Typ
e
)
<-
o
.:
"type"
(
fe_type
::
BackendError
Cod
e
)
<-
o
.:
"type"
case
fe_type
of
BE
_node_error_root_not_found
->
do
(
fe_data
::
ToFrontendErrorData
'
B
E
_node_error_root_not_found
)
<-
o
.:
"data"
EC_404_
_node_error_root_not_found
->
do
(
fe_data
::
ToFrontendErrorData
'
E
C
_404_
_node_error_root_not_found
)
<-
o
.:
"data"
pure
FrontendError
{
..
}
BE
_node_error_corpus_not_found
->
do
(
fe_data
::
ToFrontendErrorData
'
B
E
_node_error_corpus_not_found
)
<-
o
.:
"data"
EC_404_
_node_error_corpus_not_found
->
do
(
fe_data
::
ToFrontendErrorData
'
E
C
_404_
_node_error_corpus_not_found
)
<-
o
.:
"data"
pure
FrontendError
{
..
}
BE
_tree_error_root_not_found
->
do
(
fe_data
::
ToFrontendErrorData
'
B
E
_tree_error_root_not_found
)
<-
o
.:
"data"
EC_404_
_tree_error_root_not_found
->
do
(
fe_data
::
ToFrontendErrorData
'
E
C
_404_
_tree_error_root_not_found
)
<-
o
.:
"data"
pure
FrontendError
{
..
}
test/Test/Offline/JSON.hs
View file @
60e1953f
...
...
@@ -29,7 +29,7 @@ jsonRoundtrip a =
counterexample
(
"Parsed JSON: "
<>
C8
.
unpack
(
encode
a
))
$
eitherDecode
(
encode
a
)
===
Right
a
class
(
Show
a
,
FromJSON
a
,
ToJSON
a
,
Eq
a
,
Enum
a
,
Bounded
a
)
=>
EnumBoundedJSON
a
instance
EnumBoundedJSON
BackendError
Typ
e
instance
EnumBoundedJSON
BackendError
Cod
e
jsonEnumRoundtrip
::
forall
a
.
Dict
EnumBoundedJSON
a
->
Property
jsonEnumRoundtrip
d
=
case
d
of
...
...
@@ -45,7 +45,7 @@ tests = testGroup "JSON" [
,
testProperty
"Datafield roundtrips"
(
jsonRoundtrip
@
Datafield
)
,
testProperty
"WithQuery roundtrips"
(
jsonRoundtrip
@
WithQuery
)
,
testProperty
"FrontendError roundtrips"
(
jsonRoundtrip
@
FrontendError
)
,
testProperty
"BackendError
Type roundtrips"
(
jsonEnumRoundtrip
(
Dict
@
_
@
BackendErrorTyp
e
))
,
testProperty
"BackendError
Code roundtrips"
(
jsonEnumRoundtrip
(
Dict
@
_
@
BackendErrorCod
e
))
,
testCase
"WithQuery frontend compliance"
testWithQueryFrontend
,
testGroup
"Phylo"
[
testProperty
"PeriodToNode"
(
jsonRoundtrip
@
PeriodToNodeData
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment