Skip to content

Coprocessor hook for connectors request/response stages#8869

Merged
andrewmcgivery merged 19 commits intodevfrom
am/connector_request_coprocessor
Feb 13, 2026
Merged

Coprocessor hook for connectors request/response stages#8869
andrewmcgivery merged 19 commits intodevfrom
am/connector_request_coprocessor

Conversation

@andrewmcgivery
Copy link
Copy Markdown
Contributor

@andrewmcgivery andrewmcgivery commented Feb 6, 2026

You can now configure a coprocessor hook for the ConnectorRequest and ConnectorResponse stages of the Router lifecycle.

coprocessor:
  url: http://localhost:3007
  connector:
    all: 1
      request:
        uri: true
        headers: true
        body: true
        context: all
        service_name: true
      response:
        headers: true
        body: true
        context: all
        service_name: true

Checklist

Complete the checklist (and note appropriate exceptions) before the PR is marked ready-for-review.

  • PR description explains the motivation for the change and relevant context for reviewing
  • PR description links appropriate GitHub/Jira tickets (creating when necessary)
  • Changeset is included for user-facing changes
  • Changes are compatible1
  • Documentation2 completed
  • Performance impact assessed and acceptable
  • Metrics and logs are added3 and documented
  • Tests added and passing4
    • Unit tests
    • Integration tests
    • Manual tests, as necessary

Exceptions

Note any exceptions here

Notes

Footnotes

  1. It may be appropriate to bring upcoming changes to the attention of other (impacted) groups. Please endeavour to do this before seeking PR approval. The mechanism for doing this will vary considerably, so use your judgement as to how and when to do this.

  2. Configuration is an important part of many changes. Where applicable please try to document configuration examples.

  3. A lot of (if not most) features benefit from built-in observability and debug-level logs. Please read this guidance on metrics best-practices.

  4. Tick whichever testing boxes are applicable. If you are adding Manual Tests, please document the manual testing (extensively) in the Exceptions.

@apollo-librarian
Copy link
Copy Markdown
Contributor

apollo-librarian bot commented Feb 6, 2026

✅ Docs preview ready

The preview is ready to be viewed. View the preview

File Changes

0 new, 2 changed, 0 removed
* graphos/routing/(latest)/customization/coprocessor/index.mdx
* graphos/routing/(latest)/customization/coprocessor/reference.mdx

Build ID: 9b4ba4156a72ca4a0aeab67b
Build Logs: View logs

URL: https://www.apollographql.com/docs/deploy-preview/9b4ba4156a72ca4a0aeab67b

@github-actions

This comment has been minimized.

tracing::debug!(?payload, "externalized output");
let start = Instant::now();
let co_processor_result = payload.call(http_client, &coprocessor_url).await;
*executed = true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to trace through what this is used for. If the request is aborted do we record anything?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, this is the same logic that exists on the other lifecycle stages so I replicated what was already there. 😅

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth a comment on what executed is for so that we don't accidentally remove it; here's what's in coprocessor/supergraph.rs:

    // Indicate the stage was executed to raise execution metric on parent

the parent bit is important for future developers

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, in reply to @BrynCooke: if the request errors out we emit an error; if the request executes (ie, that bool above stays true), we record a metric (whether it succeeded or failed), but I don't see a metric emitted specifically for coprocessor request failures (probably because they get the same visibility normal request errors get? not sure)

@smyrick
Copy link
Copy Markdown
Member

smyrick commented Feb 11, 2026

I did some duplicate work here but this works for both subgraphs and connectors as well as the top level router http hook: #8874

