Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gateway): New federation composition format #4405

Merged
merged 12 commits into from
Jul 28, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import gql from 'graphql-tag';
import { GraphQLResolverMap } from 'apollo-graphql';

export const name = 'accounts';
export const url = `https://${name}.api.com`;
export const typeDefs = gql`
directive @stream on FIELD
directive @transform(from: String!) on FIELD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import gql from 'graphql-tag';
import { GraphQLResolverMap } from 'apollo-graphql';

export const name = 'books';
export const url = `https://${name}.api.com`;
export const typeDefs = gql`
directive @stream on FIELD
directive @transform(from: String!) on FIELD
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import gql from 'graphql-tag';

export const name = 'documents';
export const url = `https://${name}.api.com`;
export const typeDefs = gql`
directive @stream on FIELD
directive @transform(from: String!) on FIELD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import gql from 'graphql-tag';
import { GraphQLResolverMap } from 'apollo-graphql';

export const name = 'inventory';
export const url = `https://${name}.api.com`;
export const typeDefs = gql`
directive @stream on FIELD
directive @transform(from: String!) on FIELD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import gql from 'graphql-tag';
import { GraphQLResolverMap } from 'apollo-graphql';

export const name = 'product';
export const url = `https://${name}.api.com`;
export const typeDefs = gql`
directive @stream on FIELD
directive @transform(from: String!) on FIELD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GraphQLResolverMap } from 'apollo-graphql';
import gql from 'graphql-tag';

export const name = 'reviews';
export const url = `https://${name}.api.com`;
export const typeDefs = gql`
directive @stream on FIELD
directive @transform(from: String!) on FIELD
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-federation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the appropriate changes within that release will be moved into the new section.

- _Nothing yet! Stay tuned!_
- New federation composition format. Capture federation metadata in SDL [PR #4405](https://github.com/apollographql/apollo-server/pull/4405)

## v0.18.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from './validate';
import { ServiceDefinition } from './types';
import { normalizeTypeDefs } from './normalize';
import { printComposedSdl } from '../service/printComposedSdl';

export function composeAndValidate(serviceList: ServiceDefinition[]) {
const errors = validateServicesBeforeNormalization(serviceList);
Expand All @@ -30,6 +31,13 @@ export function composeAndValidate(serviceList: ServiceDefinition[]) {
}),
);

const composedSdl = printComposedSdl(compositionResult.schema, serviceList);

// TODO remove the warnings array once no longer used by clients
return { schema: compositionResult.schema, warnings: [], errors };
return {
schema: compositionResult.schema,
warnings: [],
errors,
composedSdl,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,25 +102,25 @@ type Money {

const { data, errors } = await graphql(schema, query);
expect(errors).toBeUndefined();
expect(data._service.sdl).toEqual(`"""
A user. This user is very complicated and requires so so so so so so so so so so
so so so so so so so so so so so so so so so so so so so so so so much
description text
expect(data?._service.sdl).toEqual(`"""
A user. This user is very complicated and requires so so so so so so so so so so so so so so so so so so so so so so so so so so so so so so so so much description text
"""
type User @key(fields: "id") {
"The unique ID of the user."
"""The unique ID of the user."""
id: ID!
"The user's name."

"""The user's name."""
name: String
username: String
foo(
"Description 1"
"""Description 1"""
arg1: String
"Description 2"

"""Description 2"""
arg2: String

"""
Description 3 Description 3 Description 3 Description 3 Description 3
Description 3 Description 3 Description 3 Description 3 Description 3 Description 3
Description 3 Description 3 Description 3 Description 3 Description 3 Description 3 Description 3 Description 3 Description 3 Description 3 Description 3
"""
arg3: String
): String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import { fixtures } from 'apollo-federation-integration-testsuite';
import { composeAndValidate } from '../../composition';
import { printComposedSdl } from '../printComposedSdl';

