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