Search code examples
haskellyesodbytestring

Why can't I return a ByteString from a Handler in Yesod?


I'm trying to return a ByteString from my Handler function in Yesod:

getHomeR :: Handler ByteString
getHomeR = return "foo"

but I'm getting this error:

/Users/maximiliantagher/Documents/Mercury/hs/mercury-web-backend/src/Application.hs:48:1: error:
    • No instance for (ToTypedContent ByteString)
        arising from a use of ‘yesodRunner’
    • In the expression:
        yesodRunner getHomeR env1404_axwe (Just HomeR) req1404_axwf
      In a case alternative:
          "GET"
            -> yesodRunner getHomeR env1404_axwe (Just HomeR) req1404_axwf
      In the expression:
        case Network.Wai.Internal.requestMethod req1404_axwf of {
          "GET"
            -> yesodRunner getHomeR env1404_axwe (Just HomeR) req1404_axwf
          _ -> yesodRunner
                 (void badMethod) env1404_axwe (Just HomeR) req1404_axwf }

Why is this happening and why doesn't ByteString have a ToTypedContent instance?


Solution

  • The ToTypedContent class describes what the content-type is of the data. So types that have an associated content type (e.g. UTF 8 for Text or Value (JSON)) can have a natural ToTypedContent instance.

    The problem with ByteString is that it describes any binary data—your ByteString could be a PNG, a JPEG, or anything, so it's unclear what content type to give it.

    If you really just want to return binary data, the octet-stream content type is appropriate:

    getHomeR :: Handler TypedContent
    getHomeR = return $ TypedContent typeOctet $ toContent ("x" :: ByteString)
    

    But you should try for a better content type if possible (e.g. image/jpeg for JPEG).

    In that case, you can either use the TypedContent manually as above, or write your own instance of ToTypedContent for a newtype over ByteString

    newtype Jpeg = Jpeg ByteString deriving (Show, ToContent)
    
    instance ToTypedContent Jpeg where
        toTypedContent h = TypedContent "image/jpeg" (toContent h)