parent
bd041222df
commit
9ca2364bfe
34 changed files with 2440 additions and 61 deletions
|
@ -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
|
### Ignoring trait methods
|
||||||
|
|
||||||
We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
|
We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
|
||||||
|
|
|
@ -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.
|
- Forbade default implementations of non-ignored trait methods.
|
||||||
- Supported coercion of additional `null`able arguments and return sub-typing on implementer.
|
- 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 `rename_all = "<policy>"` attribute argument influencing all its fields and their arguments. ([#971])
|
||||||
|
- Supported interfaces implementing other interfaces. ([#1028])
|
||||||
- Split `#[derive(GraphQLScalarValue)]` macro into:
|
- Split `#[derive(GraphQLScalarValue)]` macro into:
|
||||||
- `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017])
|
- `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017])
|
||||||
- Supported generic `ScalarValue`.
|
- Supported generic `ScalarValue`.
|
||||||
|
@ -94,6 +95,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
|
||||||
[#1017]: /../../pull/1017
|
[#1017]: /../../pull/1017
|
||||||
[#1025]: /../../pull/1025
|
[#1025]: /../../pull/1025
|
||||||
[#1026]: /../../pull/1026
|
[#1026]: /../../pull/1026
|
||||||
|
[#1028]: /../../pull/1028
|
||||||
[#1051]: /../../issues/1051
|
[#1051]: /../../issues/1051
|
||||||
[#1054]: /../../pull/1054
|
[#1054]: /../../pull/1054
|
||||||
[#1057]: /../../pull/1057
|
[#1057]: /../../pull/1057
|
||||||
|
|
|
@ -247,7 +247,7 @@ async fn interface_introspection() {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
type_info.get_field_value("interfaces"),
|
type_info.get_field_value("interfaces"),
|
||||||
Some(&graphql_value!(null)),
|
Some(&graphql_value!([])),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
type_info.get_field_value("enumValues"),
|
type_info.get_field_value("enumValues"),
|
||||||
|
|
|
@ -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`].
|
/// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`].
|
||||||
///
|
///
|
||||||
/// This assertion is a combination of [`assert_subtype`] and
|
/// This assertion is a combination of [`assert_subtype`] and
|
||||||
|
|
|
@ -110,6 +110,8 @@ pub struct InterfaceMeta<'a, S> {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fields: Vec<Field<'a, S>>,
|
pub fields: Vec<Field<'a, S>>,
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub interface_names: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Union type metadata
|
/// Union type metadata
|
||||||
|
@ -587,6 +589,7 @@ impl<'a, S> InterfaceMeta<'a, S> {
|
||||||
name,
|
name,
|
||||||
description: None,
|
description: None,
|
||||||
fields: fields.to_vec(),
|
fields: fields.to_vec(),
|
||||||
|
interface_names: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,6 +602,18 @@ impl<'a, S> InterfaceMeta<'a, S> {
|
||||||
self
|
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`].
|
/// Wraps this [`InterfaceMeta`] type into a generic [`MetaType`].
|
||||||
pub fn into_meta(self) -> MetaType<'a, S> {
|
pub fn into_meta(self) -> MetaType<'a, S> {
|
||||||
MetaType::Interface(self)
|
MetaType::Interface(self)
|
||||||
|
|
|
@ -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>>> {
|
fn interfaces<'s>(&self, context: &'s SchemaType<'a, S>) -> Option<Vec<TypeType<'s, S>>> {
|
||||||
match self {
|
match self {
|
||||||
TypeType::Concrete(&MetaType::Object(ObjectMeta {
|
TypeType::Concrete(
|
||||||
ref interface_names,
|
&MetaType::Object(ObjectMeta {
|
||||||
..
|
ref interface_names,
|
||||||
})) => Some(
|
..
|
||||||
|
})
|
||||||
|
| &MetaType::Interface(InterfaceMeta {
|
||||||
|
ref interface_names,
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
) => Some(
|
||||||
interface_names
|
interface_names
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|n| context.type_by_name(n))
|
.filter_map(|n| context.type_by_name(n))
|
||||||
|
|
|
@ -190,8 +190,11 @@ impl GraphQLParserTranslator {
|
||||||
position: Pos::default(),
|
position: Pos::default(),
|
||||||
description: x.description.as_ref().map(|s| From::from(s.as_str())),
|
description: x.description.as_ref().map(|s| From::from(s.as_str())),
|
||||||
name: From::from(x.name.as_ref()),
|
name: From::from(x.name.as_ref()),
|
||||||
// TODO: Support this with GraphQL October 2021 Edition.
|
implements_interfaces: x
|
||||||
implements_interfaces: vec![],
|
.interface_names
|
||||||
|
.iter()
|
||||||
|
.map(|s| From::from(s.as_str()))
|
||||||
|
.collect(),
|
||||||
directives: vec![],
|
directives: vec![],
|
||||||
fields: x
|
fields: x
|
||||||
.fields
|
.fields
|
||||||
|
|
|
@ -1173,7 +1173,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inputFields": null,
|
"inputFields": null,
|
||||||
"interfaces": null,
|
"interfaces": [],
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": [
|
"possibleTypes": [
|
||||||
{
|
{
|
||||||
|
@ -2500,7 +2500,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inputFields": null,
|
"inputFields": null,
|
||||||
"interfaces": null,
|
"interfaces": [],
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": [
|
"possibleTypes": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Definition, Document, FragmentSpread, InlineFragment},
|
ast::{Definition, Document, FragmentSpread, InlineFragment},
|
||||||
|
meta::InterfaceMeta,
|
||||||
parser::Spanning,
|
parser::Spanning,
|
||||||
schema::meta::MetaType,
|
schema::meta::MetaType,
|
||||||
validation::{ValidatorContext, Visitor},
|
validation::{ValidatorContext, Visitor},
|
||||||
|
@ -45,6 +46,23 @@ where
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|s| ctx.schema.concrete_type_by_name(s.item)),
|
.and_then(|s| ctx.schema.concrete_type_by_name(s.item)),
|
||||||
) {
|
) {
|
||||||
|
// Even if there is no object type in the overlap of interfaces
|
||||||
|
// implementers, it's OK to spread in case `frag_type` implements
|
||||||
|
// `parent_type`.
|
||||||
|
// https://spec.graphql.org/October2021#sel-JALVFJNRDABABqDy5B
|
||||||
|
if let MetaType::Interface(InterfaceMeta {
|
||||||
|
interface_names, ..
|
||||||
|
}) = frag_type
|
||||||
|
{
|
||||||
|
let implements_parent = parent_type
|
||||||
|
.name()
|
||||||
|
.map(|parent| interface_names.iter().any(|i| i == parent))
|
||||||
|
.unwrap_or_default();
|
||||||
|
if implements_parent {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !ctx.schema.type_overlap(parent_type, frag_type) {
|
if !ctx.schema.type_overlap(parent_type, frag_type) {
|
||||||
ctx.report_error(
|
ctx.report_error(
|
||||||
&error_message(
|
&error_message(
|
||||||
|
@ -67,6 +85,23 @@ where
|
||||||
ctx.parent_type(),
|
ctx.parent_type(),
|
||||||
self.fragment_types.get(spread.item.name.item),
|
self.fragment_types.get(spread.item.name.item),
|
||||||
) {
|
) {
|
||||||
|
// Even if there is no object type in the overlap of interfaces
|
||||||
|
// implementers, it's OK to spread in case `frag_type` implements
|
||||||
|
// `parent_type`.
|
||||||
|
// https://spec.graphql.org/October2021/#sel-JALVFJNRDABABqDy5B
|
||||||
|
if let MetaType::Interface(InterfaceMeta {
|
||||||
|
interface_names, ..
|
||||||
|
}) = frag_type
|
||||||
|
{
|
||||||
|
let implements_parent = parent_type
|
||||||
|
.name()
|
||||||
|
.map(|parent| interface_names.iter().any(|i| i == parent))
|
||||||
|
.unwrap_or_default();
|
||||||
|
if implements_parent {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !ctx.schema.type_overlap(parent_type, frag_type) {
|
if !ctx.schema.type_overlap(parent_type, frag_type) {
|
||||||
ctx.report_error(
|
ctx.report_error(
|
||||||
&error_message(
|
&error_message(
|
||||||
|
@ -226,6 +261,27 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_object_overlap_but_implements_parent() {
|
||||||
|
expect_passes_rule::<_, _, DefaultScalarValue>(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
fragment beingFragment on Being { ...unpopulatedFragment }
|
||||||
|
fragment unpopulatedFragment on Unpopulated { name }
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_object_overlap_but_implements_parent_inline() {
|
||||||
|
expect_passes_rule::<_, _, DefaultScalarValue>(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
fragment beingFragment on Being { ...on Unpopulated { name } }
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn different_object_into_object() {
|
fn different_object_into_object() {
|
||||||
expect_fails_rule::<_, _, DefaultScalarValue>(
|
expect_fails_rule::<_, _, DefaultScalarValue>(
|
||||||
|
|
|
@ -20,6 +20,7 @@ use crate::{
|
||||||
struct Being;
|
struct Being;
|
||||||
struct Pet;
|
struct Pet;
|
||||||
struct Canine;
|
struct Canine;
|
||||||
|
struct Unpopulated;
|
||||||
|
|
||||||
struct Dog;
|
struct Dog;
|
||||||
struct Cat;
|
struct Cat;
|
||||||
|
@ -167,6 +168,41 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S> GraphQLType<S> for Unpopulated
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
fn name(_: &()) -> Option<&'static str> {
|
||||||
|
Some("Unpopulated")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S>
|
||||||
|
where
|
||||||
|
S: 'r,
|
||||||
|
{
|
||||||
|
let fields = &[registry
|
||||||
|
.field::<Option<String>>("name", i)
|
||||||
|
.argument(registry.arg::<Option<bool>>("surname", i))];
|
||||||
|
|
||||||
|
registry
|
||||||
|
.build_interface_type::<Self>(i, fields)
|
||||||
|
.interfaces(&[registry.get_type::<Being>(i)])
|
||||||
|
.into_meta()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> GraphQLValue<S> for Unpopulated
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
type Context = ();
|
||||||
|
type TypeInfo = ();
|
||||||
|
|
||||||
|
fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> {
|
||||||
|
<Self as GraphQLType>::name(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<S> GraphQLType<S> for DogCommand
|
impl<S> GraphQLType<S> for DogCommand
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
|
@ -777,6 +813,8 @@ where
|
||||||
where
|
where
|
||||||
S: 'r,
|
S: 'r,
|
||||||
{
|
{
|
||||||
|
let _ = registry.get_type::<Unpopulated>(i);
|
||||||
|
|
||||||
let fields = [registry.field::<i32>("testInput", i).argument(
|
let fields = [registry.field::<i32>("testInput", i).argument(
|
||||||
registry.arg_with_default::<TestInput>(
|
registry.arg_with_default::<TestInput>(
|
||||||
"input",
|
"input",
|
||||||
|
|
|
@ -55,7 +55,8 @@ fn expand_on_trait(
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.map(SpanContainer::into_inner)
|
.map(SpanContainer::into_inner)
|
||||||
.unwrap_or_else(|| trait_ident.unraw().to_string());
|
.unwrap_or_else(|| trait_ident.unraw().to_string())
|
||||||
|
.into_boxed_str();
|
||||||
if !attr.is_internal && name.starts_with("__") {
|
if !attr.is_internal && name.starts_with("__") {
|
||||||
ERR.no_double_underscore(
|
ERR.no_double_underscore(
|
||||||
attr.name
|
attr.name
|
||||||
|
@ -120,17 +121,22 @@ fn expand_on_trait(
|
||||||
enum_ident,
|
enum_ident,
|
||||||
enum_alias_ident,
|
enum_alias_ident,
|
||||||
name,
|
name,
|
||||||
description: attr.description.as_deref().cloned(),
|
description: attr.description.map(|d| d.into_inner().into_boxed_str()),
|
||||||
context,
|
context,
|
||||||
scalar,
|
scalar,
|
||||||
fields,
|
fields,
|
||||||
implemented_for: attr
|
implemented_for: attr
|
||||||
.implemented_for
|
.implemented_for
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|c| c.inner().clone())
|
.map(SpanContainer::into_inner)
|
||||||
|
.collect(),
|
||||||
|
implements: attr
|
||||||
|
.implements
|
||||||
|
.into_iter()
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
.collect(),
|
.collect(),
|
||||||
suppress_dead_code: None,
|
suppress_dead_code: None,
|
||||||
src_intra_doc_link: format!("trait@{}", trait_ident),
|
src_intra_doc_link: format!("trait@{}", trait_ident).into_boxed_str(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
|
@ -242,7 +248,8 @@ fn expand_on_derive_input(
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.map(SpanContainer::into_inner)
|
.map(SpanContainer::into_inner)
|
||||||
.unwrap_or_else(|| struct_ident.unraw().to_string());
|
.unwrap_or_else(|| struct_ident.unraw().to_string())
|
||||||
|
.into_boxed_str();
|
||||||
if !attr.is_internal && name.starts_with("__") {
|
if !attr.is_internal && name.starts_with("__") {
|
||||||
ERR.no_double_underscore(
|
ERR.no_double_underscore(
|
||||||
attr.name
|
attr.name
|
||||||
|
@ -301,17 +308,22 @@ fn expand_on_derive_input(
|
||||||
enum_ident,
|
enum_ident,
|
||||||
enum_alias_ident,
|
enum_alias_ident,
|
||||||
name,
|
name,
|
||||||
description: attr.description.as_deref().cloned(),
|
description: attr.description.map(|d| d.into_inner().into_boxed_str()),
|
||||||
context,
|
context,
|
||||||
scalar,
|
scalar,
|
||||||
fields,
|
fields,
|
||||||
implemented_for: attr
|
implemented_for: attr
|
||||||
.implemented_for
|
.implemented_for
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|c| c.inner().clone())
|
.map(SpanContainer::into_inner)
|
||||||
|
.collect(),
|
||||||
|
implements: attr
|
||||||
|
.implements
|
||||||
|
.into_iter()
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
.collect(),
|
.collect(),
|
||||||
suppress_dead_code: None,
|
suppress_dead_code: None,
|
||||||
src_intra_doc_link: format!("struct@{}", struct_ident),
|
src_intra_doc_link: format!("struct@{}", struct_ident).into_boxed_str(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
|
|
|
@ -33,7 +33,8 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.map(SpanContainer::into_inner)
|
.map(SpanContainer::into_inner)
|
||||||
.unwrap_or_else(|| struct_ident.unraw().to_string());
|
.unwrap_or_else(|| struct_ident.unraw().to_string())
|
||||||
|
.into_boxed_str();
|
||||||
if !attr.is_internal && name.starts_with("__") {
|
if !attr.is_internal && name.starts_with("__") {
|
||||||
ERR.no_double_underscore(
|
ERR.no_double_underscore(
|
||||||
attr.name
|
attr.name
|
||||||
|
@ -93,17 +94,22 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
enum_ident,
|
enum_ident,
|
||||||
enum_alias_ident,
|
enum_alias_ident,
|
||||||
name,
|
name,
|
||||||
description: attr.description.as_deref().cloned(),
|
description: attr.description.map(|d| d.into_inner().into_boxed_str()),
|
||||||
context,
|
context,
|
||||||
scalar,
|
scalar,
|
||||||
fields,
|
fields,
|
||||||
implemented_for: attr
|
implemented_for: attr
|
||||||
.implemented_for
|
.implemented_for
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|c| c.inner().clone())
|
.map(SpanContainer::into_inner)
|
||||||
|
.collect(),
|
||||||
|
implements: attr
|
||||||
|
.implements
|
||||||
|
.into_iter()
|
||||||
|
.map(SpanContainer::into_inner)
|
||||||
.collect(),
|
.collect(),
|
||||||
suppress_dead_code: Some((ast.ident.clone(), data.fields.clone())),
|
suppress_dead_code: Some((ast.ident.clone(), data.fields.clone())),
|
||||||
src_intra_doc_link: format!("struct@{}", struct_ident),
|
src_intra_doc_link: format!("struct@{}", struct_ident).into_boxed_str(),
|
||||||
}
|
}
|
||||||
.into_token_stream())
|
.into_token_stream())
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,13 +81,20 @@ struct Attr {
|
||||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||||
r#enum: Option<SpanContainer<syn::Ident>>,
|
r#enum: Option<SpanContainer<syn::Ident>>,
|
||||||
|
|
||||||
/// Explicitly specified Rust types of [GraphQL objects][2] implementing
|
/// Explicitly specified Rust types of [GraphQL objects][2] or
|
||||||
/// this [GraphQL interface][1] type.
|
/// [interfaces][1] implementing this [GraphQL interface][1] type.
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||||
/// [2]: https://spec.graphql.org/June2018/#sec-Objects
|
/// [2]: https://spec.graphql.org/June2018/#sec-Objects
|
||||||
implemented_for: HashSet<SpanContainer<syn::TypePath>>,
|
implemented_for: HashSet<SpanContainer<syn::TypePath>>,
|
||||||
|
|
||||||
|
/// Explicitly specified [GraphQL interfaces, implemented][1] by this
|
||||||
|
/// [GraphQL interface][0].
|
||||||
|
///
|
||||||
|
/// [0]: https://spec.graphql.org/October2021#sec-Interfaces
|
||||||
|
/// [1]: https://spec.graphql.org/October2021#sel-GAHbhBDABAB_E-0b
|
||||||
|
implements: HashSet<SpanContainer<syn::TypePath>>,
|
||||||
|
|
||||||
/// Explicitly specified type of [`Context`] to use for resolving this
|
/// Explicitly specified type of [`Context`] to use for resolving this
|
||||||
/// [GraphQL interface][1] type with.
|
/// [GraphQL interface][1] type with.
|
||||||
///
|
///
|
||||||
|
@ -185,6 +192,18 @@ impl Parse for Attr {
|
||||||
.none_or_else(|_| err::dup_arg(impler_span))?;
|
.none_or_else(|_| err::dup_arg(impler_span))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"impl" | "implements" => {
|
||||||
|
input.parse::<token::Eq>()?;
|
||||||
|
for iface in input.parse_maybe_wrapped_and_punctuated::<
|
||||||
|
syn::TypePath, token::Bracket, token::Comma,
|
||||||
|
>()? {
|
||||||
|
let iface_span = iface.span();
|
||||||
|
out
|
||||||
|
.implements
|
||||||
|
.replace(SpanContainer::new(ident.span(), Some(iface_span), iface))
|
||||||
|
.none_or_else(|_| err::dup_arg(iface_span))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
"enum" => {
|
"enum" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let alias = input.parse::<syn::Ident>()?;
|
let alias = input.parse::<syn::Ident>()?;
|
||||||
|
@ -232,6 +251,7 @@ impl Attr {
|
||||||
context: try_merge_opt!(context: self, another),
|
context: try_merge_opt!(context: self, another),
|
||||||
scalar: try_merge_opt!(scalar: self, another),
|
scalar: try_merge_opt!(scalar: self, another),
|
||||||
implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined),
|
implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined),
|
||||||
|
implements: try_merge_hashset!(implements: self, another => span_joined),
|
||||||
r#enum: try_merge_opt!(r#enum: self, another),
|
r#enum: try_merge_opt!(r#enum: self, another),
|
||||||
asyncness: try_merge_opt!(asyncness: self, another),
|
asyncness: try_merge_opt!(asyncness: self, another),
|
||||||
rename_fields: try_merge_opt!(rename_fields: self, another),
|
rename_fields: try_merge_opt!(rename_fields: self, another),
|
||||||
|
@ -283,15 +303,15 @@ struct Definition {
|
||||||
/// [`implementers`]: Self::implementers
|
/// [`implementers`]: Self::implementers
|
||||||
enum_alias_ident: syn::Ident,
|
enum_alias_ident: syn::Ident,
|
||||||
|
|
||||||
/// Name of this [GraphQL interface][1] in GraphQL schema.
|
/// Name of this [GraphQL interface][0] in GraphQL schema.
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
/// [0]: https://spec.graphql.org/October2021#sec-Interfaces
|
||||||
name: String,
|
name: Box<str>,
|
||||||
|
|
||||||
/// Description of this [GraphQL interface][1] to put into GraphQL schema.
|
/// Description of this [GraphQL interface][0] to put into GraphQL schema.
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
/// [0]: https://spec.graphql.org/October2021#sec-Interfaces
|
||||||
description: Option<String>,
|
description: Option<Box<str>>,
|
||||||
|
|
||||||
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
||||||
/// for this [GraphQL interface][1].
|
/// for this [GraphQL interface][1].
|
||||||
|
@ -320,6 +340,12 @@ struct Definition {
|
||||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||||
implemented_for: Vec<syn::TypePath>,
|
implemented_for: Vec<syn::TypePath>,
|
||||||
|
|
||||||
|
/// [GraphQL interfaces implemented][1] by this [GraphQL interface][0].
|
||||||
|
///
|
||||||
|
/// [0]: https://spec.graphql.org/October2021#sec-Interfaces
|
||||||
|
/// [1]: https://spec.graphql.org/October2021#sel-GAHbhBDABAB_E-0b
|
||||||
|
implements: Vec<syn::TypePath>,
|
||||||
|
|
||||||
/// Unlike `#[graphql_interface]` maro, `#[derive(GraphQLInterface)]` can't
|
/// Unlike `#[graphql_interface]` maro, `#[derive(GraphQLInterface)]` can't
|
||||||
/// append `#[allow(dead_code)]` to the unused struct, representing
|
/// append `#[allow(dead_code)]` to the unused struct, representing
|
||||||
/// [GraphQL interface][1]. We generate hacky `const` which doesn't actually
|
/// [GraphQL interface][1]. We generate hacky `const` which doesn't actually
|
||||||
|
@ -329,10 +355,10 @@ struct Definition {
|
||||||
suppress_dead_code: Option<(syn::Ident, syn::Fields)>,
|
suppress_dead_code: Option<(syn::Ident, syn::Fields)>,
|
||||||
|
|
||||||
/// Intra-doc link to the [`syn::Item`] defining this
|
/// Intra-doc link to the [`syn::Item`] defining this
|
||||||
/// [GraphQL interface][1].
|
/// [GraphQL interface][0].
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
/// [0]: https://spec.graphql.org/October2021#sec-Interfaces
|
||||||
src_intra_doc_link: String,
|
src_intra_doc_link: Box<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for Definition {
|
impl ToTokens for Definition {
|
||||||
|
@ -496,11 +522,6 @@ impl Definition {
|
||||||
let (impl_generics, _, where_clause) = gens.split_for_impl();
|
let (impl_generics, _, where_clause) = gens.split_for_impl();
|
||||||
let (_, ty_generics, _) = self.generics.split_for_impl();
|
let (_, ty_generics, _) = self.generics.split_for_impl();
|
||||||
|
|
||||||
let implemented_for = &self.implemented_for;
|
|
||||||
let all_impled_for_unique = (implemented_for.len() > 1).then(|| {
|
|
||||||
quote! { ::juniper::sa::assert_type_ne_all!(#( #implemented_for ),*); }
|
|
||||||
});
|
|
||||||
|
|
||||||
let suppress_dead_code = self.suppress_dead_code.as_ref().map(|(ident, fields)| {
|
let suppress_dead_code = self.suppress_dead_code.as_ref().map(|(ident, fields)| {
|
||||||
let const_gens = self.const_trait_generics();
|
let const_gens = self.const_trait_generics();
|
||||||
let fields = fields.iter().map(|f| &f.ident);
|
let fields = fields.iter().map(|f| &f.ident);
|
||||||
|
@ -519,6 +540,49 @@ impl Definition {
|
||||||
}}
|
}}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let implemented_for = &self.implemented_for;
|
||||||
|
let all_impled_for_unique = (implemented_for.len() > 1).then(|| {
|
||||||
|
quote! { ::juniper::sa::assert_type_ne_all!(#( #implemented_for ),*); }
|
||||||
|
});
|
||||||
|
|
||||||
|
let mark_object_or_interface = self.implemented_for.iter().map(|impl_for| {
|
||||||
|
quote_spanned! { impl_for.span() =>
|
||||||
|
trait GraphQLObjectOrInterface<S: ::juniper::ScalarValue, T> {
|
||||||
|
fn mark();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
struct Object;
|
||||||
|
|
||||||
|
impl<S, T> GraphQLObjectOrInterface<S, Object> for T
|
||||||
|
where
|
||||||
|
S: ::juniper::ScalarValue,
|
||||||
|
T: ::juniper::marker::GraphQLObject<S>,
|
||||||
|
{
|
||||||
|
fn mark() {
|
||||||
|
<T as ::juniper::marker::GraphQLObject<S>>::mark()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
struct Interface;
|
||||||
|
|
||||||
|
impl<S, T> GraphQLObjectOrInterface<S, Interface> for T
|
||||||
|
where
|
||||||
|
S: ::juniper::ScalarValue,
|
||||||
|
T: ::juniper::marker::GraphQLInterface<S>,
|
||||||
|
{
|
||||||
|
fn mark() {
|
||||||
|
<T as ::juniper::marker::GraphQLInterface<S>>::mark()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#impl_for as GraphQLObjectOrInterface<#scalar, _>>::mark();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar>
|
impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar>
|
||||||
|
@ -528,7 +592,7 @@ impl Definition {
|
||||||
fn mark() {
|
fn mark() {
|
||||||
#suppress_dead_code
|
#suppress_dead_code
|
||||||
#all_impled_for_unique
|
#all_impled_for_unique
|
||||||
#( <#implemented_for as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )*
|
#( { #mark_object_or_interface } )*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -565,6 +629,25 @@ impl Definition {
|
||||||
generics.replace_type_path_with_defaults(&mut ty);
|
generics.replace_type_path_with_defaults(&mut ty);
|
||||||
ty
|
ty
|
||||||
});
|
});
|
||||||
|
let const_implements = self
|
||||||
|
.implements
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|mut ty| {
|
||||||
|
generics.replace_type_path_with_defaults(&mut ty);
|
||||||
|
ty
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let transitive_checks = const_impl_for.clone().map(|const_impl_for| {
|
||||||
|
quote_spanned! { const_impl_for.span() =>
|
||||||
|
::juniper::assert_transitive_impls!(
|
||||||
|
#const_scalar,
|
||||||
|
#ty#ty_const_generics,
|
||||||
|
#const_impl_for,
|
||||||
|
#( #const_implements ),*
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
|
@ -580,6 +663,12 @@ impl Definition {
|
||||||
#ty#ty_const_generics,
|
#ty#ty_const_generics,
|
||||||
#( #const_impl_for ),*
|
#( #const_impl_for ),*
|
||||||
);
|
);
|
||||||
|
::juniper::assert_implemented_for!(
|
||||||
|
#const_scalar,
|
||||||
|
#ty#ty_const_generics,
|
||||||
|
#( #const_implements ),*
|
||||||
|
);
|
||||||
|
#( #transitive_checks )*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -612,6 +701,20 @@ impl Definition {
|
||||||
a.cmp(&b)
|
a.cmp(&b)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sorting is required to preserve/guarantee the order of interfaces registered in schema.
|
||||||
|
let mut implements = self.implements.clone();
|
||||||
|
implements.sort_unstable_by(|a, b| {
|
||||||
|
let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string());
|
||||||
|
a.cmp(&b)
|
||||||
|
});
|
||||||
|
let impl_interfaces = (!implements.is_empty()).then(|| {
|
||||||
|
quote! {
|
||||||
|
.interfaces(&[
|
||||||
|
#( registry.get_type::<#implements>(info), )*
|
||||||
|
])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let fields_meta = self.fields.iter().map(|f| f.method_meta_tokens(None));
|
let fields_meta = self.fields.iter().map(|f| f.method_meta_tokens(None));
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -638,6 +741,7 @@ impl Definition {
|
||||||
];
|
];
|
||||||
registry.build_interface_type::<#ty#ty_generics>(info, &fields)
|
registry.build_interface_type::<#ty#ty_generics>(info, &fields)
|
||||||
#description
|
#description
|
||||||
|
#impl_interfaces
|
||||||
.into_meta()
|
.into_meta()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -801,6 +905,7 @@ impl Definition {
|
||||||
fn impl_reflection_traits_tokens(&self) -> TokenStream {
|
fn impl_reflection_traits_tokens(&self) -> TokenStream {
|
||||||
let ty = &self.enum_alias_ident;
|
let ty = &self.enum_alias_ident;
|
||||||
let implemented_for = &self.implemented_for;
|
let implemented_for = &self.implemented_for;
|
||||||
|
let implements = &self.implements;
|
||||||
let scalar = &self.scalar;
|
let scalar = &self.scalar;
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
let fields = self.fields.iter().map(|f| &f.name);
|
let fields = self.fields.iter().map(|f| &f.name);
|
||||||
|
@ -829,6 +934,15 @@ impl Definition {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[automatically_derived]
|
||||||
|
impl#impl_generics ::juniper::macros::reflect::Implements<#scalar>
|
||||||
|
for #ty#ty_generics
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
const NAMES: ::juniper::macros::reflect::Types =
|
||||||
|
&[#( <#implements as ::juniper::macros::reflect::BaseType<#scalar>>::NAME ),*];
|
||||||
|
}
|
||||||
|
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar>
|
impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar>
|
||||||
for #ty#ty_generics
|
for #ty#ty_generics
|
||||||
|
|
|
@ -873,6 +873,125 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// # Interfaces implementing other interfaces
|
||||||
|
///
|
||||||
|
/// GraphQL allows implementing interfaces on other interfaces in addition to
|
||||||
|
/// objects.
|
||||||
|
///
|
||||||
|
/// > __NOTE:__ Every interface has to specify all other interfaces/objects it
|
||||||
|
/// > implements or is implemented for. Missing one of `for = ` or
|
||||||
|
/// > `impl = ` attributes is an understandable compile-time error.
|
||||||
|
///
|
||||||
|
/// ```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"
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # 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 `null`able:
|
||||||
|
/// - `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() {}
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// # Renaming policy
|
/// # Renaming policy
|
||||||
///
|
///
|
||||||
/// By default, all [GraphQL interface][1] fields and their arguments are renamed
|
/// By default, all [GraphQL interface][1] fields and their arguments are renamed
|
||||||
|
|
|
@ -45,10 +45,6 @@ impl<T> SpanContainer<T> {
|
||||||
self.val
|
self.val
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inner(&self) -> &T {
|
|
||||||
&self.val
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map<U, F: Fn(T) -> U>(self, f: F) -> SpanContainer<U> {
|
pub fn map<U, F: Fn(T) -> U>(self, f: F) -> SpanContainer<U> {
|
||||||
SpanContainer {
|
SpanContainer {
|
||||||
expr: self.expr,
|
expr: self.expr,
|
||||||
|
|
|
@ -60,7 +60,7 @@ error[E0599]: no method named `to_input_value` found for struct `ObjectA` in the
|
||||||
--> fail/input-object/derive_incompatible_object.rs:6:10
|
--> fail/input-object/derive_incompatible_object.rs:6:10
|
||||||
|
|
|
|
||||||
2 | struct ObjectA {
|
2 | struct ObjectA {
|
||||||
| -------------- method `to_input_value` not found for this
|
| ------- method `to_input_value` not found for this struct
|
||||||
...
|
...
|
||||||
6 | #[derive(juniper::GraphQLInputObject)]
|
6 | #[derive(juniper::GraphQLInputObject)]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ObjectA`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ObjectA`
|
||||||
|
|
13
tests/codegen/fail/interface/struct/attr_cyclic_impl.rs
Normal file
13
tests/codegen/fail/interface/struct/attr_cyclic_impl.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
|
#[graphql_interface(impl = Node2Value, for = Node2Value)]
|
||||||
|
struct Node1 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = Node1Value, for = Node1Value)]
|
||||||
|
struct Node2 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
26
tests/codegen/fail/interface/struct/attr_cyclic_impl.stderr
Normal file
26
tests/codegen/fail/interface/struct/attr_cyclic_impl.stderr
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
error[E0391]: cycle detected when expanding type alias `Node1Value`
|
||||||
|
--> fail/interface/struct/attr_cyclic_impl.rs:3:46
|
||||||
|
|
|
||||||
|
3 | #[graphql_interface(impl = Node2Value, for = Node2Value)]
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
|
||||||
|
note: ...which requires expanding type alias `Node2Value`...
|
||||||
|
--> fail/interface/struct/attr_cyclic_impl.rs:8:46
|
||||||
|
|
|
||||||
|
8 | #[graphql_interface(impl = Node1Value, for = Node1Value)]
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
= note: ...which again requires expanding type alias `Node1Value`, completing the cycle
|
||||||
|
= note: type aliases cannot be recursive
|
||||||
|
= help: consider using a struct, enum, or union instead to break the cycle
|
||||||
|
= help: see <https://doc.rust-lang.org/reference/types.html#recursive-types> for more information
|
||||||
|
note: cycle used when collecting item types in top-level module
|
||||||
|
--> fail/interface/struct/attr_cyclic_impl.rs:1:1
|
||||||
|
|
|
||||||
|
1 | / use juniper::graphql_interface;
|
||||||
|
2 | |
|
||||||
|
3 | | #[graphql_interface(impl = Node2Value, for = Node2Value)]
|
||||||
|
4 | | struct Node1 {
|
||||||
|
... |
|
||||||
|
12 | |
|
||||||
|
13 | | fn main() {}
|
||||||
|
| |____________^
|
|
@ -45,8 +45,8 @@ error: any use of this value will cause an error
|
||||||
| ^^
|
| ^^
|
||||||
| |
|
| |
|
||||||
| referenced constant has errors
|
| referenced constant has errors
|
||||||
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:719:36
|
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36
|
||||||
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:782:59
|
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59
|
||||||
|
|
|
|
||||||
= note: `#[deny(const_err)]` on by default
|
= note: `#[deny(const_err)]` on by default
|
||||||
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
||||||
|
@ -538,8 +538,8 @@ error: any use of this value will cause an error
|
||||||
| ^^
|
| ^^
|
||||||
| |
|
| |
|
||||||
| referenced constant has errors
|
| referenced constant has errors
|
||||||
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:719:36
|
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36
|
||||||
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:782:59
|
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59
|
||||||
|
|
|
|
||||||
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
||||||
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
|
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
|
#[graphql_interface(for = Node2Value)]
|
||||||
|
struct Node1 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = Node1Value, for = Node3Value)]
|
||||||
|
struct Node2 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = Node2Value)]
|
||||||
|
struct Node3 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error[E0080]: evaluation of constant value failed
|
||||||
|
--> fail/interface/struct/attr_missing_transitive_impl.rs:8:46
|
||||||
|
|
|
||||||
|
8 | #[graphql_interface(impl = Node1Value, for = Node3Value)]
|
||||||
|
| ^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Node2` on `Node3`: missing `impl = ` for transitive interface `Node1` on `Node3`.', $DIR/fail/interface/struct/attr_missing_transitive_impl.rs:8:46
|
||||||
|
|
|
||||||
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
|
15
tests/codegen/fail/interface/struct/derive_cyclic_impl.rs
Normal file
15
tests/codegen/fail/interface/struct/derive_cyclic_impl.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use juniper::GraphQLInterface;
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(impl = Node2Value, for = Node2Value)]
|
||||||
|
struct Node1 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(impl = Node1Value, for = Node1Value)]
|
||||||
|
struct Node2 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,26 @@
|
||||||
|
error[E0391]: cycle detected when expanding type alias `Node1Value`
|
||||||
|
--> fail/interface/struct/derive_cyclic_impl.rs:4:36
|
||||||
|
|
|
||||||
|
4 | #[graphql(impl = Node2Value, for = Node2Value)]
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
|
||||||
|
note: ...which requires expanding type alias `Node2Value`...
|
||||||
|
--> fail/interface/struct/derive_cyclic_impl.rs:10:36
|
||||||
|
|
|
||||||
|
10 | #[graphql(impl = Node1Value, for = Node1Value)]
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
= note: ...which again requires expanding type alias `Node1Value`, completing the cycle
|
||||||
|
= note: type aliases cannot be recursive
|
||||||
|
= help: consider using a struct, enum, or union instead to break the cycle
|
||||||
|
= help: see <https://doc.rust-lang.org/reference/types.html#recursive-types> for more information
|
||||||
|
note: cycle used when collecting item types in top-level module
|
||||||
|
--> fail/interface/struct/derive_cyclic_impl.rs:1:1
|
||||||
|
|
|
||||||
|
1 | / use juniper::GraphQLInterface;
|
||||||
|
2 | |
|
||||||
|
3 | | #[derive(GraphQLInterface)]
|
||||||
|
4 | | #[graphql(impl = Node2Value, for = Node2Value)]
|
||||||
|
... |
|
||||||
|
14 | |
|
||||||
|
15 | | fn main() {}
|
||||||
|
| |____________^
|
|
@ -45,8 +45,8 @@ error: any use of this value will cause an error
|
||||||
| ^^
|
| ^^
|
||||||
| |
|
| |
|
||||||
| referenced constant has errors
|
| referenced constant has errors
|
||||||
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:719:36
|
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36
|
||||||
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:782:59
|
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59
|
||||||
|
|
|
|
||||||
= note: `#[deny(const_err)]` on by default
|
= note: `#[deny(const_err)]` on by default
|
||||||
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
||||||
|
@ -538,8 +538,8 @@ error: any use of this value will cause an error
|
||||||
| ^^
|
| ^^
|
||||||
| |
|
| |
|
||||||
| referenced constant has errors
|
| referenced constant has errors
|
||||||
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:719:36
|
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36
|
||||||
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:782:59
|
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59
|
||||||
|
|
|
|
||||||
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
||||||
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
|
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
use juniper::GraphQLInterface;
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(for = Node2Value)]
|
||||||
|
struct Node1 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(impl = Node1Value, for = Node3Value)]
|
||||||
|
struct Node2 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(impl = Node2Value)]
|
||||||
|
struct Node3 {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error[E0080]: evaluation of constant value failed
|
||||||
|
--> fail/interface/struct/derive_missing_transitive_impl.rs:10:36
|
||||||
|
|
|
||||||
|
10 | #[graphql(impl = Node1Value, for = Node3Value)]
|
||||||
|
| ^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Node2` on `Node3`: missing `impl = ` for transitive interface `Node1` on `Node3`.', $DIR/fail/interface/struct/derive_missing_transitive_impl.rs:10:36
|
||||||
|
|
|
||||||
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
|
13
tests/codegen/fail/interface/trait/cyclic_impl.rs
Normal file
13
tests/codegen/fail/interface/trait/cyclic_impl.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
|
#[graphql_interface(impl = Node2Value, for = Node2Value)]
|
||||||
|
trait Node1 {
|
||||||
|
fn id(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = Node1Value, for = Node1Value)]
|
||||||
|
trait Node2 {
|
||||||
|
fn id() -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
26
tests/codegen/fail/interface/trait/cyclic_impl.stderr
Normal file
26
tests/codegen/fail/interface/trait/cyclic_impl.stderr
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
error[E0391]: cycle detected when expanding type alias `Node1Value`
|
||||||
|
--> fail/interface/trait/cyclic_impl.rs:3:46
|
||||||
|
|
|
||||||
|
3 | #[graphql_interface(impl = Node2Value, for = Node2Value)]
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
|
||||||
|
note: ...which requires expanding type alias `Node2Value`...
|
||||||
|
--> fail/interface/trait/cyclic_impl.rs:8:46
|
||||||
|
|
|
||||||
|
8 | #[graphql_interface(impl = Node1Value, for = Node1Value)]
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
= note: ...which again requires expanding type alias `Node1Value`, completing the cycle
|
||||||
|
= note: type aliases cannot be recursive
|
||||||
|
= help: consider using a struct, enum, or union instead to break the cycle
|
||||||
|
= help: see <https://doc.rust-lang.org/reference/types.html#recursive-types> for more information
|
||||||
|
note: cycle used when collecting item types in top-level module
|
||||||
|
--> fail/interface/trait/cyclic_impl.rs:1:1
|
||||||
|
|
|
||||||
|
1 | / use juniper::graphql_interface;
|
||||||
|
2 | |
|
||||||
|
3 | | #[graphql_interface(impl = Node2Value, for = Node2Value)]
|
||||||
|
4 | | trait Node1 {
|
||||||
|
... |
|
||||||
|
12 | |
|
||||||
|
13 | | fn main() {}
|
||||||
|
| |____________^
|
|
@ -45,8 +45,8 @@ error: any use of this value will cause an error
|
||||||
| ^^
|
| ^^
|
||||||
| |
|
| |
|
||||||
| referenced constant has errors
|
| referenced constant has errors
|
||||||
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:719:36
|
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36
|
||||||
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:782:59
|
| inside `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59
|
||||||
|
|
|
|
||||||
= note: `#[deny(const_err)]` on by default
|
= note: `#[deny(const_err)]` on by default
|
||||||
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
||||||
|
@ -538,8 +538,8 @@ error: any use of this value will cause an error
|
||||||
| ^^
|
| ^^
|
||||||
| |
|
| |
|
||||||
| referenced constant has errors
|
| referenced constant has errors
|
||||||
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:719:36
|
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36
|
||||||
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:782:59
|
| inside `<CharacterValueEnum<ObjA> as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59
|
||||||
|
|
|
|
||||||
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
||||||
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
|
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
|
#[graphql_interface(for = Node2Value)]
|
||||||
|
trait Node1 {
|
||||||
|
fn id() -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = Node1Value, for = Node3Value)]
|
||||||
|
trait Node2 {
|
||||||
|
fn id(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = Node2Value)]
|
||||||
|
trait Node3 {
|
||||||
|
fn id() -> &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
error[E0080]: evaluation of constant value failed
|
||||||
|
--> fail/interface/trait/missing_transitive_impl.rs:8:46
|
||||||
|
|
|
||||||
|
8 | #[graphql_interface(impl = Node1Value, for = Node3Value)]
|
||||||
|
| ^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Node2` on `Node3`: missing `impl = ` for transitive interface `Node1` on `Node3`.', $DIR/fail/interface/trait/missing_transitive_impl.rs:8:46
|
||||||
|
|
|
||||||
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -4,7 +4,7 @@ use std::marker::PhantomData;
|
||||||
|
|
||||||
use juniper::{
|
use juniper::{
|
||||||
execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue,
|
execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue,
|
||||||
FieldError, FieldResult, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue,
|
FieldError, FieldResult, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::{schema, schema_with_scalar};
|
use crate::util::{schema, schema_with_scalar};
|
||||||
|
@ -2539,6 +2539,537 @@ mod nullable_argument_subtyping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod simple_subtyping {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[graphql_interface(for = [ResourceValue, Endpoint])]
|
||||||
|
struct Node {
|
||||||
|
id: Option<ID>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = NodeValue, for = Endpoint)]
|
||||||
|
struct Resource {
|
||||||
|
id: ID,
|
||||||
|
url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = [ResourceValue, NodeValue])]
|
||||||
|
struct Endpoint {
|
||||||
|
id: ID,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QueryRoot;
|
||||||
|
|
||||||
|
#[graphql_object]
|
||||||
|
impl QueryRoot {
|
||||||
|
fn node() -> NodeValue {
|
||||||
|
Endpoint {
|
||||||
|
id: ID::from("1".to_owned()),
|
||||||
|
url: "2".to_owned(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource() -> ResourceValue {
|
||||||
|
Endpoint {
|
||||||
|
id: ID::from("3".to_owned()),
|
||||||
|
url: "4".to_owned(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn is_graphql_interface() {
|
||||||
|
for name in ["Node", "Resource"] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
kind
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_node() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((graphql_value!({"node": {"id": "1"}}), vec![])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_node_on_resource() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
node {
|
||||||
|
... on Resource {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"node": {
|
||||||
|
"id": "1",
|
||||||
|
"url": "2",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_node_on_endpoint() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
node {
|
||||||
|
... on Endpoint {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"node": {
|
||||||
|
"id": "1",
|
||||||
|
"url": "2",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_resource() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
resource {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"resource": {
|
||||||
|
"id": "3",
|
||||||
|
"url": "4",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_resource_on_endpoint() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
resource {
|
||||||
|
... on Endpoint {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"resource": {
|
||||||
|
"id": "3",
|
||||||
|
"url": "4",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn registers_possible_types() {
|
||||||
|
for name in ["Node", "Resource"] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
possibleTypes {{
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"__type": {"possibleTypes": [
|
||||||
|
{"kind": "OBJECT", "name": "Endpoint"},
|
||||||
|
]}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn registers_interfaces() {
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
for (name, interfaces) in [
|
||||||
|
("Node", graphql_value!([])),
|
||||||
|
(
|
||||||
|
"Resource",
|
||||||
|
graphql_value!([{"kind": "INTERFACE", "name": "Node"}]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Endpoint",
|
||||||
|
graphql_value!([
|
||||||
|
{"kind": "INTERFACE", "name": "Node"},
|
||||||
|
{"kind": "INTERFACE", "name": "Resource"},
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
interfaces {{
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"__type": {"interfaces": interfaces}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod branching_subtyping {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[graphql_interface(for = [HumanValue, DroidValue, Luke, R2D2])]
|
||||||
|
struct Node {
|
||||||
|
id: ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(for = [HumanConnection, DroidConnection])]
|
||||||
|
struct Connection {
|
||||||
|
nodes: Vec<NodeValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = NodeValue, for = Luke)]
|
||||||
|
struct Human {
|
||||||
|
id: ID,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = ConnectionValue)]
|
||||||
|
struct HumanConnection {
|
||||||
|
nodes: Vec<HumanValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = NodeValue, for = R2D2)]
|
||||||
|
struct Droid {
|
||||||
|
id: ID,
|
||||||
|
primary_function: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = ConnectionValue)]
|
||||||
|
struct DroidConnection {
|
||||||
|
nodes: Vec<DroidValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = [HumanValue, NodeValue])]
|
||||||
|
struct Luke {
|
||||||
|
id: ID,
|
||||||
|
home_planet: String,
|
||||||
|
father: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = [DroidValue, NodeValue])]
|
||||||
|
struct R2D2 {
|
||||||
|
id: ID,
|
||||||
|
primary_function: String,
|
||||||
|
charge: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QueryRoot {
|
||||||
|
Luke,
|
||||||
|
R2D2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_object]
|
||||||
|
impl QueryRoot {
|
||||||
|
fn crew(&self) -> ConnectionValue {
|
||||||
|
match self {
|
||||||
|
Self::Luke => HumanConnection {
|
||||||
|
nodes: vec![Luke {
|
||||||
|
id: ID::new("1"),
|
||||||
|
home_planet: "earth".to_owned(),
|
||||||
|
father: "SPOILER".to_owned(),
|
||||||
|
}
|
||||||
|
.into()],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
Self::R2D2 => DroidConnection {
|
||||||
|
nodes: vec![R2D2 {
|
||||||
|
id: ID::new("2"),
|
||||||
|
primary_function: "roll".to_owned(),
|
||||||
|
charge: 146.0,
|
||||||
|
}
|
||||||
|
.into()],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn is_graphql_interface() {
|
||||||
|
for name in ["Node", "Connection", "Human", "Droid"] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
kind
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_human_connection() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
... on HumanConnection {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
homePlanet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "1",
|
||||||
|
"homePlanet": "earth",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_human() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on Human {
|
||||||
|
id
|
||||||
|
homePlanet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "1",
|
||||||
|
"homePlanet": "earth",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_luke() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on Luke {
|
||||||
|
id
|
||||||
|
homePlanet
|
||||||
|
father
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "1",
|
||||||
|
"homePlanet": "earth",
|
||||||
|
"father": "SPOILER",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_droid_connection() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
... on DroidConnection {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
primaryFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::R2D2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "2",
|
||||||
|
"primaryFunction": "roll",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_droid() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on Droid {
|
||||||
|
id
|
||||||
|
primaryFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::R2D2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "2",
|
||||||
|
"primaryFunction": "roll",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_r2d2() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on R2D2 {
|
||||||
|
id
|
||||||
|
primaryFunction
|
||||||
|
charge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::R2D2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "2",
|
||||||
|
"primaryFunction": "roll",
|
||||||
|
"charge": 146.0,
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod preserves_visibility {
|
mod preserves_visibility {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use juniper::{
|
use juniper::{
|
||||||
execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue,
|
execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue,
|
||||||
Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLUnion,
|
Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLUnion,
|
||||||
IntoFieldError, ScalarValue,
|
IntoFieldError, ScalarValue, ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::{schema, schema_with_scalar};
|
use crate::util::{schema, schema_with_scalar};
|
||||||
|
@ -3378,6 +3378,537 @@ mod nullable_argument_subtyping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod simple_subtyping {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[graphql_interface(for = [ResourceValue, Endpoint])]
|
||||||
|
trait Node {
|
||||||
|
fn id() -> Option<ID>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = NodeValue, for = Endpoint)]
|
||||||
|
trait Resource {
|
||||||
|
fn id(&self) -> &ID;
|
||||||
|
fn url(&self) -> Option<&str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = [ResourceValue, NodeValue])]
|
||||||
|
struct Endpoint {
|
||||||
|
id: ID,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QueryRoot;
|
||||||
|
|
||||||
|
#[graphql_object]
|
||||||
|
impl QueryRoot {
|
||||||
|
fn node() -> NodeValue {
|
||||||
|
Endpoint {
|
||||||
|
id: ID::from("1".to_owned()),
|
||||||
|
url: "2".to_owned(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource() -> ResourceValue {
|
||||||
|
Endpoint {
|
||||||
|
id: ID::from("3".to_owned()),
|
||||||
|
url: "4".to_owned(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn is_graphql_interface() {
|
||||||
|
for name in ["Node", "Resource"] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
kind
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_node() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((graphql_value!({"node": {"id": "1"}}), vec![])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_node_on_resource() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
node {
|
||||||
|
... on Resource {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"node": {
|
||||||
|
"id": "1",
|
||||||
|
"url": "2",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_node_on_endpoint() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
node {
|
||||||
|
... on Endpoint {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"node": {
|
||||||
|
"id": "1",
|
||||||
|
"url": "2",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_resource() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
resource {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"resource": {
|
||||||
|
"id": "3",
|
||||||
|
"url": "4",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_resource_on_endpoint() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
resource {
|
||||||
|
... on Endpoint {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"resource": {
|
||||||
|
"id": "3",
|
||||||
|
"url": "4",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn registers_possible_types() {
|
||||||
|
for name in ["Node", "Resource"] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
possibleTypes {{
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"__type": {"possibleTypes": [
|
||||||
|
{"kind": "OBJECT", "name": "Endpoint"},
|
||||||
|
]}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn registers_interfaces() {
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
for (name, interfaces) in [
|
||||||
|
("Node", graphql_value!([])),
|
||||||
|
(
|
||||||
|
"Resource",
|
||||||
|
graphql_value!([{"kind": "INTERFACE", "name": "Node"}]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Endpoint",
|
||||||
|
graphql_value!([
|
||||||
|
{"kind": "INTERFACE", "name": "Node"},
|
||||||
|
{"kind": "INTERFACE", "name": "Resource"},
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
interfaces {{
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"__type": {"interfaces": interfaces}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod branching_subtyping {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[graphql_interface(for = [HumanValue, DroidValue, Luke, R2D2])]
|
||||||
|
trait Node {
|
||||||
|
fn id() -> ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(for = [HumanConnection, DroidConnection])]
|
||||||
|
trait Connection {
|
||||||
|
fn nodes(&self) -> &[NodeValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = NodeValue, for = Luke)]
|
||||||
|
trait Human {
|
||||||
|
fn id(&self) -> &ID;
|
||||||
|
fn home_planet(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = ConnectionValue)]
|
||||||
|
struct HumanConnection {
|
||||||
|
nodes: Vec<HumanValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(impl = NodeValue, for = R2D2)]
|
||||||
|
trait Droid {
|
||||||
|
fn id() -> ID;
|
||||||
|
fn primary_function() -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = ConnectionValue)]
|
||||||
|
struct DroidConnection {
|
||||||
|
nodes: Vec<DroidValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = [HumanValue, NodeValue])]
|
||||||
|
struct Luke {
|
||||||
|
id: ID,
|
||||||
|
home_planet: String,
|
||||||
|
father: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = [DroidValue, NodeValue])]
|
||||||
|
struct R2D2 {
|
||||||
|
id: ID,
|
||||||
|
primary_function: String,
|
||||||
|
charge: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QueryRoot {
|
||||||
|
Luke,
|
||||||
|
R2D2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_object]
|
||||||
|
impl QueryRoot {
|
||||||
|
fn crew(&self) -> ConnectionValue {
|
||||||
|
match self {
|
||||||
|
Self::Luke => HumanConnection {
|
||||||
|
nodes: vec![Luke {
|
||||||
|
id: ID::new("1"),
|
||||||
|
home_planet: "earth".to_owned(),
|
||||||
|
father: "SPOILER".to_owned(),
|
||||||
|
}
|
||||||
|
.into()],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
Self::R2D2 => DroidConnection {
|
||||||
|
nodes: vec![R2D2 {
|
||||||
|
id: ID::new("2"),
|
||||||
|
primary_function: "roll".to_owned(),
|
||||||
|
charge: 146.0,
|
||||||
|
}
|
||||||
|
.into()],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn is_graphql_interface() {
|
||||||
|
for name in ["Node", "Connection", "Human", "Droid"] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
kind
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_human_connection() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
... on HumanConnection {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
homePlanet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "1",
|
||||||
|
"homePlanet": "earth",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_human() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on Human {
|
||||||
|
id
|
||||||
|
homePlanet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "1",
|
||||||
|
"homePlanet": "earth",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_luke() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on Luke {
|
||||||
|
id
|
||||||
|
homePlanet
|
||||||
|
father
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "1",
|
||||||
|
"homePlanet": "earth",
|
||||||
|
"father": "SPOILER",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_droid_connection() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
... on DroidConnection {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
primaryFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::R2D2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "2",
|
||||||
|
"primaryFunction": "roll",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_droid() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on Droid {
|
||||||
|
id
|
||||||
|
primaryFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::R2D2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "2",
|
||||||
|
"primaryFunction": "roll",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_r2d2() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on R2D2 {
|
||||||
|
id
|
||||||
|
primaryFunction
|
||||||
|
charge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::R2D2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "2",
|
||||||
|
"primaryFunction": "roll",
|
||||||
|
"charge": 146.0,
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod preserves_visibility {
|
mod preserves_visibility {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::marker::PhantomData;
|
||||||
|
|
||||||
use juniper::{
|
use juniper::{
|
||||||
execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, FieldError,
|
execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, FieldError,
|
||||||
FieldResult, GraphQLInterface, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue,
|
FieldResult, GraphQLInterface, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::{schema, schema_with_scalar};
|
use crate::util::{schema, schema_with_scalar};
|
||||||
|
@ -2559,6 +2559,543 @@ mod nullable_argument_subtyping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod simple_subtyping {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(for = [ResourceValue, Endpoint])]
|
||||||
|
struct Node {
|
||||||
|
id: Option<ID>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(impl = NodeValue, for = Endpoint)]
|
||||||
|
struct Resource {
|
||||||
|
id: ID,
|
||||||
|
url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = [ResourceValue, NodeValue])]
|
||||||
|
struct Endpoint {
|
||||||
|
id: ID,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QueryRoot;
|
||||||
|
|
||||||
|
#[graphql_object]
|
||||||
|
impl QueryRoot {
|
||||||
|
fn node() -> NodeValue {
|
||||||
|
Endpoint {
|
||||||
|
id: ID::from("1".to_owned()),
|
||||||
|
url: "2".to_owned(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource() -> ResourceValue {
|
||||||
|
Endpoint {
|
||||||
|
id: ID::from("3".to_owned()),
|
||||||
|
url: "4".to_owned(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn is_graphql_interface() {
|
||||||
|
for name in ["Node", "Resource"] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
kind
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_node() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((graphql_value!({"node": {"id": "1"}}), vec![])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_node_on_resource() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
node {
|
||||||
|
... on Resource {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"node": {
|
||||||
|
"id": "1",
|
||||||
|
"url": "2",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_node_on_endpoint() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
node {
|
||||||
|
... on Endpoint {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"node": {
|
||||||
|
"id": "1",
|
||||||
|
"url": "2",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_resource() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
resource {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"resource": {
|
||||||
|
"id": "3",
|
||||||
|
"url": "4",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_resource_on_endpoint() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
resource {
|
||||||
|
... on Endpoint {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"resource": {
|
||||||
|
"id": "3",
|
||||||
|
"url": "4",
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn registers_possible_types() {
|
||||||
|
for name in ["Node", "Resource"] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
possibleTypes {{
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"__type": {"possibleTypes": [
|
||||||
|
{"kind": "OBJECT", "name": "Endpoint"},
|
||||||
|
]}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn registers_interfaces() {
|
||||||
|
let schema = schema(QueryRoot);
|
||||||
|
|
||||||
|
for (name, interfaces) in [
|
||||||
|
("Node", graphql_value!([])),
|
||||||
|
(
|
||||||
|
"Resource",
|
||||||
|
graphql_value!([{"kind": "INTERFACE", "name": "Node"}]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Endpoint",
|
||||||
|
graphql_value!([
|
||||||
|
{"kind": "INTERFACE", "name": "Node"},
|
||||||
|
{"kind": "INTERFACE", "name": "Resource"},
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
interfaces {{
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"__type": {"interfaces": interfaces}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod branching_subtyping {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(for = [HumanValue, DroidValue, Luke, R2D2])]
|
||||||
|
struct Node {
|
||||||
|
id: ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(for = [HumanConnection, DroidConnection])]
|
||||||
|
struct Connection {
|
||||||
|
nodes: Vec<NodeValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(impl = NodeValue, for = Luke)]
|
||||||
|
struct Human {
|
||||||
|
id: ID,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = ConnectionValue)]
|
||||||
|
struct HumanConnection {
|
||||||
|
nodes: Vec<HumanValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLInterface)]
|
||||||
|
#[graphql(impl = NodeValue, for = R2D2)]
|
||||||
|
struct Droid {
|
||||||
|
id: ID,
|
||||||
|
primary_function: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = ConnectionValue)]
|
||||||
|
struct DroidConnection {
|
||||||
|
nodes: Vec<DroidValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = [HumanValue, NodeValue])]
|
||||||
|
struct Luke {
|
||||||
|
id: ID,
|
||||||
|
home_planet: String,
|
||||||
|
father: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(impl = [DroidValue, NodeValue])]
|
||||||
|
struct R2D2 {
|
||||||
|
id: ID,
|
||||||
|
primary_function: String,
|
||||||
|
charge: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QueryRoot {
|
||||||
|
Luke,
|
||||||
|
R2D2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_object]
|
||||||
|
impl QueryRoot {
|
||||||
|
fn crew(&self) -> ConnectionValue {
|
||||||
|
match self {
|
||||||
|
Self::Luke => HumanConnection {
|
||||||
|
nodes: vec![Luke {
|
||||||
|
id: ID::new("1"),
|
||||||
|
home_planet: "earth".to_owned(),
|
||||||
|
father: "SPOILER".to_owned(),
|
||||||
|
}
|
||||||
|
.into()],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
Self::R2D2 => DroidConnection {
|
||||||
|
nodes: vec![R2D2 {
|
||||||
|
id: ID::new("2"),
|
||||||
|
primary_function: "roll".to_owned(),
|
||||||
|
charge: 146.0,
|
||||||
|
}
|
||||||
|
.into()],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn is_graphql_interface() {
|
||||||
|
for name in ["Node", "Connection", "Human", "Droid"] {
|
||||||
|
let doc = format!(
|
||||||
|
r#"{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
kind
|
||||||
|
}}
|
||||||
|
}}"#,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_human_connection() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
... on HumanConnection {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
homePlanet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "1",
|
||||||
|
"homePlanet": "earth",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_human() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on Human {
|
||||||
|
id
|
||||||
|
homePlanet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "1",
|
||||||
|
"homePlanet": "earth",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_luke() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on Luke {
|
||||||
|
id
|
||||||
|
homePlanet
|
||||||
|
father
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::Luke);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "1",
|
||||||
|
"homePlanet": "earth",
|
||||||
|
"father": "SPOILER",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_droid_connection() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
... on DroidConnection {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
primaryFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::R2D2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "2",
|
||||||
|
"primaryFunction": "roll",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_droid() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on Droid {
|
||||||
|
id
|
||||||
|
primaryFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::R2D2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "2",
|
||||||
|
"primaryFunction": "roll",
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolves_r2d2() {
|
||||||
|
const DOC: &str = r#"{
|
||||||
|
crew {
|
||||||
|
nodes {
|
||||||
|
... on R2D2 {
|
||||||
|
id
|
||||||
|
primaryFunction
|
||||||
|
charge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let schema = schema(QueryRoot::R2D2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
|
Ok((
|
||||||
|
graphql_value!({"crew": {
|
||||||
|
"nodes": [{
|
||||||
|
"id": "2",
|
||||||
|
"primaryFunction": "roll",
|
||||||
|
"charge": 146.0,
|
||||||
|
}],
|
||||||
|
}}),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod preserves_visibility {
|
mod preserves_visibility {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue