// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// Where something went wrong in a document that arrived as text.
///
/// The offset is in UTF-16 code units from the start, which is what a
/// [`StringView`] indexes by; line and column are one-based, because that is
/// how every editor counts and an error a person cannot find in their file is
/// half an error.
pub(all) struct At {
at : Int32
line : Int32
column : Int32
} derive(Eq, Debug)
///|
pub extend At with Eq::{equal, not_equal}
///|
pub extend At with Debug::{to_repr}
///|
/// Why a document was refused, and where.
///
/// One error type for the whole family — reading text, reading bytes, walking
/// a pointer, applying a patch, writing a value a format cannot carry — so that
/// a caller has one thing to catch and one place to look. A variant that has a
/// position carries the [`At`] it was found at; a variant about a pointer or a
/// patch names the pointer itself, because a `move` and a `copy` each carry two
/// and an error that did not say which one would be half an error.
pub(all) suberror Refused {
// Reading text.
/// A character that cannot start or continue what is being read.
Unexpected(At, Char)
/// The document ends inside a value.
Truncated(At)
/// An escape sequence that is not one, or a lone half of a surrogate pair.
BadEscape(At)
/// A number the grammar does not admit — a leading zero, a bare minus, an
/// exponent with no digits.
BadNumber(At)
/// A second value after the first one ended.
Trailing(At)
/// Nesting past the depth limit. Without this, a document of ten thousand
/// open brackets would take the stack down with it.
TooDeep(At)
/// Bytes that are not UTF-8, when reading a document that arrived as bytes.
BadUtf8(At)
/// A member name that appears twice, when the dialect refuses that.
Repeated(At, String)
// Binary formats, where there is no text and so no line to name: `at` is the
// byte offset from the start of the document.
/// The document ended inside a value.
Short(at~ : Int32)
/// Something after the value ended. A document is one value, and a reader
/// that stops at the first turns a corrupt file into a plausible one.
Extra(at~ : Int32)
/// A byte that cannot be read this way: a reserved additional-information
/// value, a length or an index that does not fit, a string that is not UTF-8,
/// a nesting that went too deep.
Bad(at~ : Int32, why~ : String)
// Writing.
/// A value the format has no way to write: a number that is not finite into
/// JSON, a byte string into a text dialect, a key that is not a string into
/// one whose objects have only string keys.
Unwritable(why~ : String)
// Pointers (RFC 6901).
/// The pointer is not one: it does not start with a slash, or a `~` is
/// followed by anything but `0` or `1`. `at` is the offset within `path`.
Syntax(path~ : String, at~ : Int32)
/// What the pointer names is not there: a name no member has, or an index no
/// array reaches.
Missing(path~ : String, token~ : String)
/// The pointer walked into something that is neither an object nor an array.
NotContainer(path~ : String, token~ : String)
/// An array index that is not one — not decimal digits without a leading
/// zero — or `-` where it does not mean append.
BadIndex(path~ : String, token~ : String)
// Patches (RFC 6902).
/// The document is JSON but not a patch: not an array of operations.
Document(why~ : String)
/// The operation at `at` is not one: no `op` member, an `op` that is not one
/// of the six, or a member it requires missing or of the wrong type.
Operation(at~ : Int32, why~ : String)
/// A `test` that did not hold.
TestFailed(path~ : String)
/// A move into one of the moved value's own children (RFC 6902 §4.4).
IntoChild(from~ : String, path~ : String)
/// The operation names the root, which has no parent to take it from.
Root(path~ : String)
} derive(Eq, Debug)
///|
pub extend Refused with Eq::{equal, not_equal}
///|
pub extend Refused with Debug::{to_repr}
///|
/// Where the failure happened, when it happened in text.
///
/// `None` is not a failure to say: a pointer or a patch that was refused has a
/// path rather than a line, and a line number invented for it would be worse
/// than no line number at all.
pub fn Refused::at(self : Refused) -> At? {
match self {
Unexpected(spot, _) => Some(spot)
Truncated(spot)
| BadEscape(spot)
| BadNumber(spot)
| Trailing(spot)
| TooDeep(spot)
| BadUtf8(spot) => Some(spot)
Repeated(spot, _) => Some(spot)
Short(_)
| Extra(_)
| Bad(..)
| Unwritable(_)
| Syntax(..)
| Missing(..)
| NotContainer(..)
| BadIndex(..)
| Document(..)
| Operation(..)
| TestFailed(..)
| IntoChild(..)
| Root(..) => None
}
}