graphql-rust / juniper Goto Github PK
View Code? Open in Web Editor NEWGraphQL server library for Rust
License: Other
GraphQL server library for Rust
License: Other
After using the macros to define the schema, is it possible to retrieve the schema as a json document? A simple string would suffice. This would allow us to use a tool to automatically version the API using an external tool to analyze the schema.
I tried to take the example at https://mhallin.github.io/juniper/juniper/trait.GraphQLType.html and build it as its own crate. There are a couple of API changes that make it non-compilable, including changing Context to an associated type. After fixing that, though, there are a couple more less obvious fixes, hence the issue, and not a PR.
Similar to the custom derive functionality for input objects and enums, a derive for object types which don't need a custom resolver would be very convenient.
It's not much work to implement, mostly copy & paste from the input object derive.
I have few questions many of them are more so due to the fact I having some difficulty flowing the flow of the crate.
For example, the graphql docs gives the example
query DroidById($id: ID!) {
droid(id: $id) {
name
}
}
{
id: [1,2] // lets pretend r2d2 and 3cpo are id 1 and 2
}
I can see that there is an argument struct so would I pass them in like so?
graphql_object!(<'a> &'a Droid: Database as "Droid" |&self| {
description: "A mechanical creature in the Star Wars universe."
interfaces: [&Character]
field id(id: juniper::Arguments) -> &str as "The id of the droid" {
self.id()
}
...
Given a list of the above do I have expose two different Query Roots? One for Droids and the other Driod? That is, if we changed the Query root to
graphql_object!(Database: Database as "Query" |&self| {
...
field droid(
id: String as "id of the droid"
) -> Option<&Vec<Droid>> {
self.get_droid(&id)
}
...
Which I guess would work if the get_droid method returned Option<&Vec> and rustc would serialize it.
My apologies for these basic questions, but it just not clicking for me. ๐
Typically for mutation, I would like to just return a Fieldresult<()>, but () isn't resolvable. In essence, for mutations, I am trying to just return a body on an error and empty body on success, but I can't seem to find how to do this and FieldResult seems to be the most appropriate choice to do so.
Hi -
Have you put any thought into how this could be used with an async IO model, such as with Tokio? Right now it seems like it's all implicitly synchronous, but perhaps it's just a matter of making the various functions take/return Future
s?
Thanks
I tried to run the server example but I always get HTTP ERROR 500.
I did this in the console:
set LISTEN=localhost:8080
cargo run --example server --features "iron-handlers expose-test-schema"
Then I open localhost:8080 in a browser and get error 500, also at /graphql
What am I doing wrong?
I can't find anything about mutations in the docs.
Are they supported at all?
As suggested in 3.18 nullable types can both accept null and are optional. Furthermore, the table found in Input Object section also says this as well.
For example, I have the mutation:
mutation {
createRma(rmaInput:{
id: "test123"
status: false
companyName: null
})
}
Represented in rust like so:
graphql_input_object!(
pub struct RmaInput {
id: String,
status: bool,
company_name: Option<String>
}
);
I get an error message that says "Unexpected \"null\""
While understand that having this field added is somewhat wasted typing, but it would be nice to explicitly state that is should be null.
I am trying to reproduce the server.rs example in a test project to exercise the library. I have extracted model.rs and schema.rs from juniper::test as a scaffolding for my new project but on schema.rs it uses the Context trait to mark the Database struct:
use executor::Context;
impl Context for Database {}
on my project I tried to do the same but compiler complains about executor module being private, and lib.rs show that mod executor is not pub:
use juniper::executor::Context;
impl Context for Database {}
error: module `executor` is private
--> src/schema.rs:2:5
|
2 | use juniper::executor::Context;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
I am lacking something that has to be done or there is a real bug here ?
Currently, if one wanted to they could go beyond the -(2^31) to 2^31 -1 integer range if they used an i64 as an argument. However, the range for an i32 needs to be enforced in order to meet the specs. Also, it ensures that individuals don't accidentally receive a 64 bit integer even though js represents ints as doubles. Which is fine until you get integers larger than 2^53 or less than -2^53.
Based upon the swapi example one would expect the following error message if you say used 2147483648 as an int.
"Argument \"first\" has invalid value 2147483648.\nExpected type \"Int\", found 2147483648."
While the spec doesn't explicitly specify what a valid name of a type is, it's reasonable to assume that they should be valid GraphQL name tokens, i.e. /[_A-Za-z][_0-9A-Za-z]*/
since they're written as identifiers in e.g. fragment type conditions. This seems to be in line with what the reference implementation does. This causes tools like GraphiQL to stop working if an invalid type name is exposed.
Since Juniper's macros use stringify!
, invalid names can be generated by using e.g. multiple modules:
// Generates a GraphQL type named "auth::User"
graphql_object!(auth::User: () |&self| {
});
It's probably not possible to do this validation check at compile time, but the registry could probably panic on invalid names.
It appears the link on the main repository page is an old link please update to the new link found on crates.io.
This enum would beneficial in the following case:
struct Movie {
release_date: Option<String>
}
graphql_scalar!(Movie {
description: "Describes a movie."
resolve(&self) -> Value {
Value::Object(Movie::Into())
}
....
}
impl Into<HashMap<String, Value>> for Movie {
fn into(self) -> HashMap<String, Value> {
let mut movie_value = HashMap::new();
movie_value.insert("releaseDate".to_owned(), Value::Nullable(self.release_date))
}
This way I have means to output null or the value depending the value I get from my database, but also from_input_value can deal with nullable input.
@mhallin what do you think about setting up a Gitter channel to talk about juniper?
It would be a nice way to talk about the project, features, give support, etc.
Even though it is not officially supported by the current specs, there is currently an unofficial implementation for it. Personally, I think it would be great if it was there was perhaps a feature or is there a good workaround for this issue?
The method name ResultExt::fo_field_err is a bit confusing, since it implies an Err return type.
We could leave it as is, or rename it to to_field_result
or to_field_res
.
If we rename, it should definitely be before 1.0.
Thoughts, @mhallin ?
Hi,
I tried to look into the code but I didn't come up with a definitive answer.
I am wondering if is possible to generate classes of objects at run time.
What I want to do is to use juniper to build a SQL wrapper giving the user the possibility to create, not only new rows but also new tables at run time. Is it clear?
It is possible with juniper?
Thanks
There is a new way for creating input objects and enums via custom derive.
They still need to be documented though!
Looks like subscriptions made it into the spec.
While most users will implement subscription logic outside juniper
(like how Facebook interfaces with a MQTT server handling the actual delivery and connections) it might be useful to add some integrations as optional features.
Hey there! I am interested in using the parser from this library in one of my own projects. Would it be possible to extract it into its own crate? Great work on this project, and hope to hear from you soon.
Ian
Hello,
I'm trying to do your simple iron integration example using https://github.com/mhallin/juniper/blob/master/examples/server.rs
and these dependencies:
[dependencies]
iron = "^0.5.1"
juniper = { version="0.7.0", features = ["iron-handlers"] }
rustc-serialize = "^0.3.19"
serde = "^0.9.1"
router = "^0.5.0"
mount = "^0.3.0"
logger = "^0.3.0"
iron-test = "^0.5.0"
bencher = "^0.1.2"
What I get from cargo build
is:
error[E0432]: unresolved import `juniper::tests::model::Database`
--> src/main.rs:14:5
|
14 | use juniper::tests::model::Database;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not find `tests` in `juniper`
error: aborting due to previous error
error: Could not compile `hello_world`.
Is there a simple juniper, iron, mysql/postgresql hello world repo out there that can just run so I can do a GraphQL hello world? How can I make the sample do something?
In the following example, argument coercion crashes at an unwrap when a string input value is expected to coerce to an enum value. This should be allowed, according to spec:
Query variable transport serializations which have a different representation for nonโstring symbolic values (for example, EDN) should only allow such values as enum input values. Otherwise, for most transport serializations that do not, strings may be interpreted as the enum input value with the same name.
#[macro_use] extern crate juniper;
use juniper::{InputValue, RootNode, Value};
use std::collections::HashMap;
enum Kind { Foo, Bar }
struct QueryRoot;
graphql_enum!(Kind {
Kind::Foo => "FOO",
Kind::Bar => "BAR",
});
graphql_object!(QueryRoot: () |&self| {
field baz(kind: Kind) -> Kind {
kind
}
});
fn main() {
let document = "query GetByKind($kind: Kind!) { baz(kind: $kind) }";
let mut variables = HashMap::new();
variables.insert(String::from("kind"), InputValue::string("FOO"));
let query_root = QueryRoot;
let schema = RootNode::new(&query_root, ());
let result = juniper::execute(document, None, &schema, &variables, &());
let expected = Ok((
Value::object(vec![("baz", Value::string("FOO"))].into_iter().collect()),
vec![]
));
assert_eq!(result, expected);
}
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', ../src/libcore/option.rs:323
stack backtrace:
1: 0x10de4966a - std::sys::imp::backtrace::tracing::imp::write::h917062bce4ff48c3
2: 0x10de4bcaf - std::panicking::default_hook::{{closure}}::h0bacac31b5ed1870
3: 0x10de4aa8f - std::panicking::default_hook::h5897799da33ece67
4: 0x10de4b066 - std::panicking::rust_panic_with_hook::h109e116a3a861224
5: 0x10de4af04 - std::panicking::begin_panic::hbb38be1379e09df0
6: 0x10de4ae22 - std::panicking::begin_panic_fmt::h26713cea9bce3ab0
7: 0x10de4ad87 - rust_begin_unwind
8: 0x10de76800 - core::panicking::panic_fmt::hcfbb59eeb7f27f75
9: 0x10de76704 - core::panicking::panic::hd383cb12a44b01ff
10: 0x10dbe3b85 - <core::option::Option<T>>::unwrap::h86be723f8ea8cdfb
11: 0x10dc33b1c - juniper::types::base::Arguments::get::he752e4e3a9ddfc8a
12: 0x10dc435fe - <test::QueryRoot as juniper::types::base::GraphQLType>::resolve_field::{{closure}}::he4761edad6b47051
13: 0x10dc405dc - <test::QueryRoot as juniper::types::base::GraphQLType>::resolve_field::hf5cb49a202dd844e
14: 0x10dc33bbe - juniper::types::pointers::<impl juniper::types::base::GraphQLType for &'a T>::resolve_field::h291b9e26580e710d
15: 0x10dc34f93 - juniper::schema::schema::<impl juniper::types::base::GraphQLType for juniper::schema::model::RootNode<'a, QueryT, MutationT>>::resolve_field::h341e947b2c7dcf81
16: 0x10dc2ede7 - juniper::types::base::resolve_selection_set_into::h16ca849d03a8f2d7
17: 0x10dc2e21c - juniper::types::base::GraphQLType::resolve::h2be307e313daaa3f
18: 0x10dc33cb1 - juniper::types::pointers::<impl juniper::types::base::GraphQLType for &'a T>::resolve::h8f12fb543b434cd9
19: 0x10dc09033 - <juniper::executor::Executor<'a, CtxT>>::resolve::hbd66dfc0a273ffa2
20: 0x10dc08967 - <juniper::executor::Executor<'a, CtxT>>::resolve_into_value::hd904db4fde3036d1
21: 0x10dc3630d - juniper::executor::execute_validated_query::h0602f3001151eaa2
22: 0x10dc35720 - juniper::execute::h30a2480a59b35b89
23: 0x10dc3f565 - test::main::he51878596874a9bd
24: 0x10de4c26a - __rust_maybe_catch_panic
25: 0x10de4b406 - std::rt::lang_start::hd661476ce2fc2931
26: 0x10dc436a9 - main
To reproduce the issue you can use the patch found in this gist. Which slightly alters the test schema.
Which changes the database schema with a new graphql input object.
graphql_input_object!(
pub struct TestSearch {
episode: Option<Episode>,
meaningless_value: Option<Vec<i64>>
}
);
graphql_object!(Database: Database as "Query" |&self| {
description: "The root query object of the schema"
...
field hero(
search_input: Option<TestSearch> as
"If omitted, returns the hero of the whole saga. If provided, returns \
the hero of that particular episode",
) -> Option<&Character> {
if let Some(search_input) = search_input {
Some(self.get_hero(search_input.episode).as_character())
} else {
None
}
}
});
The following query will produce a panic:
query($test: TestSearch) {
hero(searchInput: $test){
id,
name,
appearsIn
}
}
With the query variable $test
as:
{
"test": {
"episode": "NEW_HOPE",
"meaninglessValue": [1,2]
}
}
This does work
query {
hero(
searchInput:{
episode: NEW_HOPE,
meaninglessValue: [1,2]
}
){
id,
name,
appearsIn
}
}
Unless my lack of sleep is catching up to me both of these queries should work. It appears that is failing somewhere in the graphql input macro.
Unfortunately, I am not 100% the actually cause of the error be it somewhere in the iron-handler or somewhere later. I'll update if I find any more information.
All the pieces are there.
Building a client should be relatively straight forward.
I'll tackle this when I have some time.
According to the section 7.1:
Serialization formats which can represent an ordered map should preserve the order of requested fields as defined by CollectFields() in the Execution section. Serialization formats which can only represent unordered maps should retain this order grammatically (such as JSON).
Producing a response where fields are represented in the same order in which they appear in the request improves human readability during debugging and enables more efficient parsing of responses if the order of properties can be anticipated.
and sub-section 7.1.1:
For example, if the query was { name, age }, a GraphQL server responding in JSON should respond with { "name": "Mark", "age": 30 } and should not respond with { "age": 30, "name": "Mark" }.
We may be able to achieve this by using OrderMap instead of HashMap
. Has anyone tried this yet?
In my restructuring PR, I left the examples in the main juniper crate since they were set up as part of it and not so trivial to extract.
They should be moved to a seprate crate and placed in the root directory.
Hi, thank you for this library!
I'm having some trouble using my GraphQL API from an Javascript application since it results in 405 (Method Not Allowed). I saw here that you only allow GET
and POST
: https://github.com/mhallin/juniper/blob/master/src/integrations/iron_handlers.rs#L161-L163
How come it's like this and how can I proceed from here?
Thank you!
I have found this library, the examples and docs are still not enough, I am reading the full source code and trying to understand but in the mean time I on need for Validation examples on http headers and custom headers, auth validation and access to Iron request remote_addr: https://github.com/iron/iron/blob/master/src/request/mod.rs#L37
So I can implement DDOS protetion from the same IP and checking on jwt auth tokens
The rocket example is very bare bones.
We should have a more complete example that shows a mutation and context.
Right now there is a lot of duplication between the appveyor and travis test suites.
The build + test workflow should be implemented with a build tool like (cargo-make)[https://github.com/sagiegurari/cargo-make] to prevent errors and duplication.
When I use:
graphql_object!(QueryRoot: Arc<Pool> as "Query" |&self| {
field channel(
call_sign: String as "The channel's call sign",
&mut executor
) -> FieldResult<Channel>
I get the error
error: expected ident, found &
--> src/controller/data.rs:12:9
|
12 | &mut executor
| ^
If I recalled correct @mhallin said he was close to merging a branch where executor could not be mutable so I did the following.
field channel(
call_sign: String as "The channel's call sign",
executor
) -> FieldResult<Channel> {
Which return the error
error: no rules expected the token `)`
--> <juniper macros>:28:70
|
28 | __graphql__args ! ( @ apply_args , $ reg , $ base , ( $ ( $ rest ) * ) ) } ; (
I thought I could cheat it by going
executor: Executor<Arc<Pool>>
but it doesn't like that either.
I'm currently working on a project with serde v1.0.7 but I'm getting compilation errors when trying to serialize an execution result.
the trait `serde::Serialize` is not implemented for `juniper::Value`
the trait `serde::Serialize` is not implemented for `juniper::ExecutionError`
Is this expected? Possibly user error?
Travis now uses cargo make ci-flow
for testing.
The plan was to use it for Appveyor too, but there is a bug on Windows + workspaces: sagiegurari/cargo-make#5 .
Once that is fixed, use cargo-make on appveyor as well.
Since breaking changes in iron/rocket can force a major version bump to juniper, which is the case right now with the Rocket update (#67), we'd like to pull out the integrations into separate crates.
This also makes the testing setup a bit easier to handle.
Tasks
Rocket
juniper_rocket
crateIron
juniper_iron
crateThe __typename
meta field is implemented for interfaces and unions but not objects, which panics when queried. See 4.1.4 "Type Name Introspection":
GraphQL supports type name introspection at any point within a query by the meta field __typename: String! when querying against any Object, Interface, or Union. It returns the name of the object type currently being queried.
juniper 907d78d
extern crate juniper;
use juniper::{EmptyMutation, RootNode, Value, Variables};
use juniper::tests::model::Database;
fn main() {
let document = r#"{ human(id: "1000") { __typename } }"#;
let query_root = Database::new();
let mutation_root = EmptyMutation::new();
let schema = RootNode::new(&query_root, &mutation_root);
let variables = Variables::new();
let context = Database::new();
let result = juniper::execute(document, None, &schema, &variables, &context);
let expected = Ok((
Value::object(vec![
("human", Value::object(vec![
("__typename", Value::string("Human"))
].into_iter().collect()))
].into_iter().collect()),
vec![]
));
assert_eq!(result, expected);
}
thread 'main' panicked at 'concrete_type_name must be implemented by unions and interfaces', src/types/base.rs:266
I got error when running cargo run --example server
after cargo build
.
error[E0432]: unresolved import `juniper::iron_handlers::GraphQLHandler`. Could not find `iron_handlers` in `juniper`
--> examples/server.rs:13:30
|
13 | use juniper::iron_handlers::{GraphQLHandler, GraphiQLHandler};
| ^^^^^^^^^^^^^^
error[E0432]: unresolved import `juniper::iron_handlers::GraphiQLHandler`. Could not find `iron_handlers` in `juniper`
--> examples/server.rs:13:46
|
13 | use juniper::iron_handlers::{GraphQLHandler, GraphiQLHandler};
| ^^^^^^^^^^^^^^^
error[E0432]: unresolved import `juniper::tests::model::Database`. Could not find `tests` in `juniper`
--> examples/server.rs:14:5
|
14 | use juniper::tests::model::Database;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 3 previous errors
error: Could not compile `juniper`.
Am I doing something wrong?
I realize most people won't ever directly call To/FromInputValue
, but naming the methods to
and from
is not ideal.
It requires disambiguation and is easily confused with From
.
I propose changing the method names to to_/from_input_value
before 1.0.
I'm working on integration into Rocket.
Would you be interested in a PR that adds a Rocket handler under a feature flag?
Instead of having separate tests for both the iron integration and the rocket integration perhaps it will be better having a module that leverages hyper or rocket or some rust HTTP library to test the integrations. Alternatively, something like newman could leveraged instead.
There is some debugging information that is getting outputted in the graphql_input_object macro namely on line 70. Either, logging framework like Slog or Log should be used for outputting debugging information or this line and others like it should be removed.
Looks like Relay has a thin layer on top of GraphQL. Would adding (optional?) Relay support be in scope for juniper
?
According to Rust release milestone predictions, rustc-serialize is scheduled to be deprecated in next month's beta release of Rust.
This would be a good time to let us know if there is any functionality you require from Serde before that happens.
Instead of sending queries to a database cluster one by one, I would like to batch them instead and send just one query. Is this possible?
As I can see, right now, only a simple String error for the message
field is supported.
I often need more complex errors with additional data, like this for example:
"errors": [
{
"message": "Validation failed",
"code": "validation_err",
"data": {
"username": {"code": "min_length", "message": "must be at least 5 characters long", "meta": {"min": 5, "max": 15}}
}
}
]
Do you see a way to implement this?
It would require changing the type of FieldResult
.
As an idea:
pub enum FieldError {
Simple(String),
Custom(String, json_value::Value),
}
type FieldResult<T> = Result<T, FieldError>;
The problem is the type of the extra data.
It would require either having FieldError be generic over a type that implements Serialize
(for serde) or tying it to a value type like serde_value or json_value::Value
.
The error messages I receive are:
trait `for<'r, 'r, 'r> juniper::iron_handlers::GraphiQLHandler: std::ops::Fn<(&'r mut iron::request::Request<'r, 'r>,)>` not satisfied
trait `for<'r, 'r, 'r> juniper::iron_handlers::GraphiQLHandler: std::ops::FnOnce<(&'r mut iron::request::Request<'r, 'r>,)>` not satisfied
trait `for<'r, 'r, 'r> juniper::iron_handlers::GraphQLHandler<'_, _, _, _, _>: std::ops::Fn<(&'r mut iron::request::Request<'r, 'r>,)>` not satisfied
trait `for<'r, 'r, 'r> juniper::iron_handlers::GraphQLHandler<'_, _, _, _, _>: std::ops::FnOnce<(&'r mut iron::request::Request<'r, 'r>,)>` not satisfied
Is there some way for Juniper to be compatible with tools like https://github.com/sheerun/graphqlviz or https://github.com/APIs-guru/graphql-voyager ?
Check out https://apis.guru/graphql-voyager/ if you haven't previously.
I did try pointing graphqlviz at a juniper instance (with the star wars model), but was getting errors about some aspects of the juniper schema that graphqlviz didn't like.
According to the specs, one should be able to set a default value as indicated both here input type spec and here. Also, this feature is supported in the js implementation as well as indicated in this issue. As far I can tell the input macro doesn't allow you specify this so this would be a great feature to have unless of course, it is already there and I am overlooking it.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.