Giter Club home page Giter Club logo

servant-errors's Introduction

servant-errors

Hackage MIT license Build status

Intro

This package implements a wai-middleware targeting servant-server applications with a goal of make it easier to generate custom server error responses.

Checkout accompanying blogpost for more details.

Motivation

By default, when your servant server application experiences an internal exception during endpoint route resolution, e.g. request body decode errors, the server response is just plain text with a status code in the HTTP headers.

At the same time, if you don't write custom code to customise error responses for errors thrown within servant route handlers; the default response is plain text with an HTTP content-type when provided within ServerError.

With servant-errors library, you get a single interface to structure and encode your error responses in one place as JSON error response or any other preferred form.

-- | A typical servant application is usually of this form

main :: IO ()
main = run 8001 (serve proxyApi handlers)

-- | With 'errorMw' from servant-errors library as an error processing middleware
main :: IO ()
main = run 8001
     $ errorMw @JSON @'["error", "status"]
     -- ^ Structures error response as JSON objects
     -- with 'error' and 'status' strings as error object field keys
     -- note they can be changed to any other preferred strings.
     $ serve proxyApi handlers

-- | The implementation above can also be written as below
-- if you want to output JSON error responses with 'error'
-- and 'status' as the JSON Object keys
main :: IO ()
main = run 8001
     $ errorMwDefJson
     -- ^ Default implementation of structuring error responses as JSON
     -- with no customisation option for JSON error object field keys
     $ serve proxyApi handlers

Complete Usage Example

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE TypeFamilies      #-}
{-# LANGUAGE TypeOperators     #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module Main where

import           Data.Aeson (FromJSON, ToJSON)
import           Data.Proxy (Proxy(..))
import           Data.Text (Text)
import           GHC.Generics (Generic)
import           Network.Wai (Application)
import           Network.Wai.Handler.Warp (run)
import           Network.Wai.Middleware.Servant.Errors (errorMw, HasErrorBody(..))
import           Servant (ReqBody, (:>), Post, JSON, Accept(..), serve)

-- | A greet message data type for use as Request Body
newtype Greet = Greet { msg :: Text }
  deriving (Generic, Show, FromJSON, ToJSON)

-- servant application
main' :: IO ()
main' = run 8001
  $ errorMw @JSON @'["error", "status"]
  -- ^ @JSON specifies content type encoding of errors
  -- @["error", "status"] specifies error and code text label in resulting JSON error response
  -- when an empty type level list parameter for 'errorMw' is specified
  -- the 'HasErrorBody' instance defaults it to '@["error", "status"]' for JSON and PlainText instances
  -- hence; errorMw @JSON @'[] == @JSON @["error", "status"]
  $ serve api handler
  where
    handler = return . id
    api = Proxy @(ReqBody '[JSON] Greet :> Post '[JSON] Greet)

-- | Example Below shows the derivation of an alternative 'HasErrorBody' instance
-- for JSON error responses if you want to implement more customisation
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------

-- | We need a newtype like data type to avoid orphan instances, 'Ctyp' satisfy's that
-- Also note that 'HasErrorBody' instance requires an Accept instance for a content-type

data Ctyp a

{-
 if you are using GHC 8.6 and above you can make use of deriving Via
 for creating the Accept Instance

 >> data Ctyp a
 >>   deriving Accept via JSON
-}

instance Accept (Ctyp JSON) where
  contentType _ = contentType (Proxy @JSON)

instance HasErrorBody (Ctyp JSON) '[] where
  encodeError = undefined -- write your custom implementation

-- | Example Middleware with a different 'HasErrorBody' instance for JSON
errorMwJson :: Application -> Application
errorMwJson =  errorMw @(Ctyp JSON) @'[]

If a user submits a wrong request body during an HTTP request the HTTP error response is of the formats below;

Error response body while using this package's error Middleware .


{
    "status": 400,
    "error": "Error in $: key \"msg\" not present"
}
# The response is JSON encoded and contains an HTTP content-type header plus a status code.

Default error response without middleware;


 "Error in $: key \"msg\" not present"

# The response is plain text, contains an HTTP status code but lacks an HTTP content-type header.

Documentation

This README is tested by markdown-unlit to make sure the code builds. To keep that happy, we do need a main in this file, so ignore the following :)

main :: IO ()
main = pure ()

servant-errors's People

Contributors

3kyro avatar dmjio avatar epicallan avatar mbg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

servant-errors's Issues

Do not rewrite non-error responses

The middleware rewrites responses with HTTP status codes > 200, which is an odd choice.

It definitely does not sound as a good idea to rewrite any of the Success (2xx) responses. Also, I am not sure about Redirection (3xx), I guess it would be better not to touch them too (especially 304 Not Modified, which is a fairly common response semantically equivalent to Success).

Given that the library is called servant-errors, I think, it would make perfect sense to rewrite only error responses, that is, 4xx and 5xx.

Cut a release

The latest version on Hackage is 0.1.7.0 which supports only GHC version <= 8.10.7 (base <= 4.14). The version in master supports GHC version <= 9.4.8 (base <= 4.17).

Add ContentType HTML

Hey, thanks for the work here!

Do you mind adding text/html content type to the library? As far as I understand, it's always set to JSON?
https://github.com/epicallan/servant-errors/blob/master/src/Network/Wai/Middleware/Servant/Errors.hs#L166

data Ctyp a

instance Accept (Ctyp HTML) where
  contentType _ = contentType (Proxy @HTML)

instance HasErrorBody (Ctyp HTML) '[] where
  encodeError _ _ = renderHtml $ H.div "Hello, I am an error"

errorMwHtml :: Application -> Application
errorMwHtml = errorMw @(Ctyp HTML) @'[]

This is what I tried. I can match on the Accept header, but it's not returned properly as a text/html response.

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.