Comment on lines -224 to +249
register_plugin!(
register_private_plugin!(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's behind teh change to a private plugin?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connector tower layers are currently only hooked up to the private plugin interface, not the public plugin interface.

My assumption (though I 100% could be wrong here) is that coprocessors are a "private"/internal plugin anyways so this shouldn't have any impact... but again, could be wrong there.

There is some ask from customers to move the connector tower layers to the public interface but that would be a larger undertaking so if possible, I'd prefer to do that in a separate PR.

Comment on lines -87 to +88
impl Plugin for CoprocessorPlugin<HTTPClientService> {
impl PluginPrivate for CoprocessorPlugin<HTTPClientService> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the switch to private?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

coprocessor:
url: http://localhost:3007
connector:
all:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I see why we'd want an all layer between the req/res? eg, SupergraphStage just has req/res without the all layer--any reason why we shouldn't keep that same structure?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking at the integration tests, it looks like this might be a way to have connector-specific overrides? noticing structural similarities to how subgraph is handled

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be comparing this entire PR to subgraph, not supergraph. The connectors_request layer is equivalent to the subgraph layer. If you look at subgraph coprocessors, they currently have all and I believe the idea was to eventually allow for per-subgraph configuration, but that was never implemented.

But yes, the idea here is that we're leaving open the ability to override for specific connector sources, but currently are not implementing that in this PR. (all connectors config has this pattern of all and the ability to override per source).

#[serde(default)]
pub(super) struct ConnectorStages {
#[serde(default)]
pub(super) all: ConnectorStage,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code reference for the all layer mentioned in the changeset comment; this could reflect the SupergraphStage struct's fields to keep the same structure, unless there's a reason it should be different

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per above, you should be comparing this entire PR to subgraph, not supergraph. The connectors_request layer is equivalent to the subgraph layer.

/// What information is passed to a connector request stage
#[derive(Clone, Debug, Default, Deserialize, PartialEq, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub(super) struct ConnectorRequestConf {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no sdl? what's the reason for excluding it? cf. the supergraph coprocessor's req/res handling

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per above, you should be comparing this entire PR to subgraph, not supergraph. The connectors_request layer is equivalent to the subgraph layer.

Comment on lines +285 to +286
// unwrap is safe here because validate_coprocessor_output made sure control is available
let control = co_processor_output.control.expect("validated above; qed");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we do this elsewhere, but we shouldn't do it here; the "validation above" might change in the future without someone meaning to make this panic, but we need to guard against future ourselves and emit an error here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is a direct copy/paste of what the other coprocessor layers are already doing, including subgraph.

Given this is meant to be as close to subgraph as possible, unless we're also refactoring the other stages (which I do not want to take ownership for in this PR), I'd prefer to go with being consistent with that the other stages are doing here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Though I will say in principle I agree 100% with not using unwrap! 😅)

mapped_response: MappedResponse::Error {
error: runtime_error,
key: request.key,
problems: Vec::new(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not totally sure I know what problems there would be in this case, but would problems ever lead to a break?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a connectors structure... problems are "mapping problems" with our connectors mapping language that get exposed to the mapping playground or the debugging panel on the embedded sandbox.

In this case, there is no mapping happening, so no problems to report!

return Ok(ControlFlow::Break(res));
}

// Continue flow - apply modifications
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are the modifications? this section seemed a bit mysterious to me; it looks similar to how we do coprocessor/supergraph.rs, but with some extra flavor; might be nice to flesh out this comment to give the reader more direction in what to expect

// Tests for context key deletion functionality
}

fn create_test_connector() -> Arc<Connector> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we put the connector tests in a mod block or something to make it more readable?


Ok(())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be good to have a test showing that a proper graphql error comes back to the client if the connectors coprocessor fails

@andrewmcgivery andrewmcgivery marked this pull request as ready for review February 12, 2026 18:35
@andrewmcgivery andrewmcgivery requested review from a team as code owners February 12, 2026 18:35
Copy link
Copy Markdown
Contributor

@aaronArinder aaronArinder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

panic still scares me, but alas; lgtm!

Copy link
Copy Markdown
Contributor

@pragl pragl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs LGTM! I made a handful of suggestions for your consideration based on our style guide, but none of them are substantive, so I'm going ahead and approving.

Comment thread docs/shared/coproc-typical-config.mdx Outdated
Comment thread docs/source/routing/customization/coprocessor/index.mdx Outdated
Comment thread docs/source/routing/customization/coprocessor/index.mdx Outdated
Co-authored-by: Parker <parker.ragland@apollographql.com>
@andrewmcgivery andrewmcgivery merged commit faba864 into dev Feb 13, 2026
15 checks passed
@andrewmcgivery andrewmcgivery deleted the am/connector_request_coprocessor branch February 13, 2026 02:44
Copy link
Copy Markdown
Member

@abernix abernix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-merge: Can tick the checklist on the PR description?

@abernix abernix mentioned this pull request Feb 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants