@@ -13,67 +13,70 @@
13-- such as URL pieces, headers and query parameters.
13-- such as URL pieces, headers and query parameters.
14module Web.Internal.HttpApiData where
14module Web.Internal.HttpApiData where
16−#if __GLASGOW_HASKELL__ < 710
16+import Prelude ()
17−import Control.Applicative
17+import Prelude.Compat
18−import Data.Foldable (Foldable)
19−import Data.Traversable (Traversable (traverse))
22import Control.Arrow (left, (&&&))
19import Control.Arrow (left, (&&&))
23import Control.Monad ((<=<))
20import Control.Monad ((<=<))
24−
21+import qualified Data.Attoparsec.Text as Atto
22+import qualified Data.Attoparsec.Time as Atto
25import Data.ByteString (ByteString)
23import Data.ByteString (ByteString)
26import qualified Data.ByteString as BS
24import qualified Data.ByteString as BS
25+import qualified Data.ByteString.Builder as BS
27import qualified Data.ByteString.Lazy as LBS
26import qualified Data.ByteString.Lazy as LBS
28−import Data.Monoid
27+import Data.Coerce (coerce)
29−
28+import Data.Data (Data)
30import qualified Data.Fixed as F
29import qualified Data.Fixed as F
31−import Data.Int
30+import Data.Int (Int16, Int32, Int64, Int8)
32−import Data.Word
31+import Data.Monoid (All (..), Any (..), Dual (..),
33−
32+ First (..), Last (..),
33+ Product (..), Sum (..))
34+import Data.Semigroup (Semigroup (..))
35+import qualified Data.Semigroup as Semi
36+import Data.Tagged (Tagged (..))
34import Data.Text (Text)
37import Data.Text (Text)
35import qualified Data.Text as T
38import qualified Data.Text as T
36−import Data.Text.Encoding (decodeUtf8With, decodeUtf8', encodeUtf8)
39+import Data.Text.Encoding (decodeUtf8', decodeUtf8With,
37import Data.Text.Encoding.Error (lenientDecode)
41import Data.Text.Encoding.Error (lenientDecode)
38import qualified Data.Text.Lazy as L
42import qualified Data.Text.Lazy as L
39import Data.Text.Read (Reader, decimal, rational,
43import Data.Text.Read (Reader, decimal, rational,
42−import Data.Time
45+import Data.Time (Day, FormatTime, LocalTime,
43−#if __GLASGOW_HASKELL__ < 710
46+ NominalDiffTime, TimeOfDay,
47+ UTCTime, ZonedTime, formatTime)
44−import Data.Time.Locale.Compat
48+import Data.Time.Locale.Compat (defaultTimeLocale,
45−#endif
49+ iso8601DateFormat)
46−import Data.Version
50+import Data.Typeable (Typeable)
47−
51+import qualified Data.UUID.Types as UUID
48−#if MIN_VERSION_base(4,8,0)
52+import Data.Version (Version, parseVersion,
49−import Data.Void
54+import Data.Void (Void, absurd)
55+import Data.Word (Word16, Word32, Word64, Word8)
56+import qualified Network.HTTP.Types as H
50−import Numeric.Natural
57+import Numeric.Natural (Natural)
53import Text.ParserCombinators.ReadP (readP_to_S)
58import Text.ParserCombinators.ReadP (readP_to_S)
54import Text.Read (readMaybe)
59import Text.Read (readMaybe)
60+import Web.Cookie (SetCookie, parseSetCookie,
63+#if MIN_VERSION_time(1,9,1)
64+import Data.Time (nominalDiffTimeToSeconds,
65+ secondsToNominalDiffTime)
56#if USE_TEXT_SHOW
68#if USE_TEXT_SHOW
57import TextShow (TextShow, showt)
69import TextShow (TextShow, showt)
60−import qualified Data.UUID.Types as UUID
62−import qualified Data.ByteString.Builder as BS
63−import Data.Data (Data)
64−import Data.Typeable (Typeable)
65−import qualified Network.HTTP.Types as H
67−import qualified Data.Attoparsec.Text as Atto
68−import qualified Data.Attoparsec.Time as Atto
70−import Web.Cookie (SetCookie, parseSetCookie,
75-- >>> data BasicAuthToken = BasicAuthToken Text deriving (Show)
76-- >>> data BasicAuthToken = BasicAuthToken Text deriving (Show)
76-- >>> instance FromHttpApiData BasicAuthToken where parseHeader h = BasicAuthToken <$> parseHeaderWithPrefix "Basic " h; parseQueryParam p = BasicAuthToken <$> parseQueryParam p
77-- >>> instance FromHttpApiData BasicAuthToken where parseHeader h = BasicAuthToken <$> parseHeaderWithPrefix "Basic " h; parseQueryParam p = BasicAuthToken <$> parseQueryParam p
78+-- >>> import Data.Time
79+-- >>> import Data.Version
78-- | Convert value to HTTP API data.
81-- | Convert value to HTTP API data.
@@ -432,10 +435,8 @@ instance ToHttpApiData Version where
432 toUrlPiece = T.pack . showVersion
435 toUrlPiece = T.pack . showVersion
433 toEncodedUrlPiece = unsafeToEncodedUrlPiece
436 toEncodedUrlPiece = unsafeToEncodedUrlPiece
435−#if MIN_VERSION_base(4,8,0)
436instance ToHttpApiData Void where toUrlPiece = absurd
438instance ToHttpApiData Void where toUrlPiece = absurd
437instance ToHttpApiData Natural where toUrlPiece = showt; toEncodedUrlPiece = unsafeToEncodedUrlPiece
439instance ToHttpApiData Natural where toUrlPiece = showt; toEncodedUrlPiece = unsafeToEncodedUrlPiece
440instance ToHttpApiData Bool where toUrlPiece = showTextData; toEncodedUrlPiece = unsafeToEncodedUrlPiece
441instance ToHttpApiData Bool where toUrlPiece = showTextData; toEncodedUrlPiece = unsafeToEncodedUrlPiece
441instance ToHttpApiData Ordering where toUrlPiece = showTextData; toEncodedUrlPiece = unsafeToEncodedUrlPiece
442instance ToHttpApiData Ordering where toUrlPiece = showTextData; toEncodedUrlPiece = unsafeToEncodedUrlPiece
@@ -494,36 +495,77 @@ instance ToHttpApiData UTCTime where
494 toUrlPiece = timeToUrlPiece "%H:%M:%S%QZ"
495 toUrlPiece = timeToUrlPiece "%H:%M:%S%QZ"
495 toEncodedUrlPiece = unsafeToEncodedUrlPiece
496 toEncodedUrlPiece = unsafeToEncodedUrlPiece
498+-- The CPP in both this function and the function after it are to avoid
499+-- exporting @nominalDiffTimeToSeconds@ and @secondsToNominalDiffTime@,
500+-- since these names are already used by @Data.Time@ from the @time@ library
501+-- starting in version @1.9.1@.
502+nominalDiffTimeToSecs :: NominalDiffTime -> F.Pico
503+nominalDiffTimeToSecs =
504+#if !MIN_VERSION_time(1,9,1)
507+ nominalDiffTimeToSeconds
510+secsToNominalDiffTime :: F.Pico -> NominalDiffTime
511+secsToNominalDiffTime =
512+#if !MIN_VERSION_time(1,9,1)
515+ secondsToNominalDiffTime
497instance ToHttpApiData NominalDiffTime where
518instance ToHttpApiData NominalDiffTime where
498− toUrlPiece = toUrlPiece . (floor :: NominalDiffTime -> Integer)
519+ toUrlPiece = toUrlPiece . nominalDiffTimeToSecs
499 toEncodedUrlPiece = unsafeToEncodedUrlPiece
520 toEncodedUrlPiece = unsafeToEncodedUrlPiece
501instance ToHttpApiData String where toUrlPiece = T.pack
522instance ToHttpApiData String where toUrlPiece = T.pack
502instance ToHttpApiData Text where toUrlPiece = id
523instance ToHttpApiData Text where toUrlPiece = id
503instance ToHttpApiData L.Text where toUrlPiece = L.toStrict
524instance ToHttpApiData L.Text where toUrlPiece = L.toStrict
505−instance ToHttpApiData All where toUrlPiece = toUrlPiece . getAll; toEncodedUrlPiece = toEncodedUrlPiece . getAll
526+instance ToHttpApiData All where
527+ toUrlPiece = coerce (toUrlPiece :: Bool -> Text)
528+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: Bool -> BS.Builder)
506−instance ToHttpApiData Any where toUrlPiece = toUrlPiece . getAny; toEncodedUrlPiece = toEncodedUrlPiece . getAny
530+instance ToHttpApiData Any where
531+ toUrlPiece = coerce (toUrlPiece :: Bool -> Text)
532+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: Bool -> BS.Builder)
508instance ToHttpApiData a => ToHttpApiData (Dual a) where
534instance ToHttpApiData a => ToHttpApiData (Dual a) where
509− toUrlPiece = toUrlPiece . getDual
535+ toUrlPiece = coerce (toUrlPiece :: a -> Text)
510− toEncodedUrlPiece = toEncodedUrlPiece . getDual
536+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: a -> BS.Builder)
512instance ToHttpApiData a => ToHttpApiData (Sum a) where
538instance ToHttpApiData a => ToHttpApiData (Sum a) where
513− toUrlPiece = toUrlPiece . getSum
539+ toUrlPiece = coerce (toUrlPiece :: a -> Text)
514− toEncodedUrlPiece = toEncodedUrlPiece . getSum
540+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: a -> BS.Builder)
516instance ToHttpApiData a => ToHttpApiData (Product a) where
542instance ToHttpApiData a => ToHttpApiData (Product a) where
517− toUrlPiece = toUrlPiece . getProduct
543+ toUrlPiece = coerce (toUrlPiece :: a -> Text)
518− toEncodedUrlPiece = toEncodedUrlPiece . getProduct
544+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: a -> BS.Builder)
520instance ToHttpApiData a => ToHttpApiData (First a) where
546instance ToHttpApiData a => ToHttpApiData (First a) where
521− toUrlPiece = toUrlPiece . getFirst
547+ toUrlPiece = coerce (toUrlPiece :: Maybe a -> Text)
522− toEncodedUrlPiece = toEncodedUrlPiece . getFirst
548+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: Maybe a -> BS.Builder)
524instance ToHttpApiData a => ToHttpApiData (Last a) where
550instance ToHttpApiData a => ToHttpApiData (Last a) where
525− toUrlPiece = toUrlPiece . getLast
551+ toUrlPiece = coerce (toUrlPiece :: Maybe a -> Text)
552+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: Maybe a -> BS.Builder)
554+instance ToHttpApiData a => ToHttpApiData (Semi.Min a) where
555+ toUrlPiece = coerce (toUrlPiece :: a -> Text)
556+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: a -> BS.Builder)
558+instance ToHttpApiData a => ToHttpApiData (Semi.Max a) where
559+ toUrlPiece = coerce (toUrlPiece :: a -> Text)
560+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: a -> BS.Builder)
562+instance ToHttpApiData a => ToHttpApiData (Semi.First a) where
563+ toUrlPiece = coerce (toUrlPiece :: a -> Text)
526− toEncodedUrlPiece = toEncodedUrlPiece . getLast
564+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: a -> BS.Builder)
566+instance ToHttpApiData a => ToHttpApiData (Semi.Last a) where
567+ toUrlPiece = coerce (toUrlPiece :: a -> Text)
568+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: a -> BS.Builder)
529-- >>> toUrlPiece (Just "Hello")
571-- >>> toUrlPiece (Just "Hello")
@@ -555,6 +597,12 @@ instance ToHttpApiData SetCookie where
555 toHeader = LBS.toStrict . BS.toLazyByteString . renderSetCookie
597 toHeader = LBS.toStrict . BS.toLazyByteString . renderSetCookie
556 -- toEncodedUrlPiece = renderSetCookie -- doesn't do things.
598 -- toEncodedUrlPiece = renderSetCookie -- doesn't do things.
600+instance ToHttpApiData a => ToHttpApiData (Tagged b a) where
601+ toUrlPiece = coerce (toUrlPiece :: a -> Text)
602+ toHeader = coerce (toHeader :: a -> ByteString)
603+ toQueryParam = coerce (toQueryParam :: a -> Text)
604+ toEncodedUrlPiece = coerce (toEncodedUrlPiece :: a -> BS.Builder)
559-- >>> parseUrlPiece "_" :: Either Text ()
607-- >>> parseUrlPiece "_" :: Either Text ()
560-- Right ()
608-- Right ()
@@ -566,7 +614,7 @@ instance FromHttpApiData Char where
566 parseUrlPiece s =
614 parseUrlPiece s =
567 case T.uncons s of
615 case T.uncons s of
568 Just (c, s') | T.null s' -> pure c
616 Just (c, s') | T.null s' -> pure c
569− _ -> defaultParseError s
617+ _ -> defaultParseError s
572-- >>> showVersion <$> parseUrlPiece "1.2.3"
620-- >>> showVersion <$> parseUrlPiece "1.2.3"
@@ -577,7 +625,6 @@ instance FromHttpApiData Version where
577 ((x, ""):_) -> pure x
625 ((x, ""):_) -> pure x
578 _ -> defaultParseError s
626 _ -> defaultParseError s
580−#if MIN_VERSION_base(4,8,0)
581-- | Parsing a @'Void'@ value is always an error, considering @'Void'@ as a data type with no constructors.
628-- | Parsing a @'Void'@ value is always an error, considering @'Void'@ as a data type with no constructors.
582instance FromHttpApiData Void where
629instance FromHttpApiData Void where
583 parseUrlPiece _ = Left "Void cannot be parsed!"
630 parseUrlPiece _ = Left "Void cannot be parsed!"
@@ -588,7 +635,6 @@ instance FromHttpApiData Natural where
589 then Left ("underflow: " <> s <> " (should be a non-negative integer)")
636 then Left ("underflow: " <> s <> " (should be a non-negative integer)")
590 else Right (fromInteger n)
637 else Right (fromInteger n)
593instance FromHttpApiData Bool where parseUrlPiece = parseBoundedUrlPiece
639instance FromHttpApiData Bool where parseUrlPiece = parseBoundedUrlPiece
594instance FromHttpApiData Ordering where parseUrlPiece = parseBoundedUrlPiece
640instance FromHttpApiData Ordering where parseUrlPiece = parseBoundedUrlPiece
@@ -640,16 +686,21 @@ instance FromHttpApiData ZonedTime where parseUrlPiece = runAtto Atto.zonedTime
640-- Right 2015-10-03 00:14:24 UTC
686-- Right 2015-10-03 00:14:24 UTC
641instance FromHttpApiData UTCTime where parseUrlPiece = runAtto Atto.utcTime
687instance FromHttpApiData UTCTime where parseUrlPiece = runAtto Atto.utcTime
643−instance FromHttpApiData NominalDiffTime where parseUrlPiece = fmap fromInteger . parseUrlPiece
689+instance FromHttpApiData NominalDiffTime where parseUrlPiece = fmap secsToNominalDiffTime . parseUrlPiece
691+instance FromHttpApiData All where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text Bool)
692+instance FromHttpApiData Any where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text Bool)
645−instance FromHttpApiData All where parseUrlPiece = fmap All . parseUrlPiece
694+instance FromHttpApiData a => FromHttpApiData (Dual a) where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text a)
695+instance FromHttpApiData a => FromHttpApiData (Sum a) where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text a)
696+instance FromHttpApiData a => FromHttpApiData (Product a) where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text a)
646−instance FromHttpApiData Any where parseUrlPiece = fmap Any . parseUrlPiece
697+instance FromHttpApiData a => FromHttpApiData (First a) where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text (Maybe a))
698+instance FromHttpApiData a => FromHttpApiData (Last a) where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text (Maybe a))
648−instance FromHttpApiData a => FromHttpApiData (Dual a) where parseUrlPiece = fmap Dual . parseUrlPiece
700+instance FromHttpApiData a => FromHttpApiData (Semi.Min a) where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text a)
649−instance FromHttpApiData a => FromHttpApiData (Sum a) where parseUrlPiece = fmap Sum . parseUrlPiece
650−instance FromHttpApiData a => FromHttpApiData (Product a) where parseUrlPiece = fmap Product . parseUrlPiece
701+instance FromHttpApiData a => FromHttpApiData (Semi.Max a) where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text a)
651−instance FromHttpApiData a => FromHttpApiData (First a) where parseUrlPiece = fmap First . parseUrlPiece
702+instance FromHttpApiData a => FromHttpApiData (Semi.First a) where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text a)
652−instance FromHttpApiData a => FromHttpApiData (Last a) where parseUrlPiece = fmap Last . parseUrlPiece
703+instance FromHttpApiData a => FromHttpApiData (Semi.Last a) where parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text a)
655-- >>> parseUrlPiece "Just 123" :: Either Text (Maybe Int)
706-- >>> parseUrlPiece "Just 123" :: Either Text (Maybe Int)
@@ -700,6 +751,11 @@ instance FromHttpApiData SetCookie where
700 parseUrlPiece = parseHeader . encodeUtf8
751 parseUrlPiece = parseHeader . encodeUtf8
701 parseHeader = Right . parseSetCookie
752 parseHeader = Right . parseSetCookie
754+instance FromHttpApiData a => FromHttpApiData (Tagged b a) where
755+ parseUrlPiece = coerce (parseUrlPiece :: Text -> Either Text a)
756+ parseHeader = coerce (parseHeader :: ByteString -> Either Text a)
757+ parseQueryParam = coerce (parseQueryParam :: Text -> Either Text a)
703-------------------------------------------------------------------------------
759-------------------------------------------------------------------------------
704-- Attoparsec helpers
760-- Attoparsec helpers
705-------------------------------------------------------------------------------
761-------------------------------------------------------------------------------
@@ -708,3 +764,5 @@ runAtto :: Atto.Parser a -> Text -> Either Text a
708runAtto p t = case Atto.parseOnly (p <* Atto.endOfInput) t of
764runAtto p t = case Atto.parseOnly (p <* Atto.endOfInput) t of
709 Left err -> Left (T.pack err)
765 Left err -> Left (T.pack err)
710 Right x -> Right x
766 Right x -> Right x