use serde::{Deserialize, Serialize};
const MAX_QUERY_FAILURE_BYTES: usize = 64 * 1024;
const MAX_QUERY_MESSAGE_BYTES: usize = 60 * 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QueryCode {
NotFound,
InvalidArgument,
Internal,
Unavailable,
Unimplemented,
DeadlineExceeded,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct QueryFailure {
pub code: QueryCode,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub details: Option<Vec<u8>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub details_encoding: Option<String>,
}
impl QueryFailure {
pub fn new(code: QueryCode, message: impl Into<String>) -> Self {
QueryFailure {
code,
message: message.into(),
details: None,
details_encoding: None,
}
}
pub fn not_found(message: impl Into<String>) -> Self {
Self::new(QueryCode::NotFound, message)
}
pub fn invalid_argument(message: impl Into<String>) -> Self {
Self::new(QueryCode::InvalidArgument, message)
}
pub fn internal(message: impl Into<String>) -> Self {
Self::new(QueryCode::Internal, message)
}
pub fn unavailable(message: impl Into<String>) -> Self {
Self::new(QueryCode::Unavailable, message)
}
pub fn unimplemented(message: impl Into<String>) -> Self {
Self::new(QueryCode::Unimplemented, message)
}
pub fn deadline_exceeded(message: impl Into<String>) -> Self {
Self::new(QueryCode::DeadlineExceeded, message)
}
pub fn encode(&self) -> Vec<u8> {
let encoded = rmp_serde::to_vec_named(self).expect("QueryFailure is always serializable");
if encoded.len() <= MAX_QUERY_FAILURE_BYTES {
return encoded;
}
let mut bounded = self.clone();
bounded.details = None;
bounded.details_encoding = None;
bounded.message = truncate_utf8(&bounded.message, MAX_QUERY_MESSAGE_BYTES);
let encoded =
rmp_serde::to_vec_named(&bounded).expect("QueryFailure is always serializable");
debug_assert!(encoded.len() <= MAX_QUERY_FAILURE_BYTES);
encoded
}
pub fn decode(bytes: &[u8]) -> Result<Self, rmp_serde::decode::Error> {
if bytes.len() > MAX_QUERY_FAILURE_BYTES {
return Err(rmp_serde::decode::Error::Syntax(format!(
"QueryFailure exceeds the {MAX_QUERY_FAILURE_BYTES}-byte limit"
)));
}
rmp_serde::from_slice(bytes)
}
}
fn truncate_utf8(value: &str, max_bytes: usize) -> String {
if value.len() <= max_bytes {
return value.to_string();
}
let mut end = max_bytes;
while !value.is_char_boundary(end) {
end -= 1;
}
value[..end].to_string()
}
#[derive(Debug, thiserror::Error)]
pub enum QueryError {
#[error("no responder is available for this query topic")]
Unavailable,
#[error("query timed out: {0:?}")]
Timeout(QueryFailure),
#[error("query server error: {0:?}")]
Server(QueryFailure),
#[error("failed to decode query response: {0}")]
Decode(String),
#[error("query protocol error: {0}")]
Protocol(String),
#[error("multiple responders answered an exclusive query topic")]
TooManyResponders,
}
pub type QueryResult<T> = std::result::Result<T, QueryFailure>;