Skip to main content
Glama

Convex MCP server

Official
by get-convex
error_metadata.rs9.86 kB
use std::fmt::Display; use anyhow::Context; use errors::{ ErrorCode, ErrorMetadata, INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR_MSG, }; use prost::Message; use crate::errors::{ ErrorCode as ErrorCodeProto, ErrorMetadata as ErrorMetadataProto, OccInfo as OccInfoProto, StatusDetails as StatusDetailsProto, }; impl From<ErrorCode> for ErrorCodeProto { fn from(code: ErrorCode) -> Self { match code { ErrorCode::BadRequest => ErrorCodeProto::BadRequest, ErrorCode::Conflict => ErrorCodeProto::Conflict, ErrorCode::Unauthenticated => ErrorCodeProto::Unauthenticated, ErrorCode::AuthUpdateFailed => ErrorCodeProto::AuthUpdateFailed, ErrorCode::Forbidden => ErrorCodeProto::Forbidden, ErrorCode::NotFound => ErrorCodeProto::TransientNotFound, ErrorCode::ClientDisconnect => ErrorCodeProto::ClientDisconnect, ErrorCode::RateLimited => ErrorCodeProto::RateLimited, ErrorCode::Overloaded => ErrorCodeProto::Overloaded, ErrorCode::FeatureTemporarilyUnavailable => { ErrorCodeProto::FeatureTemporarilyUnavailable }, ErrorCode::RejectedBeforeExecution => ErrorCodeProto::RejectedBeforeExecution, ErrorCode::OCC { .. } => ErrorCodeProto::Occ, ErrorCode::PaginationLimit => ErrorCodeProto::PaginationLimit, ErrorCode::OutOfRetention => ErrorCodeProto::OutOfRetention, ErrorCode::OperationalInternalServerError => { ErrorCodeProto::OperationalInternalServerError }, ErrorCode::MisdirectedRequest => ErrorCodeProto::MisdirectedRequest, } } } impl ErrorCodeProto { fn into_rust_type(self, occ_info: OccInfoProto) -> ErrorCode { match self { ErrorCodeProto::BadRequest => ErrorCode::BadRequest, ErrorCodeProto::Conflict => ErrorCode::Conflict, ErrorCodeProto::Unauthenticated => ErrorCode::Unauthenticated, ErrorCodeProto::AuthUpdateFailed => ErrorCode::AuthUpdateFailed, ErrorCodeProto::Forbidden => ErrorCode::Forbidden, ErrorCodeProto::TransientNotFound => ErrorCode::NotFound, ErrorCodeProto::ClientDisconnect => ErrorCode::ClientDisconnect, ErrorCodeProto::RateLimited => ErrorCode::RateLimited, ErrorCodeProto::Overloaded => ErrorCode::Overloaded, ErrorCodeProto::FeatureTemporarilyUnavailable => { ErrorCode::FeatureTemporarilyUnavailable }, ErrorCodeProto::RejectedBeforeExecution => ErrorCode::RejectedBeforeExecution, ErrorCodeProto::Occ => ErrorCode::OCC { table_name: occ_info.table_name, document_id: occ_info.document_id, write_source: occ_info.write_source, is_system: occ_info.is_system, }, ErrorCodeProto::PaginationLimit => ErrorCode::PaginationLimit, ErrorCodeProto::OutOfRetention => ErrorCode::OutOfRetention, ErrorCodeProto::OperationalInternalServerError => { ErrorCode::OperationalInternalServerError }, ErrorCodeProto::MisdirectedRequest => ErrorCode::MisdirectedRequest, } } } impl From<ErrorMetadata> for ErrorMetadataProto { fn from(metadata: ErrorMetadata) -> Self { ErrorMetadataProto { code: ErrorCodeProto::from(metadata.code.clone()).into(), short_msg: Some(metadata.short_msg.to_string()), msg: Some(metadata.msg.to_string()), occ_info: match metadata.code { ErrorCode::OCC { table_name, document_id, write_source, is_system, } => Some(OccInfoProto { table_name, document_id, write_source, is_system, }), _ => None, }, source: metadata.source, } } } impl TryFrom<ErrorMetadataProto> for ErrorMetadata { type Error = anyhow::Error; fn try_from(metadata: ErrorMetadataProto) -> anyhow::Result<Self> { let code = ErrorCodeProto::try_from(metadata.code)? .into_rust_type(metadata.occ_info.unwrap_or_default()); let short_msg = metadata.short_msg.context("Missing `short_msg` field")?; let msg = metadata.msg.context("Missing `msg` field")?; Ok(Self { code, short_msg: short_msg.into(), msg: msg.into(), source: metadata.source, }) } } pub trait ErrorMetadataStatusExt { fn from_anyhow(error: anyhow::Error) -> Self; fn into_anyhow(self) -> anyhow::Error; fn context<C>(self, context: C) -> anyhow::Error where C: Display + Send + Sync + 'static; } impl ErrorMetadataStatusExt for tonic::Status { fn from_anyhow(error: anyhow::Error) -> Self { let message = format!("{error:#}"); if let Some(metadata) = error.downcast_ref::<ErrorMetadata>().cloned() { let code: tonic::Code = metadata.code.grpc_status_code(); let details = StatusDetailsProto { error_metadata: Some(metadata.into()), }; tonic::Status::with_details(code, message, details.encode_to_vec().into()) } else { tonic::Status::internal(message) } } fn into_anyhow(self) -> anyhow::Error { let code = self.code(); let details = match StatusDetailsProto::decode(self.details()) { Ok(details) => details, Err(err) => { return anyhow::anyhow!("Failed to decode StatusDetails proto: {}", err); }, }; let mut error: anyhow::Error = self.into(); if let Some(error_metadata) = details.error_metadata { let error_metadata = match ErrorMetadata::try_from(error_metadata) { Ok(error_metadata) => error_metadata, Err(err) => return err.context("Failed to parse ErrorMetadata proto"), }; error = error.context(error_metadata) } else if error.downcast_ref::<tonic::transport::Error>().is_some() { error = error.context(ErrorMetadata::operational_internal_server_error()); } else if code == tonic::Code::ResourceExhausted { error = error.context(ErrorMetadata::overloaded( INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR_MSG, )); } error } fn context<C>(self, context: C) -> anyhow::Error where C: Display + Send + Sync + 'static, { self.into_anyhow().context(context) } } #[cfg(test)] mod tests { use cmd_util::env::env_config; use errors::{ ErrorMetadataAnyhowExt, INTERNAL_SERVER_ERROR_MSG, }; use proptest::prelude::*; use value::testing::assert_roundtrips; use super::ErrorMetadata; use crate::{ error_metadata::ErrorMetadataStatusExt, errors::ErrorMetadata as ErrorMetadataProto, }; proptest! { #![proptest_config( ProptestConfig { cases: 256 * env_config("CONVEX_PROPTEST_MULTIPLIER", 1), failure_persistence: None, ..ProptestConfig::default() } )] #[test] fn test_error_metadata_roundtrips(left in any::<ErrorMetadata>()) { assert_roundtrips::<ErrorMetadata, ErrorMetadataProto>(left); } #[test] fn test_status_propagates_metadata(original_metadata in any::<ErrorMetadata>()) { let status = tonic::Status::from_anyhow(anyhow::anyhow!("Error").context(original_metadata.clone())); let error = status.into_anyhow(); if let Some(received_metadata) = error.downcast_ref::<ErrorMetadata>() { assert_eq!(*received_metadata, original_metadata); } else { panic!("Didn't propagate error_metadata via Status"); } } } #[test] fn test_status_no_error_metadata() { let status = tonic::Status::from_anyhow(anyhow::anyhow!("Error")); // Empty status details should parse as zero bytes. assert!(status.details().is_empty()); // We should have no ErrorMetadata in the context. let error = status.into_anyhow(); assert!(error.downcast_ref::<ErrorMetadata>().is_none()); } #[test] fn test_context_no_error_metadata() { let status = tonic::Status::from_anyhow(anyhow::anyhow!("My special error")); let error = status.context("Test context"); // Check the error we log to sentry includes the original error and the context let error_string = format!("{error:#}"); assert!(error_string.contains("My special error")); assert!(error_string.contains("Test context")); // Check that the user facing portions haven't changed assert_eq!(error.user_facing_message(), INTERNAL_SERVER_ERROR_MSG); } #[test] fn test_context_with_error_metadata() { let status = tonic::Status::from_anyhow( ErrorMetadata::overloaded("ShortMsg", "Test long message").into(), ); let error = status.context("Test context"); // Check the error we log to sentry includes the original error and the context let error_string = format!("{error:#}"); assert!(error_string.contains("Test long message")); assert!(error_string.contains("Test context")); // Check that the user facing portions haven't changed assert_eq!(error.user_facing_message(), "Test long message"); assert_eq!(error.short_msg(), "ShortMsg") } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server