Skip to content

Commit

Permalink
feat: add support for @interfaceObject directive (#1060)
Browse files Browse the repository at this point in the history
  • Loading branch information
moonflare authored Mar 23, 2023
1 parent f0497f5 commit 9474c8f
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Added `InputType` for setting Python representations of GraphQL Input types
- Added support for passing `Enum` types directly to `make_executable_schema`
- Added `convert_names_case` option to `make_federated_schema`.
- Added support for the `@interfaceObject` directive in Apollo Federation.


## 0.18.1 (2023-02-22)
Expand Down
6 changes: 6 additions & 0 deletions ariadne/contrib/federation/definitions/fed1_0.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
directive @external on FIELD_DEFINITION
directive @extends on OBJECT | INTERFACE
scalar _FieldSet
46 changes: 46 additions & 0 deletions ariadne/contrib/federation/definitions/fed2_0.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#
# https://specs.apollo.dev/federation/v2.0/federation-v2.0.graphql
#

directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @external on OBJECT | FIELD_DEFINITION
directive @shareable on FIELD_DEFINITION | OBJECT
directive @extends on OBJECT | INTERFACE
directive @override(from: String!) on FIELD_DEFINITION
directive @inaccessible on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| UNION
| ENUM
| ENUM_VALUE
| SCALAR
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
| ARGUMENT_DEFINITION
directive @tag(name: String!) repeatable on
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| UNION
| ARGUMENT_DEFINITION
| SCALAR
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
scalar FieldSet

#
# https://specs.apollo.dev/link/v1.0/link-v1.0.graphql
#

directive @link(
url: String!,
as: String,
import: [Import])
repeatable on SCHEMA

scalar Import
52 changes: 52 additions & 0 deletions ariadne/contrib/federation/definitions/fed2_1.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#
# https://specs.apollo.dev/federation/v2.0/federation-v2.0.graphql
#

directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @external on OBJECT | FIELD_DEFINITION
directive @shareable on FIELD_DEFINITION | OBJECT
directive @extends on OBJECT | INTERFACE
directive @override(from: String!) on FIELD_DEFINITION
directive @inaccessible on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| UNION
| ENUM
| ENUM_VALUE
| SCALAR
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
| ARGUMENT_DEFINITION
directive @tag(name: String!) repeatable on
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| UNION
| ARGUMENT_DEFINITION
| SCALAR
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
scalar FieldSet

#
# federation-v2.1
#

directive @composeDirective(name: String!) repeatable on SCHEMA

#
# https://specs.apollo.dev/link/v1.0/link-v1.0.graphql
#

directive @link(
url: String!,
as: String,
import: [Import])
repeatable on SCHEMA

scalar Import
57 changes: 57 additions & 0 deletions ariadne/contrib/federation/definitions/fed2_2.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#
# https://specs.apollo.dev/federation/v2.0/federation-v2.0.graphql
#

directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @external on OBJECT | FIELD_DEFINITION
directive @extends on OBJECT | INTERFACE
directive @override(from: String!) on FIELD_DEFINITION
directive @inaccessible on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| UNION
| ENUM
| ENUM_VALUE
| SCALAR
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
| ARGUMENT_DEFINITION
directive @tag(name: String!) repeatable on
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| UNION
| ARGUMENT_DEFINITION
| SCALAR
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
scalar FieldSet

#
# federation-v2.1
#

directive @composeDirective(name: String!) repeatable on SCHEMA

#
# https://specs.apollo.dev/link/v1.0/link-v1.0.graphql
#

directive @link(
url: String!,
as: String,
import: [Import])
repeatable on SCHEMA

scalar Import

#
# federation-v2.2
#

directive @shareable repeatable on FIELD_DEFINITION | OBJECT
63 changes: 63 additions & 0 deletions ariadne/contrib/federation/definitions/fed2_3.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#
# https://specs.apollo.dev/federation/v2.0/federation-v2.0.graphql
#

directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @external on OBJECT | FIELD_DEFINITION
directive @extends on OBJECT | INTERFACE
directive @override(from: String!) on FIELD_DEFINITION
directive @inaccessible on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| UNION
| ENUM
| ENUM_VALUE
| SCALAR
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
| ARGUMENT_DEFINITION
directive @tag(name: String!) repeatable on
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| UNION
| ARGUMENT_DEFINITION
| SCALAR
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
scalar FieldSet

#
# federation-v2.1
#

directive @composeDirective(name: String!) repeatable on SCHEMA

#
# https://specs.apollo.dev/link/v1.0/link-v1.0.graphql
#

directive @link(
url: String!,
as: String,
import: [Import])
repeatable on SCHEMA

scalar Import

#
# federation-v2.2
#

directive @shareable repeatable on FIELD_DEFINITION | OBJECT

#
# federation-v2.3
#

directive @interfaceObject on OBJECT
46 changes: 23 additions & 23 deletions ariadne/contrib/federation/schema.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import os
from typing import Dict, List, Optional, Type, Union, cast

from graphql import extend_schema, parse
Expand All @@ -17,6 +18,7 @@
)
from ...schema_names import SchemaNameConverter
from ...schema_visitor import SchemaDirectiveVisitor
from ...load_schema import load_schema_from_path
from .utils import get_entity_types, purge_schema_directives, resolve_entities

