Giter VIP home page Giter VIP logo

graphql-client's Introduction

graphql_client

Github actions Status docs crates.io

A typed GraphQL client library for Rust.

Features

  • Precise types for query variables and responses.
  • Supports GraphQL fragments, objects, unions, inputs, enums, custom scalars and input objects.
  • Works in the browser (WebAssembly).
  • Subscriptions support (serialization-deserialization only at the moment).
  • Copies documentation from the GraphQL schema to the generated Rust code.
  • Arbitrary derives on the generated responses.
  • Arbitrary custom scalars.
  • Supports multiple operations per query document.
  • Supports setting GraphQL fields as deprecated and having the Rust compiler check their use.
  • Optional reqwest-based client for boilerplate-free API calls from browsers.
  • Implicit and explicit null support.

Getting started

  • If you are not familiar with GraphQL, the official website provides a very good and comprehensive introduction.

  • Once you have written your query (most likely in something like graphiql), save it in a .graphql file in your project.

  • In order to provide precise types for a response, graphql_client needs to read the query and the schema at compile-time.

    To download the schema, you have multiple options. This projects provides a CLI, however it does not matter what tool you use, the resulting schema.json is the same.

  • We now have everything we need to derive Rust types for our query. This is achieved through a procedural macro, as in the following snippet:

    use graphql_client::GraphQLQuery;
    
    // The paths are relative to the directory where your `Cargo.toml` is located.
    // Both json and the GraphQL schema language are supported as sources for the schema
    #[derive(GraphQLQuery)]
    #[graphql(
        schema_path = "tests/unions/union_schema.graphql",
        query_path = "tests/unions/union_query.graphql",
    )]
    pub struct UnionQuery;

    The derive will generate a module named union_query in this example - the name is the struct's name, but in snake case.

    That module contains all the struct and enum definitions necessary to deserialize a response to that query.

    The root type for the response is named ResponseData. The GraphQL response will take the form of a Response<ResponseData> (the Response type is always the same).

    The module also contains a struct called Variables representing the variables expected by the query.

  • We now need to create the complete payload that we are going to send to the server. For convenience, the GraphQLQuery trait, is implemented for the struct under derive, so a complete query body can be created this way:

    use graphql_client::{GraphQLQuery, Response};
    use std::error::Error;
    use reqwest;
    
    #[derive(GraphQLQuery)]
    #[graphql(
        schema_path = "tests/unions/union_schema.graphql",
        query_path = "tests/unions/union_query.graphql",
        response_derives = "Debug",
    )]
    pub struct UnionQuery;
    
    async fn perform_my_query(variables: union_query::Variables) -> Result<(), Box<dyn Error>> {
    
        // this is the important line
        let request_body = UnionQuery::build_query(variables);
    
        let client = reqwest::Client::new();
        let mut res = client.post("/graphql").json(&request_body).send().await?;
        let response_body: Response<union_query::ResponseData> = res.json().await?;
        println!("{:#?}", response_body);
        Ok(())
    }

A complete example using the GitHub GraphQL API is available.

Alternative workflow using the CLI

You can introspect GraphQL APIs and generate module from a command line interface to the library:

$ cargo install graphql_client_cli
$ graphql-client --help

Deriving specific traits on the response

The generated response types always derive serde::Deserialize but you may want to print them (Debug), compare them (PartialEq) or derive any other trait on it. You can achieve this with the response_derives option of the graphql attribute. Example:

use graphql_client::GraphQLQuery;

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "tests/unions/union_schema.graphql",
    query_path = "tests/unions/union_query.graphql",
    response_derives = "Serialize,PartialEq",
)]
struct UnionQuery;

Implicit Null

The generated code will skip the serialization of None values.

use graphql_client::GraphQLQuery;

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "tests/unions/union_schema.graphql",
    query_path = "tests/unions/union_query.graphql",
    skip_serializing_none
)]
struct UnionQuery;

Custom scalars

In GraphQL, five scalar types, Int, Float, String, Boolean, and ID, are available out of the box and are automatically mapped to equivalent types in Rust. However, in addition, custom scalar types can be defined by service providers by adding declarations like scalar URI to the server schema.

If such custom scalar types are defined in the schema, depending on the content of the query, the generated code will also reference those scalar types. This means you have to provide matching Rust types in the scope of the struct under derive. It can be as simple as declarations like type URI = String;. This gives you complete freedom on how to treat custom scalars, as long as they can be deserialized. If such declarations are not provided, you will get build errors like this:

