armature_graphql_client/
response.rs

1//! GraphQL response types.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// GraphQL response from the server.
7#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct GraphQLResponse<T = Value> {
9    /// The data returned by the query/mutation.
10    #[serde(default)]
11    pub data: Option<T>,
12    /// Errors returned by the server.
13    #[serde(default)]
14    pub errors: Option<Vec<GraphQLResponseError>>,
15    /// Extensions (for tracing, caching info, etc.).
16    #[serde(default)]
17    pub extensions: Option<Value>,
18}
19
20impl<T> GraphQLResponse<T> {
21    /// Check if the response has errors.
22    pub fn has_errors(&self) -> bool {
23        self.errors.as_ref().is_some_and(|e| !e.is_empty())
24    }
25
26    /// Get the data, returning an error if there are GraphQL errors.
27    pub fn into_result(self) -> crate::Result<T> {
28        if let Some(errors) = self.errors {
29            if !errors.is_empty() {
30                return Err(crate::GraphQLError::GraphQL(errors));
31            }
32        }
33        self.data
34            .ok_or_else(|| crate::GraphQLError::Parse("Response contained no data".to_string()))
35    }
36
37    /// Get the data, ignoring any errors.
38    pub fn data(self) -> Option<T> {
39        self.data
40    }
41
42    /// Get the errors.
43    pub fn errors(&self) -> Option<&[GraphQLResponseError]> {
44        self.errors.as_deref()
45    }
46}
47
48/// A GraphQL error from the server.
49#[derive(Debug, Clone, Deserialize, Serialize)]
50pub struct GraphQLResponseError {
51    /// Error message.
52    pub message: String,
53    /// Locations in the query where the error occurred.
54    #[serde(default)]
55    pub locations: Option<Vec<ErrorLocation>>,
56    /// Path to the field that caused the error.
57    #[serde(default)]
58    pub path: Option<Vec<PathSegment>>,
59    /// Additional error extensions.
60    #[serde(default)]
61    pub extensions: Option<Value>,
62}
63
64impl std::fmt::Display for GraphQLResponseError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(f, "{}", self.message)?;
67        if let Some(locations) = &self.locations {
68            if !locations.is_empty() {
69                write!(f, " at ")?;
70                for (i, loc) in locations.iter().enumerate() {
71                    if i > 0 {
72                        write!(f, ", ")?;
73                    }
74                    write!(f, "{}:{}", loc.line, loc.column)?;
75                }
76            }
77        }
78        Ok(())
79    }
80}
81
82/// Location in the GraphQL query.
83#[derive(Debug, Clone, Deserialize, Serialize)]
84pub struct ErrorLocation {
85    /// Line number (1-indexed).
86    pub line: u32,
87    /// Column number (1-indexed).
88    pub column: u32,
89}
90
91/// Path segment in a GraphQL error.
92#[derive(Debug, Clone, Deserialize, Serialize)]
93#[serde(untagged)]
94pub enum PathSegment {
95    /// Field name.
96    Field(String),
97    /// Array index.
98    Index(usize),
99}
100
101impl std::fmt::Display for PathSegment {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        match self {
104            Self::Field(name) => write!(f, "{}", name),
105            Self::Index(idx) => write!(f, "[{}]", idx),
106        }
107    }
108}
109
110/// Format a path as a string.
111pub fn format_path(path: &[PathSegment]) -> String {
112    path.iter()
113        .map(|s| s.to_string())
114        .collect::<Vec<_>>()
115        .join(".")
116}