Typed, correct GraphQL requests and responses in Rust

Overview

graphql_client

Github actions Status docs crates.io Join the chat

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.
  • web client for boilerplate-free API calls from browsers.

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, as well as sample rustdoc output.

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;

Custom scalars

The generated code will reference the scalar types as defined in the server schema. 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 Email = String;. This gives you complete freedom on how to treat custom scalars, as long as they can be deserialized.

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.

Comments
  • Refactor schema representation and code generation

    Refactor schema representation and code generation

    This is a huge refactoring based on an idea to model the schema with a proper normalized representation. The results so far are promising in terms of code clarity and maintainability. At the moment, this is far from complete.

    Previously, name resolution, query validation and code generation all happened in one pass. In this PR, these three are cleanly separated, leading to much more understandable code in my opinion.

    opened by tomhoule 25
  • WIP - Implicitly discover schema and query without macro attributes

    WIP - Implicitly discover schema and query without macro attributes

    So, this is the most naïve implementation I could fathom. Current problems:

    1. to_snake_case won't take care of numbers. In the existing example, Query1 is snake-cased as query1 but the file name was query_1. I suppose this is not a big issue.
    2. implicitly_discover_schema_name is extremely verbose, I suppose you can come up with some nicer way of writing this.
    3. This assumes there's src/. There should probably be another approach, and I'd like to store those magic strings somewhere nicer.
    4. I'm probably using some archaic way of manipulating strings.
    opened by fnune 16
  • Rust 2018

    Rust 2018

    https://rust-lang-nursery.github.io/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html#writing-idiomatic-code-in-a-new-edition

    after merge these PR, #194, #191.

    opened by h-michael 15
  • How to use the CLI client to generate a schema?

    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?

    bug 
    opened by djc 15
  • thread 'rustc' has overflowed its stack

    thread 'rustc' has overflowed its stack

    I have a schema generated by a hasura instance that is causing the rust compiler to panic.

    thread 'rustc' has overflowed its stack
    

    Schema and query causing the issue: rustc_panic.zip

    I had some code working successfully with a previous schema, but I added some tables and now I'm getting a stack overflow.

    I'm wondering if there's some recursive relationships in my schema that are causing issues, or just the size of the schema itself (hasura generates a ton of schema)... mine is currently 732kb of JSON

    bug 
    opened by robot-head 13
  • Fix wrong operationName when no operation is selected (fixes #149)

    Fix wrong operationName when no operation is selected (fixes #149)

    For example if we have this:

    query SelectName { person { name } }

    query SelectAge { person { age } }

    And our code looks like this:

    query_path = "..", schema_path = "...", )] struct SomeQuery;

    then the operation we generate code for will default to the first one, and operationName should be SelectName.

    opened by tomhoule 13
  • CLI command for codegen

    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.

    opened by tomhoule 13
  • Add support for recursive types

    Add support for recursive types

    Currently, the code generation doesn't allow for recursive types and compilation will fail with an error.

    Eg:

    type Node {
      left: Node
      right: Node
    }
    

    This should be supportable relatively easily by detecting the recursion and boxing the type where necessary (serde supports Box<> ed types).

    bug 
    opened by theduke 12
  • CLI codegen ergonomics

    CLI codegen ergonomics

    At the moment the CLI generates one module for one file. The general use-case however is probably projects with more than one query, and having to issue more than one command seems a bit too complicated.

    The most use similar CLI tool at the moment is apollo-cli and I think we could take a similar approach.

    Invoking graphql-client-cli could look something like this:

    graphql-client-cli generate --schema-path=./schema.json --deprecations=warn 'src/**/*.graphql', and it would generate one file per .graphql file, with one module per query.

    The shared code could live in one module for the whole project - we can achieve a lot more reuse with this approach than with the derive-based interface.

    Let's discuss what interface we want for the CLI in this isue.

    enhancement help wanted 
    opened by tomhoule 11
  • doctest: Fix regression after upgrade of reqwest to 0.10.

    doctest: Fix regression after upgrade of reqwest to 0.10.

    This was introduced in #350. Besides this, my thanks for upgrading reqwest. The old version cost me some time yesterday.

    I do not know why CI did not catch this. Maybe something for the maintainers to take a look at. Or I made a mistake in my setup.

    Below is the error I got:

    ---- /Users/user/.cargo/registry/src/github.com-1ecc6299db9ec823/doc-comment-0.3.3/src/lib.rs - (line 228) stdout ----
    error[E0433]: failed to resolve: use of undeclared type or module `reqwest`
      --> /Users/user/.cargo/registry/src/github.com-1ecc6299db9ec823/doc-comment-0.3.3/src/lib.rs:245:27
       |
    20 |     let client = reqwest::Client::new();
       |                           ^^^^^^ not found in `reqwest`
       |
    help: consider importing this struct
       |
    3  | use graphql_client::web::Client;
       |
    
    error: aborting due to previous error
    
    opened by siedentop 10
  • Adding the normalize_query_types feature to normalize all generated to CamelCase.

    Adding the normalize_query_types feature to normalize all generated to CamelCase.

    This probably isn't the final solution to this issue but I made this change so that I could continue with what I was implementing. At a minimum, the readme would need to be updated to describe the feature.

    I have added the normalize_query_types that normalises types (structs, type aliases, etc) generated by the code-gen to use CamelCase as it is my understanding that this is idiomatic rust. I have done this as a feature as it's potentially a breaking change.

    I was having issues with scalars as described in #254 but then noticed that other types were non-idiomatic.

    Fixes #254 and fixes #255.

    opened by markcatley 10
  • Generate a rust struct for a GraphQL type?

    Generate a rust struct for a GraphQL type?

    I have a type like so:

    type AuthenticationOutput {
      access_token: String!
      admin: Boolean!
      refresh_token: String!
    }
    

    and I'd like to generate a Rust struct for it. The use case is that Hasura allows you to define web hooks, and defines the inputs / outputs of the web hooks as GraphQL types. Is there a way to generate the struct for this type? or does the type have to be attached to a query? Thanks!

    opened by antholeole 0
  • Cannot decode when fragment contains a Node id

    Cannot decode when fragment contains a Node id

    Given an query

    query HomeQuery {
      viewer {
        ...home_viewer
        id
      }
    }
    
    fragment home_viewer on Customer {
      id
    }
    

    and a response

    {
      "data": {
        "viewer": {
          "id": "Q3VzdG9tZXI6MA=="
        }
      }
    }
    

    An error is is encountered.

    response_body = Err(
        reqwest::Error {
            kind: Decode,
            source: Error("missing field `id`", line: 1, column: 43),
        },
    )
    

    If id is added along side with the the fragment, i.e.

        ...home_viewer
        id
    

    somehow it cannot decode it. If id is removed then everything is okay.

    I can reproduce the same problem in the github example by adding a fragment to the query

    query RepoView($owner: String!, $name: String!) {
      repository(owner: $owner, name: $name) {
        ...home_viewer             
        id
        ...
      }
    }
    
    fragment home_viewer on Repository {
      id
    }
    
    
    opened by chungwong 0
  • FR: Consider adding support for multi-part form upload spec

    FR: Consider adding support for multi-part form upload spec

    This spec proposes an alternate request format to allow uploading files in graphql requests. It's supported by a lot of client/servers, e.g. async-graphql.

    opened by Dig-Doug 0
  • Can I convert an `IntrospectionResponse` to GraphQL Schema Language?

    Can I convert an `IntrospectionResponse` to GraphQL Schema Language?

    I've got as far as producing a schema.json file by sending an introspection query to my backend with graphql-client, but I want to produce a schema.graphql file; ~or anything that will allow me to traverse the GraphQL schema easily client-side~. Is there a simple way to do this? graphql_client_codegen seems to have the functionality but it's all private.

    Basically my end objective is to generate a schema.graphql file and Typescript types from a GraphQL schema. I'm aware of graphql-codegen, I just want to make my own implementation.

    (Edit: I'm a fool, I've realised IntrospectionResponse is good enough for traversing)

    opened by Teajey 0
  • usage of qualified paths in this context is experimental see issue #86935

    usage of qualified paths in this context is experimental see issue #86935

    I'm trying to setup my queries and for the most part its being accepted by the rust compiler. However the Variables type is acting a bit weird. Basically I have to use the fully qualified syntax or I get an error "ambiguous associated type". So then I switch to the fully qualified syntax but then later in code the compiler will not allow me to instantiate an instance of the fully qualified type. What am I supposed to do?

    ` #[derive(GraphQLQuery)] #[graphql( query_path = "src/common/gql/transactions.graphql", schema_path = "src/common/gql/schema.json", response_derives = "Debug" )] pub struct TransactionsQuery; #[allow(unused)] pub async fn transactions(config: &Config, variables: ::Variables) -> Result<(), Box> { let request_body = TransactionsQuery::build_query(variables); let mut res = client.post(&config.arweave_gql_url) .json(&request_body) .send() .await?; let response_body: Response<::ResponseData> = res.json().await?; println!("{:#?}", response_body);

    Ok(())
    

    }

    async fn get(&self, tx_id: &str) -> Result<(), Box> { let variables = ::Variables { // Fails here! ids: Some(vec!["r5_3_wqF4KGDzLnpRv70sI_Z1pE5JAl71-Bi9yJA5-8".to_string()]) }; transactions(&self.config, variables).await?; Ok(()) } `

    opened by jsoneaday 0