describe('printComposedSdl', () => {
const { schema, errors } = composeAndValidate(fixtures);

it('composes without errors', () => {
expect(errors).toHaveLength(0);
});

it('prints a fully composed schema correctly', () => {
expect(printComposedSdl(schema, fixtures)).toMatchInlineSnapshot(`
"schema
@graph(name: \\"accounts\\", url: \\"https://accounts.api.com\\")
@graph(name: \\"books\\", url: \\"https://books.api.com\\")
@graph(name: \\"documents\\", url: \\"https://documents.api.com\\")
@graph(name: \\"inventory\\", url: \\"https://inventory.api.com\\")
@graph(name: \\"product\\", url: \\"https://product.api.com\\")
@graph(name: \\"reviews\\", url: \\"https://reviews.api.com\\")
@composedGraph(version: 1)
{
query: Query
mutation: Mutation
}

directive @stream on FIELD

directive @transform(from: String!) on FIELD

union AccountType = PasswordAccount | SMSAccount

type Amazon {
referrer: String
}

union Body = Image | Text

type Book implements Product
@owner(graph: \\"books\\")
@key(fields: \\"isbn\\", graph: \\"books\\")
@key(fields: \\"isbn\\", graph: \\"inventory\\")
@key(fields: \\"isbn\\", graph: \\"product\\")
@key(fields: \\"isbn\\", graph: \\"reviews\\")
{
isbn: String!
title: String
year: Int
similarBooks: [Book]!
metadata: [MetadataOrError]
inStock: Boolean @resolve(graph: \\"inventory\\")
isCheckedOut: Boolean @resolve(graph: \\"inventory\\")
upc: String! @resolve(graph: \\"product\\")
sku: String! @resolve(graph: \\"product\\")
name(delimeter: String = \\" \\"): String @resolve(graph: \\"product\\") @requires(fields: \\"title year\\")
price: String @resolve(graph: \\"product\\")
details: ProductDetailsBook @resolve(graph: \\"product\\")
reviews: [Review] @resolve(graph: \\"reviews\\")
relatedReviews: [Review!]! @resolve(graph: \\"reviews\\") @requires(fields: \\"similarBooks { isbn }\\")
}

union Brand = Ikea | Amazon

type Car implements Vehicle
@owner(graph: \\"product\\")
@key(fields: \\"id\\", graph: \\"product\\")
@key(fields: \\"id\\", graph: \\"reviews\\")
{
id: String!
description: String
price: String
retailPrice: String @resolve(graph: \\"reviews\\") @requires(fields: \\"price\\")
}

type Error {
code: Int
message: String
}

type Furniture implements Product
@owner(graph: \\"product\\")
@key(fields: \\"upc\\", graph: \\"product\\")
@key(fields: \\"sku\\", graph: \\"product\\")
@key(fields: \\"sku\\", graph: \\"inventory\\")
@key(fields: \\"upc\\", graph: \\"reviews\\")
{
upc: String!
sku: String!
name: String
price: String
brand: Brand
metadata: [MetadataOrError]
details: ProductDetailsFurniture
inStock: Boolean @resolve(graph: \\"inventory\\")
isHeavy: Boolean @resolve(graph: \\"inventory\\")
reviews: [Review] @resolve(graph: \\"reviews\\")
}

type Ikea {
asile: Int
}

type Image {
name: String!
attributes: ImageAttributes!
}

type ImageAttributes {
url: String!
}

type KeyValue {
key: String!
value: String!
}

type Library
@owner(graph: \\"books\\")
@key(fields: \\"id\\", graph: \\"books\\")
@key(fields: \\"id\\", graph: \\"accounts\\")
{
id: ID!
name: String
userAccount(id: ID! = 1): User @resolve(graph: \\"accounts\\") @requires(fields: \\"name\\")
}

union MetadataOrError = KeyValue | Error

type Mutation {
login(username: String!, password: String!): User @resolve(graph: \\"accounts\\")
reviewProduct(upc: String!, body: String!): Product @resolve(graph: \\"reviews\\")
updateReview(review: UpdateReviewInput!): Review @resolve(graph: \\"reviews\\")
deleteReview(id: ID!): Boolean @resolve(graph: \\"reviews\\")
}

type PasswordAccount
@owner(graph: \\"accounts\\")
@key(fields: \\"email\\", graph: \\"accounts\\")
{
email: String!
}

interface Product {
upc: String!
sku: String!
name: String
price: String
details: ProductDetails
inStock: Boolean
reviews: [Review]
}

interface ProductDetails {
country: String
}

type ProductDetailsBook implements ProductDetails {
country: String
pages: Int
}

type ProductDetailsFurniture implements ProductDetails {
country: String
color: String
}

type Query {
user(id: ID!): User @resolve(graph: \\"accounts\\")
me: User @resolve(graph: \\"accounts\\")
book(isbn: String!): Book @resolve(graph: \\"books\\")
books: [Book] @resolve(graph: \\"books\\")
library(id: ID!): Library @resolve(graph: \\"books\\")
body: Body! @resolve(graph: \\"documents\\")
product(upc: String!): Product @resolve(graph: \\"product\\")
vehicle(id: String!): Vehicle @resolve(graph: \\"product\\")
topProducts(first: Int = 5): [Product] @resolve(graph: \\"product\\")
topCars(first: Int = 5): [Car] @resolve(graph: \\"product\\")
topReviews(first: Int = 5): [Review] @resolve(graph: \\"reviews\\")
}

type Review
@owner(graph: \\"reviews\\")
@key(fields: \\"id\\", graph: \\"reviews\\")
{
id: ID!
body(format: Boolean = false): String
author: User @provides(fields: \\"username\\")
product: Product
metadata: [MetadataOrError]
}

type SMSAccount
@owner(graph: \\"accounts\\")
@key(fields: \\"number\\", graph: \\"accounts\\")
{
number: String
}

type Text {
name: String!
attributes: TextAttributes!
}

type TextAttributes {
bold: Boolean
text: String
}

union Thing = Car | Ikea

input UpdateReviewInput {
id: ID!
body: String
}

type User
@owner(graph: \\"accounts\\")
@key(fields: \\"id\\", graph: \\"accounts\\")
@key(fields: \\"id\\", graph: \\"inventory\\")
@key(fields: \\"id\\", graph: \\"product\\")
@key(fields: \\"id\\", graph: \\"reviews\\")
{
id: ID!
name: String
username: String
birthDate(locale: String): String
account: AccountType
metadata: [UserMetadata]
goodDescription: Boolean @resolve(graph: \\"inventory\\") @requires(fields: \\"metadata { description }\\")
vehicle: Vehicle @resolve(graph: \\"product\\")
thing: Thing @resolve(graph: \\"product\\")
reviews: [Review] @resolve(graph: \\"reviews\\")
numberOfReviews: Int! @resolve(graph: \\"reviews\\")
goodAddress: Boolean @resolve(graph: \\"reviews\\") @requires(fields: \\"metadata { address }\\")
}

type UserMetadata {
name: String
address: String
description: String
}

type Van implements Vehicle
@owner(graph: \\"product\\")
@key(fields: \\"id\\", graph: \\"product\\")
@key(fields: \\"id\\", graph: \\"reviews\\")
{
id: String!
description: String
price: String
retailPrice: String @resolve(graph: \\"reviews\\") @requires(fields: \\"price\\")
}

interface Vehicle {
id: String!
description: String
price: String
retailPrice: String
}
"
`);
});
});
Loading