packages feed

request-0.4.2.0: src/Network/HTTP/Request/Internal/Sse.hs

{-# LANGUAGE OverloadedStrings #-}

module Network.HTTP.Request.Internal.Sse
  ( findEventSep,
    parseSseBlock,
  )
where

import qualified Data.ByteString as BS
import Data.List (foldl')
import Data.Maybe (mapMaybe)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Network.HTTP.Request.Internal.Types (SseEvent (..))

findEventSep :: BS.ByteString -> Maybe (Int, Int)
findEventSep bs =
  foldr
    earlier
    Nothing
    [ tryFind "\r\n\r\n",
      tryFind "\r\n\n",
      tryFind "\r\n\r",
      tryFind "\n\r\n",
      tryFind "\n\n",
      tryFind "\n\r",
      tryFind "\r\r\n",
      tryFind "\r\r"
    ]
  where
    tryFind pat =
      let (h, t) = BS.breakSubstring pat bs
       in if BS.null t then Nothing else Just (BS.length h, BS.length h + BS.length pat)
    earlier a Nothing = a
    earlier Nothing b = b
    earlier a@(Just (s1, e1)) b@(Just (s2, e2))
      | s1 < s2 = a
      | s1 > s2 = b
      | e1 >= e2 = a
      | otherwise = b

parseSseField :: T.Text -> Maybe (T.Text, T.Text)
parseSseField line
  | T.null line = Nothing
  | T.head line == ':' = Nothing
  | otherwise =
      let (name, rest) = T.breakOn ":" line
          value
            | T.null rest = ""
            | otherwise = case T.stripPrefix " " (T.drop 1 rest) of
                Just v -> v
                Nothing -> T.drop 1 rest
       in Just (name, value)

parseSseBlock :: BS.ByteString -> Maybe SseEvent
parseSseBlock block =
  let txt = T.replace "\r" "\n" . T.replace "\r\n" "\n" $ T.decodeUtf8Lenient block
      ls = T.lines txt
      fields = mapMaybe parseSseField ls
      dataFields = [v | (k, v) <- fields, k == "data"]
      dataVal = T.intercalate "\n" dataFields
      lastField name = foldl' (\current (k, v) -> if k == name then Just v else current) Nothing fields
      typeVal = lastField "event"
      idVal = lastField "id"
   in if null dataFields then Nothing else Just (SseEvent dataVal typeVal idVal)