Releases(0.8.0)
  • 0.8.0(May 24, 2019)

  • 0.5.0(Oct 1, 2018)

  • 0.4.0(Aug 25, 2018)

    There are a number of breaking changes due to the new features, read the Added section attentively if you are upgrading.

    Added

    • (breaking) Control over which types custom scalars deserialize to is given to the user: you now have to provide type aliases for the custom scalars in the scope of the struct under derive.
    • (breaking) Support for multi-operations documents. You can select a particular operation by naming the struct under derive after it. In case there is no match, we revert to the current behaviour: select the first operation.
    • (breaking) Support arbitrary derives on the generated response types via the response_derives option on the graphql attribute. If you were relying on the Debug impl on generated structs before, you need to add response_derives = "Debug" in the #[graphql()] attributes in your structs.

    Fixed

    Fixed codegen of fields with leading underscores - they were ignored, leading to wrong derived types for deserialization.
    
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Jul 24, 2018)

  • 0.2.0(Jul 22, 2018)

  • 0.1.0(Jul 24, 2018)

Owner
GraphQL Rust
GraphQL Rust
A GraphQL server library implemented in Rust

A GraphQL server library implemented in Rust Async-graphql is a high-performance server-side library that supports all GraphQL specifications. Feature

null 2.6k Jan 8, 2023
Multi-stream HTTP downloader using range requests

