use frust_devtools_protocol::{
Capability, HandshakeInfo, InputScrollParams, InputTapParams, MetricsSnapshot,
PROTOCOL_VERSION, RpcError, ScreenshotResult, WidgetProps, WidgetTreeDump,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AppInfo {
pub app_name: String,
pub frust_version: String,
}
impl AppInfo {
pub fn new(app_name: impl Into<String>, frust_version: impl Into<String>) -> Self {
Self {
app_name: app_name.into(),
frust_version: frust_version.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BackendError {
NotSupported(String),
Unavailable(String),
InvalidRequest(String),
Internal(String),
}
impl BackendError {
pub fn not_supported(what: impl Into<String>) -> Self {
BackendError::NotSupported(what.into())
}
pub fn unavailable(what: impl Into<String>) -> Self {
BackendError::Unavailable(what.into())
}
pub fn invalid_request(what: impl Into<String>) -> Self {
BackendError::InvalidRequest(what.into())
}
pub fn internal(what: impl Into<String>) -> Self {
BackendError::Internal(what.into())
}
pub fn to_rpc_error(&self) -> RpcError {
match self {
BackendError::NotSupported(m) => RpcError::new(RpcError::NOT_SUPPORTED, m.clone()),
BackendError::Unavailable(m) => RpcError::new(RpcError::INTERNAL_ERROR, m.clone()),
BackendError::InvalidRequest(m) => RpcError::new(RpcError::INVALID_PARAMS, m.clone()),
BackendError::Internal(m) => RpcError::new(RpcError::INTERNAL_ERROR, m.clone()),
}
}
}
impl std::fmt::Display for BackendError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BackendError::NotSupported(m) => write!(f, "not supported: {m}"),
BackendError::Unavailable(m) => write!(f, "unavailable: {m}"),
BackendError::InvalidRequest(m) => write!(f, "invalid request: {m}"),
BackendError::Internal(m) => write!(f, "internal backend error: {m}"),
}
}
}
impl std::error::Error for BackendError {}
pub trait DevtoolsBackend: Send + 'static {
fn handshake_info(&self, app: &AppInfo) -> HandshakeInfo {
HandshakeInfo {
app_name: app.app_name.clone(),
frust_version: app.frust_version.clone(),
protocol_version: PROTOCOL_VERSION,
capabilities: vec![
Capability::WidgetTree,
Capability::FrameStats,
Capability::Input,
Capability::Metrics,
],
}
}
fn widget_tree(&self) -> WidgetTreeDump;
fn widget_props(&self, id: u64) -> Option<WidgetProps>;
fn metrics_snapshot(&self) -> MetricsSnapshot;
fn inject_tap(&self, params: InputTapParams) -> Result<(), BackendError>;
fn inject_scroll(&self, params: InputScrollParams) -> Result<(), BackendError>;
fn inject_text(&self, text: &str) -> Result<(), BackendError>;
fn screenshot(&self) -> Result<ScreenshotResult, BackendError> {
Err(BackendError::not_supported(
"screenshot capture is not implemented by this backend",
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_backend_error_maps_to_its_own_rpc_code() {
assert_eq!(
BackendError::not_supported("x").to_rpc_error().code,
RpcError::NOT_SUPPORTED
);
assert_eq!(
BackendError::invalid_request("x").to_rpc_error().code,
RpcError::INVALID_PARAMS
);
assert_eq!(
BackendError::unavailable("x").to_rpc_error().code,
RpcError::INTERNAL_ERROR
);
assert_eq!(
BackendError::internal("x").to_rpc_error().code,
RpcError::INTERNAL_ERROR
);
}
#[test]
fn rpc_error_message_carries_the_backend_message() {
let err = BackendError::unavailable("no window attached").to_rpc_error();
assert_eq!(err.message, "no window attached");
}
#[test]
fn backend_error_is_a_std_error() {
let err = BackendError::internal("boom");
let _: &dyn std::error::Error = &err;
assert_eq!(err.to_string(), "internal backend error: boom");
}
}