base_federation_service_type_defs = """
Expand All @@ -29,27 +31,6 @@
{type_token} Query {{
_service: _Service!
}}
directive @external on FIELD_DEFINITION
directive @requires(fields: String!) on FIELD_DEFINITION
directive @provides(fields: String!) on FIELD_DEFINITION
directive @extends on OBJECT | INTERFACE
"""

# fed 1 typedefs; only @key differs from the rest
federation_one_service_type_defs = """
directive @key(fields: String!) repeatable on OBJECT | INTERFACE
directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
"""

# fed 2 typedefs; adds the new directives, although they are gateway-driven, not subgraph-driven
federation_two_service_type_defs = """
directive @key(fields: String!, resolvable: Boolean) repeatable on OBJECT | INTERFACE
directive @link(import: [String!], url: String!) repeatable on SCHEMA
directive @shareable on OBJECT | FIELD_DEFINITION
directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
directive @override(from: String!) on FIELD_DEFINITION
directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
"""

federation_entity_type_defs = """
Expand Down Expand Up @@ -94,10 +75,29 @@ def make_federated_schema(
link = re.search(
r"(?<=@link).*?url:.*?\"(.*?)\".?[^)]+?", sdl, re.MULTILINE | re.DOTALL
) # use regex to parse if it's fed 1 or fed 2; adds dedicated typedefs per spec
dirname = os.path.dirname(__file__)
if link and link.group(1) == "https://specs.apollo.dev/federation/v2.0":
tdl.append(federation_two_service_type_defs)
definitions = load_schema_from_path(
os.path.join(dirname, "./definitions/fed2_0.graphql")
)
elif link and link.group(1) == "https://specs.apollo.dev/federation/v2.1":
definitions = load_schema_from_path(
os.path.join(dirname, "./definitions/fed2_1.graphql")
)
elif link and link.group(1) == "https://specs.apollo.dev/federation/v2.2":
definitions = load_schema_from_path(
os.path.join(dirname, "./definitions/fed2_2.graphql")
)
elif link and link.group(1) == "https://specs.apollo.dev/federation/v2.3":
definitions = load_schema_from_path(
os.path.join(dirname, "./definitions/fed2_3.graphql")
)
else:
tdl.append(federation_one_service_type_defs)
definitions = load_schema_from_path(
os.path.join(dirname, "./definitions/fed1_0.graphql")
)

tdl.append(definitions)

type_defs = join_type_defs(tdl)
schema = make_executable_schema(
Expand Down
2 changes: 2 additions & 0 deletions ariadne/contrib/federation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
"tag", # Federation 2 directive.
"override", # Federation 2 directive.
"inaccessible", # Federation 2 directive.
"composeDirective", # Federation 2.1 directive.
"interfaceObject", # Federation 2.3 directive.
]


Expand Down
62 changes: 26 additions & 36 deletions tests/federation/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,19 @@
def test_federation_one_schema_mark_type_tags():
type_defs = """
type Query
directive @tag(name: String!) repeatable on
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| UNION
| ARGUMENT_DEFINITION
| SCALAR
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
type Product @tag(name: "test") {
upc: String!
name: String
Expand All @@ -45,7 +57,19 @@ def test_federation_one_schema_mark_type_tags():
def test_federation_one_schema_mark_type_repeated_tags():
type_defs = """
type Query
directive @tag(name: String!) repeatable on
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| UNION
| ARGUMENT_DEFINITION
| SCALAR
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
type Product @tag(name: "test") @tag(name: "test3") {
upc: String!
name: String
Expand Down Expand Up @@ -884,37 +908,3 @@ def test_federated_schema_without_query_is_valid():

assert result.errors is None
assert sic(result.data["_service"]["sdl"]) == sic(type_defs)


def test_federation_2_0_version_is_detected_in_schema():
type_defs = """
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable", "@provides", "@external", "@tag", "@extends", "@override"])
type Product @key(fields: "upc") {
upc: String!
name: String
price: Int
weight: Int
}
type User @key(fields: "email") @extends {
email: ID! @external
name: String @override(from:"users")
totalProductsCreated: Int @external
}
"""

schema = make_federated_schema(type_defs)
result = graphql_sync(
schema,
"""
query GetServiceDetails {
_service {
sdl
}
}
""",
)

assert result.errors is None
assert sic(result.data["_service"]["sdl"]) == sic(type_defs)
Loading

0 comments on commit 9474c8f

Please sign in to comment.