PdfElixide.Error exception (pdf_elixide v0.15.0)

Copy Markdown View Source

Structured error raised or returned by PdfElixide operations.

Non-bang functions return {:error, %PdfElixide.Error{}}; bang functions raise the same struct (it is an exception). Match on :reason to handle specific failures:

case PdfElixide.Document.open(path) do
  {:ok, doc} -> doc
  {:error, %PdfElixide.Error{reason: :encrypted}} -> prompt_for_password()
  {:error, %PdfElixide.Error{reason: :invalid_pdf}} -> reject()
end

Reasons

:message is a human-readable description. :details is reserved for future structured payloads and is currently always nil.

Errors versus exceptions

This struct is reserved for PDF and runtime failures. A malformed argument raises instead, even from a non-bang function, because that is a bug in the calling code rather than a condition of the document:

  • FunctionClauseError when a guard rejects it — a negative page index, an options argument that is not a keyword list.
  • ArgumentError for everything else, and the message names the offending key. That covers every way an option can be wrong — an unknown key (detect_heading: for :detect_headings), a key given twice, a value the native layer cannot decode, a value out of range ({:min_overlap, 2.0}) — and undecodable values outside an options map, such as a form field value that is none of the shapes PdfElixide.Form.put_value/3 accepts. A path is opaque bytes and so has almost nothing to reject — only Windows, which cannot name a file in arbitrary bytes, raises here; see the "File paths" section of PdfElixide.

Build option lists with Keyword.merge/2 rather than ++, since a duplicated key is rejected instead of resolved to the first occurrence. So nothing about the caller arrives as a %PdfElixide.Error{}; if you get one, the arguments were accepted and the document, the filesystem or the handle is what failed.

Predicates ending in ? are the mirror image: they return a bare boolean, so a failure has nowhere to be reported and this struct is raised instead, from a function with no ! in its name. PdfElixide.Document.has_structure_tree?/1 and PdfElixide.Document.has_xfa?/1 answer false for a feature that cannot be read, so only a failure of the handle raises — their strict counterparts PdfElixide.Document.has_structure_tree/1 and PdfElixide.Document.has_xfa/1 return the error instead. PdfElixide.Signature.document_timestamp?/1 degrades the same way and takes bytes rather than a handle, so nothing can raise from it at all; its strict counterpart PdfElixide.Signature.document_timestamp/1 reports bytes that are not a PDF. PdfElixide.Document.Page.has_text_layer?/1 raises for everything, since answering false would invert the meaning callers act on. PdfElixide.Document.encrypted?/1 asks something that cannot fail, so only the handle can raise, and PdfElixide.Document.closed?/1 never raises at all.

Summary

Types

reason()

@type reason() ::
  :encrypted
  | :wrong_password
  | :invalid_pdf
  | :invalid_pattern
  | :unsupported
  | :not_found
  | :out_of_range
  | :io
  | :panic
  | :lock_poisoned
  | :closed
  | :other

t()

@type t() :: %PdfElixide.Error{
  __exception__: true,
  details: map() | nil,
  message: String.t(),
  reason: reason()
}