-- |
-- Module      : Amazonka.Data.Query
-- Copyright   : (c) 2013-2023 Brendan Hay
-- License     : Mozilla Public License, v. 2.0.
-- Maintainer  : Brendan Hay <brendan.g.hay+amazonka@gmail.com>
-- 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 (QueryString -> QueryString -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: QueryString -> QueryString -> Bool
$c/= :: QueryString -> QueryString -> Bool
== :: QueryString -> QueryString -> Bool
$c== :: QueryString -> QueryString -> Bool
Eq, Int -> QueryString -> ShowS
[QueryString] -> ShowS
QueryString -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [QueryString] -> ShowS
$cshowList :: [QueryString] -> ShowS
show :: QueryString -> String
$cshow :: QueryString -> String
showsPrec :: Int -> QueryString -> ShowS
$cshowsPrec :: Int -> QueryString -> ShowS
Show)

instance Semigroup QueryString where
  QueryString
a <> :: QueryString -> QueryString -> QueryString
<> QueryString
b = case (QueryString
a, QueryString
b) of
    (QList [QueryString]
l, QList [QueryString]
r) -> [QueryString] -> QueryString
QList ([QueryString]
l forall a. [a] -> [a] -> [a]
++ [QueryString]
r)
    (QList [QueryString]
l, QueryString
r) -> [QueryString] -> QueryString
QList (QueryString
r forall a. a -> [a] -> [a]
: [QueryString]
l)
    (QueryString
l, QList [QueryString]
r) -> [QueryString] -> QueryString
QList (QueryString
l forall a. a -> [a] -> [a]
: [QueryString]
r)
    (QueryString
l, QueryString
r) -> [QueryString] -> QueryString
QList [QueryString
l, QueryString
r]

instance Monoid QueryString where
  mempty :: QueryString
mempty = [QueryString] -> QueryString
QList []
  mappend :: QueryString -> QueryString -> QueryString
mappend = forall a. Semigroup a => a -> a -> a
(<>)

instance IsString QueryString where
  fromString :: String -> QueryString
fromString = ByteString -> QueryString
parseQueryString forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. IsString a => String -> a
fromString
  {-# INLINE fromString #-}

parseQueryString :: ByteString -> QueryString
parseQueryString :: ByteString -> QueryString
parseQueryString ByteString
bs
  | ByteString -> Bool
BS8.null ByteString
bs = forall a. Monoid a => a
mempty
  | Bool
otherwise =
      [QueryString] -> QueryString
QList (forall a b. (a -> b) -> [a] -> [b]
map ByteString -> QueryString
breakPair forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Bool
BS8.null) forall a b. (a -> b) -> a -> b
$ Char -> ByteString -> [ByteString]
BS8.split Char
'&' ByteString
bs)
  where
    breakPair :: ByteString -> QueryString
breakPair ByteString
x =
      case (Char -> Bool) -> ByteString -> (ByteString, ByteString)
BS8.break (forall a. Eq a => a -> a -> Bool
== Char
'=') ByteString
x of
        (ByteString
"", ByteString
"") -> forall a. Monoid a => a
mempty
        (ByteString
"", ByteString
v) -> ByteString -> QueryString
stripValue ByteString
v
        (ByteString
k, ByteString
v) -> ByteString -> QueryString -> QueryString
QPair (Bool -> ByteString -> ByteString
URI.urlDecode Bool
True ByteString
k) (ByteString -> QueryString
stripValue ByteString
v)

    stripValue :: ByteString -> QueryString
    stripValue :: ByteString -> QueryString
stripValue ByteString
x =
      case ByteString
x of
        ByteString
"" -> Maybe ByteString -> QueryString
QValue forall a. Maybe a
Nothing
        ByteString
"=" -> Maybe ByteString -> QueryString
QValue forall a. Maybe a
Nothing
        ByteString
_ -> Maybe ByteString -> QueryString
QValue (forall a. a -> Maybe a
Just (Bool -> ByteString -> ByteString
URI.urlDecode Bool
True forall a b. (a -> b) -> a -> b
$ Int -> ByteString -> ByteString
BS8.drop Int
1 ByteString
x))

-- FIXME: use Builder
instance ToByteString QueryString where
  toBS :: QueryString -> ByteString
