feat(poc): enhance SDK client error handling in user agent module
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::user_agent::{UserAgentRequest, UserAgentResponse},
|
proto::user_agent::{
|
||||||
|
SdkClientError as ProtoSdkClientError, UserAgentRequest, UserAgentResponse,
|
||||||
|
},
|
||||||
transport::Bi,
|
transport::Bi,
|
||||||
};
|
};
|
||||||
use kameo::actor::Spawn as _;
|
use kameo::actor::Spawn as _;
|
||||||
@@ -24,12 +26,27 @@ pub enum TransportResponseError {
|
|||||||
StateTransitionFailed,
|
StateTransitionFailed,
|
||||||
#[error("Vault is not available")]
|
#[error("Vault is not available")]
|
||||||
KeyHolderActorUnreachable,
|
KeyHolderActorUnreachable,
|
||||||
|
#[error("SDK client approve failed: {0:?}")]
|
||||||
|
SdkClientApprove(ProtoSdkClientError),
|
||||||
|
#[error("SDK client list failed: {0:?}")]
|
||||||
|
SdkClientList(ProtoSdkClientError),
|
||||||
|
#[error("SDK client revoke failed: {0:?}")]
|
||||||
|
SdkClientRevoke(ProtoSdkClientError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Auth(#[from] auth::Error),
|
Auth(#[from] auth::Error),
|
||||||
#[error("Failed registering connection")]
|
#[error("Failed registering connection")]
|
||||||
ConnectionRegistrationFailed,
|
ConnectionRegistrationFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TransportResponseError {
|
||||||
|
pub fn is_terminal(&self) -> bool {
|
||||||
|
!matches!(
|
||||||
|
self,
|
||||||
|
Self::SdkClientApprove(_) | Self::SdkClientList(_) | Self::SdkClientRevoke(_)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Transport =
|
pub type Transport =
|
||||||
Box<dyn Bi<UserAgentRequest, Result<UserAgentResponse, TransportResponseError>> + Send>;
|
Box<dyn Bi<UserAgentRequest, Result<UserAgentResponse, TransportResponseError>> + Send>;
|
||||||
|
|
||||||
|
|||||||
@@ -304,11 +304,9 @@ impl UserAgentSession {
|
|||||||
use sdk_client_approve_response::Result as ApproveResult;
|
use sdk_client_approve_response::Result as ApproveResult;
|
||||||
|
|
||||||
if req.pubkey.len() != 32 {
|
if req.pubkey.len() != 32 {
|
||||||
return Ok(response(UserAgentResponsePayload::SdkClientApprove(
|
return Err(TransportResponseError::SdkClientApprove(
|
||||||
SdkClientApproveResponse {
|
ProtoSdkClientError::Internal,
|
||||||
result: Some(ApproveResult::Error(ProtoSdkClientError::Internal.into())),
|
));
|
||||||
},
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
@@ -320,11 +318,9 @@ impl UserAgentSession {
|
|||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(?e, "Failed to get DB connection for sdk_client_approve");
|
error!(?e, "Failed to get DB connection for sdk_client_approve");
|
||||||
return Ok(response(UserAgentResponsePayload::SdkClientApprove(
|
return Err(TransportResponseError::SdkClientApprove(
|
||||||
SdkClientApproveResponse {
|
ProtoSdkClientError::Internal,
|
||||||
result: Some(ApproveResult::Error(ProtoSdkClientError::Internal.into())),
|
));
|
||||||
},
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -363,33 +359,23 @@ impl UserAgentSession {
|
|||||||
)),
|
)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(?e, "Failed to fetch inserted SDK client");
|
error!(?e, "Failed to fetch inserted SDK client");
|
||||||
Ok(response(UserAgentResponsePayload::SdkClientApprove(
|
Err(TransportResponseError::SdkClientApprove(
|
||||||
SdkClientApproveResponse {
|
ProtoSdkClientError::Internal,
|
||||||
result: Some(ApproveResult::Error(
|
))
|
||||||
ProtoSdkClientError::Internal.into(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(diesel::result::Error::DatabaseError(
|
Err(diesel::result::Error::DatabaseError(
|
||||||
diesel::result::DatabaseErrorKind::UniqueViolation,
|
diesel::result::DatabaseErrorKind::UniqueViolation,
|
||||||
_,
|
_,
|
||||||
)) => Ok(response(UserAgentResponsePayload::SdkClientApprove(
|
)) => Err(TransportResponseError::SdkClientApprove(
|
||||||
SdkClientApproveResponse {
|
ProtoSdkClientError::AlreadyExists,
|
||||||
result: Some(ApproveResult::Error(
|
)),
|
||||||
ProtoSdkClientError::AlreadyExists.into(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
))),
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(?e, "Failed to insert SDK client");
|
error!(?e, "Failed to insert SDK client");
|
||||||
Ok(response(UserAgentResponsePayload::SdkClientApprove(
|
Err(TransportResponseError::SdkClientApprove(
|
||||||
SdkClientApproveResponse {
|
ProtoSdkClientError::Internal,
|
||||||
result: Some(ApproveResult::Error(ProtoSdkClientError::Internal.into())),
|
))
|
||||||
},
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,13 +385,9 @@ impl UserAgentSession {
|
|||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(?e, "Failed to get DB connection for sdk_client_list");
|
error!(?e, "Failed to get DB connection for sdk_client_list");
|
||||||
return Ok(response(UserAgentResponsePayload::SdkClientList(
|
return Err(TransportResponseError::SdkClientList(
|
||||||
SdkClientListResponse {
|
ProtoSdkClientError::Internal,
|
||||||
result: Some(sdk_client_list_response::Result::Error(
|
));
|
||||||
ProtoSdkClientError::Internal.into(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -434,13 +416,9 @@ impl UserAgentSession {
|
|||||||
))),
|
))),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(?e, "Failed to list SDK clients");
|
error!(?e, "Failed to list SDK clients");
|
||||||
Ok(response(UserAgentResponsePayload::SdkClientList(
|
Err(TransportResponseError::SdkClientList(
|
||||||
SdkClientListResponse {
|
ProtoSdkClientError::Internal,
|
||||||
result: Some(sdk_client_list_response::Result::Error(
|
))
|
||||||
ProtoSdkClientError::Internal.into(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -452,11 +430,9 @@ impl UserAgentSession {
|
|||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(?e, "Failed to get DB connection for sdk_client_revoke");
|
error!(?e, "Failed to get DB connection for sdk_client_revoke");
|
||||||
return Ok(response(UserAgentResponsePayload::SdkClientRevoke(
|
return Err(TransportResponseError::SdkClientRevoke(
|
||||||
SdkClientRevokeResponse {
|
ProtoSdkClientError::Internal,
|
||||||
result: Some(RevokeResult::Error(ProtoSdkClientError::Internal.into())),
|
));
|
||||||
},
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -465,11 +441,9 @@ impl UserAgentSession {
|
|||||||
.execute(&mut conn)
|
.execute(&mut conn)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(0) => Ok(response(UserAgentResponsePayload::SdkClientRevoke(
|
Ok(0) => Err(TransportResponseError::SdkClientRevoke(
|
||||||
SdkClientRevokeResponse {
|
ProtoSdkClientError::NotFound,
|
||||||
result: Some(RevokeResult::Error(ProtoSdkClientError::NotFound.into())),
|
)),
|
||||||
},
|
|
||||||
))),
|
|
||||||
Ok(_) => Ok(response(UserAgentResponsePayload::SdkClientRevoke(
|
Ok(_) => Ok(response(UserAgentResponsePayload::SdkClientRevoke(
|
||||||
SdkClientRevokeResponse {
|
SdkClientRevokeResponse {
|
||||||
result: Some(RevokeResult::Ok(())),
|
result: Some(RevokeResult::Ok(())),
|
||||||
@@ -478,20 +452,14 @@ impl UserAgentSession {
|
|||||||
Err(diesel::result::Error::DatabaseError(
|
Err(diesel::result::Error::DatabaseError(
|
||||||
diesel::result::DatabaseErrorKind::ForeignKeyViolation,
|
diesel::result::DatabaseErrorKind::ForeignKeyViolation,
|
||||||
_,
|
_,
|
||||||
)) => Ok(response(UserAgentResponsePayload::SdkClientRevoke(
|
)) => Err(TransportResponseError::SdkClientRevoke(
|
||||||
SdkClientRevokeResponse {
|
ProtoSdkClientError::HasRelatedData,
|
||||||
result: Some(RevokeResult::Error(
|
)),
|
||||||
ProtoSdkClientError::HasRelatedData.into(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
))),
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(?e, "Failed to delete SDK client");
|
error!(?e, "Failed to delete SDK client");
|
||||||
Ok(response(UserAgentResponsePayload::SdkClientRevoke(
|
Err(TransportResponseError::SdkClientRevoke(
|
||||||
SdkClientRevokeResponse {
|
ProtoSdkClientError::Internal,
|
||||||
result: Some(RevokeResult::Error(ProtoSdkClientError::Internal.into())),
|
))
|
||||||
},
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,8 +526,15 @@ impl Actor for UserAgentSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let _ = self.props.transport.send(Err(err)).await;
|
let should_stop = err.is_terminal();
|
||||||
return Some(kameo::mailbox::Signal::Stop);
|
if self.props.transport.send(Err(err)).await.is_err() {
|
||||||
|
error!(actor = "useragent", reason = "channel closed", "send.failed");
|
||||||
|
return Some(kameo::mailbox::Signal::Stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_stop {
|
||||||
|
return Some(kameo::mailbox::Signal::Stop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::{
|
proto::{
|
||||||
client::{ClientRequest, ClientResponse},
|
client::{ClientRequest, ClientResponse},
|
||||||
user_agent::{UserAgentRequest, UserAgentResponse},
|
user_agent::{
|
||||||
|
SdkClientApproveResponse, SdkClientListResponse, SdkClientRevokeResponse,
|
||||||
|
UserAgentRequest, UserAgentResponse, sdk_client_approve_response,
|
||||||
|
sdk_client_list_response, sdk_client_revoke_response,
|
||||||
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
transport::{IdentityRecvConverter, SendConverter, grpc},
|
transport::{IdentityRecvConverter, SendConverter, grpc},
|
||||||
};
|
};
|
||||||
@@ -37,6 +42,27 @@ impl SendConverter for UserAgentGrpcSender {
|
|||||||
fn convert(&self, item: Self::Input) -> Self::Output {
|
fn convert(&self, item: Self::Input) -> Self::Output {
|
||||||
match item {
|
match item {
|
||||||
Ok(message) => Ok(message),
|
Ok(message) => Ok(message),
|
||||||
|
Err(TransportResponseError::SdkClientApprove(code)) => Ok(UserAgentResponse {
|
||||||
|
payload: Some(UserAgentResponsePayload::SdkClientApprove(
|
||||||
|
SdkClientApproveResponse {
|
||||||
|
result: Some(sdk_client_approve_response::Result::Error(code.into())),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
Err(TransportResponseError::SdkClientList(code)) => Ok(UserAgentResponse {
|
||||||
|
payload: Some(UserAgentResponsePayload::SdkClientList(
|
||||||
|
SdkClientListResponse {
|
||||||
|
result: Some(sdk_client_list_response::Result::Error(code.into())),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
Err(TransportResponseError::SdkClientRevoke(code)) => Ok(UserAgentResponse {
|
||||||
|
payload: Some(UserAgentResponsePayload::SdkClientRevoke(
|
||||||
|
SdkClientRevokeResponse {
|
||||||
|
result: Some(sdk_client_revoke_response::Result::Error(code.into())),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
}),
|
||||||
Err(err) => Err(user_agent_error_status(err)),
|
Err(err) => Err(user_agent_error_status(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,6 +129,11 @@ fn user_agent_error_status(value: TransportResponseError) -> Status {
|
|||||||
TransportResponseError::KeyHolderActorUnreachable => {
|
TransportResponseError::KeyHolderActorUnreachable => {
|
||||||
Status::internal("Vault is not available")
|
Status::internal("Vault is not available")
|
||||||
}
|
}
|
||||||
|
TransportResponseError::SdkClientApprove(_)
|
||||||
|
| TransportResponseError::SdkClientList(_)
|
||||||
|
| TransportResponseError::SdkClientRevoke(_) => {
|
||||||
|
Status::internal("SDK client operation failed")
|
||||||
|
}
|
||||||
TransportResponseError::Auth(ref err) => auth_error_status(err),
|
TransportResponseError::Auth(ref err) => auth_error_status(err),
|
||||||
TransportResponseError::ConnectionRegistrationFailed => {
|
TransportResponseError::ConnectionRegistrationFailed => {
|
||||||
Status::internal("Failed registering connection")
|
Status::internal("Failed registering connection")
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ use arbiter_proto::proto::user_agent::{
|
|||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
};
|
};
|
||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::{GlobalActors, user_agent::session::UserAgentSession},
|
actors::{
|
||||||
|
GlobalActors,
|
||||||
|
user_agent::{TransportResponseError, session::UserAgentSession},
|
||||||
|
},
|
||||||
db,
|
db,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,22 +71,15 @@ async fn test_sdk_client_approve_duplicate_returns_already_exists() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let response = session
|
let err = session
|
||||||
.process_transport_inbound(req)
|
.process_transport_inbound(req)
|
||||||
.await
|
.await
|
||||||
.expect("second insert should not panic");
|
.expect_err("second insert should return typed TransportResponseError");
|
||||||
|
|
||||||
match response.payload.unwrap() {
|
assert_eq!(
|
||||||
UserAgentResponsePayload::SdkClientApprove(resp) => match resp.result.unwrap() {
|
err,
|
||||||
sdk_client_approve_response::Result::Error(code) => {
|
TransportResponseError::SdkClientApprove(ProtoSdkClientError::AlreadyExists)
|
||||||
assert_eq!(code, ProtoSdkClientError::AlreadyExists as i32);
|
);
|
||||||
}
|
|
||||||
sdk_client_approve_response::Result::Client(_) => {
|
|
||||||
panic!("Expected AlreadyExists error for duplicate pubkey")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
other => panic!("Expected SdkClientApprove, got {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -203,26 +199,19 @@ async fn test_sdk_client_revoke_not_found_returns_error() {
|
|||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let mut session = make_session(&db).await;
|
let mut session = make_session(&db).await;
|
||||||
|
|
||||||
let response = session
|
let err = session
|
||||||
.process_transport_inbound(UserAgentRequest {
|
.process_transport_inbound(UserAgentRequest {
|
||||||
payload: Some(UserAgentRequestPayload::SdkClientRevoke(
|
payload: Some(UserAgentRequestPayload::SdkClientRevoke(
|
||||||
SdkClientRevokeRequest { client_id: 9999 },
|
SdkClientRevokeRequest { client_id: 9999 },
|
||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.expect_err("missing client should return typed TransportResponseError");
|
||||||
|
|
||||||
match response.payload.unwrap() {
|
assert_eq!(
|
||||||
UserAgentResponsePayload::SdkClientRevoke(resp) => match resp.result.unwrap() {
|
err,
|
||||||
sdk_client_revoke_response::Result::Error(code) => {
|
TransportResponseError::SdkClientRevoke(ProtoSdkClientError::NotFound)
|
||||||
assert_eq!(code, ProtoSdkClientError::NotFound as i32);
|
);
|
||||||
}
|
|
||||||
sdk_client_revoke_response::Result::Ok(_) => {
|
|
||||||
panic!("Expected NotFound error for missing client_id")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
other => panic!("Expected SdkClientRevoke, got {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
Reference in New Issue
Block a user