{-# language ApplicativeDo #-}
{-# language BangPatterns #-}
{-# language DuplicateRecordFields #-}
{-# language MagicHash #-}
{-# language NamedFieldPuns #-}
{-# language OverloadedStrings #-}
{-# language UnboxedTuples #-}

-- | Responses from Search API (@http:\/\/elasticsearch.example.com\/{index}\/_search@)
module Elasticsearch.Search.Response
  ( -- * Types
    Response(..)
  , Hits(..)
  , Hit(..)
  , Total(..)
    -- * Response Parser
  , parser
    -- * Example Data
    -- $example
  ) where

import Prelude hiding (id)

import Control.Monad ((>=>))
import Data.Primitive (SmallArray)
import Data.Text.Short (ShortText)
import Data.Word (Word64)
import Json.Parser (Parser,MemberParser)

import qualified Json as J
import qualified Json.Parser as P

-- | A response from a search.
--
-- One strange thing about the organization of the response is that there
-- is a @hits@ field with another @hits@ field inside of it. This is how
-- elasticsearch presents this information, and that unusual structure is
-- simply mirrored by these types.
data Response = Response
  { Response -> Word64
took :: !Word64
    -- ^ How many milliseconds did the operation take?
  , Response -> Hits
hits :: !Hits
    -- ^ A hits object
  } deriving (Int -> Response -> ShowS
[Response] -> ShowS
Response -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Response] -> ShowS
$cshowList :: [Response] -> ShowS
show :: Response -> String
$cshow :: Response -> String
showsPrec :: Int -> Response -> ShowS
$cshowsPrec :: Int -> Response -> ShowS
Show)

data Hits = Hits
  { Hits -> Total
total :: !Total
    -- ^ Information about the total number of documents that matched
  , Hits -> SmallArray Hit
hits :: !(SmallArray Hit)
    -- ^ Array of hits 
  } deriving (Int -> Hits -> ShowS
[Hits] -> ShowS
Hits -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Hits] -> ShowS
$cshowList :: [Hits] -> ShowS
show :: Hits -> String
$cshow :: Hits -> String
showsPrec :: Int -> Hits -> ShowS
$cshowsPrec :: Int -> Hits -> ShowS
Show)

-- | A document that matched the search criteria
data Hit = Hit
  { Hit -> ShortText
index :: !ShortText
    -- ^ Index name
  , Hit -> ShortText
id :: !ShortText
    -- ^ Document id
  , Hit -> Value
source :: !J.Value
    -- ^ Source document
  } deriving (Int -> Hit -> ShowS
[Hit] -> ShowS
Hit -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Hit] -> ShowS
$cshowList :: [Hit] -> ShowS
show :: Hit -> String
$cshow :: Hit -> String
showsPrec :: Int -> Hit -> ShowS
$cshowsPrec :: Int -> Hit -> ShowS
Show)

data Total = Total
  { Total -> Word64
value :: !Word64
  , Total -> ShortText
relation :: !ShortText
  } deriving (Int -> Total -> ShowS
[Total] -> ShowS
Total -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Total] -> ShowS
$cshowList :: [Total] -> ShowS
show :: Total -> String
$cshow :: Total -> String
showsPrec :: Int -> Total -> ShowS
$cshowsPrec :: Int -> Total -> ShowS
Show)

-- | Decode the JSON response to a bulk request.
parser :: J.Value -> Parser Response
parser :: Value -> Parser Response
parser Value
v = do
  SmallArray Member
mbrs <- Value -> Parser (SmallArray Member)
P.object Value
v
  forall a. MemberParser a -> SmallArray Member -> Parser a
P.members
    ( do Word64
took <- forall a. ShortText -> (Value -> Parser a) -> MemberParser a
P.key ShortText
"took" (Value -> Parser Scientific
P.number forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> Scientific -> Parser Word64
P.word64)
         Hits
hits <- forall a. ShortText -> (Value -> Parser a) -> MemberParser a
P.key ShortText
"hits" (Value -> Parser (SmallArray Member)
P.object forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> forall a. MemberParser a -> SmallArray Member -> Parser a
P.members MemberParser Hits
hitsParser)
         pure Response{Word64
took :: Word64
$sel:took:Response :: Word64
took,Hits
hits :: Hits
$sel:hits:Response :: Hits
hits}
    ) SmallArray Member
mbrs

hitsParser :: MemberParser Hits
hitsParser :: MemberParser Hits
hitsParser = do
  Total
total <- forall a. ShortText -> (Value -> Parser a) -> MemberParser a
P.key ShortText
"total" (Value -> Parser (SmallArray Member)
P.object forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> forall a. MemberParser a -> SmallArray Member -> Parser a
P.members MemberParser Total
totalParser)
  SmallArray Hit
hits <- forall a. ShortText -> (Value -> Parser a) -> MemberParser a
P.key ShortText
"hits"
    (Value -> Parser (SmallArray Value)
P.array forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> forall a.
(Value -> Parser a) -> SmallArray Value -> Parser (SmallArray a)
P.smallArray (Value -> Parser (SmallArray Member)
P.object forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> forall a. MemberParser a -> SmallArray Member -> Parser a
P.members MemberParser Hit
hitParser))
  pure Hits{Total
total :: Total
$sel:total:Hits :: Total
total,SmallArray Hit
hits :: SmallArray Hit
$sel:hits:Hits :: SmallArray Hit
hits}

totalParser :: MemberParser Total
totalParser :: MemberParser Total
totalParser = do
  Word64
value <- forall a. ShortText -> (Value -> Parser a) -> MemberParser a
P.key ShortText
"value" (Value -> Parser Scientific
P.number forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> Scientific -> Parser Word64
P.word64)
  ShortText
relation <- forall a. ShortText -> (Value -> Parser a) -> MemberParser a
P.key ShortText
"relation" Value -> Parser ShortText
P.string
  pure Total{Word64
value :: Word64
$sel:value:Total :: Word64
value,ShortText
relation :: ShortText
$sel:relation:Total :: ShortText
relation}

hitParser :: MemberParser Hit
hitParser :: MemberParser Hit
hitParser = do
  ShortText
index <- forall a. ShortText -> (Value -> Parser a) -> MemberParser a
P.key ShortText
"_index" Value -> Parser ShortText
P.string
  ShortText
id <- forall a. ShortText -> (Value -> Parser a) -> MemberParser a
P.key ShortText
"_id" Value -> Parser ShortText
P.string
  Value
source <- forall a. ShortText -> (Value -> Parser a) -> MemberParser a
P.key ShortText
"_source" forall (f :: * -> *) a. Applicative f => a -> f a
pure
  pure Hit{ShortText
index :: ShortText
$sel:index:Hit :: ShortText
index,ShortText
id :: ShortText
$sel:id:Hit :: ShortText
id,Value
source :: Value
$sel:source:Hit :: Value
source}

-- $example
--
-- Example response from Elasticsearch documentation:
--
-- > {
-- >   "took": 5,
-- >   "timed_out": false,
-- >   "_shards": {
-- >     "total": 1,
-- >     "successful": 1,
-- >     "skipped": 0,
-- >     "failed": 0
-- >   },
-- >   "hits": {
-- >     "total": {
-- >       "value": 20,
-- >       "relation": "eq"
-- >     },
-- >     "max_score": 1.3862942,
-- >     "hits": [
-- >       {
-- >         "_index": "my-index-000001",
-- >         "_type" : "_doc",
-- >         "_id": "0",
-- >         "_score": 1.3862942,
-- >         "_source": {
-- >           "@timestamp": "2099-11-15T14:12:12",
-- >           "http": {
-- >             "request": {
-- >               "method": "get"
-- >             },
-- >             "response": {
-- >               "status_code": 200,
-- >               "bytes": 1070000
-- >             },
-- >             "version": "1.1"
-- >           },
-- >           "source": {
-- >             "ip": "127.0.0.1"
-- >           },
-- >           "message": "GET /search HTTP/1.1 200 1070000",
-- >           "user": {
-- >             "id": "kimchy"
-- >           }
-- >         }
-- >       }
-- >     ]
-- >   }
-- > }