error[E0412]: cannot find type `URI` in module `super`
   |
   | #[derive(GraphQLQuery)]
   |          ^^^^^^^^^^^^ not found in `super`
   |
   = note: possible candidate is found in another module, you can import it into scope:
           crate::repo_view::URI

Deprecations

The generated code has support for @deprecated field annotations. You can configure how deprecations are handled via the deprecated argument in the GraphQLQuery derive:

use graphql_client::GraphQLQuery;

#[derive(GraphQLQuery)]
#[graphql(
  schema_path = "tests/unions/union_schema.graphql",
  query_path = "tests/unions/union_query.graphql",
  deprecated = "warn"
)]
pub struct UnionQuery;

Valid values are:

  • allow: the response struct fields are not marked as deprecated.
  • warn: the response struct fields are marked as #[deprecated].
  • deny: The struct fields are not included in the response struct and using them is a compile error.

The default is warn.

Query documents with multiple operations

You can write multiple operations in one query document (one .graphql file). You can then select one by naming the struct you #[derive(GraphQLQuery)] on with the same name as one of the operations. This is neat, as it allows sharing fragments between operations.

Note that the struct and the operation in the GraphQL file must have the same name. We enforce this to make the generated code more predictable.

use graphql_client::GraphQLQuery;

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "tests/unions/union_schema.graphql",
    query_path = "tests/unions/union_query.graphql",
)]
pub struct UnionQuery;

There is an example in the tests.

Documentation for the generated modules

You can use cargo doc --document-private-items to generate rustdoc documentation on the generated code.

Make cargo recompile when .graphql files have changed

There is an include option you can add to your Cargo.toml. It currently has issues however (see this issue).

Examples

See the examples directory in this repository.

Contributors

Warmest thanks to all those who contributed in any way (not only code) to this project:

  • Alex Vlasov (@indifferentalex)
  • Ben Boeckel (@mathstuf)
  • Chris Fung (@aergonaut)
  • Christian Legnitto (@LegNeato)
  • David Gräff (@davidgraeff)
  • Dirkjan Ochtman (@djc)
  • Fausto Nunez Alberro (@brainlessdeveloper)
  • Hirokazu Hata (@h-michael)
  • Peter Gundel (@peterfication)
  • Sonny Scroggin (@scrogson)
  • Sooraj Chandran (@SoorajChandran)
  • Tom Houlé (@tomhoule)

Code of conduct

Anyone who interacts with this project in any space, including but not limited to this GitHub repository, must follow our code of conduct.

License

Licensed under either of these:

Contributing

Unless you explicitly state otherwise, any contribution you intentionally submit for inclusion in the work, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.

graphql-client's People

Contributors

aedm avatar bolinfest avatar boxdot avatar brahmlower avatar cairomassimo avatar danieleades avatar dingxiangfei2009 avatar enselic avatar h-michael avatar jakmeier avatar jbourassa avatar jkelleyrtp avatar legneato avatar luro02 avatar mathstuf avatar maxymshg avatar miterst avatar nickwesselman avatar noverby avatar o0ignition0o avatar panicbit avatar pruthvikar avatar qwandor avatar scrogson avatar selyatin avatar surma avatar tomhoule avatar travismiller avatar upsuper avatar visd0m avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-client's Issues

Improve error messages

I'm seeing error messages like operation type not in schema. Getting the operation type name in there would be handy :) .

How to use the CLI client to generate a schema?

I've built a Juniper-based GraphQL server this week and now I'd like to execute tests against it. This project seems like the best way to do so from Rust, and so I'm trying to generate a schema for my server. The built-in GraphiQL instance can read the introspection data just fine, but when I run graphql-client introspect-schema http://localhost:3000/graphql I just get {"data":{"schema":null},"errors":null}.

(Just to be sure, I get the same result if I run introspect-schema against the GitHub GraphQL endpoint.)

What, if anything, am I getting wrong?

Release 0.5

