#juniper #graphql #web #schema-file

juniper-from-schema

从您的 GraphQL 模式生成 Juniper 代码

26 个不稳定版本 (5 个重大变更)

0.5.2 2020 年 2 月 19 日
0.5.1 2019 年 11 月 14 日
0.5.0 2019 年 10 月 23 日
0.3.0 2019 年 6 月 18 日
0.1.1 2018 年 12 月 21 日

#14 in #juniper

Download history 49/week @ 2024-03-14 101/week @ 2024-03-21 135/week @ 2024-03-28 143/week @ 2024-04-04 89/week @ 2024-04-11 98/week @ 2024-04-18 89/week @ 2024-04-25 70/week @ 2024-05-02 84/week @ 2024-05-09 135/week @ 2024-05-16 78/week @ 2024-05-23 52/week @ 2024-05-30 44/week @ 2024-06-06 48/week @ 2024-06-13 49/week @ 2024-06-20 22/week @ 2024-06-27

每月 166 次下载
2 crates 中使用

MIT 许可证

63KB
373

juniper-from-schema

Documentation

该库包含一个过程宏,可以读取 GraphQL 模式文件,并生成相应的 Juniper 宏调用。这意味着您可以拥有一个实际的模式文件,并确保它与您的 Rust 实现匹配。它还消除了使用 Juniper 所涉及的大部分样板代码。

请参阅 crates 文档 以获取使用示例和更多信息。


lib.rs:

该库包含一个过程宏,可以读取 GraphQL 模式文件,并生成相应的 Juniper 宏调用。这意味着您可以拥有一个实际的模式文件,并确保它与您的 Rust 实现匹配。它还消除了使用 Juniper 所涉及的大部分样板代码。

目录

示例

模式

schema {
  query: Query
  mutation: Mutation
}

type Query {
  // The directive makes the return value `FieldResult<String>`
  // rather than the default `FieldResult<&String>`
  helloWorld(name: String!): String! @juniper(ownership: "owned")
}

type Mutation {
  noop: Boolean!
}

如何实现该模式

#[macro_use]
extern crate juniper;

use juniper_from_schema::graphql_schema_from_file;

// This is the important line
graphql_schema_from_file!("tests/schemas/doc_schema.graphql");

pub struct Context;
impl juniper::Context for Context {}

pub struct Query;

impl QueryFields for Query {
    fn field_hello_world(
        &self,
        executor: &juniper::Executor<'_, Context>,
        name: String,
    ) -> juniper::FieldResult<String> {
        Ok(format!("Hello, {}!", name))
    }
}

pub struct Mutation;

impl MutationFields for Mutation {
    fn field_noop(&self, executor: &juniper::Executor<'_, Context>) -> juniper::FieldResult<&bool> {
        Ok(&true)
    }
}

fn main() {
    let ctx = Context;

    let query = "query { helloWorld(name: \"Ferris\") }";

    let (result, errors) = juniper::execute(
        query,
        None,
        &Schema::new(Query, Mutation),
        &juniper::Variables::new(),
        &ctx,
    )
    .unwrap();

    assert_eq!(errors.len(), 0);
    assert_eq!(
        result
            .as_object_value()
            .unwrap()
            .get_field_value("helloWorld")
            .unwrap()
            .as_scalar_value::<String>()
            .unwrap(),
        "Hello, Ferris!",
    );
}

使用 graphql_schema_from_file! 展开后,您的代码将类似于以下内容

#[macro_use]
extern crate juniper;

pub struct Context;
impl juniper::Context for Context {}

pub struct Query;

juniper::graphql_object!(Query: Context |&self| {
    field hello_world(&executor, name: String) -> juniper::FieldResult<String> {
        <Self as QueryFields>::field_hello_world(&self, &executor, name)
    }
});

trait QueryFields {
    fn field_hello_world(
        &self,
        executor: &juniper::Executor<'_, Context>,
        name: String,
    ) -> juniper::FieldResult<String>;
}

