From ecf5727dbce97dddc7569b0b6d11d062db6a4136 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 25 Jun 2025 11:33:52 -0400 Subject: [PATCH] pq: include operation name in "PQ not found" error We have found that during migrations to PQ-based safelisting, it is helpful to have a bit more information about what operation the client is trying to run than just the hashed PQ ID, to help find the client app that is being blocked. While the client name can be found in the context, the operation name (if provided explicitly in the body's `operationName` field by the client) is helpful. We now provide this as an extension on the error. (It is both directly helpful in that it is available in the JSON error response if you're looking at individual failures, and it also makes it easy for a router_service-level Rust or Rhai plugin that's trying to provide more observability into PQ errors to look at the error for the extra data. This error occurs before supergraph_service runs, which means that the operation name is not in an easily accessible place for plugins.) --- ...eat_glasser_pq_error_include_extensions.md | 5 +++ .../services/layers/persisted_queries/mod.rs | 31 ++++++++++++++----- 2 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 .changesets/feat_glasser_pq_error_include_extensions.md diff --git a/.changesets/feat_glasser_pq_error_include_extensions.md b/.changesets/feat_glasser_pq_error_include_extensions.md new file mode 100644 index 0000000000..129a7692e6 --- /dev/null +++ b/.changesets/feat_glasser_pq_error_include_extensions.md @@ -0,0 +1,5 @@ +### pq: include operation name in `PERSISTED_QUERY_NOT_IN_LIST` error ([PR #7768](https://github.com/apollographql/router/pull/7768)) + +When persisted query safelisting is enabled and a request has an unknown PQ ID, the GraphQL error now has the extension field `operation_name` containing the GraphQL operation name (if provided explicitly in the request). + +By [@glasser](https://github.com/glasser) in https://github.com/apollographql/router/pull/7768 \ No newline at end of file diff --git a/apollo-router/src/services/layers/persisted_queries/mod.rs b/apollo-router/src/services/layers/persisted_queries/mod.rs index 3e4defe184..1e2ac67c01 100644 --- a/apollo-router/src/services/layers/persisted_queries/mod.rs +++ b/apollo-router/src/services/layers/persisted_queries/mod.rs @@ -371,11 +371,19 @@ impl ErrorCacheStrategy { } } -fn graphql_err_operation_not_found(persisted_query_id: &str) -> GraphQLError { - graphql_err( - "PERSISTED_QUERY_NOT_IN_LIST", - &format!("Persisted query '{persisted_query_id}' not found in the persisted query list"), - ) +fn graphql_err_operation_not_found( + persisted_query_id: &str, + operation_name: Option, +) -> GraphQLError { + let mut builder = GraphQLError::builder() + .extension_code("PERSISTED_QUERY_NOT_IN_LIST") + .message(format!( + "Persisted query '{persisted_query_id}' not found in the persisted query list" + )); + if let Some(operation_name) = operation_name { + builder = builder.extension("operation_name", operation_name); + } + builder.build() } fn supergraph_err_operation_not_found( @@ -383,7 +391,10 @@ fn supergraph_err_operation_not_found( persisted_query_id: &str, ) -> SupergraphResponse { supergraph_err( - graphql_err_operation_not_found(persisted_query_id), + graphql_err_operation_not_found( + persisted_query_id, + request.supergraph_request.body().operation_name.clone(), + ), request, ErrorCacheStrategy::DontCache, StatusCode::NOT_FOUND, @@ -705,7 +716,7 @@ mod tests { .expect("could not get response from pq layer"); assert_eq!( response.errors, - vec![graphql_err_operation_not_found(invalid_id)] + vec![graphql_err_operation_not_found(invalid_id, None)] ); } @@ -1067,6 +1078,7 @@ mod tests { "persistedQuery", json!({"version": 1, "sha256Hash": invalid_id}), ) + .operation_name("SomeOperation") .build() .unwrap(); @@ -1080,7 +1092,10 @@ mod tests { .expect("could not get response from pq layer"); assert_eq!( response.errors, - vec![graphql_err_operation_not_found(invalid_id)] + vec![graphql_err_operation_not_found( + invalid_id, + Some("SomeOperation".to_string()), + )] ); }