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
148
Issues
148
List
Board
Labels
Milestones
Merge Requests
7
Merge Requests
7
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