async-graphql / async-graphql Goto Github PK
View Code? Open in Web Editor NEWA GraphQL server library implemented in Rust
Home Page: https://async-graphql.github.io/
License: Apache License 2.0
A GraphQL server library implemented in Rust
Home Page: https://async-graphql.github.io/
License: Apache License 2.0
I get the following error when trying to upload a .png:
panicked at 'byte index 19 is not a char boundary; it is inside '|' (bytes 18..19) of `test.png:image/png|PNG`
And this is my form data:
Content-Disposition: form-data; name="operations"
{"operationName":"upload","variables":{"file":null},"query":"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n"}
------WebKitFormBoundaryF5RA2VMgDmAIU1OT
Content-Disposition: form-data; name="map"
{"1":["variables.file"]}
------WebKitFormBoundaryF5RA2VMgDmAIU1OT
Content-Disposition: form-data; name="1"; filename="test.png"
Content-Type: image/png
------WebKitFormBoundaryF5RA2VMgDmAIU1OT--
I am looking to replace juniper with this library and I was looking through the docs and it looks like all the examples use proc macro attributes to add a description. Is it currently (or planned) possible to parse rustdoc comments for the graphql description?
Export all fields on Object
, SimpleObject
, InputObject
, Subscription
by default, and #[field(skip)]
is used to disable this feature.
Hello, i'm sorry if question is stupid. Does async-graphql federation support subscriptions form remote graphql schemes?
I would like to implement impl From serde-json::Value for Any
which is currently blocked by graphql-parsers inability to convert from serde_json::Value
due to mismatch of underlying types for Number. Once this Issue resolves I will implement the conversion for Any
.
I automatically add a context parameter for all the object's field definitions.
Nice implementation - congrats
Surprised to see federation built in
Would be nice to see:
With basic annotations (http proxy, oauth validation, structure getter) you can support 70-80% of the typical graphql use cases.
While using the #[DataSource]
macro, any errors inside the implementation of the trait are marked on the trait itself (and in some weird cases, hoisted to line 1 of main).
I looked into the macro and found it is just adding #[async_trait::async_trait]
so I used that directly and the errors are highlighted correctly in my code.
I have a new idea to implement a type-safe interface and make it look like rust. I will try it tomorrow.
First, awesome work on this library! I started using it with actix as Juniper doesn't support subscriptions well and so far everything has worked great. But I've recently run into an issue:
Is it possible to pass actix-web session data into the execution context of a GraphQL resolver? I would like to use this to authenticate certain queries against a session token. Poor pseudocode for what I'm looking to accomplish would be:
impl QueryRoot {
#[field]
async fn mydata(&self, ctx: &Context<'_>) -> Option<Vec<MyData>> {
// the line below is what I would like to accomplish. how to get session data in here?
let token = ctx.data::<Session>().get("token");
// then validation is easy. I have the db from Schema::build(..).data(...)
let db = ctx.data::<Database>();
match validate(token, db) {
Some(result) => Some(db.get_all().await),
None => None
}
}
}
In Juniper, it seems like you can extend the Context to do something similar. For example, this issue led to the suggestion: https://github.com/davidpdrsn/graphql-app-example/blob/master/src/graphql.rs#L20
Any ideas how to best accomplish this here? I'd be happy to document it with an example if someone could help me figure it out. Ideally, it would be nice to use actix-session or something similar.
It's possible to make PageInfo
public ?
Also make Connection
fields public too ?
It seems that Interfaces don't support the FieldResult type. This might be a scoping issue in the macro expansion?
Here is an example:
use async_graphql::FieldResult;
struct MyObj;
#[async_graphql::Object]
impl MyObj {
#[field]
async fn value_a(&self) -> FieldResult<i32> {
Ok(1)
}
}
#[async_graphql::Interface(field(name = "value_a", type = "FieldResult<i32>"))]
struct InterfaceA(MyObj);
Now that subscription handlers return a stream. What is the most idiomatic way for subscribers sharing a stream as opposed to each subscriber starting a new stream? Also is there an idiomatic way of implementing something like the publish method to emit to subscribers from a mutation handler directly?
I can think of two solutions:
Is there a better solution I am not aware of? I find it quite hard to migrate my code to 1.6.7 in a clean fashion.
If you could point me in the right direction I could extend the examples
I think it is time to export the most important types and traits in a prelude to increase usability.
How to pass some context to schema with web::Json(req.into_inner().execute(&s).await)
? like a token from request
I need to build a query endpoint that would support something like this:
pub struct Criteria {
pub value: HashMap<String, Vec<Criterion>>,
}
impl QueryRoot {
async fn records(&self, criteria: Criteria, start: i64, skip: i64, order: SortCriteria)
}
I'm trying to figure out a way to do this without manually implementing InputValueType. I'm sure it's possible, but I couldn't find a relevant example.
The Upload
type should provide a stream of bytes instead of a Vec<u8>
leaving it to the user to decide what to do with it eg. stream it into a tempfile. I think loading the file contents into memory is a big performance and security issue.
Alternatively let the user define a location for uploads (eg. /tmp) and stream the content into files at this location and pass handles to these files to the user.
The second solution might be easier to integrate into the existing codebase although as a library user I would much prefer the first solution, giving me more control (eg. the opportunity to cancel requests early on before loading the contents / if not enough disk space is available / dynamic filesize checks / streaming to 3rd party APIs etc.)
I have a repro here:
https://github.com/dusty-phillips/async-graphql/blob/parking_lot_repro/examples/parking_lot.rs
In short, I'm using the parking_lot crate to get an RwLock.
I've put the lock inside an Arc, and tried to access it from both a query and a mutation. However, I'm getting the following compile errors:
master* $ cargo run --example parking_lot
Compiling async-graphql v0.10.8 (/home/dusty/beanstalk/async-graphql)
error: future cannot be sent between threads safely
--> examples/parking_lot.rs:26:1
|
26 | #[Object]
| ^^^^^^^^^ future returned by `__resolve_field` is not `Send`
|
= help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `*mut ()`
note: future is not `Send` as this value is used across an await
--> examples/parking_lot.rs:30:9
|
30 | self.thing.read().get_the_value().await
| -----------------^^^^^^^^^^^^^^^^^^^^^^
| |
| await occurs here, with `self.thing.read()` maybe used later
| has type `lock_api::rwlock::RwLockReadGuard<'_, parking_lot::raw_rwlock::RawRwLock, Thing>`
31 | }
| - `self.thing.read()` is later dropped here
= note: required for the cast to the object type `dyn std::future::Future<Output = std::result::Result<serde_json::value::Value, anyhow::Error>> + std::marker::Send`
error: future cannot be sent between threads safely
--> examples/parking_lot.rs:38:1
|
38 | #[Object]
| ^^^^^^^^^ future returned by `__resolve_field` is not `Send`
|
= help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `*mut ()`
note: future is not `Send` as this value is used across an await
--> examples/parking_lot.rs:42:9
|
42 | self.thing.write().set_the_value(thing_value).await;
| ------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- `self.thing.write()` is later dropped here
| |
| await occurs here, with `self.thing.write()` maybe used later
| has type `lock_api::rwlock::RwLockWriteGuard<'_, parking_lot::raw_rwlock::RawRwLock, Thing>`
= note: required for the cast to the object type `dyn std::future::Future<Output = std::result::Result<serde_json::value::Value, anyhow::Error>> + std::marker::Send`
error: aborting due to 2 previous errors
error: could not compile `async-graphql`.
To learn more, run the command again with --verbose.
I've tried moving the guard out, but it doesn't seem to work. Intuitively, I can imagine it might not be possible to hold a lock across an await, but there is no compile error when I try to access it in the main() function (line 72) I don't get a compile error there. So the issue seems to be something in the Object macro, but I don't know how to debug. Any suggestions?
First, I wanted to say thank you for this amazing project! ๐
I'm currently implementing a Relay client against my async-graphql server and they require everything to implement the Node { id }
interface for every object.
If I implement Node
for every interface, I'm not sure how to implement another interface on the same object. Have you considered this for the #[async_graphql::Interface]
macro?
Hey there. Nice project! I like it especially because it addresses features like query complexity and uploading files. But what about the N+1 problem? Are there any plans in this direction?
And a second question I want to put here. Is there a way to pass the request object to the async_graphql::Object implementation? I think it would be pretty helpful sometimes (For example to read a cookie header).
Please can you provide federation example.
As discussed in #28, it would be nice to have a flatten attribute on fields. Note: This does not seem to be possible as of now! This issue is just a reminder to implement it when it becomes possible. Here is a very basic example of what we want to accomplish:
#[SimpleObject]
struct Info {
#[field]
base_field: String
}
#[SimpleObject]
struct MyInfo {
#[field(flatten)] // <<<-------------------------
info: Info,
#[field]
additional_field: String,
}
pub struct QueryRoot;
impl QueryRoot {
async fn info(&self) -> MyInfo {
unimplemented!()
}
}
Instead of getting this:
{
info {
info {
baseField
}
additionalField
}
}
You would be getting this:
{
info {
baseField,
additionalField
}
Async-graphql
lacks an important feature that most graphql implementations do not. Support both static and dynamic schema, and only with dynamic schema can write something like hasaru
.
I have some preliminary ideas about the implementation of this feature. Do you think this feature is important?
Thanks again for the great library and the fast response to my question yesterday! I now have a similar question around subscriptions. How do we pass data into the GraphQL resolver for a subscription? Here is pseudocode for what I would like to do:
pub async fn index_ws(
schema: web::Data<MySchema>,
req: HttpRequest,
payload: web::Payload,
session: Session,
db: web::Data<database::Database>
) -> Result<HttpResponse> {
let user: Option<User> = user_from_session(db.users.clone(), db.sessions.clone(), session).await;
// something like ".data(...)" to extend context does not seem to exist
ws::start_with_protocols(WSSubscription::new(&schema).data(user), &["graphql-ws"], &req, payload)
}
And then:
#[async_graphql::Subscription]
impl SubscriptionRoot {
#[field]
async fn mydata(&self, ctx: &Context<'_>, mutation_type: Option<MutationType>) -> impl Stream<Item = DataChanged> {
let current_user = ctx.data::<Option<User>>();
// logic here, for example use current_user.id to filter a stream ...
}
}
Is this possible? I reviewed the subscription example and didn't see anything. Looking over the implementation of the library, I also didn't see any way to add additional data to a subscription context, or access the context.
Currently if I want to define a struct with a bunch of fields as an InputType I would also have to define the impl
for it and all the fields there, which makes the amount of boilerplate unbearable.
I'd like to be able to simply define a struct and use it's fields as input fields.
I ran into the following bug:
use serde::Deserialize;
#[async_graphql::Enum]
#[derive(Deserialize)]
enum Test {
#[serde(alias = "Other")]
Real,
}
#[derive(Deserialize)]
struct TestStruct {
value: Test,
}
fn main() {
serde_json::from_str::<TestStruct>(r#"{ "value" : "Other" }"#).unwrap();
}
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("unknown variant `Other`, expected `Real`", line: 1, column: 19)', src/libcore/result.rs:1188:5
Without the async_graphql::Enum
annotation everything is fine.
I was about to upgrade from 1.7.8 to 1.9.3 when I realised that quite a lot has changed. So I looked at the examples and codebase but I could not find any info on how I would transition the below code to 1.9.3. Where do I specify max_file_size
and temp_dir
?
HttpServer::new(move || {
let schema = Schema::build(QueryRoot, MutationRoot, SubscriptionRoot)
.extension(|| ApolloTracing::default())
.finish();
let handler = async_graphql_actix_web::HandlerBuilder::new(schema)
.enable_ui("http://localhost:3001", Some("ws://localhost:3001"))
.max_file_size(100_000_000_000) //100 GB
.enable_subscription()
.build();
App::new()
.wrap(Logger::default())
.wrap(Cors::new().finish())
.service(web::resource("/").to(handler))
})
.bind(format!("0.0.0.0:{}", 3001))
.unwrap()
.run()
In my application, IDs are a custom scalar that wraps the Uuid type. Would it be valuable for the ID scalar provided here to have a From<Uuid>
implementation?
Add the guard attribute in #[field] as a precondition to resolve the field.
#field(guard(RequireAuth(role = ADMIN)))
I am trying to make async-graphql
work with dataloader
, examples.
cargo run --example juniper
cargo run --example async_graphql
the async-await branch of juniper
is able to do batch load, but async-grapqhl
still not able to do so.
I was trying to implement an Interface for my structs annotated with SimpleObject and it fails with field, not a method
. Is it possible to support Interfaces using SimpleObjects?
Here's an example:
#[async_graphql::SimpleObject]
pub struct MyObj {
#[field]
pub id: i32,
#[field]
pub title: String,
};
#[async_graphql::Interface(
field(name = "id", type = "i32"),
)]
pub struct Node(MyObj);
I want to do something like this:
#[Object(desc = "Information about the client's account")]
impl Client {
#[field(desc = "The client's current balance")]
async fn balance(&mut self) -> u64 {
self.client.write().balance()
}
}
Where the balance function needs to accept self mutably. But I'm getting this error:
--> src/graphql.rs:151:1
|
151 | #[Object(desc = "Information about the client's account")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| `_self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
| help: consider changing this to be a mutable reference: `&mut graphql::Client`
Is there a way to do this without resorting to interior mutability?
Since the stable version of rust does not support generic specialization, this is a bit of a hassle to implement. I'm doing it in other ways, and I'm not sure if I can finish it.
Are there any plans to support connectionParams
passed by an Apollo client? These are parameters passed by the client with its first websocket message and used to initialize a context object on Apollo Server (example): https://www.apollographql.com/docs/apollo-server/data/subscriptions/#authentication-over-websocket
I think this feature is specific to Apollo Client, but it would be very convenient!
This issue is used to track vague languages when translating TypeSystem in Async-graphql Book.
I'm thinking of something like this:
struct UserGuard {
id: ID
}
#[async_trait::async_trait]
impl Guard for UserGuard {
async fn check(&self, ctx: &Context<'_>) -> FieldResult<()> {
if let Some(current_user) = ctx.data_opt::<User>() {
if current_user.is_admin() {
return OK(())
}
if current_user.id == self.id {
return Ok(());
}
}
Err("Forbidden".into())
}
}
#[async_graphql::Object]
impl QueryRoot {
#[field(guard(UserGuard(id = "@id")))]
async fn user(&self, ctx: &Context<'_>, id: ID) -> FieldResult<User> {
// ...
}
}
This would allow for more granular ACL implementation at the field guard level.
The type safety of the interface causes some functionality to be unavailable. I'll remove the compile-time type safety of the interface and check it when I call Schema::new
. It doesn't have any performance loss, but there is no guarantee that a compile-time type safety, I think it is worth to do so.
I just thought it would be really neat to move some common fields from my concrete impl blocks to a shared trait. I can think of a lot of situations where this might be handy:
So Is this possible?
Using the actix subscriptions example, adding validations to InputObject
fails to validate
use actix_web::{web, App, HttpServer};
use async_graphql::validators::StringMinLength;
use async_graphql::{publish, Context, InputObject, Result, Schema, ID};
use futures::lock::Mutex;
use slab::Slab;
use std::sync::Arc;
#[derive(Clone)]
struct Book {
id: ID,
name: String,
author: String,
}
#[InputObject]
struct CreateBookInput {
#[field(
desc = "name of the book",
validator(StringMinLength(length = 5))
)]
name: String,
#[field(
desc = "name of the author",
validator(StringMinLength(length = 5))
)]
author: String,
}
#[async_graphql::Object]
impl Book {
#[field]
async fn id(&self) -> &str {
&self.id
}
#[field]
async fn name(&self) -> &str {
&self.name
}
#[field]
async fn author(&self) -> &str {
&self.author
}
}
type Storage = Arc<Mutex<Slab<Book>>>;
struct QueryRoot;
#[async_graphql::Object(cache_control(max_age = 5))]
impl QueryRoot {
#[field]
async fn books(&self, ctx: &Context<'_>) -> Vec<Book> {
let books = ctx.data::<Storage>().lock().await;
books.iter().map(|(_, book)| book).cloned().collect()
}
}
struct MutationRoot;
#[async_graphql::Object]
impl MutationRoot {
#[field]
async fn create_book_input(
&self,
ctx: &Context<'_>,
input: CreateBookInput,
) -> ID {
let mut books = ctx.data::<Storage>().lock().await;
let entry = books.vacant_entry();
let id: ID = entry.key().into();
let book = Book {
id: id.clone(),
name: input.name,
author: input.author,
};
entry.insert(book);
publish(BookChanged {
mutation_type: MutationType::Created,
id: id.clone(),
})
.await;
id
}
#[field]
async fn create_book_args(
&self,
ctx: &Context<'_>,
#[arg(validator(StringMinLength(length = 5)))] name: String,
#[arg(validator(StringMinLength(length = 5)))] author: String,
) -> ID {
let mut books = ctx.data::<Storage>().lock().await;
let entry = books.vacant_entry();
let id: ID = entry.key().into();
let book = Book {
id: id.clone(),
name: name,
author: author,
};
entry.insert(book);
publish(BookChanged {
mutation_type: MutationType::Created,
id: id.clone(),
})
.await;
id
}
}
#[async_graphql::Enum]
enum MutationType {
Created,
Deleted,
}
struct BookChanged {
mutation_type: MutationType,
id: ID,
}
#[async_graphql::Object]
impl BookChanged {
#[field]
async fn mutation_type(&self) -> &MutationType {
&self.mutation_type
}
#[field]
async fn id(&self) -> &ID {
&self.id
}
}
struct SubscriptionRoot;
#[async_graphql::Subscription]
impl SubscriptionRoot {
#[field]
fn books(
&self,
changed: &BookChanged,
mutation_type: Option<MutationType>,
) -> bool {
if let Some(mutation_type) = mutation_type {
return changed.mutation_type == mutation_type;
}
true
}
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
let schema = Schema::build(QueryRoot, MutationRoot, SubscriptionRoot)
.data(Storage::default())
.finish();
let handler = async_graphql_actix_web::HandlerBuilder::new(schema)
.enable_ui("http://localhost:8000", Some("ws://localhost:8000"))
.enable_subscription()
.build();
App::new().service(web::resource("/").to(handler))
})
.bind("127.0.0.1:8000")?
.run()
.await
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::dev::Service;
use actix_web::{
http::{header, StatusCode},
test, web, App,
};
#[actix_rt::test]
async fn test_validates_input_with_errors_fails() {
let schema = Schema::build(QueryRoot, MutationRoot, SubscriptionRoot)
.data(Storage::default())
.finish();
let handler = async_graphql_actix_web::HandlerBuilder::new(schema)
.enable_subscription()
.build();
let mut app = test::init_service(
App::new().service(web::resource("/").to(handler)),
)
.await;
let req = test::TestRequest::post()
.uri("/")
.header(header::CONTENT_TYPE, "application/json")
.set_payload(r#"{"operationName":null,"variables":{},"query":"mutation { createBookInput(input: {name: \"foo\", author: \"bar\"})}"}"#)
.to_request();
let res = app.call(req).await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}
#[actix_rt::test]
async fn test_validates_arguments_with_errors() {
let schema = Schema::build(QueryRoot, MutationRoot, SubscriptionRoot)
.data(Storage::default())
.finish();
let handler = async_graphql_actix_web::HandlerBuilder::new(schema)
.enable_subscription()
.build();
let mut app = test::init_service(
App::new().service(web::resource("/").to(handler)),
)
.await;
let req = test::TestRequest::post()
.uri("/")
.header(header::CONTENT_TYPE, "application/json")
.set_payload(r#"{"operationName":null,"variables":{},"query":"mutation { createBookArgs(author: \"foo\", name: \"bar\")}"}"#)
.to_request();
let res = app.call(req).await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let response_body = match res.response().body().as_ref() {
Some(actix_web::body::Body::Bytes(bytes)) => bytes,
_ => panic!("Response error"),
};
assert_eq!(
response_body,
r#"Invalid value for argument "author", The value length is 3, but the length must be greater than or equal to 5
Invalid value for argument "name", The value length is 3, but the length must be greater than or equal to 5
"#
);
}
}
Hey,
first of all, I really enjoy using your library so far. I think it is already very comprehensive. One thing I am missing though is returning extensions for errors as described here in the spec. Something like:
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ],
"extensions": {
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018"
}
}
]
}
In terms of api, I think it would be nice to have a trait with a single method with_extensions
which you could implement on your errors. Something like:
// somewhere in async-graphql
pub trait ErrorExtensions {
fn with_extensions(&self) -> Option<serde_json::Value> {
None
}
}
impl<T: Into<Error>> ErrorExtensions for T {}
// somewhere in users code...
impl ErrorExtensions for MyError {
fn with_extensions(&self) -> Option<serde_json::Value> {
let extensions = serde_json::json!({
"code": self.code.to_string(),
"timestamp": get_current_timestamp()
});
Some(extensions)
}
}
I am happy to contribute, so if you want any help just let me know.
Hello,
I'm not sure if this is realted to the recent changes on FieldResult
but I've just noticed that we can use ?
in field resolvers even if they don't return a FieldResult
:
#[async_graphql::Object]
impl QueryRoot {
#[field]
async fn user(&self, ctx: &Context<'_>, id: ID) -> models::User {
let id = ObjectId::with_string(&id.to_string())?;
ctx.data::<Loaders>().user.load(id).await?
}
}
It feel a bit odd, but is also very convenient and makes error management easier. The documentation doesn't seems to mention this at the moment, is this a feature we can rely on or is it subject to change in the future ?
Cheers
https://www.apollographql.com/blog/apollo-federation-f260cf525d21 This here is a very nice tool, also to help load-balancing as when the schema is split between services - it's easier to have several different entrypoints with different pools of servers.
Even if one does want to code a monolith - this is easily achievable by having features in the source code and just running the same service with different features enabled.
But using the node library is suboptimal, so I've been thinking this might actually be worth implementing in Rust. It's not really an issue for that repo, but something to chew on. I'll be looking into prototyping this sometime soon.
Here's an example: I have an object that has 10 fields, only one of which would need to be defined as a custom Scalar. But in order to expose this object's fields I would have to actually create a getter for each of it's fields. Or otherwise I'd have to use a gql scalar type within my business logic and the database layer, which would also be somewhat painful.
Hello,
When using fragments in subscriptions, I never receive any notifications in the websocket
This works:
subscription s {
order(id: "5eabbe77103ca80276efdf70") {
id
createdAt
}
}
This doesn't:
subscription s {
order(id: "5eabbe77103ca80276efdf70") {
id
...orderFrag
}
}
fragment orderFrag on Order {
id
createdAt
}
tested with both playground and an apollo client
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.