Giter Site home page Giter Site logo

cdepillabout / servant-checked-exceptions Goto Github PK

View Code? Open in Web Editor NEW
71.0 7.0 14.0 249 KB

type-level errors for Servant APIs.

Home Page: https://hackage.haskell.org/package/servant-checked-exceptions

License: BSD 3-Clause "New" or "Revised" License

Makefile 1.44% Haskell 98.56%
haskell servant exceptions errors json server client docs hacktoberfest

servant-checked-exceptions's Issues

add to stackage

It would be nice to add this package to stackage.

Before adding to stackage, I'd like to implement #8. It would also be nice to figure out what to do about #5.

Build failure with latest servant libraries

[ 7 of 12] Compiling Servant.Checked.Exceptions.Internal.Servant.Server ( src/Servant/Checked/Exceptions/Internal/Servant/Server
.hs, dist/build/Servant/Checked/Exceptions/Internal/Servant/Server.o )

src/Servant/Checked/Exceptions/Internal/Servant/Server.hs:42:10: warning: [-Wmissing-methods]
    • No explicit implementation for
        ‘hoistServerWithContext’
    • In the instance declaration for
        ‘HasServer (Throws e :> api) context’
   |
42 | instance (HasServer (Throwing '[e] :> api) context) =>
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

src/Servant/Checked/Exceptions/Internal/Servant/Server.hs:57:10: warning: [-Wmissing-methods]
    • No explicit implementation for
        ‘hoistServerWithContext’
    • In the instance declaration for
        ‘HasServer (Throwing es :> Verb method status ctypes a) context’
   |
57 | instance (HasServer (Verb method status ctypes (Envelope es a)) context) =>
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

src/Servant/Checked/Exceptions/Internal/Servant/Server.hs:72:10: warning: [-Wmissing-methods]
    • No explicit implementation for
        ‘hoistServerWithContext’
    • In the instance declaration for
        ‘HasServer (NoThrow :> Verb method status ctypes a) context’
   |
72 | instance (HasServer (Verb method status ctypes (Envelope '[] a)) context) =>
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

How can we perform case analysis on envelopes?

I'm seeing unusual behaviour with the following piece of client code:

getLocationById   
  :: Integer  
  -> ClientM (Envelope '[InvalidLocationError, NoSuchLocationError] Location) 
getLocationById = client api  
  
main :: IO () 
main = do 
  manager <- newManager defaultManagerSettings
  let env = mkClientEnv manager baseUrl   
  print =<< runClientM program env
  
program :: ClientM () 
program = do  
  liftIO $ putStrLn "Enter the ID of a location (non-negative integer):"  
  locationId <- liftIO readLn 
  result <- getLocationById locationId
  liftIO $ do 
    putStrLn "Result:"
    putStrLn $ catchesEnvelope (show, show) show result

A successful lookup with the program produces the following output:

Enter the ID of a location (non-negative integer):
0
Result:
Location {locationId = 0, locationName = "Amsterdam"}
Right ()

But an unsuccessful lookup produces the following output:

Enter the ID of a location (non-negative integer):
-1
Left (FailureResponse (Response {responseStatusCode = Status {statusCode = 400, statusMessage = "Bad Request"}, responseHeaders = fromList [("Transfer-Encoding","chunked"),("Date","Wed, 13 Feb 2019 01:47:56 GMT"),("Server","Warp/3.2.26"),("Content-Type","application/json;charset=utf-8")], responseHttpVersion = HTTP/1.1, responseBody = "{\"err\":[]}"}))

In the event that calling getLocationById results in an exception (either InvalidLocationError or NoSuchLocationError), I would expect the case analysis of catchesEnvelope to kick in. However, what actually happens is that putStrLn "Result" is never executed.

It seems servant-client returns a Left (FailureResponse ..) in the case of statusCode == 400 or statusCode == 404, causing the above function to short-circuit, meaning that we're unable to perform case analysis.

Is this behaviour intentional? If not, is there a known workaround?

(original source: https://github.com/jonathanknowles/servant-checked-exceptions-example)

fix doctests on GHC-9

The doctests for servant-checked-exceptions-core appear to not work on GHC-9.

See commercialhaskell/stackage#6092 for an example of what this looks like.

This should be fixed so that doctests do successfully work on GHC-9.

servant-checked-exceptions-core should probably be moved to either use cabal-doctest or cabal-docspec.

Simplify the From/ToJSON instances for Envelope

Currently, Envelope wraps errors with { "err": ... } and successes with { "data": ... }. These cannot be customized by the user, and they prevent some structures I would like to be able to return. I think the ToJSON instance should simply defer to the underlying ToJSON instances without doing any wrapping to avoid imposing requirements on the user’s responses.

I also question whether or not the FromJSON instance is even a good idea at all. I’m not sure when I would want to construct an Envelope from JSON input, and as the documentation mentions, it’s problematic if the instances are ambiguous. My preference would simply be to eliminate the FromJSON instance entirely.

throw envelope errors in a short-circuiting monad

It would be nice to be able to throw Envelope errors in some sort of short circuiting monad.

I'm not sure if there is a good / clean / easy way to implement this, but I would be interested in different possibilities.

servant-client doesn't return envelopes for non-2xx status codes

Full reproduction: https://github.com/bergmark/sce

Using servant-0.13.1 and servant-checked-exceptions-2.0.0.0

data BadReq = BadReq deriving Show

deriveJSON defaultOptions ''BadReq

instance ErrStatus BadReq where toErrStatus BadReq = badRequest400

type API = Throws BadReq :> Get '[JSON] Value

api :: Proxy API
api = Proxy

server :: Server API
server = pureErrEnvelope BadReq

main :: IO ()
main = do
  tid <- forkIO (run 8080 $ serve api server)
  manager' <- newManager defaultManagerSettings
  print =<< runClientM (client api) (mkClientEnv manager' (BaseUrl Http "localhost" 8080 ""))
  killThread tid

I expected this to print the Error envelope but instead I get

Left (FailureResponse (Response {responseStatusCode = Status {statusCode = 400, statusMessage = "Bad Request"}, responseHeaders = fromList [("Transfer-Encoding","chunked"),("Date","Sun, 15 Jul 2018 17:53:24 GMT"),("Server","Warp/3.2.22"),("Content-Type","application/json;charset=utf-8")], responseHttpVersion = HTTP/1.1, responseBody = "{\"err\":[]}"}))

If i change the ErrStatus instance to return ok200 I get the envelope as expected:

Right (ErrEnvelope (Identity BadReq))

Is there something I'm missing?

NamedRoutes not implemented

I’m trying to use the servant-checked-exceptions package with NamedRoutes. But I want to add the "Throws" at the top of the api, for instance:

data ApiFoo mode = ApiFoo
  {
    get :: mode :- Get '[JSON] Foo
   , post :: mode :- "new" :> ReqBody '[JSON] Foo :> Post '[JSON] ()
  }

data ApiStore mode = ApiStore
  {
    fooStore :: mode :- "foo" :> NamedRoutes ApiFoo
  , barStore :: mode :- "bar" :> NamedRoutes ApiBar

  }

type API = Throws StoreError :> "store" :> NamedRoutes ApiStore

But Throws is not able to traverse NamedRoutes types. I tried to implement a HasServer (Throwing '[e] :> NamedRoutes api) instance, but I seem unable to do it correctly (I tried to find other packages that implement a similar instance too to use as example, in vain). I’d appreciate help for implementing that.

Thanks!

Break into combinator/backend packages

We use servant-checked-exceptions in the frontend, and would like to avoid a transitive dependency on servant-server, servant-client, conduit etc.

What do you think of having two packages here servant-checked-exceptions-core (just types and pure functions) and servant-checked-exceptions (instances and functions that depend on server-side code)?

Build failure with servant 0.16

[3 of 6] Compiling Servant.Checked.Exceptions.Internal.Servant.Server ( src/Servant/Checked/Exceptions/Interna
l/Servant/Server.hs, dist/build/Servant/Checked/Exceptions/Internal/Servant/Server.o )

src/Servant/Checked/Exceptions/Internal/Servant/Server.hs:49:5: error:
    Module
    ‘Servant.Server.Internal.RoutingApplication’
    does not export
    ‘Delayed’
   |
49 |   ( Delayed
   |     ^^^^^^^

src/Servant/Checked/Exceptions/Internal/Servant/Server.hs:50:5: error:
    Module
    ‘Servant.Server.Internal.RoutingApplication’
    does not export
    ‘DelayedIO’
   |
50 |   , DelayedIO
   |     ^^^^^^^^^

src/Servant/Checked/Exceptions/Internal/Servant/Server.hs:51:5: error:
    Module
    ‘Servant.Server.Internal.RoutingApplication’
    does not export
    ‘RouteResult(FailFatal, Route)’
   |
51 |   , RouteResult(FailFatal, Route)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/Servant/Checked/Exceptions/Internal/Servant/Server.hs:52:5: error:
    Module
    ‘Servant.Server.Internal.RoutingApplication’
    does not export
    ‘addAcceptCheck’
   |
52 |   , addAcceptCheck
   |     ^^^^^^^^^^^^^^

And more

add note about having to be careful with letting aeson derive FromJSON and ToJSON instances

By default, Aeson may derive instances for error types that cannot be differentiated from one another.

For example, given the following code:

data FooErr = FooErr deriving (Eq, Read, Show)
 
$(deriveJSON defaultOptions ''FooErr)

data BarErr = BarErr deriving (Eq, Read, Show)
 
$(deriveJSON defaultOptions ''BarErr)

deriveJSON will derive instances that work like the following:

> encode (toErrEnvelope FooErr :: Envelope '[FooErr] Int)
"{\"err\":[]}"
> encode (toErrEnvelope BarErr :: Envelope '[BarErr] Int)
"{\"err\":[]}"

Just by looking at the output JSON, it is not possible to tell whether the error was originally a FooErr or a BarErr.

It is necessary to write the ToJSON and FromJSON instances by hand like the following:

data FooErr = FooErr deriving (Eq, Read, Show)

instance FromJSON FooErr where
  parseJSON = withText "FooErr" $ \case
    "FooErr" -> pure FooErr
    other ->
      fail $ "Trying to parse FooErr, but got \"" <> unpack other <> "\""

instance ToJSON FooErr where { toJSON _ = String "FooErr" }

data BarErr = BarErr deriving (Eq, Read, Show)

instance FromJSON BarErr where
  parseJSON = withText "BarErr" $ \case
    "BarErr" -> pure BarErr
    other ->
      fail $ "Trying to parse BarErr, but got \"" <> unpack other <> "\""

instance ToJSON BarErr where { toJSON _ = String "BarErr" }

This lets the FromJSON and ToJSON instance for Envelope work correctly:

> encode (toErrEnvelope FooErr  :: Envelope '[FooErr ] Int)
"{\"err\":\"FooErr\"}"
> encode (toErrEnvelope BarErr  :: Envelope '[BarErr ] Int)
"{\"err\":\"BarErr\"}"

It would be nice to add a note warning about this somewhere in this package. Probably on the ToJSON and FromJSON instances for Envelope.

Ability to place Throws/Throwing before multiple grouped route definitions

Currently, this doesn’t work:

data API = Throws MyErr :> "foo" :> Get '[JSON] ()

Specifically, the relevant HasServer instance does not exist, so this is probably related to #4. This would be very useful, since it would allow doing something like this:

data API = Throws MyErr :> ("foo" :> Get '[JSON] () :<|> "bar" :> Get '[JSON] ())

…which helps to reduce boilerplate when a group of APIs all produce the same errors.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.