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

Allow interfaces to implement other interfaces (#1000) #1028

Merged
merged 150 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
150 commits
Select commit Hold shift + click to select a range
01d7cb8
WIP
ilslv Dec 20, 2021
205cfcd
WIP
ilslv Dec 20, 2021
928cb9c
WIP (with IsSubtype)
ilslv Dec 20, 2021
3dad162
WIP (It looks like we're onto something)
ilslv Dec 20, 2021
af04486
WIP (return to const assertions)
ilslv Dec 21, 2021
4ae8c5d
Make `chrono` scalars similar to the `time`
ilslv Dec 23, 2021
279ae22
Remove FixedOffset scalar
ilslv Dec 23, 2021
bba4af8
Corrections
ilslv Dec 23, 2021
ecf9179
Merge branch 'master' into chrono-scalars
ilslv Dec 23, 2021
15e527f
It's alive!
ilslv Dec 24, 2021
cb9cad5
Merge branch 'master' into 1000-new-interfaces
ilslv Dec 24, 2021
7a78f9e
Use Field inside object's field resolver
ilslv Dec 24, 2021
5c2087c
Add async object fields
ilslv Dec 24, 2021
e9c6046
Fix
ilslv Dec 24, 2021
f2275a4
Corrections
tyranron Dec 25, 2021
0739c3e
Fix Wrapped type for tuple with context
ilslv Dec 27, 2021
263d544
WIP (Make graphql_interface_new attribute work)
ilslv Dec 28, 2021
f4c48a6
WIP (move to the new tests volume 1)
ilslv Dec 28, 2021
a1df387
WIP (move to the new tests volume 2)
ilslv Dec 29, 2021
5040d68
WIP (move to the new tests volume 3)
ilslv Dec 29, 2021
f5533e3
WIP (move to the new tests volume 4)
ilslv Dec 29, 2021
527325c
WIP (move to the new tests volume 5)
ilslv Dec 29, 2021
704f079
WIP (move to the new tests volume 5)
ilslv Dec 29, 2021
b84009e
WIP (refactor)
ilslv Dec 30, 2021
11345f7
WIP (refactor)
ilslv Dec 30, 2021
5e7230f
WIP (moving to readable const asserts)
ilslv Dec 30, 2021
d17ba4f
WIP (finally good const assert message)
ilslv Dec 30, 2021
8cb9b81
WIP (corrections)
ilslv Dec 30, 2021
116cdd9
WIP (pretty field arguments check)
ilslv Jan 3, 2022
7bd8c73
WIP (pretty subtype check)
ilslv Jan 3, 2022
3ebc8fd
WIP (corrections)
ilslv Jan 3, 2022
ce249b0
WIP (FieldMeta trait)
ilslv Jan 3, 2022
d9971a0
WIP (Refactor graphql_interface_new a bit)
ilslv Jan 3, 2022
8a966fb
WIP (Corrections)
ilslv Jan 3, 2022
21f4940
WIP (Prettify non-existent Field assertions)
ilslv Jan 4, 2022
e03901b
WIP (Move to macros::reflection module)
ilslv Jan 4, 2022
3ad8399
WIP (Docs vol. 1)
ilslv Jan 4, 2022
463bbb8
WIP (Docs vol. 2)
ilslv Jan 4, 2022
26ab27e
WIP (Refactoring)
ilslv Jan 4, 2022
cddb252
Merge branch 'master' into 1000-new-interfaces
ilslv Jan 4, 2022
9977746
WIP (Refactoring)
ilslv Jan 4, 2022
f5340a7
WIP (Tests corrections)
ilslv Jan 4, 2022
4c31c13
WIP (More tests vol. 1)
ilslv Jan 4, 2022
7bd09d0
WIP (Move everything to the new graphql_interface macro)
ilslv Jan 5, 2022
b5f552a
WIP (More codegen_fail tests)
ilslv Jan 5, 2022
66819e9
WIP (prettify missing references in `impl` and `for` attributes)
ilslv Jan 5, 2022
3d53dc1
WIP (more tests)
ilslv Jan 5, 2022
732bc13
WIP (Correct docs)
ilslv Jan 6, 2022
81d96ce
WIP (Forbid default trait method impls)
ilslv Jan 6, 2022
94164eb
WIP (Corrections)
ilslv Jan 6, 2022
1345bff
WIP (Corrections)
ilslv Jan 6, 2022
6cb5cef
WIP (Corrections)
ilslv Jan 6, 2022
41a2dcf
WIP (CHANGELOG)
ilslv Jan 6, 2022
eb8c56d
WIP (Correction)
ilslv Jan 6, 2022
7f45aef
WIP
ilslv Jan 10, 2022
81c4df9
Migrate to real GraphQLScalar trait
ilslv Jan 10, 2022
d359524
Corrections
ilslv Jan 10, 2022
888789a
Corrections
ilslv Jan 11, 2022
e28ca92
Add generic test and remove redundant lifetime
ilslv Jan 11, 2022
f531037
Some corrections [skip ci]
tyranron Jan 11, 2022
8f169cf
WIP
ilslv Jan 12, 2022
a2287dc
Minor tests corrections
ilslv Jan 12, 2022
ceb2cf6
Don't inject `#[async_trait]` in case some trait methods are async
ilslv Jan 12, 2022
d77a1ab
Corrections
ilslv Jan 13, 2022
8be555c
Merge branch '1000-new-interfaces' into generic-scalar
ilslv Jan 13, 2022
ebc0149
Merge branch 'generic-scalar' into new-derive-scalar
ilslv Jan 13, 2022
8fb4587
WIP
ilslv Jan 13, 2022
52c14b2
WIP
ilslv Jan 13, 2022
4b6df34
Corrections
ilslv Jan 14, 2022
0cb101d
Corrections
ilslv Jan 14, 2022
8d47ba7
Merge branch '1000-new-interfaces' into generic-scalar
ilslv Jan 14, 2022
22b3fe2
Merge branch 'generic-scalar' into new-derive-scalar
ilslv Jan 14, 2022
7635a1e
Corrections
ilslv Jan 14, 2022
3da33ba
Corrections
ilslv Jan 14, 2022
cc365d3
Corrections
ilslv Jan 14, 2022
30117a8
Docs and the book
ilslv Jan 14, 2022
2362b27
Fix The Book
ilslv Jan 14, 2022
193c77b
Merge branch 'master' into generic-scalar
ilslv Jan 28, 2022
86f1c10
CHANGELOG
ilslv Jan 28, 2022
7db2adc
Sprinkle `#[automatically_derived]` attributes
ilslv Jan 28, 2022
859b3be
Merge branch 'generic-scalar' into new-derive-scalar
ilslv Jan 28, 2022
6fb0dbf
Implement `#[graphql(with)]` attribute
ilslv Feb 2, 2022
f063728
CHANGELOG
ilslv Feb 2, 2022
10bea73
Implement `GraphQLScalarValue` derive macro
ilslv Feb 3, 2022
512f377
Add `codegen_fail` tests
ilslv Feb 3, 2022
60fcbbc
WIP
ilslv Feb 4, 2022
8b3e80a
Merge branch 'master' into new-derive-scalar
ilslv Feb 4, 2022
8706043
Corrections and tests
ilslv Feb 4, 2022
5b96861
Correction
ilslv Feb 4, 2022
81bb650
WIP
ilslv Feb 15, 2022
b4df74a
Corrections
ilslv Feb 15, 2022
2c5cf99
Merge branch 'new-derive-scalar' into derive-scalar-value
ilslv Feb 15, 2022
aed11f9
Corrections
ilslv Feb 15, 2022
a5cf713
CHANGELOG
ilslv Feb 15, 2022
5cc24db
CHANGELOG
ilslv Feb 15, 2022
ea6c4f7
WIP
ilslv Feb 17, 2022
8cea820
WIP
ilslv Feb 18, 2022
9c58419
Corrections
ilslv Feb 21, 2022
baad84b
Corrections
ilslv Feb 21, 2022
8ebd520
Merge branch 'new-derive-scalar' into derive-scalar-value
ilslv Feb 21, 2022
a2ecb44
Corrections
ilslv Feb 21, 2022
7a7d6ea
Merge branch 'derive-scalar-value' into derive-interface-on-structs
ilslv Feb 21, 2022
943526f
Corrections
ilslv Feb 21, 2022
9144d02
Corrections
ilslv Feb 21, 2022
6c5c7b7
Merge branch 'derive-scalar-value' into derive-interface-on-structs
ilslv Feb 21, 2022
7fab2ce
WIP
ilslv Feb 21, 2022
91fd9e4
WIP
ilslv Feb 22, 2022
da857c7
Fix introspection tests
ilslv Feb 23, 2022
fb2f43c
Correction
ilslv Feb 23, 2022
eb8bbbe
Fix fragment spread tests
ilslv Feb 23, 2022
d5b75fd
Corrections
ilslv Feb 23, 2022
d29806b
CHANGELOG
ilslv Feb 24, 2022
e58da4f
Merge branch 'derive-scalar-value' into chrono-scalars
ilslv Feb 24, 2022
173cddb
WIP
ilslv Feb 24, 2022
a7d4b28
Corrections
ilslv Feb 25, 2022
28bd546
Merge branch 'master' into derive-scalar-value
ilslv Mar 1, 2022
aa8a345
Corrections
ilslv Mar 1, 2022
b28cc4a
Merge branch 'derive-scalar-value' into chrono-scalars
ilslv Mar 1, 2022
641860b
Corrections
ilslv Mar 1, 2022
31641e0
Merge branch 'chrono-scalars' into interface-inheritance
ilslv Mar 1, 2022
4226d19
Merge branch 'derive-scalar-value' into derive-interface-on-structs
ilslv Mar 1, 2022
466e7ae
Merge branch 'chrono-scalars' into derive-interface-on-structs
ilslv Mar 1, 2022
b4a7370
WIP
ilslv Mar 1, 2022
84552ba
Merge branch 'derive-interface-on-structs' into interface-inheritance
ilslv Mar 1, 2022
d443190
Corrections
ilslv Mar 1, 2022
4233799
Corrections
ilslv Mar 1, 2022
32576cc
Merge branch 'master' into derive-interface-on-structs
ilslv Mar 8, 2022
f25488e
Corrections
ilslv Mar 10, 2022
f4bbdaf
CHANGELOG
ilslv Mar 10, 2022
c77fa9a
Corrections [skip ci]
tyranron Mar 11, 2022
6aa9b0b
Corrections
ilslv Mar 24, 2022
e9961ec
Corrections
ilslv Mar 25, 2022
33ee5b0
Merge remote-tracking branch 'origin/derive-interface-on-structs' int…
ilslv Mar 25, 2022
a89162c
Merge branch 'master' into derive-interface-on-structs
ilslv Mar 25, 2022
30558b4
Merge branch 'derive-interface-on-structs' into interface-inheritance
ilslv Mar 28, 2022
678d00a
Corrections
ilslv Mar 28, 2022
5e38cd7
Reference issue in CHANGELOG
ilslv Mar 28, 2022
f969976
Add docs section
ilslv Mar 28, 2022
fb44c84
Add docs section
ilslv Mar 29, 2022
0799528
Add docs section
ilslv Mar 29, 2022
b43e711
Corrections
ilslv Mar 29, 2022
d5bbdd7
Corrections
ilslv Jun 24, 2022
c9643ab
Merge branch 'master' into interface-inheritance
ilslv Jun 24, 2022
bc2cb20
Corrections
ilslv Jun 24, 2022
0240ab4
Fix integration tests
ilslv Jun 27, 2022
f7be617
Merge branch 'master' into interface-inheritance
ilslv Jun 27, 2022
2832e35
Corrections
ilslv Jun 27, 2022
f6bae6c
Fix codegen test
ilslv Jun 27, 2022
563b108
Merge branch 'master' into interface-inheritance
tyranron Jun 27, 2022
080f4f0
Corrections
tyranron Jun 27, 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
184 changes: 184 additions & 0 deletions book/src/types/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,190 @@ struct Human {
```


### Interfaces implementing other interfaces

GraphQL allows implementing interfaces on other interfaces in addition to objects.

```rust
# extern crate juniper;
use juniper::{graphql_interface, graphql_object, ID};

#[graphql_interface(for = [HumanValue, Luke])]
struct Node {
id: ID,
}

#[graphql_interface(impl = NodeValue, for = Luke)]
struct Human {
id: ID,
home_planet: String,
}

struct Luke {
id: ID,
}

#[graphql_object(impl = [HumanValue, NodeValue])]
impl Luke {
fn id(&self) -> &ID {
&self.id
}

// As `String` and `&str` aren't distinguished by
// GraphQL spec, you can use them interchangeably.
// Same is applied for `Cow<'a, str>`.
// ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
fn home_planet() -> &'static str {
"Tatooine"
}
}
#
# fn main() {}
```

> __NOTE:__ Every interface has to specify all other interfaces/objects it implements or implemented for. Missing one of `for = ` or `impl = ` attributes is a compile-time error.

```compile_fail
# extern crate juniper;
use juniper::{graphql_interface, GraphQLObject};

#[derive(GraphQLObject)]
pub struct ObjA {
id: String,
}

#[graphql_interface(for = ObjA)]
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at
// 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.'
struct Character {
id: String,
}

fn main() {}
```


### GraphQL subtyping and additional `null`able fields

GraphQL allows implementers (both objects and other interfaces) to return "subtypes" instead of an original value. Basically, this allows you to impose additional bounds on the implementation.

Valid "subtypes" are:
- interface implementer instead of an interface itself:
- `I implements T` in place of a `T`;
- `Vec<I implements T>` in place of a `Vec<T>`.
- non-null value in place of a nullable:
- `T` in place of a `Option<T>`;
- `Vec<T>` in place of a `Vec<Option<T>>`.

These rules are recursively applied, so `Vec<Vec<I implements T>>` is a valid "subtype" of a `Option<Vec<Option<Vec<Option<T>>>>>`.

Also, GraphQL allows implementers to add `null`able fields, which aren't present on an original interface.

```rust
# extern crate juniper;
use juniper::{graphql_interface, graphql_object, ID};

#[graphql_interface(for = [HumanValue, Luke])]
struct Node {
id: ID,
}

#[graphql_interface(for = HumanConnectionValue)]
struct Connection {
nodes: Vec<NodeValue>,
}

#[graphql_interface(impl = NodeValue, for = Luke)]
struct Human {
id: ID,
home_planet: String,
}

#[graphql_interface(impl = ConnectionValue)]
struct HumanConnection {
nodes: Vec<HumanValue>,
// ^^^^^^^^^^ notice not `NodeValue`
// This can happen, because every `Human` is a `Node` too, so we are just
// imposing additional bounds, which still can be resolved with
// `... on Connection { nodes }`.
}

struct Luke {
id: ID,
}

#[graphql_object(impl = [HumanValue, NodeValue])]
impl Luke {
fn id(&self) -> &ID {
&self.id
}

fn home_planet(language: Option<String>) -> &'static str {
// ^^^^^^^^^^^^^^
// Notice additional `null`able field, which is missing on `Human`.
// Resolving `...on Human { homePlanet }` will provide `None` for this
// argument.
match language.as_deref() {
None | Some("en") => "Tatooine",
Some("ko") => "타투인",
_ => todo!(),
}
}
}
#
# fn main() {}
```

Violating GraphQL "subtyping" or additional nullable field rules is a compile-time error.

```compile_fail
# extern crate juniper;
use juniper::{graphql_interface, graphql_object};

pub struct ObjA {
id: String,
}

#[graphql_object(impl = CharacterValue)]
impl ObjA {
fn id(&self, is_present: bool) -> &str {
// ^^ the evaluated program panicked at
// 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!`
// isn't present on the interface and so has to be nullable.'
is_present.then(|| self.id.as_str()).unwrap_or("missing")
}
}

#[graphql_interface(for = ObjA)]
struct Character {
id: String,
}
#
# fn main() {}
```

```compile_fail
# extern crate juniper;
use juniper::{graphql_interface, GraphQLObject};

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
id: Vec<String>,
// ^^ the evaluated program panicked at
// 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of
// interface's return object: `[String!]!` is not a subtype of `String!`.'
}

