1use std::ops::Deref;
3use std::sync::Arc;
4
5use apollo_compiler::validation::DiagnosticList;
6use apollo_compiler::validation::WithErrors;
7use apollo_federation::error::FederationError;
8use displaydoc::Display;
9use serde::Deserialize;
10use serde::Serialize;
11use thiserror::Error;
12use tokio::task::JoinError;
13use tower::BoxError;
14
15use crate::apollo_studio_interop::UsageReporting;
16pub(crate) use crate::configuration::ConfigurationError;
17pub(crate) use crate::graphql::Error;
18use crate::graphql::ErrorExtension;
19use crate::graphql::IntoGraphQLErrors;
20use crate::graphql::Location as ErrorLocation;
21use crate::graphql::Response;
22use crate::json_ext::Path;
23use crate::json_ext::Value;
24use crate::spec::SpecError;
25use crate::spec::operation_limits::OperationLimits;
26
27const MAX_VALIDATION_ERRORS: usize = 100;
31
32#[derive(Error, Display, Debug, Clone, Serialize, Eq, PartialEq)]
37#[serde(untagged)]
38#[ignore_extra_doc_attributes]
39#[non_exhaustive]
40#[allow(missing_docs)] pub(crate) enum FetchError {
42 ValidationInvalidTypeVariable {
44 name: serde_json_bytes::ByteString,
45 message: crate::spec::InvalidInputValue,
46 },
47
48 ValidationPlanningError {
50 reason: String,
52 },
53
54 MalformedRequest {
56 reason: String,
58 },
59
60 MalformedResponse {
62 reason: String,
64 },
65
66 SubrequestMalformedResponse {
68 service: String,
70
71 reason: String,
73 },
74
75 SubrequestUnexpectedPatchResponse {
77 service: String,
79 },
80
81 SubrequestHttpError {
85 status_code: Option<u16>,
86
87 service: String,
89
90 reason: String,
92 },
93 SubrequestWsError {
97 service: String,
99
100 reason: String,
102 },
103
104 ExecutionPathNotFound { reason: String },
106
107 SubrequestBatchingError {
109 service: String,
111
112 reason: String,
114 },
115}
116
117impl FetchError {
118 pub(crate) fn to_graphql_error(&self, path: Option<Path>) -> Error {
120 let mut value: Value = serde_json_bytes::to_value(self).unwrap_or_default();
123 if let Some(extensions) = value.as_object_mut() {
124 extensions
125 .entry("code")
126 .or_insert_with(|| self.extension_code().into());
127 match self {
129 FetchError::SubrequestHttpError {
130 service,
131 status_code,
132 ..
133 } => {
134 extensions
135 .entry("service")
136 .or_insert_with(|| service.clone().into());
137 extensions.remove("status_code");
138 if let Some(status_code) = status_code {
139 extensions
140 .insert("http", serde_json_bytes::json!({ "status": status_code }));
141 }
142 }
143 FetchError::SubrequestMalformedResponse { service, .. }
144 | FetchError::SubrequestUnexpectedPatchResponse { service }
145 | FetchError::SubrequestWsError { service, .. } => {
146 extensions
147 .entry("service")
148 .or_insert_with(|| service.clone().into());
149 }
150 FetchError::ValidationInvalidTypeVariable { name, .. } => {
151 extensions.remove("message");
152 extensions
153 .entry("name")
154 .or_insert_with(|| Value::String(name.clone()));
155 }
156 _ => (),
157 }
158 }
159
160 Error::builder()
161 .message(self.to_string())
162 .locations(Vec::default())
163 .and_path(path)
164 .extensions(value.as_object().unwrap().to_owned())
165 .build()
166 }
167
168 pub(crate) fn to_response(&self) -> Response {
170 Response {
171 errors: vec![self.to_graphql_error(None)],
172 ..Response::default()
173 }
174 }
175}
176
177impl ErrorExtension for FetchError {
178 fn extension_code(&self) -> String {
179 match self {
180 FetchError::ValidationInvalidTypeVariable { .. } => "VALIDATION_INVALID_TYPE_VARIABLE",
181 FetchError::ValidationPlanningError { .. } => "VALIDATION_PLANNING_ERROR",
182 FetchError::SubrequestMalformedResponse { .. } => "SUBREQUEST_MALFORMED_RESPONSE",
183 FetchError::SubrequestUnexpectedPatchResponse { .. } => {
184 "SUBREQUEST_UNEXPECTED_PATCH_RESPONSE"
185 }
186 FetchError::SubrequestHttpError { .. } => "SUBREQUEST_HTTP_ERROR",
187 FetchError::SubrequestWsError { .. } => "SUBREQUEST_WEBSOCKET_ERROR",
188 FetchError::ExecutionPathNotFound { .. } => "EXECUTION_PATH_NOT_FOUND",
189 FetchError::MalformedRequest { .. } => "MALFORMED_REQUEST",
190 FetchError::MalformedResponse { .. } => "MALFORMED_RESPONSE",
191 FetchError::SubrequestBatchingError { .. } => "SUBREQUEST_BATCHING_ERROR",
192 }
193 .to_string()
194 }
195}
196
197impl From<QueryPlannerError> for FetchError {
198 fn from(err: QueryPlannerError) -> Self {
199 FetchError::ValidationPlanningError {
200 reason: err.to_string(),
201 }
202 }
203}
204
205#[derive(Error, Debug, Display, Clone)]
207pub(crate) enum CacheResolverError {
208 RetrievalError(Arc<QueryPlannerError>),
210 Backpressure(crate::compute_job::ComputeBackPressureError),
212 BatchingError(String),
214}
215
216impl IntoGraphQLErrors for CacheResolverError {
217 fn into_graphql_errors(self) -> Result<Vec<Error>, Self> {
218 match self {
219 CacheResolverError::RetrievalError(retrieval_error) => retrieval_error
220 .deref()
221 .clone()
222 .into_graphql_errors()
223 .map_err(|_err| CacheResolverError::RetrievalError(retrieval_error)),
224 CacheResolverError::Backpressure(e) => Ok(vec![e.to_graphql_error()]),
225 CacheResolverError::BatchingError(msg) => Ok(vec![
226 Error::builder()
227 .message(msg)
228 .extension_code("BATCH_PROCESSING_FAILED")
229 .build(),
230 ]),
231 }
232 }
233}
234
235impl From<QueryPlannerError> for CacheResolverError {
236 fn from(qp_err: QueryPlannerError) -> Self {
237 Self::RetrievalError(Arc::new(qp_err))
238 }
239}
240
241#[derive(Error, Debug, Display)]
243pub(crate) enum ServiceBuildError {
244 QpInitError(FederationError),
246
247 Schema(SchemaError),
249
250 ServiceError(BoxError),
252}
253
254impl From<SchemaError> for ServiceBuildError {
255 fn from(err: SchemaError) -> Self {
256 ServiceBuildError::Schema(err)
257 }
258}
259
260impl From<BoxError> for ServiceBuildError {
261 fn from(err: BoxError) -> Self {
262 ServiceBuildError::ServiceError(err)
263 }
264}
265
266#[derive(Error, Debug, Display, Clone, Serialize, Deserialize)]
270pub(crate) enum QueryPlannerError {
271 OperationValidationErrors(ValidationErrors),
273
274 JoinError(String),
276
277 EmptyPlan(String), UnhandledPlannerResult,
282
283 SpecError(SpecError),
285
286 LimitExceeded(OperationLimits<bool>),
288
289 Unauthorized(Vec<Path>),
292
293 FederationError(FederationErrorBridge),
295
296 Timeout(String),
298
299 MemoryLimitExceeded(String),
301}
302
303impl From<FederationErrorBridge> for QueryPlannerError {
304 fn from(value: FederationErrorBridge) -> Self {
305 Self::FederationError(value)
306 }
307}
308
309#[derive(Error, Debug, Display, Clone, Serialize, Deserialize)]
316pub(crate) enum FederationErrorBridge {
317 UnknownOperation(String),
319 OperationNameNotProvided(String),
321 Other(String),
323 Cancellation(String),
325}
326
327impl From<FederationError> for FederationErrorBridge {
328 fn from(value: FederationError) -> Self {
329 match &value {
330 err @ FederationError::SingleFederationError(
331 apollo_federation::error::SingleFederationError::UnknownOperation,
332 ) => Self::UnknownOperation(err.to_string()),
333 err @ FederationError::SingleFederationError(
334 apollo_federation::error::SingleFederationError::OperationNameNotProvided,
335 ) => Self::OperationNameNotProvided(err.to_string()),
336 err @ FederationError::SingleFederationError(
337 apollo_federation::error::SingleFederationError::PlanningCancelled,
338 ) => Self::Cancellation(err.to_string()),
339 err => Self::Other(err.to_string()),
340 }
341 }
342}
343
344impl IntoGraphQLErrors for FederationErrorBridge {
345 fn into_graphql_errors(self) -> Result<Vec<Error>, Self> {
346 match self {
347 FederationErrorBridge::UnknownOperation(msg) => Ok(vec![
348 Error::builder()
349 .message(msg)
350 .extension_code("GRAPHQL_VALIDATION_FAILED")
351 .build(),
352 ]),
353 FederationErrorBridge::OperationNameNotProvided(msg) => Ok(vec![
354 Error::builder()
355 .message(msg)
356 .extension_code("GRAPHQL_VALIDATION_FAILED")
357 .build(),
358 ]),
359 err => Err(err),
361 }
362 }
363}
364
365impl IntoGraphQLErrors for Vec<apollo_compiler::response::GraphQLError> {
366 fn into_graphql_errors(self) -> Result<Vec<Error>, Self> {
367 Ok(self
368 .into_iter()
369 .map(|err| {
370 Error::builder()
371 .message(err.message)
372 .locations(
373 err.locations
374 .into_iter()
375 .map(|location| ErrorLocation {
376 line: location.line as u32,
377 column: location.column as u32,
378 })
379 .collect::<Vec<_>>(),
380 )
381 .extension_code("GRAPHQL_VALIDATION_FAILED")
382 .build()
383 })
384 .take(MAX_VALIDATION_ERRORS)
385 .collect())
386 }
387}
388
389impl IntoGraphQLErrors for QueryPlannerError {
390 fn into_graphql_errors(self) -> Result<Vec<Error>, Self> {
391 match self {
392 QueryPlannerError::SpecError(err) => err
393 .into_graphql_errors()
394 .map_err(QueryPlannerError::SpecError),
395
396 QueryPlannerError::OperationValidationErrors(errs) => errs
397 .into_graphql_errors()
398 .map_err(QueryPlannerError::OperationValidationErrors),
399
400 QueryPlannerError::LimitExceeded(OperationLimits {
401 depth,
402 height,
403 root_fields,
404 aliases,
405 }) => {
406 let mut errors = Vec::new();
407 let mut build = |exceeded, code, message| {
408 if exceeded {
409 errors.push(
410 Error::builder()
411 .message(message)
412 .extension_code(code)
413 .build(),
414 )
415 }
416 };
417 build(
418 depth,
419 "MAX_DEPTH_LIMIT",
420 "Maximum depth limit exceeded in this operation",
421 );
422 build(
423 height,
424 "MAX_HEIGHT_LIMIT",
425 "Maximum height (field count) limit exceeded in this operation",
426 );
427 build(
428 root_fields,
429 "MAX_ROOT_FIELDS_LIMIT",
430 "Maximum root fields limit exceeded in this operation",
431 );
432 build(
433 aliases,
434 "MAX_ALIASES_LIMIT",
435 "Maximum aliases limit exceeded in this operation",
436 );
437 Ok(errors)
438 }
439 QueryPlannerError::FederationError(err) => err
440 .into_graphql_errors()
441 .map_err(QueryPlannerError::FederationError),
442 err => Err(err),
443 }
444 }
445}
446
447impl QueryPlannerError {
448 pub(crate) fn usage_reporting(&self) -> Option<UsageReporting> {
449 match self {
450 QueryPlannerError::SpecError(e) => {
451 Some(UsageReporting::Error(e.get_error_key().to_string()))
452 }
453 _ => None,
454 }
455 }
456}
457
458impl From<JoinError> for QueryPlannerError {
459 fn from(err: JoinError) -> Self {
460 QueryPlannerError::JoinError(err.to_string())
461 }
462}
463
464impl From<SpecError> for QueryPlannerError {
465 fn from(err: SpecError) -> Self {
466 match err {
467 SpecError::ValidationError(errors) => {
468 QueryPlannerError::OperationValidationErrors(errors)
469 }
470 _ => QueryPlannerError::SpecError(err),
471 }
472 }
473}
474
475impl From<ValidationErrors> for QueryPlannerError {
476 fn from(err: ValidationErrors) -> Self {
477 QueryPlannerError::OperationValidationErrors(ValidationErrors { errors: err.errors })
478 }
479}
480impl From<OperationLimits<bool>> for QueryPlannerError {
481 fn from(error: OperationLimits<bool>) -> Self {
482 QueryPlannerError::LimitExceeded(error)
483 }
484}
485
486impl From<QueryPlannerError> for Response {
487 fn from(err: QueryPlannerError) -> Self {
488 FetchError::from(err).to_response()
489 }
490}
491
492#[derive(Debug, Error, Display, derive_more::From)]
494#[non_exhaustive]
495pub(crate) enum SchemaError {
496 UrlParse(String, http::uri::InvalidUri),
498 #[from(ignore)]
500 MissingSubgraphUrl(String),
501 Parse(ParseErrors),
503 Validate(ValidationErrors),
505 FederationError(FederationError),
507 #[from(ignore)]
509 Api(String),
510
511 #[from(ignore)]
513 Connector(FederationError),
514}
515
516#[derive(Debug)]
518pub(crate) struct ParseErrors {
519 pub(crate) errors: DiagnosticList,
520}
521
522impl std::fmt::Display for ParseErrors {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 let mut errors = self.errors.iter();
525 for (i, error) in errors.by_ref().take(5).enumerate() {
526 if i > 0 {
527 f.write_str("\n")?;
528 }
529 write!(f, "{error}")?;
530 }
531 let remaining = errors.count();
532 if remaining > 0 {
533 write!(f, "\n...and {remaining} other errors")?;
534 }
535 Ok(())
536 }
537}
538
539impl IntoGraphQLErrors for ParseErrors {
540 fn into_graphql_errors(self) -> Result<Vec<Error>, Self> {
541 Ok(self
542 .errors
543 .iter()
544 .map(|diagnostic| {
545 Error::builder()
546 .message(diagnostic.error.to_string())
547 .locations(
548 diagnostic
549 .line_column_range()
550 .map(|location| {
551 vec![ErrorLocation {
552 line: location.start.line as u32,
553 column: location.start.column as u32,
554 }]
555 })
556 .unwrap_or_default(),
557 )
558 .extension_code("GRAPHQL_PARSING_FAILED")
559 .build()
560 })
561 .take(MAX_VALIDATION_ERRORS)
562 .collect())
563 }
564}
565
566#[derive(Debug, Clone, Serialize, Deserialize)]
568pub(crate) struct ValidationErrors {
569 pub(crate) errors: Vec<apollo_compiler::response::GraphQLError>,
570}
571
572impl ValidationErrors {
573 pub(crate) fn into_graphql_errors_infallible(self) -> Vec<Error> {
574 self.errors
575 .iter()
576 .map(|diagnostic| {
577 Error::builder()
578 .message(diagnostic.message.to_string())
579 .locations(
580 diagnostic
581 .locations
582 .iter()
583 .map(|loc| ErrorLocation {
584 line: loc.line as u32,
585 column: loc.column as u32,
586 })
587 .collect(),
588 )
589 .extension_code("GRAPHQL_VALIDATION_FAILED")
590 .build()
591 })
592 .take(MAX_VALIDATION_ERRORS)
593 .collect()
594 }
595}
596impl IntoGraphQLErrors for ValidationErrors {
597 fn into_graphql_errors(self) -> Result<Vec<Error>, Self> {
598 Ok(self.into_graphql_errors_infallible())
599 }
600}
601
602impl From<DiagnosticList> for ValidationErrors {
603 fn from(errors: DiagnosticList) -> Self {
604 Self {
605 errors: errors
606 .iter()
607 .map(|e| e.unstable_to_json_compat())
608 .take(MAX_VALIDATION_ERRORS)
609 .collect(),
610 }
611 }
612}
613
614impl<T> From<WithErrors<T>> for ValidationErrors {
615 fn from(WithErrors { errors, .. }: WithErrors<T>) -> Self {
616 errors.into()
617 }
618}
619
620impl std::fmt::Display for ValidationErrors {
621 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622 for (index, error) in self.errors.iter().enumerate() {
623 if index > 0 {
624 f.write_str("\n")?;
625 }
626 if let Some(location) = error.locations.first() {
627 write!(
628 f,
629 "[{}:{}] {}",
630 location.line, location.column, error.message
631 )?;
632 } else {
633 write!(f, "{}", error.message)?;
634 }
635 }
636 Ok(())
637 }
638}
639
640#[derive(Debug, Error, Display)]
642pub(crate) enum SubgraphBatchingError {
643 SenderUnavailable,
645 RequestsIsEmpty,
647 ProcessingFailed(String),
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654 use crate::assert_error_eq_ignoring_id;
655 use crate::graphql;
656
657 #[test]
658 fn test_into_graphql_error() {
659 let error = FetchError::SubrequestHttpError {
660 status_code: Some(400),
661 service: String::from("my_service"),
662 reason: String::from("invalid request"),
663 };
664 let expected_gql_error = graphql::Error::builder()
665 .message("HTTP fetch failed from 'my_service': invalid request")
666 .extension_code("SUBREQUEST_HTTP_ERROR")
667 .extension("reason", Value::String("invalid request".into()))
668 .extension("service", Value::String("my_service".into()))
669 .extension(
670 "http",
671 serde_json_bytes::json!({"status": Value::Number(400.into())}),
672 )
673 .build();
674
675 assert_error_eq_ignoring_id!(expected_gql_error, error.to_graphql_error(None));
676 }
677}