impl QueryFields for Query {
    fn field_hello_world(
        &self,
        executor: &juniper::Executor<'_, Context>,
        name: String,
    ) -> juniper::FieldResult<String> {
        Ok(format!("Hello, {}!", name))
    }
}

pub struct Mutation;

juniper::graphql_object!(Mutation: Context |&self| {
    field noop(&executor) -> juniper::FieldResult<&bool> {
        <Self as MutationFields>::field_noop(&self, &executor)
    }
});

trait MutationFields {
    fn field_noop(&self, executor: &juniper::Executor<'_, Context>) -> juniper::FieldResult<&bool>;
}

impl MutationFields for Mutation {
    fn field_noop(&self, executor: &juniper::Executor<'_, Context>) -> juniper::FieldResult<&bool> {
        Ok(&true)
    }
}

type Schema = juniper::RootNode<'static, Query, Mutation>;

fn main() {
    let ctx = Context;

    let query = "query { helloWorld(name: \"Ferris\") }";

    let (result, errors) = juniper::execute(
        query,
        None,
        &Schema::new(Query, Mutation),
        &juniper::Variables::new(),
        &ctx,
    )
    .unwrap();

    assert_eq!(errors.len(), 0);
    assert_eq!(
        result
            .as_object_value()
            .unwrap()
            .get_field_value("helloWorld")
            .unwrap()
            .as_scalar_value::<String>()
            .unwrap(),
        "Hello, Ferris!",
    );
}

示例网页应用

您可以在 https://github.com/davidpdrsn/graphql-app-example 中找到如何将此库与 RocketDiesel 一起用于构建 GraphQL 网页应用的示例,或者可以在 https://github.com/husseinraoouf/graphql-actix-example 中找到如何将此库与 ActixDiesel 一起使用的示例。

GraphQL 功能

此库的目标是支持尽可能多的 GraphQL 功能,与 Juniper 相同。

以下是完整的功能列表

支持

  • 对象类型,包括将列表和非空转换为 Rust 类型
  • 包括 ID 类型的自定义标量类型
  • 接口
  • 联合
  • 输入对象
  • 枚举类型

尚不支持

  • 订阅(一旦 Juniper 支持订阅,将支持订阅)
  • 类型扩展

ID 类型

GraphQL 类型 ID 将生成到 juniper::ID

自定义标量类型

自定义标量类型将生成到围绕 String 的新类型包装器中。例如

scalar Cursor

将导致

pub struct Cursor(pub String);

特殊情况的标量

一些标量名称具有特殊含义。它们是

Juniper 不支持 chrono::Date,因此此库也无法支持。您可以在此处了解 Juniper 的支持集成这里

接口

Juniper 有几种方式在 Rust 中表示 GraphQL 接口。它们列在这里,包括它们的优缺点。

对于生成的代码,我们使用 enum 模式,因为我们发现它是最灵活的。

简略示例(完整示例请见此处

#
graphql_schema! {
    schema {
        query: Query
    }

    type Query {
        search(query: String!): [SearchResult!]! @juniper(ownership: "owned")
    }

    interface SearchResult {
        id: ID!
        text: String!
    }

    type Article implements SearchResult {
        id: ID!
        text: String!
    }

    type Tweet implements SearchResult {
        id: ID!
        text: String!
    }
}

pub struct Query;

impl QueryFields for Query {
    fn field_search(
        &self,
        executor: &Executor<'_, Context>,
        trail: &QueryTrail<'_, SearchResult, juniper_from_schema::Walked>,
        query: String,
    ) -> FieldResult<Vec<SearchResult>> {
        let article: Article = Article { id: ID::new("1"), text: "Business".to_string() };
        let tweet: Tweet = Tweet { id: ID::new("2"), text: "1 weird tip".to_string() };

        let posts = vec![
            SearchResult::from(article),
            SearchResult::from(tweet),
        ];

        Ok(posts)
    }
}

生成的枚举将为实现接口并实现每个类型的 From<T> 的每种类型提供变体。

联合类型

联合类型基本上就是接口,所以它们的工作方式非常相似。

简略示例(完整示例请见此处

#
graphql_schema! {
    schema {
        query: Query
    }

    type Query {
        search(query: String!): [SearchResult!]! @juniper(ownership: "owned")
    }

    union SearchResult = Article | Tweet

    type Article {
        id: ID!
        text: String!
    }

    type Tweet {
        id: ID!
        text: String!
    }
}

pub struct Query;

impl QueryFields for Query {
    fn field_search(
        &self,
        executor: &Executor<'_, Context>,
        trail: &QueryTrail<'_, SearchResult, juniper_from_schema::Walked>,
        query: String,
    ) -> FieldResult<Vec<SearchResult>> {
        let article: Article = Article { id: ID::new("1"), text: "Business".to_string() };
        let tweet: Tweet = Tweet { id: ID::new("2"), text: "1 weird tip".to_string() };

        let posts = vec![
            SearchResult::from(article),
            SearchResult::from(tweet),
        ];

        Ok(posts)
    }
}

输入对象

输入对象将被转换为具有公共字段的 Rust 结构体。

简略示例(完整示例请见此处

graphql_schema! {
    schema {
        query: Query
        mutation: Mutation
    }

    type Mutation {
        createPost(input: CreatePost!): Post @juniper(ownership: "owned")
    }

    input CreatePost {
        title: String!
    }

    type Post {
        id: ID!
        title: String!
    }

    type Query { noop: Boolean! }
}

pub struct Mutation;

impl MutationFields for Mutation {
    fn field_create_post(
        &self,
        executor: &Executor<'_, Context>,
        trail: &QueryTrail<'_, Post, juniper_from_schema::Walked>,
        input: CreatePost,
    ) -> FieldResult<Option<Post>> {
        let title: String = input.title;

        unimplemented!()
    }
}

从该示例中,CreatePost 将定义为

pub struct CreatePost {
    pub title: String,
}

枚举类型

GraphQL 枚举类型将被转换为正常的 Rust 枚举。每个变体的名称将是驼峰式。

简略示例(完整示例请见此处

#
graphql_schema! {
    schema {
        query: Query
    }

    enum Status {
        PUBLISHED
        UNPUBLISHED
    }

    type Query {
        allPosts(status: Status!): [Post!]! @juniper(ownership: "owned")
    }

    type Post {
        id: ID!
    }
}

pub struct Query;

impl QueryFields for Query {
    fn field_all_posts(
        &self,
        executor: &Executor<'_, Context>,
        trail: &QueryTrail<'_, Post, juniper_from_schema::Walked>,
        status: Status,
    ) -> FieldResult<Vec<Post>> {
        match status {
            Status::Published => unimplemented!("find published posts"),
            Status::Unpublished => unimplemented!("find unpublished posts"),
        }
    }
}

默认参数值

在 GraphQL 中,如果该参数是可空的,则可以提供字段参数的默认值。

以下类型的参数支持默认值

  • Float
  • Int
  • String
  • 布尔值
  • 枚举
  • 输入对象(作为字段参数,见下文)
  • 包含其他支持类型的列表

简化的示例(完整示例请见此处

#
graphql_schema! {
    schema {
        query: Query
    }

    enum Status {
        PUBLISHED
        UNPUBLISHED
    }

    input Pagination {
        pageSize: Int!
        cursor: ID
    }

    type Query {
        allPosts(
            status: Status = PUBLISHED,
            pagination: Pagination = { pageSize: 20 }
        ): [Post!]! @juniper(ownership: "owned")
    }

    type Post {
        id: ID!
    }
}

pub struct Query;

impl QueryFields for Query {
    fn field_all_posts(
        &self,
        executor: &Executor<'_, Context>,
        trail: &QueryTrail<'_, Post, juniper_from_schema::Walked>,
        status: Status,
        pagination: Pagination,
    ) -> FieldResult<Vec<Post>> {
        // `status` will be `Status::Published` if not given in the query

        match status {
            Status::Published => unimplemented!("find published posts"),
            Status::Unpublished => unimplemented!("find unpublished posts"),
        }
    }
}

输入对象的问题点

输入对象的默认值仅支持作为字段参数。以下不支持

input SomeType {
  field: Int = 1
}

这不被支持,因为规范对处理多个嵌套默认值的方式不够明确

此外,默认值只有在没有传递参数时才会使用。所以给定以下模式

input Input {
  a: String
  b: String
}

type Query {
  field(arg: Input = { a: "a" }): Int!
}

和查询

query MyQuery {
  field(arg: { b: "my b" })
}

解析器内 arg 的值将是 Input { a: None, b: Some(("my b")) }。请注意,尽管字段中 a 有默认值,但在这里没有使用,因为我们已经在查询中设置了 arg

支持的模式指令

支持一些模式指令,允许您自定义生成的代码

  • @juniper(ownership: "owned|borrowed|as_ref")。用于自定义返回数据的所有权。更多信息请见此处
  • @juniper(infallible: true|false)。自定义字段是否应该返回 Result<T, _> 或只是 T。更多信息请见此处
  • @deprecated。用于在您的模式中弃用类型。还支持使用 @deprecated(reason: "...") 提供原因

@juniper 的定义

一些在您的 GraphQL 模式中运行的工具要求您包含所有使用的指令的定义。因此,如果您需要,@juniper 的定义如下

directive @juniper(
    ownership: String = "borrowed",
    infallible: Boolean = false,
    with_time_zone: Boolean = true
) on FIELD_DEFINITION

此指令定义允许在您的模式中使用,以及任何其他指令定义。但不同于此定义的 @juniper 定义是不允许的。

该定义可能会在未来版本中更改。请参阅变更日志

juniper-from-schema 不需要将此放在您的模式中,因此您只需要在需要时包含它。

自定义所有权

默认情况下,所有字段都返回借用值。具体来说,类型是 juniper::FieldResult<&'a T>,其中 'aself 的生命周期。这对于返回由 self 拥有的数据非常有效,并且避免了如果字段返回拥有值时必须调用的不必要的 .clone() 调用。

但是,如果您需要更改所有权,您必须将指令 @juniper(ownership:) 添加到模式中的字段。

它接受以下参数

  • @juniper(ownership: "borrowed"):返回的数据将是从 self 借用的(FieldResult<&T>)。
  • @juniper(ownership: "owned"):返回类型将是拥有的(FieldResult<T>)。
  • @juniper(ownership: "as_ref"):仅适用于 OptionVec 返回类型。将内部类型更改为借用(FieldResult<Option<&T>>FieldResult<Vec<&T>>)。

示例

graphql_schema! {
    schema {
        query: Query
    }

    type Query {
        borrowed: String!
        owned: String! @juniper(ownership: "owned")
        asRef: String @juniper(ownership: "as_ref")
    }
}

pub struct Query;

impl QueryFields for Query {
    fn field_borrowed(&self, _: &Executor<'_, Context>) -> FieldResult<&String> {
        // ...
        # unimplemented!()
    }

    fn field_owned(&self, _: &Executor<'_, Context>) -> FieldResult<String> {
        // ...
        # unimplemented!()
    }

    fn field_as_ref(&self, _: &Executor<'_, Context>) -> FieldResult<Option<&String>> {
        // ...
        # unimplemented!()
    }
}

所有字段参数都将拥有所有权。

不可失败字段

默认情况下,生成的解析器是可失败的,这意味着它们返回一个 Result<T, _> 而不是裸的 T。您可以使用 @juniper(infallible: true) 来自定义它。

示例

graphql_schema! {
    schema {
        query: Query
    }

    type Query {
        canError: String!
        cannotError: String! @juniper(infallible: true)
        cannotErrorAndOwned: String! @juniper(infallible: true, ownership: "owned")
    }
}

pub struct Query;

impl QueryFields for Query {
    fn field_can_error(&self, _: &Executor<'_, Context>) -> FieldResult<&String> {
        // ...
        # unimplemented!()
    }

    fn field_cannot_error(&self, _: &Executor<'_, Context>) -> &String {
        // ...
        # unimplemented!()
    }

    fn field_cannot_error_and_owned(&self, _: &Executor<'_, Context>) -> String {
        // ...
        # unimplemented!()
    }
}

GraphQL 到 Rust 类型的映射

这是标准 GraphQL 类型将如何映射到 Rust

  • Int -> i32
  • Float -> f64
  • String -> String
  • Boolean -> bool
  • ID -> juniper::ID

查询轨迹

如果您在深度嵌套查询中对预加载关联项不够小心,则可能会遇到大量的N+1查询错误。Juniper提供了一个前瞻API,允许您检查查询中更远处的项目。但是,该API基于字符串,因此您可能会犯拼写错误,并检查不存在的字段。

QueryTrail是Juniper前瞻的一个轻量级包装,为您的所有类型上的每个字段生成方法。这意味着编译器会拒绝您的代码,如果您检查了无效的字段。

返回对象类型(非标量值)的解析方法(field_*)将除了执行器之外,还会得到一个QueryTrail参数。

由于QueryTrail类型本身是在这个crate中定义的(而不是插入到您的代码中),因此我们无法直接为您的GraphQL字段添加方法。这些方法必须通过“扩展特性”添加。所以如果您看到像这样的错误

   |  trail.foo();
   |        ^^^ method not found in `&juniper_from_schema::QueryTrail<'a, User, juniper_from_schema::Walked>`
   |
   = help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope, perhaps add a `use` for it:
   |
2  | use crate::graphql_schema::query_trails::QueryTrailUserExtensions;
   |

那么将use crate::graphql_schema::query_trails::*添加到您的模块中应该可以修复它。这是必要的,因为所有扩展特性都是在名为query_trails的模块内生成的。这样做是为了您可以在不全局导入您的GraphQL模式中的所有内容的情况下,全局导入QueryTrail扩展特性。

如果您只想从模式中获取所有内容,use crate::graphql_schema::*也会导入扩展特性。

简略示例

完整示例请见这里

#
graphql_schema! {
    schema {
        query: Query
    }

    type Query {
        allPosts: [Post!]! @juniper(ownership: "owned")
    }

    type Post {
        id: Int!
        author: User!
    }

    type User {
        id: Int!
    }
}

pub struct Query;

impl QueryFields for Query {
    fn field_all_posts(
        &self,
        executor: &Executor<'_, Context>,
        trail: &QueryTrail<'_, Post, juniper_from_schema::Walked>,
    ) -> FieldResult<Vec<Post>> {
        // Check if the query includes the author
        if let Some(_) = trail.author().walk() {
            // Somehow preload the users to avoid N+1 query bugs
            // Exactly how to do this depends on your setup
        }

        // Normally this would come from the database
        let post = Post {
            id: 1,
            author: User { id: 1 },
        };

        Ok(vec![post])
    }
}

pub struct Post {
    id: i32,
    author: User,
}

impl PostFields for Post {
    fn field_id(&self, executor: &Executor<'_, Context>) -> FieldResult<&i32> {
        Ok(&self.id)
    }

    fn field_author(
        &self,
        executor: &Executor<'_, Context>,
        trail: &QueryTrail<'_, User, juniper_from_schema::Walked>,
    ) -> FieldResult<&User> {
        Ok(&self.author)
    }
}

pub struct User {
    id: i32,
}

impl UserFields for User {
    fn field_id(
        &self,
        executor: &Executor<'_, Context>,
    ) -> FieldResult<&i32> {
        Ok(&self.id)
    }
}

类型

查询轨迹有两个泛型参数:QueryTrail<'a, T, K>T是当前字段返回的类型,而KWalkedNotWalked

生命周期'a来自Juniper,是传入查询的生命周期。

T

T允许我们为不同的类型实现不同的方法。例如,在上面的示例中,我们为QueryTrail<'_, Post, K>实现了idauthor,但只为QueryTrail<'_, User, K>实现了id

如果您的字段返回一个 Vec<T>Option<T>,则给定的查询路径将变为 QueryTrail<'_, T, _>。因此,VecOption 将被移除,您将只得到最内层的类型。这是因为 GraphQL 查询语法中,您查询的是 User 还是 [User] 并不重要。您可以访问的字段是相同的。

K

WalkedNotWalked 类型用于检查给定的路径是否实际被检查为查询的一部分。对 QueryTrail<'_, T, K> 的任何方法调用将返回 QueryTrail<'_, T, NotWalked>,要检查路径是否确实是查询的一部分,您必须调用 .walk(),它返回 Option<QueryTrail<'_, T, Walked>>。如果是 Some(_),则表示路径是查询的一部分,您可以执行必要的预加载。

示例

if let Some(walked_trail) = trail
    .some_field()
    .some_other_field()
    .third_field()
    .walk()
{
    // preload stuff
}

您始终可以运行 cargo doc 并检查 QueryTrail 上的所有方法及其调用上下文。

接口和联合 QueryTrail 的向下转换

本节主要与您使用 juniper-eager-loading 有关,但它并不仅限于该库。

如果您有一个 QueryTrail<'a, T, Walked>,其中 T 是一个接口或联合类型,您可以使用 .downcast() 将该 QueryTrail 转换为接口或联合类型的实现之一。

示例

#
graphql_schema! {
    schema {
        query: Query
    }

    type Query {
        search(query: String!): [SearchResult!]!
    }

    interface SearchResult {
        id: ID!
    }

    type Article implements SearchResult {
        id: ID!
    }

    type Tweet implements SearchResult {
        id: ID!
    }
}

pub struct Query;

impl QueryFields for Query {
    fn field_search(
        &self,
        executor: &Executor<'_, Context>,
        trail: &QueryTrail<'_, SearchResult, juniper_from_schema::Walked>,
        query: String,
    ) -> FieldResult<&Vec<SearchResult>> {
        let article_trail: QueryTrail<'_, Article, Walked> = trail.downcast();
        let tweet_trail: QueryTrail<'_, Tweet, Walked> = trail.downcast();

        // ...
        # unimplemented!()
    }
}

这有什么用呢?

如果您要执行某种数据预加载,您可能有一个函数可以检查 QueryTrail 并从数据库中加载数据。这样的函数可能看起来像这样

fn preload_users(
    mut users: Vec<User>,
    query_trail: &QueryTrail<'_, User, Walked>,
    db: &Database,
) -> Vec<User> {
    // ...
}

此函数在存在返回 [User!]! 的字段时运行良好。该字段将获得 QueryTrail<'a, User, Walked>,这正是 preload_users 所需要的。

然而,现在想象一下您有一个这样的模式

type Query {
    search(query: String!): [SearchResult!]!
}

union SearchResult = User | City | Country

type User {
    id: ID!
    city: City!
}

type City {
    id: ID!
    country: Country!
}

type Country {
    id: ID!
}

方法 QueryFields::field_search 将接收一个 QueryTrail<'a, SearchResult, Walked>。这种类型与 preload_users 不兼容。因此,我们必须将我们的 QueryTrail<'a, SearchResult, Walked> 转换为 QueryTrail<'a, User, Walked>

可以通过调用 .downcast() 来实现,这对于接口和联合查询路径是自动实现的。参见上面的示例。

带有参数的字段的 QueryTrail

有时你可能有 GraphQL 字段,它们接受影响你的解析器应该返回哪些内容的参数。因此,QueryTrail 允许你检查字段的参数。

简略示例

use chrono::prelude::*;

graphql_schema! {
    schema {
        query: Query
    }

    type Query {
        countries: [Country!]! @juniper(ownership: "owned")
    }

    type Country {
        users(activeSince: DateTimeUtc!): [User!]! @juniper(ownership: "owned")
    }

    type User {
        id: ID!
    }

    scalar DateTimeUtc
}

pub struct Query;

impl QueryFields for Query {
    fn field_countries<'a>(
        &self,
        executor: &'a juniper::Executor<'a, Context>,
        trail: &'a QueryTrail<'a, Country, Walked>
    ) -> juniper::FieldResult<Vec<Country>> {
        // Get struct that has all arguments passed to `Country.users`
        let args: CountryUsersArgs<'a> = trail.users_args();

        // The struct has methods for each argument, e.g. `active_since`.
        //
        // Notice that it automatically converts the incoming value to
        // a `DateTime<Utc>`.
        let _: DateTime<Utc> = args.active_since();

        # unimplemented!()
        // ...
    }
}

你也可以省略 'a 生命周期。

#
impl QueryFields for Query {
    fn field_countries(
        &self,
        executor: &juniper::Executor<'_, Context>,
        trail: &QueryTrail<'_, Country, Walked>
    ) -> juniper::FieldResult<Vec<Country>> {
        let args: CountryUsersArgs = trail.users_args();

        # unimplemented!()
        // ...
    }
}

参数结构的名称总是 {name of type}{name of field}Args(例如 CountryUsersArgs)。方法名称总是以 snake case 形式表示参数的名称。

*_args 方法仅在 Walked 查询路径上定义,所以如果你遇到错误

---- src/lib.rs -  (line 10) stdout ----
error[E0599]: no method named `users_args` found for type `&QueryTrail<'_, Country, Walked>` in the current
 scope
  --> src/lib.rs:10:1
   |
10 |         trail.users_args();
   |               ^^^^^^^^^^^^ method not found in `&QueryTrail<'_, Country, Walked>`

这很可能是你忘记在 trail 上调用 .walk()

请记住,您始终可以运行 cargo doc 来获取生成代码的高级概述。

自定义错误类型

默认情况下,生成的字段方法的返回类型将是 juniper::FieldResult<T>。这只是一个类型别名,对应于 std::result::Result<T, juniper::FieldError>。如果您想使用不同于 juniper::FieldError 的错误类型,可以在 graphql_schema_from_file! 中传递 , error_type: YourType

请注意,您的自定义错误类型必须实现 juniper::IntoFieldError 以进行类型检查。

示例

graphql_schema_from_file!("tests/schemas/doc_schema.graphql", error_type: MyError);

pub struct MyError(String);

impl juniper::IntoFieldError for MyError {
    fn into_field_error(self) -> juniper::FieldError {
        // Perform custom error handling
        juniper::FieldError::from(self.0)
    }
}

pub struct Query;

impl QueryFields for Query {
    fn field_hello_world(
        &self,
        executor: &Executor<'_, Context>,
        name: String,
    ) -> Result<String, MyError> {
        Ok(format!("Hello, {}!", name))
    }
}

graphql_schema! 不支持更改错误类型。

自定义上下文类型

默认情况下,生成的代码将假设您的上下文类型名为 Context。如果不是这种情况,您可以通过调用 graphql_schema_from_file! 并传递 context_type: NewName 来自定义它。

示例

graphql_schema_from_file!("tests/schemas/doc_schema.graphql", context_type: MyContext);

pub struct MyContext;
impl juniper::Context for MyContext {}

pub struct Query;

impl QueryFields for Query {
    fn field_hello_world(
        &self,
        executor: &Executor<'_, MyContext>,
        name: String,
    ) -> juniper::FieldResult<String> {
        Ok(format!("Hello, {}!", name))
    }
}

graphql_schema! 不支持更改上下文类型。

检查生成的代码

如果您想查看生成的代码,可以在编译时设置环境变量 JUNIPER_FROM_SCHEMA_DEBUG1。例如:

JUNIPER_FROM_SCHEMA_DEBUG=1 cargo build

生成的代码将不会进行格式化,可能难以阅读。最简单的方法是将打印的代码复制到文件中,然后通过 rustfmt 运行。

或者,您可以包含名为 "format-debug-output"功能。这将运行 rustfmt 在打印输出之前。这样您就不必手动进行。例如 Cargo.toml

[dependencies]
juniper-from-schema = { version = "x.y.z", features = ["format-debug-output"] }

不幸的是,这需要您使用 nightly,因为 rustfmt 需要 nightly。它也可能因为 rustfmt 无法编译的原因而破坏您的构建 ¯\\_(ツ)_/¯。如果您遇到这种情况,请删除 "format-debug-output" 功能并手动格式化输出。

依赖项

~7–16MB
~238K SLoC