Add support for "dyn" trait object syntax (#385)
* Use "dyn" for TraitObjects * Cleanup after adding "dyn" support * Add entry to CHANGELOG.md
This commit is contained in:
parent
97e1005178
commit
3373935046
6 changed files with 42 additions and 37 deletions
|
@ -18,6 +18,7 @@ struct UserId(i32);
|
||||||
### Other Changes
|
### Other Changes
|
||||||
|
|
||||||
- The `ID` scalar now implements Serde's `Serialize` and `Deserialize`
|
- The `ID` scalar now implements Serde's `Serialize` and `Deserialize`
|
||||||
|
- Add support for `dyn` trait object syntax to procedural macros
|
||||||
|
|
||||||
# [[0.12.0] 2019-05-16](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.12.0)
|
# [[0.12.0] 2019-05-16](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.12.0)
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub trait Character {
|
||||||
fn friend_ids(&self) -> &[String];
|
fn friend_ids(&self) -> &[String];
|
||||||
fn appears_in(&self) -> &[Episode];
|
fn appears_in(&self) -> &[Episode];
|
||||||
fn secret_backstory(&self) -> &Option<String>;
|
fn secret_backstory(&self) -> &Option<String>;
|
||||||
fn as_character(&self) -> &Character;
|
fn as_character(&self) -> &dyn Character;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Human: Character {
|
pub trait Human: Character {
|
||||||
|
@ -62,7 +62,7 @@ impl Character for HumanData {
|
||||||
fn secret_backstory(&self) -> &Option<String> {
|
fn secret_backstory(&self) -> &Option<String> {
|
||||||
&self.secret_backstory
|
&self.secret_backstory
|
||||||
}
|
}
|
||||||
fn as_character(&self) -> &Character {
|
fn as_character(&self) -> &dyn Character {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ impl Character for DroidData {
|
||||||
fn secret_backstory(&self) -> &Option<String> {
|
fn secret_backstory(&self) -> &Option<String> {
|
||||||
&self.secret_backstory
|
&self.secret_backstory
|
||||||
}
|
}
|
||||||
fn as_character(&self) -> &Character {
|
fn as_character(&self) -> &dyn Character {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,7 @@ impl Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hero(&self, episode: Option<Episode>) -> &Character {
|
pub fn get_hero(&self, episode: Option<Episode>) -> &dyn Character {
|
||||||
if episode == Some(Episode::Empire) {
|
if episode == Some(Episode::Empire) {
|
||||||
self.get_human("1000").unwrap().as_character()
|
self.get_human("1000").unwrap().as_character()
|
||||||
} else {
|
} else {
|
||||||
|
@ -256,15 +256,15 @@ impl Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_human(&self, id: &str) -> Option<&Human> {
|
pub fn get_human(&self, id: &str) -> Option<&dyn Human> {
|
||||||
self.humans.get(id).map(|h| h as &Human)
|
self.humans.get(id).map(|h| h as &dyn Human)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_droid(&self, id: &str) -> Option<&Droid> {
|
pub fn get_droid(&self, id: &str) -> Option<&dyn Droid> {
|
||||||
self.droids.get(id).map(|d| d as &Droid)
|
self.droids.get(id).map(|d| d as &dyn Droid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_character(&self, id: &str) -> Option<&Character> {
|
pub fn get_character(&self, id: &str) -> Option<&dyn Character> {
|
||||||
if let Some(h) = self.humans.get(id) {
|
if let Some(h) = self.humans.get(id) {
|
||||||
Some(h)
|
Some(h)
|
||||||
} else if let Some(d) = self.droids.get(id) {
|
} else if let Some(d) = self.droids.get(id) {
|
||||||
|
@ -274,7 +274,7 @@ impl Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_friends(&self, c: &Character) -> Vec<&Character> {
|
pub fn get_friends(&self, c: &dyn Character) -> Vec<&dyn Character> {
|
||||||
c.friend_ids()
|
c.friend_ids()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|id| self.get_character(id))
|
.flat_map(|id| self.get_character(id))
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::tests::model::{Character, Database, Droid, Episode, Human};
|
||||||
|
|
||||||
impl Context for Database {}
|
impl Context for Database {}
|
||||||
|
|
||||||
graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
|
graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| {
|
||||||
description: "A character in the Star Wars Trilogy"
|
description: "A character in the Star Wars Trilogy"
|
||||||
|
|
||||||
field id() -> &str as "The id of the character" {
|
field id() -> &str as "The id of the character" {
|
||||||
|
@ -16,7 +16,7 @@ graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
|
||||||
Some(self.name())
|
Some(self.name())
|
||||||
}
|
}
|
||||||
|
|
||||||
field friends(&executor) -> Vec<&Character>
|
field friends(&executor) -> Vec<&dyn Character>
|
||||||
as "The friends of the character" {
|
as "The friends of the character" {
|
||||||
executor.context().get_friends(self.as_character())
|
executor.context().get_friends(self.as_character())
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,8 @@ graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
|
||||||
}
|
}
|
||||||
|
|
||||||
instance_resolvers: |&context| {
|
instance_resolvers: |&context| {
|
||||||
&Human => context.get_human(&self.id()),
|
&dyn Human => context.get_human(&self.id()),
|
||||||
&Droid => context.get_droid(&self.id()),
|
&dyn Droid => context.get_droid(&self.id()),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
|
||||||
interfaces = [&dyn Character],
|
interfaces = [&dyn Character],
|
||||||
)]
|
)]
|
||||||
/// A humanoid creature in the Star Wars universe.
|
/// A humanoid creature in the Star Wars universe.
|
||||||
impl<'a> &'a Human {
|
impl<'a> &'a dyn Human {
|
||||||
/// The id of the human
|
/// The id of the human
|
||||||
fn id(&self) -> &str {
|
fn id(&self) -> &str {
|
||||||
self.id()
|
self.id()
|
||||||
|
@ -49,7 +49,7 @@ impl<'a> &'a Human {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The friends of the human
|
/// The friends of the human
|
||||||
fn friends(&self, ctx: &Database) -> Vec<&Character> {
|
fn friends(&self, ctx: &Database) -> Vec<&dyn Character> {
|
||||||
ctx.get_friends(self.as_character())
|
ctx.get_friends(self.as_character())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ impl<'a> &'a Human {
|
||||||
interfaces = [&dyn Character],
|
interfaces = [&dyn Character],
|
||||||
)]
|
)]
|
||||||
/// A mechanical creature in the Star Wars universe.
|
/// A mechanical creature in the Star Wars universe.
|
||||||
impl<'a> &'a Droid {
|
impl<'a> &'a dyn Droid {
|
||||||
/// The id of the droid
|
/// The id of the droid
|
||||||
fn id(&self) -> &str {
|
fn id(&self) -> &str {
|
||||||
self.id()
|
self.id()
|
||||||
|
@ -82,7 +82,7 @@ impl<'a> &'a Droid {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The friends of the droid
|
/// The friends of the droid
|
||||||
fn friends(&self, ctx: &Database) -> Vec<&Character> {
|
fn friends(&self, ctx: &Database) -> Vec<&dyn Character> {
|
||||||
ctx.get_friends(self.as_character())
|
ctx.get_friends(self.as_character())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,19 +106,19 @@ pub struct Query;
|
||||||
/// The root query object of the schema
|
/// The root query object of the schema
|
||||||
impl Query {
|
impl Query {
|
||||||
#[graphql(arguments(id(description = "id of the human")))]
|
#[graphql(arguments(id(description = "id of the human")))]
|
||||||
fn human(database: &Database, id: String) -> Option<&Human> {
|
fn human(database: &Database, id: String) -> Option<&dyn Human> {
|
||||||
database.get_human(&id)
|
database.get_human(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql(arguments(id(description = "id of the droid")))]
|
#[graphql(arguments(id(description = "id of the droid")))]
|
||||||
fn droid(database: &Database, id: String) -> Option<&Droid> {
|
fn droid(database: &Database, id: String) -> Option<&dyn Droid> {
|
||||||
database.get_droid(&id)
|
database.get_droid(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql(arguments(episode(
|
#[graphql(arguments(episode(
|
||||||
description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode"
|
description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode"
|
||||||
)))]
|
)))]
|
||||||
fn hero(database: &Database, episode: Option<Episode>) -> Option<&Character> {
|
fn hero(database: &Database, episode: Option<Episode>) -> Option<&dyn Character> {
|
||||||
Some(database.get_hero(episode).as_character())
|
Some(database.get_hero(episode).as_character())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,19 +52,23 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) ->
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ident
|
.ident
|
||||||
.to_string(),
|
.to_string(),
|
||||||
syn::Type::Reference(ref reference) => match &*reference.elem {
|
syn::Type::Reference(ref reference) => {
|
||||||
syn::Type::Path(ref type_path) => type_path
|
let path = match &*reference.elem {
|
||||||
.path
|
syn::Type::Path(ref type_path) => &type_path.path,
|
||||||
.segments
|
syn::Type::TraitObject(ref trait_obj) => {
|
||||||
.iter()
|
match trait_obj.bounds.iter().nth(0).unwrap() {
|
||||||
.last()
|
syn::TypeParamBound::Trait(ref trait_bound) => &trait_bound.path,
|
||||||
.unwrap()
|
_ => {
|
||||||
.ident
|
panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")");
|
||||||
.to_string(),
|
}
|
||||||
_ => {
|
}
|
||||||
panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")");
|
}
|
||||||
}
|
_ => {
|
||||||
},
|
panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
path.segments.iter().last().unwrap().ident.to_string()
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")");
|
panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")");
|
||||||
}
|
}
|
||||||
|
@ -169,7 +173,7 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) ->
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
panic!(
|
panic!(
|
||||||
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
||||||
quote!(captured.ty),
|
quote!(captured.ty),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -294,7 +294,7 @@ impl Error for GraphQLRequestError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&Error> {
|
fn cause(&self) -> Option<&dyn Error> {
|
||||||
match *self {
|
match *self {
|
||||||
GraphQLRequestError::BodyHyper(ref err) => Some(err),
|
GraphQLRequestError::BodyHyper(ref err) => Some(err),
|
||||||
GraphQLRequestError::BodyUtf8(ref err) => Some(err),
|
GraphQLRequestError::BodyUtf8(ref err) => Some(err),
|
||||||
|
|
|
@ -416,7 +416,7 @@ impl Error for GraphQLIronError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&Error> {
|
fn cause(&self) -> Option<&dyn Error> {
|
||||||
match *self {
|
match *self {
|
||||||
GraphQLIronError::Serde(ref err) => Some(err),
|
GraphQLIronError::Serde(ref err) => Some(err),
|
||||||
GraphQLIronError::Url(ref err) => Some(err),
|
GraphQLIronError::Url(ref err) => Some(err),
|
||||||
|
|
Loading…
Reference in a new issue