def construct_error_details_map(error)
      return {} unless @partial_success
      error_details_map = Hash.new { |h, k| h[k] = [] }

      error_details = ensure_array(
        ensure_hash(ensure_hash(JSON.parse(error.body))['error'])['details'])
      partial_errors = error_details.detect(
        -> { raise JSON::ParserError, "No type #{PARTIAL_ERROR_FIELD}." }
      ) do |error_detail|
        ensure_hash(error_detail)['@type'] == PARTIAL_ERROR_FIELD
      end
      log_entry_errors = ensure_hash(
        ensure_hash(partial_errors)['logEntryErrors'])
      log_entry_errors.each do |index, log_entry_error|
        error_hash = ensure_hash(log_entry_error)
        raise JSON::ParserError,
              "Entry #{index} is missing 'code' or 'message'." unless
          error_hash['code'] && error_hash['message']
        error_key = [error_hash['code'], error_hash['message']].freeze
        # TODO(qingling128): Convert indexes to integers.
        error_details_map[error_key] << index
      end
      error_details_map
    rescue JSON::ParserError => e
      @log.warn 'Failed to extract log entry errors from the error details:' \
                " #{error.body}.", error: e
      {}
    end
def construct_error_details_map(error)
      return {} unless @partial_success
      error_details_map = Hash.new { |h, k| h[k] = [] }

      error_details = ensure_array(
        ensure_hash(ensure_hash(JSON.parse(error.body))['error'])['details'])
      partial_errors = error_details.detect(
        -> { raise JSON::ParserError, "No type #{PARTIAL_ERROR_FIELD}." }
      ) do |error_detail|
        ensure_hash(error_detail)['@type'] == PARTIAL_ERROR_FIELD
      end
      log_entry_errors = ensure_hash(
        ensure_hash(partial_errors)['logEntryErrors'])
      log_entry_errors.each do |index, log_entry_error|
        error_hash = ensure_hash(log_entry_error)
        raise JSON::ParserError,
              "Entry #{index} is missing 'code' or 'message'." unless
          error_hash['code'] && error_hash['message']
        error_key = [error_hash['code'], error_hash['message']].freeze
        # TODO(qingling128): Convert indexes to integers.
        error_details_map[error_key] << index
      end
      error_details_map
    rescue JSON::ParserError => e
      @log.warn 'Failed to extract log entry errors from the error details:' \
                " #{error.body}.", error: e
      {}
    end
Extract a map of error details from a potentially partially successful
 REST request. Return an empty map if @partial_success is not enabled.

 The keys in this map are [error_code, error_message] pairs, and the values
 are a list of stringified indexes of log entries that failed due to this
 error.

 A sample error.body looks like:
 {
   "error": {
     "code": 403,
     "message": "User not authorized.",
     "status": "PERMISSION_DENIED",
     "details": [
       {
         "@type": "type.googleapis.com/google.logging.v2.WriteLogEntriesPar
           tialErrors",
         "logEntryErrors": {
           "0": {
             "code": 7,
             "message": "User not authorized."
           },
           "1": {
             "code": 3,
             "message": "Log name contains illegal character :"
           },
           "3": {
             "code": 3,
             "message": "Log name contains illegal character :"
           }
         }
       },
       {
         "@type": "type.googleapis.com/google.rpc.DebugInfo",
         "detail": ...
       }
     ]
   }
 }

 The root level "code", "message", and "status" simply match the root
 cause of the first failed log entry. For example, if we switched the order
 of the log entries, then we would get:
 {
    "error" : {
       "code" : 400,
       "message" : "Log name contains illegal character :",
       "status" : "INVALID_ARGUMENT",
       "details": ...
    }
 }
 We will ignore it anyway and look at the details instead which includes
 info for all failed log entries.

 In this example, the logEntryErrors that we care are:
 {
   "0": {
     "code": 7,
     "message": "User not authorized."
   },
   "1": {
     "code": 3,
     "message": "Log name contains illegal character :"
   },
   "3": {
     "code": 3,
     "message": "Log name contains illegal character :"
   }
 }

 The ultimate map that is constructed is:
 {
   [7, 'User not authorized.']: ['0'],
   [3, 'Log name contains illegal character :']: ['1', '3']
 }