# Errors, safety, and deployment

## Handle conversion errors

Conversion functions return `{:ok, value}` or an error map:

```elixir
case ExAnydoc.to_markdown(path) do
  {:ok, markdown} -> {:ok, markdown}
  {:error, %{code: :encrypted}} -> {:error, "remove the document password"}
  {:error, %{code: :resource_limit}} -> {:error, "document is too complex"}
  {:error, %{code: :io_error} = error} -> {:error, error.message}
  {:error, error} -> {:error, "conversion failed: #{error.message}"}
end
```

The stable codes are:

| Code | Meaning |
| --- | --- |
| `:unsupported` | The format or requested operation is not supported |
| `:malformed` | The document structure is invalid |
| `:encrypted` | The document is encrypted |
| `:resource_limit` | An upstream parsing safety limit was reached |
| `:missing_part` | A required part of a compound document is absent |
| `:io_error` | Reading a file failed |

Messages provide diagnostic detail but should not be used as stable identifiers.
Invalid Elixir argument types raise instead of returning an error tuple.

## Safety

The upstream parser applies fixed limits to archive expansion, nesting, node
counts, and retained asset bytes. Still treat native parsing as a resource-heavy
operation: validate upload sizes, bound concurrency, and apply application-level
timeouts where appropriate. Scanned PDFs require a separate OCR pipeline.

## Build and release environments

ExAnydoc currently ships Rust source, not precompiled NIF artifacts. Every
environment that runs `mix compile` for the dependency needs a compatible Rust
toolchain and Cargo. In a multi-stage container build, install Rust in the build
stage; it does not need to remain in the final runtime image after the release
has been assembled.

After changing the Rust or Elixir interface, rebuild and run both suites:

```shell
mix test
cargo test --workspace
```
