use crate::protocol::FrameError;
use crate::types::CatchClass;
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("no medius device found")]
NotFound,
#[error("no reply to version query during handshake")]
NoReply,
#[error("unsupported protocol version {got} (expected {expected})", expected = crate::protocol::PROTO_VER)]
BadProtoVer { got: u8 },
#[error("query timed out waiting for a response")]
QueryTimeout,
#[error("device disconnected")]
Disconnected,
#[error("frame payload too long (max {max} bytes)", max = crate::protocol::MAX_PAYLOAD)]
FrameTooLong,
#[error(
"a lock scale is a percent of the physical value bounded by {min} to {max}: 0 blocks, 100 \
passes untouched, above that amplifies, and a negative one reverses what it keeps. {scale} \
is outside it"
)]
LockScaleRange { scale: i16, min: i16, max: i16 },
#[error(
"a lock scale of {scale} reverses what it keeps, and class {class} carries one bit, which has \
nothing to reverse; use 0 to block it or 100 to pass it"
)]
LockScaleUsage { scale: i16, class: u8 },
#[error("the box holds at most {limit} catch entries and this subscription needs {needed}")]
CatchTableFull { needed: usize, limit: usize },
#[error("a catch subscription needs at least one filter")]
EmptySubscription,
#[error(
"the advanced control layer (§3.14) is gated on the imperfect-clone opt-in, which the box reports \
off; call allow_imperfect_clones(true) first"
)]
ImperfectRequired,
#[error(
"a rewrite rule's match and mask must be the same length (match {match_len}, mask {mask_len})"
)]
RewriteMaskLength { match_len: usize, mask_len: usize },
#[error("a rewrite rule compares at most {limit} match bytes, and this one has {len}")]
RewriteMatchTooLong { len: usize, limit: usize },
#[error("the box holds {limit} rewrite rules and they are all in use; remove one first")]
RewriteTableFull { limit: usize },
#[error("{action:?} is not a valid action for a {class:?} rewrite rule")]
RewriteActionClass {
action: crate::types::RewriteAction,
class: crate::types::RewriteClass,
},
#[error(
"a {action:?} rewrite payload of {len} bytes at offset {offset} exceeds the {cap}-byte head \
the box holds for a {class:?} rule"
)]
RewritePayloadTooLarge {
action: crate::types::RewriteAction,
class: crate::types::RewriteClass,
len: usize,
offset: usize,
cap: usize,
},
#[error(
"a {op:?} transform cannot address {src:?} → {dst:?}: a swap is two axes, a remap is \
axis→axis, button→button, button→key or button→media, and neither takes one field as both \
ends. To weigh a field in place, use scale"
)]
TransformOpFields {
op: crate::types::TransformOp,
src: crate::types::LockTarget,
dst: crate::types::LockTarget,
},
#[error(
"{limit} transforms are already held for this device; remove one first. This counts what the \
host holds, which can include an entry the box refused for naming a field the clone does not \
declare: query_transforms reports what the box actually has"
)]
TransformTableFull { limit: usize },
#[error("{class:?} arrives decoded and carries no packet, so a capture on it does nothing")]
CaptureNotApplicable { class: CatchClass },
#[error("{class:?} is traffic and cannot be decoded to an input edge; use catch_events")]
NotAnInputFilter { class: CatchClass },
#[error(
"CatchFilter::everything() covers traffic as well as input; use CatchFilter::all_input()"
)]
WildcardNotInput,
#[error(
"{direction:?} is measured against the bearing at emit time, which a {what} is addressed \
before; use Both, Positive or Negative"
)]
RelativeDirection {
direction: crate::types::Direction,
what: &'static str,
},
#[error(
"a raw injection puts bytes on one cloned endpoint flow, so it needs Direction::IN or \
Direction::OUT; {direction:?} names neither"
)]
RawDirection { direction: crate::types::Direction },
#[error("a clip frame carries at most {limit} {what} and this one has {count}")]
ClipFrameCount {
what: &'static str,
count: usize,
limit: usize,
},
#[error(
"a clip frame encodes to {len} bytes and one append carries at most {max}; split it across \
frames",
max = crate::types::CLIP_ENTRY_MAX
)]
ClipFrameTooLong { len: usize },
#[error(
"a clip transfer carries exactly the bytes its setup packet announces: wLength for an OUT \
request, none for an IN one. This one needs {want} and has {got}"
)]
ClipTransferData { want: usize, got: usize },
#[error("a clip packet trigger {reason}")]
ClipPacketTrigger { reason: &'static str },
#[error(
"id 0x{id:04X} is the blanket sentinel on the wire, so an exact {class:?} subscription to it \
would address the whole class instead"
)]
ReservedId { class: CatchClass, id: u16 },
#[error(
"an input subscription must cover both edges: without the release edge a fresh press cannot \
be told from a chord, and without the opposite sign an axis never returns to rest. Drop the \
direction and match on Input::Press or the sign of the delta"
)]
HalfEdgeInputFilter,
#[error("{} failed: {}", crate::types::update_doing(*op), crate::types::update_reason(*op, *status, *arg))]
Update {
op: u8,
status: crate::types::UpdateStatus,
arg: u32,
},
}
pub type Result<T> = core::result::Result<T, Error>;
impl From<FrameError> for Error {
fn from(err: FrameError) -> Self {
match err {
FrameError::PayloadTooLong { .. } => Error::FrameTooLong,
}
}
}