{-# language ApplicativeDo #-}
{-# language BangPatterns #-}
{-# language DuplicateRecordFields #-}
{-# language MagicHash #-}
{-# language NamedFieldPuns #-}
{-# language OverloadedStrings #-}
{-# language UnboxedTuples #-}
-- | Response to @/_bulk@ request.
module Elasticsearch.Bulk.Response
( -- * Types
Response(..)
, Action(..)
, Item(..)
, ConcurrencyControl(..)
, Error(..)
, Details(..)
-- * Response Parser
, parser
-- * Example Data
-- $example
) where
import Prelude hiding (id)
import Control.Monad ((>=>))
import Data.Foldable (find)
import Data.Primitive (SmallArray)
import Data.Text.Short (ShortText)
import Data.Word (Word64,Word16)
import Elasticsearch.Bulk.Request (Action(Update,Create,Delete,Index))
import Json (Member(Member))
import Json.Parser (Parser,MemberParser)
import Json.Context (Context(Key))
import qualified Data.Primitive as PM
import qualified Json as J
import qualified Json.Parser as P
data Response = Response
{ took :: !Word64
-- ^ How many milliseconds did the operation take?
, errors :: !Bool
-- ^ Did anything go wrong?
, items :: !(SmallArray Item)
-- ^ Individual responses for each operation.
} deriving (Show)
-- | The _type field is omitted because it is always _doc in Elasticsearch 7+.
-- Some other fields are omitted because they are only present when an operation
-- succeeds.
data Item = Item
{ action :: !Action
, index :: !ShortText
-- ^ @_index@
, id :: !ShortText
-- ^ @_id@
, status :: !Word16
-- ^ @status@
, details :: !Details
-- ^ No single field dictates the details.
} deriving (Show)
-- | An item has different fields depending on whether the operation
-- was considered to have succeeded.
data Details
= Success !ConcurrencyControl
| Failure !Error
deriving (Show)
data ConcurrencyControl = ConcurrencyControl
{ version :: !Word64
-- ^ @_version@
, sequenceNumber :: !Word64
-- ^ @_seq_no@
} deriving (Show)
data Error = Error
{ type_ :: !ShortText
, reason :: !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)
errors <- P.key "errors" P.boolean
items <- P.key "items" (P.array >=> P.smallArray itemParser)
pure Response{took,errors,items}
) mbrs
itemParser :: J.Value -> Parser Item
itemParser v = do
mbrsTop <- P.object v
case PM.sizeofSmallArray mbrsTop of
1 -> do
let !(# Member{key=keyAction,value=valueAction} #) = PM.indexSmallArray## mbrsTop 0
action <- case keyAction of
"update" -> pure Update
"create" -> pure Create
"delete" -> pure Delete
"index" -> pure Index
_ -> P.fail "expected one of: update, create, delete, index"
P.contextually (Key keyAction) $ do
mbrsProps <- P.object valueAction
case find (\Member{key=keyProp} -> keyProp == "error") mbrsProps of
Nothing -> P.members -- item indicates a success
( do index <- P.key "_index" P.string
id <- P.key "_id" P.string
status <- P.key "status" (P.number >=> P.word16)
version <- P.key "_version" (P.number >=> P.word64)
sequenceNumber <- P.key "_seq_no" (P.number >=> P.word64)
pure Item
{ action,index,id,status
, details=Success ConcurrencyControl{version,sequenceNumber}
}
) mbrsProps
Just Member{value=errorValue} -> do -- item indicates a failure
mbrsError <- P.object errorValue
err <- P.contextually (Key "error") (P.members errorMemberParser mbrsError)
P.members
( do index <- P.key "_index" P.string
id <- P.key "_id" P.string
status <- P.key "status" (P.number >=> P.word16)
pure Item
{ action,index,id,status
, details=Failure err
}
) mbrsProps
_ -> P.fail "expected object with single member"
errorMemberParser :: MemberParser Error
errorMemberParser = do
type_ <- P.key "type" P.string
reason <- P.key "reason" P.string
pure Error{type_,reason}
-- $example
--
-- Example responses from Elasticsearch documentation with additional
-- commentary. This one does not include any detailed error messages:
--
-- > {
-- > "took": 30,
-- > "errors": false,
-- > "items": [
-- > {
-- > "index": {
-- > "_index": "test",
-- > "_type": "_doc",
-- > "_id": "1",
-- > "_version": 1,
-- > "result": "created",
-- > "_shards": {
-- > "total": 2,
-- > "successful": 1,
-- > "failed": 0
-- > },
-- > "status": 201,
-- > "_seq_no" : 0,
-- > "_primary_term": 1
-- > }
-- > },
-- > {
-- > "delete": {
-- > "_index": "test",
-- > "_type": "_doc",
-- > "_id": "2",
-- > "_version": 1,
-- > "result": "not_found",
-- > "_shards": {
-- > "total": 2,
-- > "successful": 1,
-- > "failed": 0
-- > },
-- > "status": 404,
-- > "_seq_no" : 1,
-- > "_primary_term" : 2
-- > }
-- > }
-- > ]
-- > }
--
-- This one does have detailed error messages in it:
--
-- > {
-- > "took": 486,
-- > "errors": true,
-- > "items": [
-- > {
-- > "update": {
-- > "_index": "index1",
-- > "_type" : "_doc",
-- > "_id": "5",
-- > "status": 404,
-- > "error": {
-- > "type": "document_missing_exception",
-- > "reason": "[_doc][5]: document missing",
-- > "index_uuid": "aAsFqTI0Tc2W0LCWgPNrOA",
-- > "shard": "0",
-- > "index": "index1"
-- > }
-- > }
-- > },
-- > {
-- > "update": {
-- > "_index": "index1",
-- > "_type" : "_doc",
-- > "_id": "6",
-- > "status": 404,
-- > "error": {
-- > "type": "document_missing_exception",
-- > "reason": "[_doc][6]: document missing",
-- > "index_uuid": "aAsFqTI0Tc2W0LCWgPNrOA",
-- > "shard": "0",
-- > "index": "index1"
-- > }
-- > }
-- > },
-- > {
-- > "create": {
-- > "_index": "index1",
-- > "_type" : "_doc",
-- > "_id": "7",
-- > "_version": 1,
-- > "result": "created",
-- > "_shards": {
-- > "total": 2,
-- > "successful": 1,
-- > "failed": 0
-- > },
-- > "_seq_no": 0,
-- > "_primary_term": 1,
-- > "status": 201
-- > }
-- > }
-- > ]
-- > }
--
-- Even though the documentation shows an @index_uuid@ field in the
-- error details, Elasticsearch 7.10 does not always populate this field.
-- It is not terribly useful, so it is omitted from the 'Error' type.