Coverage Report

Created: 2025-11-28 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/async-graphql-7.0.17/src/http/mod.rs
Line
Count
Source
1
//! A helper module that supports HTTP
2
3
#[cfg(feature = "altair")]
4
mod altair_source;
5
#[cfg(feature = "graphiql")]
6
mod graphiql_plugin;
7
#[cfg(feature = "graphiql")]
8
mod graphiql_source;
9
#[cfg(feature = "graphiql")]
10
mod graphiql_v2_source;
11
mod multipart;
12
mod multipart_subscribe;
13
#[cfg(feature = "playground")]
14
mod playground_source;
15
mod websocket;
16
17
#[cfg(feature = "altair")]
18
pub use altair_source::*;
19
use futures_util::io::{AsyncRead, AsyncReadExt};
20
#[cfg(feature = "graphiql")]
21
pub use graphiql_plugin::{GraphiQLPlugin, graphiql_plugin_explorer};
22
#[cfg(feature = "graphiql")]
23
pub use graphiql_source::graphiql_source;
24
#[cfg(feature = "graphiql")]
25
pub use graphiql_v2_source::{Credentials, GraphiQLSource};
26
pub use multipart::MultipartOptions;
27
pub use multipart_subscribe::{create_multipart_mixed_stream, is_accept_multipart_mixed};
28
#[cfg(feature = "playground")]
29
pub use playground_source::{GraphQLPlaygroundConfig, playground_source};
30
use serde::Deserialize;
31
pub use websocket::{
32
    ALL_WEBSOCKET_PROTOCOLS, ClientMessage, DefaultOnConnInitType, DefaultOnPingType,
33
    Protocols as WebSocketProtocols, WebSocket, WsMessage, default_on_connection_init,
34
    default_on_ping,
35
};
36
37
use crate::{BatchRequest, ParseRequestError, Request};
38
39
/// Parse a GraphQL request from a query string.
40
0
pub fn parse_query_string(input: &str) -> Result<Request, ParseRequestError> {
41
    #[derive(Deserialize)]
42
    struct RequestSerde {
43
        #[serde(default)]
44
        pub query: String,
45
        pub operation_name: Option<String>,
46
        pub variables: Option<String>,
47
        pub extensions: Option<String>,
48
    }
49
50
0
    let request: RequestSerde = serde_urlencoded::from_str(input).map_err(std::io::Error::other)?;
51
0
    let variables = request
52
0
        .variables
53
0
        .map(|data| serde_json::from_str(&data))
54
0
        .transpose()
55
0
        .map_err(|err| std::io::Error::other(format!("invalid variables: {}", err)))?
56
0
        .unwrap_or_default();
57
0
    let extensions = request
58
0
        .extensions
59
0
        .map(|data| serde_json::from_str(&data))
60
0
        .transpose()
61
0
        .map_err(|err| std::io::Error::other(format!("invalid extensions: {}", err)))?
62
0
        .unwrap_or_default();
63
64
0
    Ok(Request {
65
0
        operation_name: request.operation_name,
66
0
        variables,
67
0
        extensions,
68
0
        ..Request::new(request.query)
69
0
    })
70
0
}
71
72
/// Receive a GraphQL request from a content type and body.
73
0
pub async fn receive_body(
74
0
    content_type: Option<impl AsRef<str>>,
75
0
    body: impl AsyncRead + Send,
76
0
    opts: MultipartOptions,
77
0
) -> Result<Request, ParseRequestError> {
78
0
    receive_batch_body(content_type, body, opts)
79
0
        .await?
80
0
        .into_single()
81
0
}
82
83
/// Receive a GraphQL request from a content type and body.
84
0
pub async fn receive_batch_body(
85
0
    content_type: Option<impl AsRef<str>>,
86
0
    body: impl AsyncRead + Send,
87
0
    opts: MultipartOptions,
88
0
) -> Result<BatchRequest, ParseRequestError> {
89
    // if no content-type header is set, we default to json
90
0
    let content_type = content_type
91
0
        .as_ref()
92
0
        .map(AsRef::as_ref)
93
0
        .unwrap_or("application/graphql-response+json");
94
95
0
    let content_type: mime::Mime = content_type.parse()?;
96
97
0
    match (content_type.type_(), content_type.subtype()) {
98
        // try to use multipart
99
0
        (mime::MULTIPART, _) => {
100
0
            if let Some(boundary) = content_type.get_param("boundary") {
101
0
                multipart::receive_batch_multipart(body, boundary.to_string(), opts).await
102
            } else {
103
0
                Err(ParseRequestError::InvalidMultipart(
104
0
                    multer::Error::NoBoundary,
105
0
                ))
106
            }
107
        }
108
        // application/json or cbor (currently)
109
        // cbor is in application/octet-stream.
110
        // Note: cbor will only match if feature ``cbor`` is active
111
        // TODO: wait for mime to add application/cbor and match against that too
112
0
        _ => receive_batch_body_no_multipart(&content_type, body).await,
113
    }
114
0
}
115
116
/// Receives a GraphQL query which is either cbor or json but NOT multipart
117
/// This method is only to avoid recursive calls with [``receive_batch_body``]
118
/// and [``multipart::receive_batch_multipart``]
119
0
pub(super) async fn receive_batch_body_no_multipart(
120
0
    content_type: &mime::Mime,
121
0
    body: impl AsyncRead + Send,
122
0
) -> Result<BatchRequest, ParseRequestError> {
123
0
    assert_ne!(content_type.type_(), mime::MULTIPART, "received multipart");
124
0
    match (content_type.type_(), content_type.subtype()) {
125
        #[cfg(feature = "cbor")]
126
        // cbor is in application/octet-stream.
127
        // TODO: wait for mime to add application/cbor and match against that too
128
        (mime::OCTET_STREAM, _) | (mime::APPLICATION, mime::OCTET_STREAM) => {
129
            receive_batch_cbor(body).await
130
        }
131
        // default to json
132
0
        _ => receive_batch_json(body).await,
133
    }
134
0
}
135
/// Receive a GraphQL request from a body as JSON.
136
0
pub async fn receive_json(body: impl AsyncRead) -> Result<Request, ParseRequestError> {
137
0
    receive_batch_json(body).await?.into_single()
138
0
}
139
140
/// Receive a GraphQL batch request from a body as JSON.
141
0
pub async fn receive_batch_json(body: impl AsyncRead) -> Result<BatchRequest, ParseRequestError> {
142
0
    let mut data = Vec::new();
143
0
    futures_util::pin_mut!(body);
144
0
    body.read_to_end(&mut data)
145
0
        .await
146
0
        .map_err(ParseRequestError::Io)?;
147
0
    serde_json::from_slice::<BatchRequest>(&data)
148
0
        .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))
