Skip to main content

apollo_router/services/
mod.rs

1//! Implementation of the various steps in the router's processing pipeline.
2
3use std::sync::Arc;
4
5use parking_lot::Mutex;
6use schemars::JsonSchema;
7use serde::Deserialize;
8use serde::Serialize;
9use strum::Display;
10
11pub(crate) use self::execution::service::*;
12pub(crate) use self::query_planner::*;
13pub(crate) use self::subgraph_service::*;
14pub(crate) use self::supergraph::service::*;
15use crate::graphql::Request;
16use crate::http_ext;
17pub use crate::http_ext::TryIntoHeaderName;
18pub use crate::http_ext::TryIntoHeaderValue;
19pub use crate::query_planner::OperationKind;
20pub(crate) use crate::services::connect::Request as ConnectRequest;
21pub(crate) use crate::services::connect::Response as ConnectResponse;
22pub(crate) use crate::services::execution::Request as ExecutionRequest;
23pub(crate) use crate::services::execution::Response as ExecutionResponse;
24pub(crate) use crate::services::fetch::FetchRequest;
25pub(crate) use crate::services::fetch::Response as FetchResponse;
26pub(crate) use crate::services::query_planner::Request as QueryPlannerRequest;
27pub(crate) use crate::services::query_planner::Response as QueryPlannerResponse;
28pub(crate) use crate::services::router::Request as RouterRequest;
29pub(crate) use crate::services::router::Response as RouterResponse;
30pub(crate) use crate::services::subgraph::Request as SubgraphRequest;
31pub(crate) use crate::services::subgraph::Response as SubgraphResponse;
32pub(crate) use crate::services::supergraph::Request as SupergraphRequest;
33pub(crate) use crate::services::supergraph::Response as SupergraphResponse;
34pub(crate) use crate::services::supergraph::service::SupergraphCreator;
35
36pub(crate) mod connect;
37pub(crate) mod connector;
38pub(crate) mod connector_service;
39pub mod execution;
40pub(crate) mod external;
41pub(crate) mod fetch;
42pub(crate) mod fetch_service;
43pub(crate) mod header_masking;
44pub(crate) mod hickory_dns_connector;
45pub(crate) mod http;
46pub(crate) mod layers;
47pub(crate) mod new_service;
48pub(crate) mod query_planner;
49pub mod router;
50pub mod subgraph;
51pub(crate) mod subgraph_service;
52pub mod supergraph;
53
54/// Represents the steps of the pipeline that can support user-extensibility.
55#[derive(Clone, Debug, Display, Deserialize, PartialEq, Serialize, JsonSchema)]
56pub(crate) enum PipelineStep {
57    RouterRequest,
58    RouterResponse,
59    SupergraphRequest,
60    SupergraphResponse,
61    ExecutionRequest,
62    ExecutionResponse,
63    SubgraphRequest,
64    SubgraphResponse,
65    ConnectorRequest,
66    ConnectorResponse,
67}
68
69impl From<PipelineStep> for opentelemetry::Value {
70    fn from(val: PipelineStep) -> Self {
71        val.to_string().into()
72    }
73}
74
75impl AsRef<Request> for http_ext::Request<Request> {
76    fn as_ref(&self) -> &Request {
77        self.body()
78    }
79}
80
81impl AsRef<Request> for Arc<http_ext::Request<Request>> {
82    fn as_ref(&self) -> &Request {
83        self.body()
84    }
85}
86
87// Public-hidden for tests
88#[allow(missing_docs)]
89pub static APOLLO_KEY: Mutex<Option<String>> = Mutex::new(None);
90#[allow(missing_docs)]
91pub static APOLLO_GRAPH_REF: Mutex<Option<String>> = Mutex::new(None);
92
93pub(crate) fn apollo_key() -> Option<String> {
94    APOLLO_KEY.lock().clone()
95}
96
97pub(crate) fn apollo_graph_reference() -> Option<String> {
98    APOLLO_GRAPH_REF.lock().clone()
99}
100
101// set the supported `@defer` specification version to https://github.com/graphql/graphql-spec/pull/742/commits/01d7b98f04810c9a9db4c0e53d3c4d54dbf10b82
102pub(crate) const MULTIPART_DEFER_SPEC_PARAMETER: &str = "deferSpec";
103pub(crate) const MULTIPART_DEFER_SPEC_VALUE: &str = "20220824";
104pub(crate) const MULTIPART_DEFER_ACCEPT: &str = "multipart/mixed;deferSpec=20220824";
105pub(crate) const MULTIPART_DEFER_CONTENT_TYPE: &str =
106    "multipart/mixed;boundary=\"graphql\";deferSpec=20220824";
107
108pub(crate) const MULTIPART_SUBSCRIPTION_ACCEPT: &str = "multipart/mixed;subscriptionSpec=1.0";
109pub(crate) const MULTIPART_SUBSCRIPTION_CONTENT_TYPE: &str =
110    "multipart/mixed;boundary=\"graphql\";subscriptionSpec=1.0";
111pub(crate) const MULTIPART_SUBSCRIPTION_SPEC_PARAMETER: &str = "subscriptionSpec";
112pub(crate) const MULTIPART_SUBSCRIPTION_SPEC_VALUE: &str = "1.0";
113
114#[cfg(unix)]
115pub(crate) const DEFAULT_SOCKET_PATH: &str = "/";
116pub(crate) const PATH_QUERY_PARAM: &str = "path=";
117
118/// Parse a Unix socket URL path (the part after `unix://`) and extract the socket path
119/// and HTTP path (if provided). Supports an optional `path` query parameter to specify the HTTP path.
120///
121/// Examples:
122/// - `/tmp/socket.sock` -> (`/tmp/socket.sock`, `/`)
123/// - `/tmp/socket.sock?path=/api/v1` -> (`/tmp/socket.sock`, `/api/v1`)
124///
125/// Requires:
126/// - when using query params, the param must be denoted by `?path=`
127#[cfg(unix)]
128pub(crate) fn parse_unix_socket_url(url_path: &str) -> (&str, &str) {
129    if let Some(query_start) = url_path.find('?') {
130        let socket_path = &url_path[..query_start];
131        let query = &url_path[query_start + 1..];
132
133        // Parse the `path` parameter from the query string
134        let http_path = query
135            .split('&')
136            .find_map(|param| param.strip_prefix(PATH_QUERY_PARAM))
137            .unwrap_or(DEFAULT_SOCKET_PATH);
138
139        (socket_path, http_path)
140    } else {
141        (url_path, DEFAULT_SOCKET_PATH)
142    }
143}
144
145#[cfg(unix)]
146#[cfg(test)]
147mod unix_socket_url_tests {
148    use rstest::rstest;
149
150    use super::parse_unix_socket_url;
151
152    #[rstest]
153    #[case::without_query("/tmp/coprocessor.sock", "/tmp/coprocessor.sock", "/")]
154    #[case::with_path_param(
155        "/tmp/coprocessor.sock?path=/api/v1",
156        "/tmp/coprocessor.sock",
157        "/api/v1"
158    )]
159    #[case::with_multiple_params(
160        "/tmp/coprocessor.sock?other=value&path=/api/v1&another=x",
161        "/tmp/coprocessor.sock",
162        "/api/v1"
163    )]
164    #[case::with_other_params_only(
165        "/tmp/coprocessor.sock?other=value",
166        "/tmp/coprocessor.sock",
167        "/"
168    )]
169    #[case::with_empty_query("/tmp/coprocessor.sock?", "/tmp/coprocessor.sock", "/")]
170    #[case::with_nested_http_path(
171        "/tmp/coprocessor.sock?path=/api/v1/coprocessor/hook",
172        "/tmp/coprocessor.sock",
173        "/api/v1/coprocessor/hook"
174    )]
175    #[case::with_empty_path_param("/tmp/coprocessor.sock?path", "/tmp/coprocessor.sock", "/")]
176    #[case::without_leading_slash(
177        "/tmp/coprocessor.sock?path=no_leading_slash",
178        "/tmp/coprocessor.sock",
179        "no_leading_slash"
180    )]
181    fn parse_socket_url(
182        #[case] input: &str,
183        #[case] expected_socket: &str,
184        #[case] expected_http_path: &str,
185    ) {
186        let (socket, http_path) = parse_unix_socket_url(input);
187        assert_eq!(socket, expected_socket);
188        assert_eq!(http_path, expected_http_path);
189    }
190}