#[graphql_interface(for = ObjA)]
struct Character {
id: String,
}
#
# fn main() {}
```


### Ignoring trait methods

We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
Expand Down
2 changes: 2 additions & 0 deletions juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Forbade default implementations of non-ignored trait methods.
- Supported coercion of additional `null`able arguments and return sub-typing on implementer.
- Supported `rename_all = "<policy>"` attribute argument influencing all its fields and their arguments. ([#971])
- Supported interfaces implementing other interfaces. ([#1028])
- Split `#[derive(GraphQLScalarValue)]` macro into:
- `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017])
- Supported generic `ScalarValue`.
Expand Down Expand Up @@ -94,6 +95,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1017]: /../../pull/1017
[#1025]: /../../pull/1025
[#1026]: /../../pull/1026
[#1028]: /../../pull/1028
[#1051]: /../../issues/1051
[#1054]: /../../pull/1054
[#1057]: /../../pull/1057
Expand Down
2 changes: 1 addition & 1 deletion juniper/src/executor_tests/introspection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ async fn interface_introspection() {
);
assert_eq!(
type_info.get_field_value("interfaces"),
Some(&graphql_value!(null)),
Some(&graphql_value!([])),
);
assert_eq!(
type_info.get_field_value("enumValues"),
Expand Down
32 changes: 32 additions & 0 deletions juniper/src/macros/reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,38 @@ macro_rules! assert_interfaces_impls {
};
}

/// Asserts that all [transitive interfaces][0] (the ones implemented by the
/// `$interface`) are also implemented by the `$implementor`.
///
/// [0]: https://spec.graphql.org/October2021#sel-FAHbhBHCAACGB35P
#[macro_export]
macro_rules! assert_transitive_impls {
($scalar: ty, $interface: ty, $implementor: ty $(, $transitive: ty)* $(,)?) => {
const _: () = {
$({
let is_present = $crate::macros::reflect::str_exists_in_arr(
<$implementor as ::juniper::macros::reflect::BaseType<$scalar>>::NAME,
<$transitive as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES,
);
if !is_present {
const MSG: &str = $crate::const_concat!(
"Failed to implement interface `",
<$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `",
<$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"`: missing `impl = ` for transitive interface `",
<$transitive as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `",
<$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"`."
);
::std::panic!("{}", MSG);
}
})*
};
};
}

/// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`].
///
/// This assertion is a combination of [`assert_subtype`] and
Expand Down
15 changes: 15 additions & 0 deletions juniper/src/schema/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ pub struct InterfaceMeta<'a, S> {
pub description: Option<String>,
#[doc(hidden)]
pub fields: Vec<Field<'a, S>>,
#[doc(hidden)]
pub interface_names: Vec<String>,
}

/// Union type metadata
Expand Down Expand Up @@ -587,6 +589,7 @@ impl<'a, S> InterfaceMeta<'a, S> {
name,
description: None,
fields: fields.to_vec(),
interface_names: Vec::new(),
}
}

Expand All @@ -599,6 +602,18 @@ impl<'a, S> InterfaceMeta<'a, S> {
self
}

/// Sets the `interfaces` this [`InterfaceMeta`] interface implements.
///
/// Overwrites any previously set list of interfaces.
#[must_use]
pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self {
self.interface_names = interfaces
.iter()
.map(|t| t.innermost_name().to_owned())
.collect();
self
}

/// Wraps this [`InterfaceMeta`] type into a generic [`MetaType`].
pub fn into_meta(self) -> MetaType<'a, S> {
MetaType::Interface(self)
Expand Down
14 changes: 10 additions & 4 deletions juniper/src/schema/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,16 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {

fn interfaces<'s>(&self, context: &'s SchemaType<'a, S>) -> Option<Vec<TypeType<'s, S>>> {
match self {
TypeType::Concrete(&MetaType::Object(ObjectMeta {
ref interface_names,
..
})) => Some(
TypeType::Concrete(
&MetaType::Object(ObjectMeta {
ref interface_names,
..
})
| &MetaType::Interface(InterfaceMeta {
ref interface_names,
..
}),
) => Some(
interface_names
.iter()
.filter_map(|n| context.type_by_name(n))
Expand Down
7 changes: 5 additions & 2 deletions juniper/src/schema/translate/graphql_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,11 @@ impl GraphQLParserTranslator {
position: Pos::default(),
description: x.description.as_ref().map(|s| From::from(s.as_str())),
name: From::from(x.name.as_ref()),
// TODO: Support this with GraphQL October 2021 Edition.
implements_interfaces: vec![],
implements_interfaces: x
.interface_names
.iter()
.map(|s| From::from(s.as_str()))
.collect(),
directives: vec![],
fields: x
.fields
Expand Down
4 changes: 2 additions & 2 deletions juniper/src/tests/schema_introspection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,7 @@ pub(crate) fn schema_introspection_result() -> Value {
}
],
"inputFields": null,
"interfaces": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": [
{
Expand Down Expand Up @@ -2500,7 +2500,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
}
],
"inputFields": null,
"interfaces": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": [
{
Expand Down
Loading