Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f3601a0
Remove no-op use of make_service on axum’s Router
SimonSapin Aug 8, 2022
a66a665
Make constructors private when there’s a public builder
SimonSapin Aug 8, 2022
4ee35e7
Rename enums *Kind -> *Source
SimonSapin Aug 8, 2022
194ec7c
Rename ApolloRouter to RouterHttpServer
SimonSapin Aug 8, 2022
53dbad6
Merge RouterHandle into RouterHttpServer
SimonSapin Aug 8, 2022
4f10bc0
Replace router_builder_fn() with shutdown()
SimonSapin Aug 8, 2022
772da4f
Add RouterHttpServer::shutdown
SimonSapin Aug 8, 2022
fb445b1
Revert glob imports at the crate root
SimonSapin Aug 8, 2022
6e26603
Remove deprecated type aliases
SimonSapin Aug 8, 2022
64e54bb
RouterRequest::fake_builder defaults to Content-Type: application/json
SimonSapin Aug 8, 2022
472e315
Add apollo_router::TestHarness
SimonSapin Aug 8, 2022
8b81aa5
Port rhai_tests to the new TestHarness
SimonSapin Aug 8, 2022
5c65ca8
Port integration_tests to the new TestHarness
SimonSapin Aug 8, 2022
537979c
Port apollo-router-benchmarks to the new TestHarness
SimonSapin Aug 8, 2022
7dae42c
Port examples/embedded to the new TestHarness
SimonSapin Aug 8, 2022
f4b54a9
Make some things private
SimonSapin Aug 8, 2022
889326c
Add apollo_router::stages, replacing apollo_router::services in the p…
SimonSapin Aug 9, 2022
e559002
Make more things private
SimonSapin Aug 9, 2022
9d65cf0
Add ad-hoc plugins with callbacks in TestHarness
SimonSapin Aug 9, 2022
f60586c
Actually implement TestHarness::with_subgraph_network_requests
SimonSapin Aug 9, 2022
909c56f
Add TestHarness::configuration_json
SimonSapin Aug 9, 2022
2c6b5d9
Port apollo-router-scaffold to the new TestHarness
SimonSapin Aug 9, 2022
33326e7
Port remaining examples/* to the new TestHarness
SimonSapin Aug 9, 2022
0a79e20
Port telemetry::metrics::apollo to the new test harness
SimonSapin Aug 9, 2022
3c17878
Remove PluginTestHarness, as it was replaced by TestHarness
SimonSapin Aug 9, 2022
d430813
Make Schema actually private
SimonSapin Aug 9, 2022
3499374
Make plugins private
SimonSapin Aug 9, 2022
56690db
Add apollo_router::stages::*::Result aliases
SimonSapin Aug 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

237 changes: 237 additions & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,214 @@ This rewrites the mocked services API to remove the `build()` method, and make t
using an `expect_clone` call with mockall.

By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/1440

### Some items were renamed or moved ([PR #FIXME])

At the crate root:

* `SchemaKind` → `SchemaSource`
* `SchemaKind::String(String)` → `SchemaSource::Static { schema_sdl: String }`
* `ConfigurationKind` → `ConfigurationSource`
* `ConfigurationKind::Instance` → `ConfigurationSource::Static`
* `ShutdownKind` → `ShutdownSource`
* `ApolloRouter` → `RouterHttpServer`

A new `apollo_router::stages` module replaces `apollo_router::services` in the public API,
reexporting its items and adding `BoxService` and `BoxCloneService` type aliases.
In pseudo-syntax:

```rust
mod router {
use apollo_router::services::RouterRequest as Request;
use apollo_router::services::RouterResponse as Response;
type BoxService = tower::util::BoxService<Request, Response, BoxError>;
type BoxCloneService = tower::util::BoxCloneService<Request, Response, BoxError>;
}

mod query_planner {
use apollo_router::services::QueryPlannerRequest as Request;
use apollo_router::services::QueryPlannerResponse as Response;
type BoxService = tower::util::BoxService<Request, Response, BoxError>;
type BoxCloneService = tower::util::BoxCloneService<Request, Response, BoxError>;

// Reachable from Request or Response:
use apollo_router::query_planner::QueryPlan;
use apollo_router::query_planner::QueryPlanOptions;
use apollo_router::services::QueryPlannerContent;
use apollo_router::spec::Query;
}

mod execution {
use apollo_router::services::ExecutionRequest as Request;
use apollo_router::services::ExecutionResponse as Response;
type BoxService = tower::util::BoxService<Request, Response, BoxError>;
type BoxCloneService = tower::util::BoxCloneService<Request, Response, BoxError>;
}

mod subgraph {
use super::*;
use apollo_router::services::SubgraphRequest as Request;
use apollo_router::services::SubgraphResponse as Response;
type BoxService = tower::util::BoxService<Request, Response, BoxError>;
type BoxCloneService = tower::util::BoxCloneService<Request, Response, BoxError>;

// Reachable from Request or Response:
use apollo_router::query_planner::OperationKind;
}
```

Migration example:

```diff
-use tower::util::BoxService;
-use tower::BoxError;
-use apollo_router::services::{RouterRequest, RouterResponse};
+use apollo_router::stages::router;

-async fn example(service: BoxService<RouterRequest, RouterResponse, BoxError>) -> RouterResponse {
+async fn example(service: router::BoxService) -> router::Response {
- let request = RouterRequest::builder()/*…*/.build();
+ let request = router::Request::builder()/*…*/.build();
service.oneshot(request).await
}
```

By [@SimonSapin](https://github.com/SimonSapin)

### Some items were removed from the public API ([PR #FIXME])

If you used some of them and don’t find a replacement,
please [file an issue](https://github.com/apollographql/router/issues/)
with details about the use case.

```
apollo_router::errors::CacheResolverError
apollo_router::errors::JsonExtError
apollo_router::errors::PlanError
apollo_router::errors::PlannerError
apollo_router::errors::PlannerErrors
apollo_router::errors::QueryPlannerError
apollo_router::errors::ServiceBuildError
apollo_router::json_ext
apollo_router::mock_service!
apollo_router::plugins
apollo_router::plugin::plugins
apollo_router::plugin::PluginFactory
apollo_router::plugin::DynPlugin
apollo_router::plugin::test::IntoSchema
apollo_router::plugin::test::MockSubgraphFactory
apollo_router::plugin::test::PluginTestHarness
apollo_router::query_planner::QueryPlan::execute
apollo_router::services
apollo_router::Schema
```

By [@SimonSapin](https://github.com/SimonSapin)

### Router startup API changes ([PR #FIXME])

The `RouterHttpServer::serve` method and its return type `RouterHandle` were removed,
their functionality merged into `RouterHttpServer` (formerly `ApolloRouter`).
The builder for `RouterHttpServer` now ends with a `start` method instead of `build`.
This method immediatly starts the server in a new Tokio task.

```diff
RouterHttpServer::builder()
.configuration(configuration)
.schema(schema)
- .build()
- .serve()
+ .start()
.await
```

By [@SimonSapin](https://github.com/SimonSapin)

### `router_builder_fn` replaced by `shutdown` in the `Executable` builder ([PR #FIXME])

The builder for `apollo_router::Executable` had a `router_builder_fn` method
allowing to specify how a `RouterHttpServer` (previously `ApolloRouter`) was to be created
with a provided configuration and schema.
The only possible variation there was specifying when the server should shut down
with a `ShutdownSource` parameter,
so `router_builder_fn` was replaced with a new `shutdown` method that takes that.

```diff
use apollo_router::Executable;
-use apollo_router::RouterHttpServer;
use apollo_router::ShutdownSource;