149
0
}
150
151
/// Receive a GraphQL request from a body as CBOR.
152
#[cfg(feature = "cbor")]
153
#[cfg_attr(docsrs, doc(cfg(feature = "cbor")))]
154
pub async fn receive_cbor(body: impl AsyncRead) -> Result<Request, ParseRequestError> {
155
    receive_batch_cbor(body).await?.into_single()
156
}
157
158
/// Receive a GraphQL batch request from a body as CBOR
159
#[cfg(feature = "cbor")]
160
#[cfg_attr(docsrs, doc(cfg(feature = "cbor")))]
161
pub async fn receive_batch_cbor(body: impl AsyncRead) -> Result<BatchRequest, ParseRequestError> {
162
    let mut data = Vec::new();
163
    futures_util::pin_mut!(body);
164
    body.read_to_end(&mut data)
165
        .await
166
        .map_err(ParseRequestError::Io)?;
167
    serde_cbor::from_slice::<BatchRequest>(&data)
168
        .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))
169
}
170
171
#[cfg(test)]
172
mod tests {
173
    use std::collections::HashMap;
174
175
    use async_graphql_value::Extensions;
176
177
    use super::*;
178
    use crate::{Variables, value};
179
180
    #[test]
181
    fn test_parse_query_string() {
182
        let request = parse_query_string("variables=%7B%7D&extensions=%7B%22persistedQuery%22%3A%7B%22sha256Hash%22%3A%22cde5de0a350a19c59f8ddcd9646e5f260b2a7d5649ff6be8e63e9462934542c3%22%2C%22version%22%3A1%7D%7D").unwrap();
183
        assert_eq!(request.query.as_str(), "");
184
        assert_eq!(request.variables, Variables::default());
185
        assert_eq!(request.extensions, {
186
            let mut extensions = HashMap::new();
187
            extensions.insert("persistedQuery".to_string(), value!({
188
                "sha256Hash": "cde5de0a350a19c59f8ddcd9646e5f260b2a7d5649ff6be8e63e9462934542c3",
189
                "version": 1,
190
            }));
191
            Extensions(extensions)
192
        });
193
194
        let request = parse_query_string("query={a}&variables=%7B%22a%22%3A10%7D").unwrap();
195
        assert_eq!(request.query.as_str(), "{a}");
196
        assert_eq!(
197
            request.variables,
198
            Variables::from_value(value!({ "a" : 10 }))
199
        );
200
    }
201
}