// error.mbt — Structured error model for moon-httpsig.
//
// RFC 9421 HTTP Message Signatures has many failure modes across distinct
// processing stages. Public APIs never return `Result[T, String]`; they
// return `Result[T, HsError]` so that callers can dispatch on a stable
// (stage, kind) pair, an optional UTF-8 byte offset, and a short context
// string without leaking secrets.
//
// Note: `HsError` is declared as a `suberror` (MoonBit's raise-based error
// type) so that internal processing functions can use `raise HsError` and
// let errors propagate through the call stack with zero plumbing. Public
// boundary functions wrap those internals in `try`/`catch` and convert to
// `Result[T, HsError]`. Field access is provided by accessor methods because
// MoonBit suberror payloads are positional.
///|
/// The processing stage in which an error was detected. Used to answer the
/// question "where did this fail?" at a glance.
pub(all) enum HsErrorStage {
MessageConstruction
StructuredFieldParsing
SignatureInputParsing
SignatureFieldParsing
ComponentResolution
SignatureBaseConstruction
Signing
KeyResolution
PolicyValidation
CryptographicVerification
ReplayProtection
DigestBinding
}
///|
/// The concrete error category. Stable across versions so callers can switch
/// on it without string matching.
pub(all) enum HsErrorKind {
UnexpectedEnd
UnexpectedByte(Byte)
InvalidHeaderName
InvalidHeaderValue
InvalidMethod
InvalidScheme
InvalidAuthority
InvalidPath
InvalidStatus
DuplicateLabel
MissingSignatureInput
MissingSignature
LabelMismatch
InvalidSignatureInput
InvalidSignatureField
InvalidAcceptSignature
InvalidCoveredComponent
UnsupportedDerivedComponent
UnsupportedComponentParameter
MissingComponent
InvalidComponentCombination
InvalidStructuredField
InvalidBase64
InvalidInteger
InvalidTimestamp
CreatedInFuture
SignatureExpired
SignatureTooOld
MissingKeyId
KeyIdTooLong
KeyNotFound
AlgorithmMissing
AlgorithmNotAllowed
AlgorithmMismatch
InvalidKeyMaterial
SignatureMismatch
MissingRequiredComponent
DuplicateNonce
NonceRequired
NonceTooLong
InvalidTag
ContentDigestRequired
ContentDigestNotCovered
ContentDigestInvalid
InputTooLarge
TooManyHeaders
TooManySignatures
TooManyComponents
TooManyParameters
SerializationFailure
}
///|
/// A structured error returned by every public API.
///
/// - `offset()` is a UTF-8 byte offset into the input being processed when
/// the offset is meaningful, otherwise `0`.
/// - `context()` is a short, caller-visible description. It never contains
/// keys, full signatures, full bodies, or huge header values.
pub(all) suberror HsError {
HsError(HsErrorStage, HsErrorKind, Int, String)
}
///|
/// Constructs an `HsError` with the given stage, kind, and context and a zero
/// offset.
pub fn hs_error(
stage : HsErrorStage,
kind : HsErrorKind,
context : String,
) -> HsError {
HsError(stage, kind, 0, context)
}
///|
/// Constructs an `HsError` carrying an explicit UTF-8 byte offset.
pub fn hs_error_at(
stage : HsErrorStage,
kind : HsErrorKind,
offset : Int,
context : String,
) -> HsError {
HsError(stage, kind, offset, context)
}
///|
/// The processing stage of the error.
pub fn HsError::stage(self : HsError) -> HsErrorStage {
let HsError(stage, _, _, _) = self
stage
}
///|
/// The error kind.
pub fn HsError::kind(self : HsError) -> HsErrorKind {
let HsError(_, kind, _, _) = self
kind
}
///|
/// The UTF-8 byte offset, or `0` when not meaningful.
pub fn HsError::offset(self : HsError) -> Int {
let HsError(_, _, offset, _) = self
offset
}
///|
/// The short diagnostic context string.
pub fn HsError::context(self : HsError) -> String {
let HsError(_, _, _, context) = self
context
}
///|
/// Returns the stage name as a stable string (used by CLI JSON output).
pub fn HsError::stage_name(self : HsError) -> String {
match self.stage() {
MessageConstruction => "MessageConstruction"
StructuredFieldParsing => "StructuredFieldParsing"
SignatureInputParsing => "SignatureInputParsing"
SignatureFieldParsing => "SignatureFieldParsing"
ComponentResolution => "ComponentResolution"
SignatureBaseConstruction => "SignatureBaseConstruction"
Signing => "Signing"
KeyResolution => "KeyResolution"
PolicyValidation => "PolicyValidation"
CryptographicVerification => "CryptographicVerification"
ReplayProtection => "ReplayProtection"
DigestBinding => "DigestBinding"
}
}
///|
/// Returns the error kind name as a stable string.
pub fn HsError::kind_name(self : HsError) -> String {
match self.kind() {
UnexpectedEnd => "UnexpectedEnd"
UnexpectedByte(_) => "UnexpectedByte"
InvalidHeaderName => "InvalidHeaderName"
InvalidHeaderValue => "InvalidHeaderValue"
InvalidMethod => "InvalidMethod"
InvalidScheme => "InvalidScheme"
InvalidAuthority => "InvalidAuthority"
InvalidPath => "InvalidPath"
InvalidStatus => "InvalidStatus"
DuplicateLabel => "DuplicateLabel"
MissingSignatureInput => "MissingSignatureInput"
MissingSignature => "MissingSignature"
LabelMismatch => "LabelMismatch"
InvalidSignatureInput => "InvalidSignatureInput"
InvalidSignatureField => "InvalidSignatureField"
InvalidAcceptSignature => "InvalidAcceptSignature"
InvalidCoveredComponent => "InvalidCoveredComponent"
UnsupportedDerivedComponent => "UnsupportedDerivedComponent"
UnsupportedComponentParameter => "UnsupportedComponentParameter"
MissingComponent => "MissingComponent"
InvalidComponentCombination => "InvalidComponentCombination"
InvalidStructuredField => "InvalidStructuredField"
InvalidBase64 => "InvalidBase64"
InvalidInteger => "InvalidInteger"
InvalidTimestamp => "InvalidTimestamp"
CreatedInFuture => "CreatedInFuture"
SignatureExpired => "SignatureExpired"
SignatureTooOld => "SignatureTooOld"
MissingKeyId => "MissingKeyId"
KeyIdTooLong => "KeyIdTooLong"
KeyNotFound => "KeyNotFound"
AlgorithmMissing => "AlgorithmMissing"
AlgorithmNotAllowed => "AlgorithmNotAllowed"
AlgorithmMismatch => "AlgorithmMismatch"
InvalidKeyMaterial => "InvalidKeyMaterial"
SignatureMismatch => "SignatureMismatch"
MissingRequiredComponent => "MissingRequiredComponent"
DuplicateNonce => "DuplicateNonce"
NonceRequired => "NonceRequired"
NonceTooLong => "NonceTooLong"
InvalidTag => "InvalidTag"
ContentDigestRequired => "ContentDigestRequired"
ContentDigestNotCovered => "ContentDigestNotCovered"
ContentDigestInvalid => "ContentDigestInvalid"
InputTooLarge => "InputTooLarge"
TooManyHeaders => "TooManyHeaders"
TooManySignatures => "TooManySignatures"
TooManyComponents => "TooManyComponents"
TooManyParameters => "TooManyParameters"
SerializationFailure => "SerializationFailure"
}
}
///|
/// Renders the error as a short human-readable line.
pub fn HsError::to_debug_string(self : HsError) -> String {
let mut s = self.stage_name() + "/" + self.kind_name()
if self.offset() > 0 {
s = s + " @" + self.offset().to_string()
}
if !self.context().is_empty() {
s = s + " (" + self.context() + ")"
}
s
}