This has to be a breaking release because the deprecation feature can break existing code by just upgrading the library (if you use #![deny(deprecated)] and you have deprecated fields in your queries). Are there other breaking changes we should wait for before doing this?

Support using the codegen part of the library as a build script?

I want to gather feedback on the idea here. This would be an alternative to the custom derive. I see three potential advantages:

  • More implicit codegen (no struct in user code, the modules are generated next to the queries)
  • More consistent configuration
  • Potential code sharing between queries (queries could share fragments, for example, but it might not be a good thing)

operationName

What is it? What is its agenda? Are we being told the whole truth? Theories here or tune in next time I have time to read up on this.

Improve query validation

The focus so far has been on making valid queries work - we should also ensure that the queries are valid and fail with nice error messages.

For example, we don't verify that the variables are used correctly.

Arbitrary custom derives in rust 2018

Since derive macros will now be imported (with use) in idiomatic code, we should provide the option to add imports to the generated module.

Or maybe use super::* is a sane option? It feels like it could go wrong, and it does not solve the issue that you need to have the derive macros in scope.

CLI command for codegen

It would be useful for people who want to generate the code in advance to get better editor support (RLS for example does not take into account generated code from derives yet).

Linking proc_macro2 could be a challenge, from my past experience.

Support multiple operations per query document

Probably via a struct attribute on the query struct to specify which operation it should implement.

For now we should document that we only support one operation by document (like Apollo Client, I think?)

Copy documentation from schema to generated code

We need to do three things:

  • Add a description field to all our intermediate types (GqlObject, GqlObjectField, GqlUnion...)
  • Put the description there when building from a schema (both graphql schema language and json version)
  • Output the description in doc attributes in the generated rust code

Apropos: reviewers

I would be happy to have my (and others') pull requests code-reviewed, if anybody is interested please comment here to get mentioned when something needs reviewing.

[RFE] Support dumping data structures to a separate module

It'd be nice to have a way to dump all types from a schema into a module and then using that module to pull all of the data types for queries. This would simplify creating impl From<query_name::SomeGQLType> for each query's copy of relevant types.

Maybe:

#[derive(GraphQLQuery)]
#[graphql(schema_path = "…", only_types)]
pub struct SchemaTypes;

#[derive(GraphQLQuery)]
#[graphql(schema_path = "…", query_path = "…", types_from = schema_types)]
pub struct SchemaTypes;

Not sure how feasible this is.

Variables should impl Clone

For pagination queries, it can be nice to make a "model" variables block and just update the "pagination" field where possible. Since all of the primitive types in GraphQL impl Clone, it should be possible to just stick impl Clone on these types (and probably impl PartialEq as well, maybe Eq?). Bonus points for detecting when impl Copy is possible as well.

CLI command idea: rustdoc output for the generated modules

Knowing what the generated types look like is harder than it should be at the moment. Maybe we could use the CLI to generate code, then rustdoc output since it's the format all rust programmers are used to.

This is just a note but I think we should start the discussion because it's an ergonomics problem.

Documentation blockers for 0.1

  • All the setup you need for the derive to work (serde, schema and query files)
  • Needs a step-by-step guide
  • The docs.rs link is wrong

Do we need a yew client?

I think that as long as the target is wasm32-unknown-unknown a yew-specifc client could just be a wrapper around a more general browser client using wasm-bindgen and web-sys. Am I missing something?

Where should we maintain comprehensive documentation?

Many projects in the rust community maintain guide-level documentation with gitbook and keep the top-level crate docs and the README minimal. So far every new feature has gone into the README but this is not scalable.

There are also projects like structopt that maintain their guide documentation at the root of the rustdoc docs.

Does someone have some experience or advice on this front?

Rename public structs

IMO, the types exposed by the crate have a bad "stutter" problem. It'd be much easier to just rename:

  • GraphQLQueryQuery
  • GraphQLBodyBody
  • GraphQLResponseResponse
  • GraphQLErrorError

collisions should be dealt with on the use side of things (e.g., a crate may rename them back to GraphQL prefixes, it may be easier to use a Http prefix, or possibly just use them scoped).

Codegen Observation

I'm a bit of an outsider to Rust, but I've been interested in Yew and Juniper. I've had some experience finding different tools to generate TypeScript types through GraphQL introspection.

I really like that graphql-code-generator is using templating to empower people to add any language they wish more easily.

My thought is that it's great that a single command line tool can support so many languages and allow new languages to be added so easily. It would be great to see the Rust language added and other projects take inspiration, but I find myself leveraging Prisma as a pragmatic decision without the ability to build with or contribute to Rust as of yet, but I hope my comment helps in a small way.

Implicitly discover schema and queries?

Schema path: based on .graphqlconfig

Queries: expect a file named like the struct next to the rust file.

This was suggested by @brainlessdeveloper

This would be a fallback if the explicit paths are omitted of course.

HTTP client integrations

We should provide them optionally (separate crates, or as opt-in features?)

  • Reqwest
  • actix-web
  • wasm in browsers (we could have to separate between stdweb and wasm-bindgen implementations)
  • wasm in nodejs

Make scalars codegen lazy

At the moment, we generate definitions for all the scalars defined in a query's schema, but we could define only those needed by the query instead - this would alleviate some of the burden of defining mappings for all custom scalars.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.