toBS = ByteString -> ByteString
LBS.toStrict forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteStringBuilder -> ByteString
Build.toLazyByteString forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ByteString] -> ByteStringBuilder
cat forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Ord a => [a] -> [a]
List.sort forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe ByteString -> QueryString -> [ByteString]
enc forall a. Maybe a
Nothing
    where
      enc :: Maybe ByteString -> QueryString -> [ByteString]
      enc :: Maybe ByteString -> QueryString -> [ByteString]
enc Maybe ByteString
p = \case
        QList [QueryString]
xs -> forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Maybe ByteString -> QueryString -> [ByteString]
enc Maybe ByteString
p) [QueryString]
xs
        QPair (Bool -> ByteString -> ByteString
URI.urlEncode Bool
True -> ByteString
k) QueryString
x
          | Just ByteString
n <- Maybe ByteString
p -> Maybe ByteString -> QueryString -> [ByteString]
enc (forall a. a -> Maybe a
Just (ByteString
n forall a. Semigroup a => a -> a -> a
<> ByteString
kdelim forall a. Semigroup a => a -> a -> a
<> ByteString
k)) QueryString
x -- <prev>.key <recur>
          | Bool
otherwise -> Maybe ByteString -> QueryString -> [ByteString]
enc (forall a. a -> Maybe a
Just ByteString
k) QueryString
x -- key <recur>
        QValue (Just (Bool -> ByteString -> ByteString
URI.urlEncode Bool
True -> ByteString
v))
          | Just ByteString
n <- Maybe ByteString
p -> [ByteString
n forall a. Semigroup a => a -> a -> a
<> ByteString
vsep forall a. Semigroup a => a -> a -> a
<> ByteString
v] -- key=value
          | Bool
otherwise -> [ByteString
v forall a. Semigroup a => a -> a -> a
<> ByteString
vsep] -- value= -- note: required for signing.
        QueryString
_
          | Just ByteString
n <- Maybe ByteString
p -> [ByteString
n forall a. Semigroup a => a -> a -> a
<> ByteString
vsep] -- key=
          -- note: this case required for request signing
          | Bool
otherwise -> []

      cat :: [ByteString] -> ByteStringBuilder
      cat :: [ByteString] -> ByteStringBuilder
cat [] = forall a. Monoid a => a
mempty
      cat [ByteString
x] = ByteString -> ByteStringBuilder
Build.byteString ByteString
x
      cat (ByteString
x : [ByteString]
xs) = ByteString -> ByteStringBuilder
Build.byteString ByteString
x forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
ksep forall a. Semigroup a => a -> a -> a
<> [ByteString] -> ByteStringBuilder
cat [ByteString]
xs

      kdelim :: ByteString
kdelim = ByteString
"."
      ksep :: ByteStringBuilder
ksep = ByteStringBuilder
"&"
      vsep :: ByteString
vsep = ByteString
"="

pair :: ToQuery a => ByteString -> a -> QueryString -> QueryString
pair :: forall a.
ToQuery a =>
ByteString -> a -> QueryString -> QueryString
pair ByteString
k a
v = forall a. Monoid a => a -> a -> a
mappend (ByteString -> QueryString -> QueryString
QPair ByteString
k (forall a. ToQuery a => a -> QueryString
toQuery a
v))

infixr 7 =:

(=:) :: ToQuery a => ByteString -> a -> QueryString
ByteString
k =: :: forall a. ToQuery a => ByteString -> a -> QueryString
=: a
v = ByteString -> QueryString -> QueryString
QPair ByteString
k (forall a. ToQuery a => a -> QueryString
toQuery a
v)

toQueryList ::
  (IsList a, ToQuery (Item a)) =>
  ByteString ->
  a ->
  QueryString
toQueryList :: forall a.
(IsList a, ToQuery (Item a)) =>
ByteString -> a -> QueryString
toQueryList ByteString
k = ByteString -> QueryString -> QueryString
QPair ByteString
k forall b c a. (b -> c) -> (a -> b) -> a -> c
. [QueryString] -> QueryString
QList forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith forall a. ToQuery a => Int -> a -> QueryString
f [Int
1 ..] forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall l. IsList l => l -> [Item l]
toList
  where
    f :: ToQuery a => Int -> a -> QueryString
    f :: forall a. ToQuery a => Int -> a -> QueryString
