use connectrpc::{ConnectError, ErrorCode, ErrorDetail};
use polyc_proto::proto::polychrome::state::v1 as pb;
use polyc_state::{
error::StateError,
query_audit::{QueryAuditError, QueryId},
};
use crate::{
error::{TransportFallback, decode_detail, from_connect_error as state_from_connect},
wire::Kernel,
};
pub const QUERY_AUDIT_ERROR_DETAIL_TYPE: &str = "polychrome.state.v1.QueryAuditErrorDetail";
impl From<Kernel<&QueryAuditError>> for pb::QueryAuditErrorDetail {
fn from(value: Kernel<&QueryAuditError>) -> Self {
use pb::__buffa::oneof::query_audit_error_detail::Outcome;
let outcome = match value.0 {
QueryAuditError::State(error) => {
Outcome::from(pb::StateErrorDetail::from(Kernel(error)))
}
QueryAuditError::NoRecordedIntent { query } => {
Outcome::from(pb::NoRecordedQueryIntentDetail {
query: query.as_str().to_owned(),
__buffa_unknown_fields: buffa::UnknownFields::default(),
})
}
};
Self {
outcome: Some(outcome),
__buffa_unknown_fields: buffa::UnknownFields::default(),
}
}
}
impl TryFrom<pb::QueryAuditErrorDetail> for Kernel<QueryAuditError> {
type Error = pb::QueryAuditErrorDetail;
fn try_from(value: pb::QueryAuditErrorDetail) -> Result<Self, Self::Error> {
use pb::__buffa::oneof::query_audit_error_detail::Outcome;
let Some(outcome) = value.outcome.clone() else {
return Err(value);
};
match outcome {
Outcome::State(detail) => Kernel::<StateError>::try_from(*detail)
.map(|error| Self(QueryAuditError::State(error.into_inner())))
.map_err(|_| value),
Outcome::NoRecordedIntent(detail) => Ok(Self(QueryAuditError::NoRecordedIntent {
query: QueryId::new(detail.query),
})),
}
}
}
#[must_use]
pub fn to_connect_error(error: &QueryAuditError) -> ConnectError {
let code = match error {
QueryAuditError::State(state) => crate::error::code_for(state),
QueryAuditError::NoRecordedIntent { .. } => ErrorCode::FailedPrecondition,
};
ConnectError::new(code, error.to_string()).with_detail(ErrorDetail::from_message(
QUERY_AUDIT_ERROR_DETAIL_TYPE,
&pb::QueryAuditErrorDetail::from(Kernel(error)),
))
}
#[must_use]
pub fn from_connect_error(error: &ConnectError, fallback: &TransportFallback) -> QueryAuditError {
for detail in &error.details {
if detail.type_url != QUERY_AUDIT_ERROR_DETAIL_TYPE {
continue;
}
let Some(encoded) = detail.value.as_deref() else {
continue;
};
let Ok(bytes) = decode_detail(encoded) else {
continue;
};
let Ok(message) = <pb::QueryAuditErrorDetail as buffa::Message>::decode_from_slice(&bytes)
else {
continue;
};
if let Ok(typed) = Kernel::<QueryAuditError>::try_from(message) {
return typed.into_inner();
}
}
QueryAuditError::State(state_from_connect(error, fallback))
}
#[cfg(test)]
mod tests {
use super::*;
use polyc_state::query_audit;
#[test]
fn no_intent_round_trips_as_its_own_variant() {
let original = QueryAuditError::NoRecordedIntent {
query: QueryId::new("q"),
};
let wire = to_connect_error(&original);
let back = from_connect_error(
&wire,
&TransportFallback::new(query_audit::family(), 65_536, 1),
);
assert_eq!(back, original);
}
}