Exception: ActiveSanction::ParseError

Inherits:
SourceError
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/active_sanction/error.rb

Overview

The bytes arrived and could not be read as the format they were declared to be: an HTML error page served under a .xml URL, a truncated download, an encoding that cannot be decoded, a ZIP member that will not inflate.

Distinct from a Parsers::Warning, which is one row that could not be read while the rest of the file could. A list is not a file we control, and refusing 19,321 records because one of them is malformed fails exactly when the list is most needed -- so a bad row is a warning, and only a payload that cannot be read at all raises this.

Where

A 25 MB XML payload that "is not XML" is not a diagnosable complaint, so this carries a locator whenever the parser can produce one:

rescue ActiveSanction::ParseError => e
e.line     # => 418_223
e.record   # => 12_004      (1-based, in the order the file yielded them)
e.offset   # => 8_388_608   (byte offset into the payload)
e.locator  # => "line 418223"

All three are nil where the parser cannot say -- libxml2 reports no position for some failures, and a ZIP directory that ends inside an entry header has an offset but no line. An error that cannot point at a line still says what went wrong rather than pointing at the wrong one.

Instance Attribute Summary collapse

Attributes included from Error

#source_id, #status

Instance Method Summary collapse

Methods included from Error

#in_source, #retryable?

Constructor Details

#initialize(message = nil, line: nil, record: nil, offset: nil, **options) ⇒ void

Parameters:

  • message (T.untyped) (defaults to: nil)
  • line (Integer, nil) (defaults to: nil)
  • record (Integer, nil) (defaults to: nil)
  • offset (Integer, nil) (defaults to: nil)
  • options (T.untyped)


234
235
236
237
238
239
# File 'lib/active_sanction/error.rb', line 234

def initialize(message = nil, line: nil, record: nil, offset: nil, **options)
  @line = T.let(line, T.nilable(Integer))
  @record = T.let(record, T.nilable(Integer))
  @offset = T.let(offset, T.nilable(Integer))
  super(message, **options)
end

Instance Attribute Details

#line ⇒ Integer? (readonly)

1-based line within the payload, or nil.

Returns:

  • (Integer, nil)


218
219
220
# File 'lib/active_sanction/error.rb', line 218

def line
  @line
end

#offset ⇒ Integer? (readonly)

Byte offset into the payload, or nil.

Returns:

  • (Integer, nil)


228
229
230
# File 'lib/active_sanction/error.rb', line 228

def offset
  @offset
end

#record ⇒ Integer? (readonly)

1-based index of the record being read, in the order the parser yielded them, or nil. The locator that means something for a format with no lines -- a spreadsheet, a stream of XML elements on one line.

Returns:

  • (Integer, nil)


224
225
226
# File 'lib/active_sanction/error.rb', line 224

def record
  @record
end

Instance Method Details

#locator ⇒ String?

Where in the payload, in the terms the parser could supply, or nil when it could supply none. Appended to #to_s -- and so to #message, which is defined in terms of it -- so a log line that records nothing but the message still says where.

Returns:

  • (String, nil)


246
247
248
249
250
251
252
# File 'lib/active_sanction/error.rb', line 246

def locator
  parts = []
  parts << "line #{line}" if line
  parts << "record #{record}" if record
  parts << "byte #{offset}" if offset
  parts.empty? ? nil : parts.join(", ")
end

#to_h ⇒ Hash{Symbol => T.untyped}

Returns:

  • (Hash{Symbol => T.untyped})


261
# File 'lib/active_sanction/error.rb', line 261

def to_h = super.merge({ line: line, record: record, offset: offset }.compact)

#to_s ⇒ String

Returns:

  • (String)


255
256
257
258
# File 'lib/active_sanction/error.rb', line 255

def to_s
  where = locator
  where ? "#{super} (at #{where})" : super
end