f Int
n a
v = forall a. ToByteString a => a -> ByteString
toBS Int
n forall a. ToQuery a => ByteString -> a -> QueryString
=: forall a. ToQuery a => a -> QueryString
toQuery a
v

toQueryMap ::
  (ToQuery k, ToQuery v) =>
  ByteString ->
  ByteString ->
  ByteString ->
  HashMap k v ->
  QueryString
toQueryMap :: forall k v.
(ToQuery k, ToQuery v) =>
ByteString
-> ByteString -> ByteString -> HashMap k v -> QueryString
toQueryMap ByteString
e ByteString
k ByteString
v = forall a.
(IsList a, ToQuery (Item a)) =>
ByteString -> a -> QueryString
toQueryList ByteString
e forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (k, v) -> QueryString
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k v. HashMap k v -> [(k, v)]
HashMap.toList
  where
    f :: (k, v) -> QueryString
f (k
x, v
y) = [QueryString] -> QueryString
QList [ByteString
k forall a. ToQuery a => ByteString -> a -> QueryString
=: forall a. ToQuery a => a -> QueryString
toQuery k
x, ByteString
v forall a. ToQuery a => ByteString -> a -> QueryString
=: forall a. ToQuery a => a -> QueryString
toQuery v
y]

class ToQuery a where
  toQuery :: a -> QueryString
  default toQuery :: ToText a => a -> QueryString
  toQuery = forall a. ToQuery a => a -> QueryString
toQuery forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToText a => a -> Text
toText

instance ToQuery QueryString where
  toQuery :: QueryString -> QueryString
toQuery = forall a. a -> a
id

instance (ToByteString k, ToQuery v) => ToQuery (k, v) where
  toQuery :: (k, v) -> QueryString
toQuery (k
k, v
v) = ByteString -> QueryString -> QueryString
QPair (forall a. ToByteString a => a -> ByteString
toBS k
k) (forall a. ToQuery a => a -> QueryString
toQuery v
v)

instance ToQuery Char where
  toQuery :: Char -> QueryString
toQuery = forall a. ToQuery a => a -> QueryString
toQuery forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> ByteString
BS8.singleton

instance ToQuery ByteString where
  toQuery :: ByteString -> QueryString
toQuery ByteString
"" = Maybe ByteString -> QueryString
QValue forall a. Maybe a
Nothing
  toQuery ByteString
bs = Maybe ByteString -> QueryString
QValue (forall a. a -> Maybe a
Just ByteString
bs)

instance ToQuery Text where toQuery :: Text -> QueryString
toQuery = forall a. ToQuery a => a -> QueryString
toQuery forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> ByteString
Text.encodeUtf8

instance ToQuery Int where toQuery :: Int -> QueryString
toQuery = forall a. ToQuery a => a -> QueryString
toQuery forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToByteString a => a -> ByteString
toBS

instance ToQuery Integer where toQuery :: Integer -> QueryString
toQuery = forall a. ToQuery a => a -> QueryString
toQuery forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToByteString a => a -> ByteString
toBS

instance ToQuery Double where toQuery :: Double -> QueryString
toQuery = forall a. ToQuery a => a -> QueryString
toQuery forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToByteString a => a -> ByteString
toBS

instance ToQuery Natural where toQuery :: Natural -> QueryString
toQuery = forall a. ToQuery a => a -> QueryString
toQuery forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToByteString a => a -> ByteString
toBS

instance ToQuery a => ToQuery (Maybe a) where
  toQuery :: Maybe a -> QueryString
toQuery (Just a
x) = forall a. ToQuery a => a -> QueryString
toQuery a
x
  toQuery Maybe a
Nothing = forall a. Monoid a => a
mempty

instance ToQuery Bool where
  toQuery :: Bool -> QueryString
toQuery Bool
True = forall a. ToQuery a => a -> QueryString
toQuery (ByteString
"true" :: ByteString)
  toQuery Bool
False = forall a. ToQuery a => a -> QueryString
toQuery (ByteString
"false" :: ByteString)