chooch - An Amazing Project Downloads files faster than wget/curl (in theory) using multiple connections. Chooch recycles the slowest connection and r

null 13 Sep 14, 2022
An easy and powerful Rust HTTP Client

reqwest An ergonomic, batteries-included HTTP Client for Rust. Plain bodies, JSON, urlencoded, multipart Customizable redirect policy HTTP Proxies HTT

Sean McArthur 6.8k Dec 31, 2022
Minimal Rust HTTP client for both native and WASM

ehttp: a minimal Rust HTTP client for both native and WASM If you want to do HTTP requests and are targetting both native and web (WASM), then this is

Emil Ernerfeldt 105 Dec 25, 2022
Fast and friendly HTTP server framework for async Rust

Tide Serve the web API Docs | Contributing | Chat Tide is a minimal and pragmatic Rust web application framework built for rapid development. It comes

http-rs 4.1k Jan 2, 2023
A more modern http framework benchmarker supporting HTTP/1 and HTTP/2 benchmarks.

rewrk A more modern http framework benchmark utility.

Harrison Burt 273 Dec 27, 2022
🕵️Scrape multiple media providers on a cron job and dispatch webhooks when changes are detected.

Jiu is a multi-threaded media scraper capable of juggling thousands of endpoints from different providers with unique restrictions/requirements.

Xetera 47 Dec 6, 2022
Rust bindings to libcurl

curl-rust libcurl bindings for Rust Quick Start use std::io::{stdout, Write}; use curl::easy::Easy; // Print a web page onto stdout fn main() {

Alex Crichton 882 Dec 31, 2022
An HTTP library for Rust

hyper A fast and correct HTTP implementation for Rust. HTTP/1 and HTTP/2 Asynchronous design Leading in performance Tested and correct Extensive produ

null 11k Jan 8, 2023
FeignHttp is a declarative HTTP client. Based on rust macros.

FeignHttp is a declarative HTTP client. Based on rust macros. Features Easy to use Asynchronous request Configurable timeout settings Suppor

null 46 Nov 30, 2022
Pretend is a macros-based declarative Rust HTTP client

pretend is a modular, Feign-inspired HTTP, client based on macros. It's goal is to decouple the definition of a REST API from it's implementation.

null 24 Aug 3, 2022
Pyre - A fast python HTTP server inspired by japronto written in rust.

Pyre - A fast python HTTP server inspired by japronto written in rust.

null 135 Nov 26, 2022
A rule34 scraper made in rust this time

rust-34-scraper A rule34 scraper made in rust this time Building Clone the repository Execute cargo build --release --release is an optimized build pr

null 3 Jun 10, 2022
ratpack: a simpleton's HTTP framework (for rust-lang)

ratpack: a simpleton's HTTP framework (for rust-lang) ratpack is idealized in the simplicity of the sinatra (ruby) framework in its goal, and attempts

ZeroTier, Inc. 5 Jun 29, 2022
A backend providing a HTTP REST like interface for uploading files written in rust.

UploadServer A backend providing a HTTP REST like interface for uploading files written in rust. API Documentation License This project is licensed un

null 4 Nov 20, 2022
This is an example Nostr rust project to enable '402 Payment Required' responses for requests to paid content.

Nostr Paywall Example This is an example Nostr rust project to enable 402 Payment Required responses for requests to paid content. To prove payment, a

Blake Jakopovic 6 May 6, 2023
JSON-RPC endpoint proxy that dumps requests/responses for debugging

json_rpc_snoop How to build Ensure you have cargo installed and in your PATH (the easiest way is to visit https://rustup.rs/) make This will create t

null 10 Dec 14, 2022
NixEl is a Rust library that turns Nix code into a variety of correct, typed, memory-safe data-structures

?? NixEL Lexer, Parser, Abstract Syntax Tree and Concrete Syntax Tree for the Nix Expressions Language. NixEl is a Rust library that turns Nix code in

Kevin Amado 56 Dec 29, 2022
A Discord bot, written in Rust, that generates responses using the LLaMA language model.

llamacord A Discord bot, written in Rust, that generates responses using the LLaMA language model. Built on top of llama-rs. Setup Model Obtain the LL

Philpax 6 Mar 20, 2023
A Discord bot, written in Rust, that generates responses using the LLaMA language model.

llamacord A Discord bot, written in Rust, that generates responses using the LLaMA language model. Built on top of llama-rs. Setup Model Obtain the LL

Rustformers 18 Apr 9, 2023