use crate::domain::ProtocolVersion;
use crate::domain::feature::Feature;
use crate::domain::idempotency::IdempotencyError;
use crate::domain::ir::{IrErrorCode, JsonObject};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum GatedError {
#[error("{feature} was not negotiated (session is {negotiated})")]
FeatureUnavailable {
feature: Feature,
negotiated: ProtocolVersion,
},
#[error("this request has no progress token; the client did not open one")]
NoProgressToken,
#[error("this server has no request-state key to seal with")]
NoStateKey,
#[error("the exchange did not complete: {reason}")]
MalformedAnswer {
reason: String,
},
}
impl GatedError {
#[must_use]
pub const fn feature(&self) -> Option<Feature> {
match self {
Self::FeatureUnavailable { feature, .. } => Some(*feature),
Self::NoProgressToken | Self::NoStateKey | Self::MalformedAnswer { .. } => None,
}
}
#[must_use]
pub const fn wrote_nothing(&self) -> bool {
match self {
Self::FeatureUnavailable { .. } | Self::NoProgressToken | Self::NoStateKey => true,
Self::MalformedAnswer { .. } => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum DispatchError {
#[error("no such tool")]
UnknownTool,
#[error("the session did not negotiate what this tool requires")]
NotCallable,
#[error("no such method")]
UnknownMethod,
#[error("no such resource")]
UnknownResource,
#[error("no such prompt")]
UnknownPrompt,
#[error("the handler refused: {0}")]
HandlerFailed(String),
#[error("the call parameters are not a tool call")]
MalformedParams,
#[error("the initialize parameters are not a handshake")]
MalformedInitialize,
#[error("the session is on a revision this build cannot serve")]
UnnegotiableVersion,
#[error("the request `_meta` is missing a required key")]
MalformedRequestMeta,
#[error("this server does not implement the requested protocol version")]
UnsupportedVersion {
requested: String,
supported: Vec<String>,
},
#[error("request {} is already in flight on this session", .0)]
DuplicateInFlight(String),
#[error("the request needs client capabilities it did not declare")]
MissingClientCapability {
required: Vec<&'static str>,
},
#[error("requestState failed its integrity check")]
TamperedRequestState,
#[error("recorded results are unavailable: {0}")]
IdempotencyUnavailable(String),
}
impl DispatchError {
#[must_use]
pub const fn jsonrpc_code(&self) -> IrErrorCode {
match self {
Self::UnknownMethod => IrErrorCode::MethodNotFound,
Self::MalformedParams
| Self::MalformedInitialize
| Self::UnknownTool
| Self::UnknownResource
| Self::UnknownPrompt
| Self::MalformedRequestMeta
| Self::TamperedRequestState => IrErrorCode::InvalidParams,
Self::MissingClientCapability { .. } => IrErrorCode::MISSING_REQUIRED_CLIENT_CAPABILITY,
Self::DuplicateInFlight(_) => IrErrorCode::InvalidRequest,
Self::NotCallable | Self::UnnegotiableVersion | Self::IdempotencyUnavailable(_) => {
IrErrorCode::InternalError
}
Self::UnsupportedVersion { .. } => IrErrorCode::UNSUPPORTED_PROTOCOL_VERSION,
Self::HandlerFailed(_) => IrErrorCode::InternalError,
}
}
#[must_use]
pub fn wire_data(&self) -> Option<JsonObject> {
let data = match self {
Self::UnsupportedVersion {
requested,
supported,
} => serde_json::json!({ "requested": requested, "supported": supported }),
Self::MissingClientCapability { required } => {
let capabilities: serde_json::Map<String, serde_json::Value> = required
.iter()
.map(|name| {
(
(*name).to_owned(),
serde_json::Value::Object(serde_json::Map::new()),
)
})
.collect();
serde_json::json!({ "requiredCapabilities": capabilities })
}
_ => return None,
};
JsonObject::from_value(data).ok()
}
#[must_use]
pub fn as_wire(&self) -> (IrErrorCode, String) {
(self.jsonrpc_code(), self.to_string())
}
}
impl From<crate::application::server::result::OutcomeError> for DispatchError {
fn from(error: crate::application::server::result::OutcomeError) -> Self {
Self::HandlerFailed(error.to_string())
}
}
impl From<IdempotencyError> for DispatchError {
fn from(error: IdempotencyError) -> Self {
Self::IdempotencyUnavailable(error.to_string())
}
}