iota_graphql_rpc/
error.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use async_graphql::{ErrorExtensionValues, ErrorExtensions, Pos, Response, ServerError};
6use async_graphql_axum::GraphQLResponse;
7use iota_indexer::errors::IndexerError;
8use iota_indexer_streaming::error::IndexerStreamingError;
9use iota_names::error::IotaNamesError;
10
11/// Error codes for the `extensions.code` field of a GraphQL error that
12/// originates from outside GraphQL.
13/// `<https://www.apollographql.com/docs/apollo-server/data/errors/#built-in-error-codes>`
14pub(crate) mod code {
15    pub const BAD_REQUEST: &str = "BAD_REQUEST";
16    pub const BAD_USER_INPUT: &str = "BAD_USER_INPUT";
17    pub const INTERNAL_SERVER_ERROR: &str = "INTERNAL_SERVER_ERROR";
18    pub const REQUEST_TIMEOUT: &str = "REQUEST_TIMEOUT";
19    pub const UNKNOWN: &str = "UNKNOWN";
20}
21
22/// Create a GraphQL Response containing an Error.
23///
24/// Most errors produced by the service will automatically be wrapped in a
25/// `GraphQLResponse`, because they will originate from within the GraphQL
26/// implementation.  This function is intended for errors that originated from
27/// outside of GraphQL (such as in middleware), but that need to be ingested by
28/// GraphQL clients.
29pub(crate) fn graphql_error_response(code: &str, message: impl Into<String>) -> GraphQLResponse {
30    let error = graphql_error(code, message);
31    Response::from_errors(error.into()).into()
32}
33
34/// Create a generic GraphQL Server Error.
35///
36/// This error has no path, source, or locations, just a message and an error
37/// code.
38pub(crate) fn graphql_error(code: &str, message: impl Into<String>) -> ServerError {
39    let mut ext = ErrorExtensionValues::default();
40    ext.set("code", code);
41
42    ServerError {
43        message: message.into(),
44        source: None,
45        locations: vec![],
46        path: vec![],
47        extensions: Some(ext),
48    }
49}
50
51pub(crate) fn graphql_error_at_pos(
52    code: &str,
53    message: impl Into<String>,
54    pos: Pos,
55) -> ServerError {
56    let mut ext = ErrorExtensionValues::default();
57    ext.set("code", code);
58
59    ServerError {
60        message: message.into(),
61        source: None,
62        locations: vec![pos],
63        path: vec![],
64        extensions: Some(ext),
65    }
66}
67
68#[derive(Clone, Debug, thiserror::Error)]
69#[non_exhaustive]
70pub enum Error {
71    #[error("Unsupported protocol version requested. Min supported: {0}, max supported: {1}")]
72    ProtocolVersionUnsupported(u64, u64),
73    #[error("'first' and 'last' must not be used together")]
74    CursorNoFirstLast,
75    #[error("Connection's page size of {0} exceeds max of {1}")]
76    PageTooLarge(u64, u32),
77    // Catch-all for client-fault errors
78    #[error("{0}")]
79    Client(String),
80    #[error("Internal error occurred while processing request: {0}")]
81    Internal(String),
82    #[error(transparent)]
83    IotaNames(#[from] IotaNamesError),
84    #[error("{0}")]
85    ServerInit(String),
86    #[error("Unsupported feature: {0}")]
87    UnsupportedFeature(String),
88}
89
90impl ErrorExtensions for Error {
91    fn extend(&self) -> async_graphql::Error {
92        async_graphql::Error::new(format!("{self}")).extend_with(|_err, e| match self {
93            Error::CursorNoFirstLast
94            | Error::PageTooLarge(_, _)
95            | Error::ProtocolVersionUnsupported(_, _)
96            | Error::Client(_) => {
97                e.set("code", code::BAD_USER_INPUT);
98            }
99            Error::Internal(_) => {
100                e.set("code", code::INTERNAL_SERVER_ERROR);
101            }
102            Error::IotaNames(_) => {
103                e.set("code", code::BAD_REQUEST);
104            }
105            Error::ServerInit(_) => {
106                e.set("code", code::UNKNOWN);
107            }
108            Error::UnsupportedFeature(_) => {
109                e.set("code", code::BAD_REQUEST);
110            }
111        })
112    }
113}
114
115impl From<IndexerError> for Error {
116    fn from(e: IndexerError) -> Self {
117        Error::Internal(e.to_string())
118    }
119}
120
121impl From<IndexerStreamingError> for Error {
122    fn from(e: IndexerStreamingError) -> Self {
123        Error::Internal(e.to_string())
124    }
125}