-- | -- Module : Amazonka.Data.Query -- Copyright : (c) 2013-2023 Brendan Hay -- License : Mozilla Public License, v. 2.0. -- Maintainer : Brendan Hay -- Stability : provisional -- Portability : non-portable (GHC extensions) module Amazonka.Data.Query where import Amazonka.Data.ByteString import Amazonka.Data.Text import Amazonka.Prelude import qualified Data.ByteString.Builder as Build import qualified Data.ByteString.Char8 as BS8 import qualified Data.ByteString.Lazy as LBS import qualified Data.HashMap.Strict as HashMap import qualified Data.List as List import qualified Data.Text.Encoding as Text import qualified Network.HTTP.Types.URI as URI -- | Structured representation of a query string. -- -- Some operations (e.g., [sqs:CreateQueue](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html)) -- use query parameters to pass structured data like lists and maps, -- which is why this type is more complicatated than the -- @[(ByteString, Maybe ByteString)]@ from @http-types@ that you may -- have expected here. data QueryString = QList [QueryString] | QPair ByteString QueryString | QValue (Maybe ByteString) deriving stock (Eq, Show) instance Semigroup QueryString where a <> b = case (a, b) of (QList l, QList r) -> QList (l ++ r) (QList l, r) -> QList (r : l) (l, QList r) -> QList (l : r) (l, r) -> QList [l, r] instance Monoid QueryString where mempty = QList [] mappend = (<>) instance IsString QueryString where fromString = parseQueryString . fromString {-# INLINE fromString #-} parseQueryString :: ByteString -> QueryString parseQueryString bs | BS8.null bs = mempty | otherwise = QList (map breakPair . filter (not . BS8.null) $ BS8.split '&' bs) where breakPair x = case BS8.break (== '=') x of ("", "") -> mempty ("", v) -> stripValue v (k, v) -> QPair (URI.urlDecode True k) (stripValue v) stripValue :: ByteString -> QueryString stripValue x = case x of "" -> QValue Nothing "=" -> QValue Nothing _ -> QValue (Just (URI.urlDecode True $ BS8.drop 1 x)) -- FIXME: use Builder instance ToByteString QueryString where toBS = LBS.toStrict . Build.toLazyByteString . cat . List.sort . enc Nothing where enc :: Maybe ByteString -> QueryString -> [ByteString] enc p = \case QList xs -> concatMap (enc p) xs QPair (URI.urlEncode True -> k) x | Just n <- p -> enc (Just (n <> kdelim <> k)) x -- .key | otherwise -> enc (Just k) x -- key QValue (Just (URI.urlEncode True -> v)) | Just n <- p -> [n <> vsep <> v] -- key=value | otherwise -> [v <> vsep] -- value= -- note: required for signing. _ | Just n <- p -> [n <> vsep] -- key= -- note: this case required for request signing | otherwise -> [] cat :: [ByteString] -> ByteStringBuilder cat [] = mempty cat [x] = Build.byteString x cat (x : xs) = Build.byteString x <> ksep <> cat xs kdelim = "." ksep = "&" vsep = "=" pair :: ToQuery a => ByteString -> a -> QueryString -> QueryString pair k v = mappend (QPair k (toQuery v)) infixr 7 =: (=:) :: ToQuery a => ByteString -> a -> QueryString k =: v = QPair k (toQuery v) toQueryList :: (IsList a, ToQuery (Item a)) => ByteString -> a -> QueryString toQueryList k = QPair k . QList . zipWith f [1 ..] . toList where f :: ToQuery a => Int -> a -> QueryString f n v = toBS n =: toQuery v toQueryMap :: (ToQuery k, ToQuery v) => ByteString -> ByteString -> ByteString -> HashMap k v -> QueryString toQueryMap e k v = toQueryList e . map f . HashMap.toList where f (x, y) = QList [k =: toQuery x, v =: toQuery y] class ToQuery a where toQuery :: a -> QueryString default toQuery :: ToText a => a -> QueryString toQuery = toQuery . toText instance ToQuery QueryString where toQuery = id instance (ToByteString k, ToQuery v) => ToQuery (k, v) where toQuery (k, v) = QPair (toBS k) (toQuery v) instance ToQuery Char where toQuery = toQuery . BS8.singleton instance ToQuery ByteString where toQuery "" = QValue Nothing toQuery bs = QValue (Just bs) instance ToQuery Text where toQuery = toQuery . Text.encodeUtf8 instance ToQuery Int where toQuery = toQuery . toBS instance ToQuery Integer where toQuery = toQuery . toBS instance ToQuery Double where toQuery = toQuery . toBS instance ToQuery Natural where toQuery = toQuery . toBS instance ToQuery a => ToQuery (Maybe a) where toQuery (Just x) = toQuery x toQuery Nothing = mempty instance ToQuery Bool where toQuery True = toQuery ("true" :: ByteString) toQuery False = toQuery ("false" :: ByteString)