# `Statifier.Parser.ParseError`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.0.0/lib/statifier/parser/parse_error.ex#L1)

The parser's own error shape: never `%Saxy.ParseError{}` directly, so
callers get one error type regardless of what failed inside.

`reason` carries Saxy's own reason term unchanged (`{:token, _}`,
`{:wrong_closing_tag, _, _}`, `{:invalid_pi, _}`, `{:invalid_encoding, _}`,
`{:bad_return, _}`) plus two v2-only reasons:
`{:location_desync, expected, got}`, raised when the markup scanner's queue
and the Saxy event stream disagree about which element comes next (see
`Statifier.Parser.Markup`), and `{:saxy_crash, exception}`, which converts
an exception Saxy raised instead of returning (see `from_exception/1`) so
that malformed input stays a return value. `location`
is a zero-width `Statifier.Parser.Location.t()` at the failing byte offset -
the same span type every DOM node uses, so callers have one shape to
handle - and is `nil` exactly when Saxy reports no `position` (observed
upstream on at least one `:bad_return` case), which `byte_offset` mirrors.

# `reason`

```elixir
@type reason() ::
  Saxy.ParseError.reason()
  | {:location_desync, expected :: binary(), got :: binary()}
  | {:saxy_crash, Exception.t()}
```

# `t`

```elixir
@type t() :: %Statifier.Parser.ParseError{
  __exception__: true,
  byte_offset: non_neg_integer() | nil,
  location: Statifier.Parser.Location.t() | nil,
  message: binary(),
  reason: reason()
}
```

# `desync`

```elixir
@spec desync(
  expected :: binary(),
  got :: binary(),
  location :: Statifier.Parser.Location.t()
) :: t()
```

Builds the `{:location_desync, expected, got}` error the handler raises
when the popped markup record's name does not match the Saxy event's name
at `location` - the guard that turns a mismatched two-pass zip into a
reported error instead of a silently wrong location.

# `from_exception`

```elixir
@spec from_exception(exception :: Exception.t()) :: t()
```

Wraps an `exception` that escaped `Saxy.parse_string/4` into `t()`.

Saxy 1.6.1 raises rather than returning an error when a comment or
processing instruction *before the root element* is truncated:
`Saxy.Parser.Binary.prolog_misc_comment/7` and `prolog_pi_content/7` have
no clause for an exhausted binary, so `"<!-- x"` and `"<?pi"` come back as
a `CaseClauseError`. Malformed input is a return value in this parser, so
the crash is converted here rather than leaking a dependency's bug to every
caller. Saxy reports no position on that path, so `location` and
`byte_offset` are `nil`.

# `from_saxy`

```elixir
@spec from_saxy(error :: Saxy.ParseError.t(), source :: binary()) :: t()
```

Converts a `%Saxy.ParseError{}` raised against `source` into `t()`.

Tolerates a `nil` position (Saxy's own `:bad_return` reason can arrive with
`binary: nil, position: nil`): `location` and `byte_offset` are `nil` in
that case rather than raising on the offset conversion.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
