nash_protocol/protocol/
graphql.rs

1//! Share protocol logic for structuring GraphQL requests and parsing responses
2
3use crate::errors::{ProtocolError, Result};
4use thiserror::Error;
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7use std::convert::{TryFrom, TryInto};
8use std::fmt::Display;
9use super::traits::TryFromState;
10use super::state::State;
11use std::sync::Arc;
12use tokio::sync::RwLock;
13use std::collections::HashMap;
14
15//****************************************//
16//  GraphQL response parsing              //
17//****************************************//
18
19/// Helper to convert JSON to a response or error
20pub fn json_to_type_or_error<T: DeserializeOwned>(
21    response: serde_json::Value,
22) -> Result<ResponseOrError<T>> {
23    Ok(serde_json::from_value(response)
24        .map_err(|x| {
25            ProtocolError::coerce_static_from_str(&format!("{:#?}", x))
26        })?)
27}
28
29pub fn serializable_to_json<T: Serialize>(obj: &T) -> Result<serde_json::Value> {
30    let str_val = serde_json::to_string(obj)
31        .map_err(|_| ProtocolError("Unexpected problem serializing T to string"))?;
32    Ok(serde_json::from_str(&str_val)
33        .expect("Transforming to JSON failed. This should never happen"))
34}
35
36/// Helper to convert data corresponding to raw GraphQL types (B) into
37/// nicer library managed types A when failure is possible
38pub fn try_response_from_json<A, B>(response: serde_json::Value) -> Result<ResponseOrError<A>>
39where
40    A: TryFrom<B>,
41    <A as TryFrom<B>>::Error: Display,
42    B: DeserializeOwned,
43{
44    let parse_graphql = json_to_type_or_error::<B>(response)?;
45    // Maybe we get back a parsed result from server response
46    let mapped = parse_graphql.map(Box::new(|data| data.try_into()));
47    // Unpacking the inner state is annoying, but need to surface the conversion error if encountered
48    match mapped {
49        ResponseOrError::Response(DataResponse { data }) => match data {
50            Ok(data) => Ok(ResponseOrError::from_data(data)),
51            Err(err) => Err(ProtocolError::coerce_static_from_str(&format!("{}", err))),
52        },
53        ResponseOrError::Error(e) => Ok(ResponseOrError::Error(e)),
54    }
55}
56
57/// Helper to convert data corresponding to raw GraphQL types (B) into
58/// nicer library managed types A when failure is possible
59pub async fn try_response_with_state_from_json<A, B>(
60    response: serde_json::Value,
61    state: Arc<RwLock<State>>
62) -> Result<ResponseOrError<A>>
63where
64    A: TryFromState<B>,
65    B: DeserializeOwned,
66{
67    let parse_graphql = json_to_type_or_error::<B>(response)?;
68    // Maybe we get back a parsed result from server response
69    match parse_graphql {
70        ResponseOrError::Response(DataResponse { data }) => {
71            let conversion = TryFromState::from(data, state.clone()).await;
72            match conversion {
73                Ok(data) => Ok(ResponseOrError::from_data(data)),
74                Err(err) => Err(ProtocolError::coerce_static_from_str(&format!("{}", err))),
75            }
76        },
77        ResponseOrError::Error(e) => Ok(ResponseOrError::Error(e)),
78    }
79}
80
81pub enum ErrorOrData<T> {
82    Data(T),
83    Error(ErrorResponse),
84}
85
86/// Wrapper type to account for GraphQL errors
87#[derive(Deserialize, Serialize, Debug)]
88#[serde(untagged)]
89pub enum ResponseOrError<T> {
90    Response(DataResponse<T>),
91    Error(ErrorResponse),
92}
93
94impl<T> ResponseOrError<T> {
95    /// Build from data
96    pub fn from_data(data: T) -> Self {
97        Self::Response(DataResponse { data })
98    }
99
100    // TODO: why can't the enum look like this?
101    pub fn consume_match(self) -> ErrorOrData<T> {
102        match self {
103            Self::Response(DataResponse { data }) => ErrorOrData::Data(data),
104            Self::Error(e) => ErrorOrData::Error(e),
105        }
106    }
107
108    /// Get response from wrapper if it exists
109    pub fn response(&self) -> Option<&T> {
110        match self {
111            Self::Response(DataResponse { data }) => Some(data),
112            Self::Error(_) => None,
113        }
114    }
115    /// Get response from wrapper if it exists, will destroy wrapper
116    pub fn consume_response(self) -> Option<T> {
117        match self {
118            Self::Response(DataResponse { data }) => Some(data),
119            Self::Error(_) => None,
120        }
121    }
122    pub fn consume_error(self) -> Option<ErrorResponse> {
123        match self {
124            Self::Response(_) => None,
125            Self::Error(e) => Some(e),
126        }
127    }
128    /// Get response or else error
129    pub fn response_or_error(self) -> Result<T> {
130        match self {
131            Self::Response(DataResponse { data}) => Ok(data),
132            Self::Error(e) => Err(ProtocolError::coerce_static_from_str(&format!("{:?}", e)))
133        }
134    }
135    /// Get error from wrapper if it exists
136    pub fn error(&self) -> Option<&ErrorResponse> {
137        match self {
138            Self::Response(_) => None,
139            Self::Error(error) => Some(error),
140        }
141    }
142    /// Is the response a GraphQL error?
143    pub fn is_error(&self) -> bool {
144        match self {
145            Self::Error(_) => true,
146            Self::Response(_) => false,
147        }
148    }
149    /// Transform the inner value of a valid response. Will not transform an error
150    pub fn map<M>(self, f: Box<dyn Fn(T) -> M>) -> ResponseOrError<M> {
151        match self {
152            Self::Error(e) => ResponseOrError::Error(e),
153            Self::Response(r) => ResponseOrError::Response(r.map(f)),
154        }
155    }
156}
157
158/// Inner wrapper on valid GraphQL response data
159#[derive(Deserialize, Serialize, Debug)]
160pub struct DataResponse<T> {
161    pub data: T,
162}
163
164impl<T> DataResponse<T> {
165    fn map<M>(self, f: Box<dyn Fn(T) -> M>) -> DataResponse<M> {
166        DataResponse { data: f(self.data) }
167    }
168}
169
170#[derive(Debug, Serialize, Deserialize)]
171pub struct GraphQLResponse {
172    #[serde(default)]
173    pub data: HashMap<String, serde_json::Value>,
174    #[serde(default)]
175    pub errors: Vec<Error>
176}
177
178impl TryFrom<serde_json::Value> for GraphQLResponse {
179    type Error = ProtocolError;
180    fn try_from(response: serde_json::Value) -> Result<Self> {
181        serde_json::from_value(response).map_err(|e|
182            ProtocolError::coerce_static_from_str(
183                &format!("Couldn't parse response: {:#?}", e)
184            )
185        )
186    }
187}
188
189/// Inner wrapper on error GraphQL response data
190#[derive(Clone, Deserialize, Serialize, Debug, Error)]
191#[error("")]
192pub struct ErrorResponse {
193    pub errors: Vec<Error>,
194}
195
196/// Inner wrapper on error GraphQL response data
197#[derive(Clone, Deserialize, Serialize, Debug)]
198pub struct Error {
199    pub message: String,
200    pub path: Vec<String>
201}
202
203//****************************************//
204//  Common blockchain types               //
205//****************************************//
206
207/// This signature format is used for GraphQL request payload signatures
208/// Data is SHA256 hashed and signed with a secp256k1 ECDSA key
209#[derive(Clone, Debug)]
210pub struct RequestPayloadSignature {
211    pub signed_digest: String,
212    pub public_key: String,
213}
214
215impl RequestPayloadSignature {
216    pub fn empty() -> Self {
217        Self {
218            signed_digest: "".to_string(),
219            public_key: "".to_string(),
220        }
221    }
222}