{-# 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 { took :: !Word64 -- ^ How many milliseconds did the operation take? , hits :: !Hits -- ^ A hits object } deriving (Show) data Hits = Hits { total :: !Total -- ^ Information about the total number of documents that matched , hits :: !(SmallArray Hit) -- ^ Array of hits } deriving (Show) -- | A document that matched the search criteria data Hit = Hit { index :: !ShortText -- ^ Index name , id :: !ShortText -- ^ Document id , source :: !J.Value -- ^ Source document } deriving (Show) data Total = Total { value :: !Word64 , relation :: !ShortText } deriving (Show) -- | Decode the JSON response to a bulk request. parser :: J.Value -> Parser Response parser v = do mbrs <- P.object v P.members ( do took <- P.key "took" (P.number >=> P.word64) hits <- P.key "hits" (P.object >=> P.members hitsParser) pure Response{took,hits} ) mbrs hitsParser :: MemberParser Hits hitsParser = do total <- P.key "total" (P.object >=> P.members totalParser) hits <- P.key "hits" (P.array >=> P.smallArray (P.object >=> P.members hitParser)) pure Hits{total,hits} totalParser :: MemberParser Total totalParser = do value <- P.key "value" (P.number >=> P.word64) relation <- P.key "relation" P.string pure Total{value,relation} hitParser :: MemberParser Hit hitParser = do index <- P.key "_index" P.string id <- P.key "_id" P.string source <- P.key "_source" pure pure Hit{index,id,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" -- > } -- > } -- > } -- > ] -- > } -- > }