Executable::builder()
- .router_builder_fn(|configuration, schema| RouterHttpServer::builder()
- .configuration(configuration)
- .schema(schema)
- .shutdown(ShutdownSource::None)
- .start())
+ .shutdown(ShutdownSource::None)
.start()
.await
```

By [@SimonSapin](https://github.com/SimonSapin)

### Removed constructors when there is a public builder ([PR #FIXME])

Many types in the Router API can be constructed with the builder pattern.
We use the [`buildstructor`](https://crates.io/crates/buildstructor) crate
to auto-generate builder boilerplate based on the parameters of a constructor.
These constructors have been made private so that users must go through the builder instead,
which will allow us to add parameters in the future without a breaking API change.
If you were using one of these constructors, the migration generally looks like this:

```diff
-apollo_router::graphql::Error::new(m, vec![l], Some(p), Default::default())
+apollo_router::graphql::Error::build()
+ .message(m)
+ .location(l)
+ .path(p)
+ .build()
```

By [@SimonSapin](https://github.com/SimonSapin)

### Removed deprecated type aliases ([PR #FIXME])

A few versions ago, some types were moved from the crate root to a new `graphql` module.
To help the transition, type aliases were left at the old location with a deprecation warning.
These aliases are now removed, remaining imports must be changed to the new location:

```diff
-use apollo_router::Error;
-use apollo_router::Request;
-use apollo_router::Response;
+use apollo_router::graphql::Error;
+use apollo_router::graphql::Request;
+use apollo_router::graphql::Response;
```

Alternatively, import the module with `use apollo_router::graphql`
then use qualified paths such as `graphql::Request`.
This can help disambiguate when multiple types share a name.

By [@SimonSapin](https://github.com/SimonSapin)

### `RouterRequest::fake_builder` defaults to `Content-Type: application/json` ([PR #FIXME])

`apollo_router::services::RouterRequest` has a builder for creating a “fake” request during tests.
When no `Content-Type` header is specified, this builder will now default to `application/json`.
This will help tests where a request goes through mandatory plugins including CSRF protection.
which makes the request be accepted by CSRF protection.

If a test requires a request specifically *without* a `Content-Type` header,
this default can be removed from a `RouterRequest` after building it:

```rust
let mut router_request = RouterRequesT::fake_builder().build();
router_request.originating_request.headers_mut().remove("content-type");
```

By [@SimonSapin](https://github.com/SimonSapin)

## 🚀 Features

### Expose query plan in extensions for GraphQL response (experimental) ([PR #1470](https://github.com/apollographql/router/pull/1470))
Expand Down Expand Up @@ -113,6 +321,35 @@ traffic_shaping:

By [@bnjjj](https://github.com/bnjjj) in https://github.com/apollographql/router/pull/1347

### Explicit `shutdown` for `RouterHttpServer` handle ([PR #FIXME])

If you explicitly create a `RouterHttpServer` handle,
dropping it while the server is running instructs the server shut down gracefuly.
However with the handle dropped, there is no way to wait for shutdown to end
or check that it went without error.
Instead, the new `shutdown` async method can be called explicitly
to obtain a `Result`:

```diff
use RouterHttpServer;
let server = RouterHttpServer::builder().schema("schema").start();
// …
-drop(server);
+server.shutdown().await.unwrap();
```

By [@SimonSapin](https://github.com/SimonSapin)

### Added `apollo_router::TestHarness` ([PR #FIXME])

This is a builder for the part of an Apollo Router that handles GraphQL requests,
as a `tower::Service`.
This allows tests, benchmarks, etc
to manipulate request and response objects in memory without going over the network.
See the API documentation for an example. (It can be built with `cargo doc --open`.)

By [@SimonSapin](https://github.com/SimonSapin)

Copy link
Contributor

Choose a reason for hiding this comment

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

tremendous effort on the changelog, that's really GREAT WORK 🎉

NIT: you can now update the PR #

## 🐛 Fixes

## 🛠 Maintenance
Expand Down
2 changes: 1 addition & 1 deletion apollo-router-benchmarks/benches/basic_composition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn from_elem(c: &mut Criterion) {

let router = runtime.block_on(builder.build()).unwrap();
b.to_async(runtime)
.iter(|| basic_composition_benchmark(router.test_service()));
.iter(|| basic_composition_benchmark(router.clone()));
});
}

Expand Down
2 changes: 1 addition & 1 deletion apollo-router-benchmarks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ pub mod tests {

let builder = setup();
let router = runtime.block_on(builder.build()).unwrap();
runtime.block_on(async move { basic_composition_benchmark(router.test_service()).await });
runtime.block_on(async move { basic_composition_benchmark(router).await });
}
}
53 changes: 38 additions & 15 deletions apollo-router-benchmarks/src/shared.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// this file is shared between the tests and benchmarks, using
// include!() instead of as a pub module, so it is only compiled
// in dev mode
use apollo_router::plugin::Plugin;
use apollo_router::plugin::PluginInit;
use apollo_router::plugin::test::MockSubgraph;
use apollo_router::services::{ RouterRequest, RouterResponse};
use apollo_router::services::PluggableRouterServiceBuilder;
use apollo_router::Schema;
use apollo_router::stages::router;
use apollo_router::stages::subgraph;
use apollo_router::TestHarness;
use apollo_router::graphql::Response;
use once_cell::sync::Lazy;
use serde_json::json;
use std::sync::Arc;
use tower::{util::BoxCloneService, BoxError, Service, ServiceExt};
use std::collections::HashMap;

use tower::{BoxError, Service, ServiceExt};

static EXPECTED_RESPONSE: Lazy<Response> = Lazy::new(|| {
serde_json::from_str(r#"{"data":{"topProducts":[{"upc":"1","name":"Table","reviews":[{"id":"1","product":{"name":"Table"},"author":{"id":"1","name":"Ada Lovelace"}},{"id":"4","product":{"name":"Table"},"author":{"id":"2","name":"Alan Turing"}}]},{"upc":"2","name":"Couch","reviews":[{"id":"2","product":{"name":"Couch"},"author":{"id":"1","name":"Ada Lovelace"}}]}]}}"#).unwrap()
Expand All @@ -18,9 +21,9 @@ static EXPECTED_RESPONSE: Lazy<Response> = Lazy::new(|| {
static QUERY: &str = r#"query TopProducts($first: Int) { topProducts(first: $first) { upc name reviews { id product { name } author { id name } } } }"#;

pub async fn basic_composition_benchmark(
mut router_service: BoxCloneService<RouterRequest, RouterResponse, BoxError>,
mut router_service: router::BoxCloneService,
) {
let request = RouterRequest::fake_builder()
let request = router::Request::fake_builder()
.query(QUERY.to_string())
.variable("first", 2usize)
.build().expect("expecting valid request");
Expand All @@ -39,7 +42,7 @@ pub async fn basic_composition_benchmark(
assert_eq!(response, *EXPECTED_RESPONSE);
}

pub fn setup() -> PluggableRouterServiceBuilder {
pub fn setup() -> TestHarness<'static> {
let account_mocks = vec![
(
json!{{
Expand Down Expand Up @@ -217,13 +220,33 @@ pub fn setup() -> PluggableRouterServiceBuilder {
].into_iter().map(|(query, response)| (serde_json::from_value(query).unwrap(), serde_json::from_value(response).unwrap())).collect();
let product_service = MockSubgraph::new(product_mocks);

let mut mocks = HashMap::new();
mocks.insert("accounts", account_service);
mocks.insert("reviews", review_service);
mocks.insert("products", product_service);

let schema = include_str!("../benches/fixtures/supergraph.graphql");
let schema = Arc::new(Schema::parse(schema, &Default::default()).unwrap());
TestHarness::builder().schema(schema).extra_plugin(MockedSubgraphs(mocks))
}

let builder = PluggableRouterServiceBuilder::new(schema);
struct MockedSubgraphs(HashMap<&'static str, MockSubgraph>);

builder
.with_subgraph_service("accounts", account_service)
.with_subgraph_service("reviews", review_service)
.with_subgraph_service("products", product_service)
}
#[async_trait::async_trait]
impl Plugin for MockedSubgraphs {
type Config = ();

async fn new(_: PluginInit<Self::Config>) -> Result<Self, BoxError> {
unreachable!()
}

fn subgraph_service(
&self,
subgraph_name: &str,
default: subgraph::BoxService,
) -> subgraph::BoxService {
self.0
.get(subgraph_name)
.map(|service| service.clone().boxed())
.unwrap_or(default)
}
}
Loading